Persistence in Unity Using Realm
Rate this tutorial
When creating a game with Unity, we often reach the point where we need to save data that we need at a later point in time. This could be something simple, like a table of high scores, or a lot more complex, like the state of the game that got paused and now needs to be resumed exactly the way the user left it when they quit it earlier. Maybe you have tried this before using
PlayerPrefs
but your data was too complex to save it in there. Or you have tried SQL only to find it to be very complicated and cumbersome to use.Realm can help you achieve this easily and quickly with just some minor adjustments to your code.
The goal of this article is to show you how to add Realm to your Unity game and make sure your data is persisted. The Realm Unity SDK is part of our Realm .NET SDK. The documentation for the Realm .NET SDK will help you get started easily.
The first part of this tutorial will describe the example itself. If you are already familiar with Unity or really just want to see Realm in action, you can also skip it and jump straight to the second part.
We will be using a simple 3D chess game for demonstration purposes. Creating this game itself will not be part of this tutorial. However, this section will provide you with an overview so that you can follow along and add Realm to the game. This example can be found in our Unity examples repository.
The final implementation of the game including the usage of Realm is also part of the example repository.

To make it easy to find your way around this example, here are some notes to get you started:
The interesting part in the
MainScene
to look at is the Board
which is made up of Squares
and Pieces
. The Squares
are just slightly scaled and colored default Cube
objects which we utilize to visualize the Board
but also detect clicks for moving Pieces
by using its already attached Box Collider
component.
The
Pieces
have to be activated first, which happens by making them clickable as well. Pieces
are not initially added to the Board
but instead will be spawned by the PieceSpawner
. You can find them in the Prefabs
folder in the Project
hierarchy.
The important part to look for here is the
Piece
script which detects clicks on this Piece
(3) and offers a color change via Select()
(1) and Deselect()
(2) to visualize if a Piece
is active or not.We use two events to actually track the click on a
Piece
(1) or a Square
(2):The
InputListener
waits for those events to be invoked and will then notify other parts of our game about those updates. Pieces need to be selected when clicked (1) and deselected if another one was clicked (2).Clicking a
Square
while a Piece
is selected will send a message (3) to the GameState
to update the position of this Piece
.The actual movement as well as controlling the spawning and destroying of pieces is done by the
GameState
, in which all the above information eventually comes together to update Piece
positions and possibly destroy other Piece
objects. Whenever we move a Piece
(1), we not only update its position (2) but also need to check if there is a Piece
in that position already (3) and if so, destroy it (4).In addition to updating the game while it is running, the
GameState
offers two more functionalities:- set up the initial board (5)
- reset the board to its initial state (6)
Go ahead and try it out yourself if you like. You can play around with the board and pieces and reset if you want to start all over again.
To make sure the example is not overly complex and easy to follow, there are no rules implemented. You can move the pieces however you want. Also, the game is purely local for now and will be expanded using our Sync component in a later article to be playable online with others.
In the following section, I will explain how to make sure that the current game state gets saved and the players can resume the game at any state.
The first thing we need to do is to import the Realm framework into Unity.
The easiest way to do this is by using NPM.
You'll find it via
Windows
→ Package Manager
→ cogwheel in the top right corner → Advanced Project Settings
:
Within the
Scoped Registries
, you can add the Name
, URL
, and Scope
as follows:
This adds
NPM
as a source for libraries. The final step is to tell the project which dependencies to actually integrate into the project. This is done in the manifest.json
file which is located in the Packages
folder of your project.Here you need to add the following line to the
dependencies
:Replace
<version-number>
with the most recent Realm version found in https://github.com/realm/realm-dotnet/releases and you're all set.The final
manifest.json
should look something like this:When you switch back to Unity, it will reload the dependencies. If you then open the
Package Manager
again, you should see Realm
as a new entry in the list on the left:
We can now start using Realm in our Unity project.
Before we actually start adding Realm to our code, we need to think about how we want to achieve this and how the UI and database will interact with each other.
There are basically two options we can choose from: top-down or bottom-up.
The top-down approach would be to have the UI drive the changes. The
Piece
would know about its database object and whenever a Piece
is moved, it would also update the database with its new position.The preferred approach would be bottom-up, though. Changes will be applied to the Realm and it will then take care of whatever implications this has on the UI by sending notifications.
Let's first look into the initial setup of the board.
The first thing we want to do is to define a Realm representation of our piece since we cannot save the
MonoBehaviour
directly in Realm. Classes that are supposed to be saved in Realm need to subclass RealmObject
. The class PieceEntity
will represent such an object. Note that we cannot just duplicate the types from Piece
since not all of them can be saved in Realm, like Vector3
and enum
.Add the following scripts to the project:
Even though we cannot save the
PieceType
(1) and the position (2) directly in the Realm, we can still expose them using backing variables (3) to make working with this class easier while still fulfilling the requirements for saving data in Realm.Additionally, we provide a convenience constructor (4) for setting those two properties. A default constructor (6) also has to be provided for every
RealmObject
. Since we are not going to use it here, though, we can set it to private
.Note that one of these backing variables is a
RealmObject
itself, or rather a subclass of it: EmbeddedObject
(7). By extracting the position to a separate class Vector3Entity
the PieceEntity
is more readable. Another plus is that we can use the EmbeddedObject
to represent a 1:1 relationship. Every PieceEntity
can only have one Vector3Entity
and even more importantly, every Vector3Entity
can only belong to one PieceEntity
because there can only ever be one Piece
on any given Square
.The
Vector3Entity
, like the PieceEntity
, has some convenience functionality like a constructor that takes a Vector3
(8), the ToVector3()
function (9) and the private, mandatory default constructor (10) like PieceEntity
.Looking back at the
PieceEntity
, you will notice one more function: OnPropertyChanged
(5). Realm sends notifications for changes to fields saved in the database. Since we expose those fields using PieceType
and Position
, we need to make sure those notifications are passed on. This is achieved by calling RaisePropertyChanged(nameof(Position));
whenever PositionEntity
changes.The next step is to add some way to actually add
Pieces
to the Realm
. The current database state will always represent the current state of the board. When we create a new PieceEntity
—for example, when setting up the board—the GameObject
for it (Piece
) will be created. If a Piece
gets moved, the PieceEntity
will be updated by the GameState
which then leads to the Piece
's GameObject
being updated using above mentioned notifications.First, we will need to set up the board. To achieve this using the bottom-up approach, we adjust the
PieceSpawner
as follows:The important change here is
CreateNewBoard
. Instead of spawning the Piece
s, we now add PieceEntity
objects to the Realm. When we look at the changes in GameState
, we will see how this actually creates a Piece
per PieceEntity
.Here we just wipe the database (1) and then add new
PieceEntity
objects (2). Note that this is wrapped by a realm.write
block. Whenever we want to change the database, we need to enclose it in a write transaction. This makes sure that no other piece of code can change the database at the same time since transactions block each other.The last step to create a new board is to update the
GameState
to make use of the new PieceSpawner
and the PieceEntity
that we just created.We'll go through these changes step by step. First we also need to import Realm here as well:
Then we add a private field to save our
Realm
instance to avoid creating it over and over again. We also create another private field to save the collection of pieces that are on the board and a notification token which we need for above mentioned notifications:In
Awake
, we do need to get access to the Realm
. This is achieved by opening an instance of it (1) and then asking it for all PieceEntity
objects currently saved using realm.All
(2) and assigning them to our pieceEntities
field:Note that collections are live objects. This has two positive implications: Every access to the object reference always returns an updated representation of said object. Because of this, every subsequent change to the object will be visible any time the object is accessed again. We also get notifications for those changes if we subscribed to them. This can be done by calling
SubscribeForNotifications
on a collection (3).Apart from an error object that we need to check (4), we also receive the
changes
and the sender
(the updated collection itself) with every notification. For every new collection of objects, an initial notification is sent that does not include any changes
but gives us the opportunity to do some initial setup work (5).In case we resume a game, we'll already see
PieceEntity
objects in the database even for the initial notification. We need to spawn one Piece
per PieceEntity
to represent it (6). We make use of the SpawnPiece
function in PieceSpawner
to achieve this. In case the database does not have any objects yet, we need to create the board from scratch (7). Here we use the CreateNewBoard
function we added earlier to the PieceSpawner
.On top of the initial notification, we also expect to receive a notification every time a
PieceEntity
is inserted into the Realm. This is where we continue the CreateNewBoard
functionality we started in the PieceSpawner
by adding new objects to the database. After those changes happen, we end up with changes
(8) inside the notifications. Now we need to iterate over all new PieceEntity
objects in the sender
(which represents the pieceEntities
collection) and add a Piece
for each new PieceEntity
to the board.Apart from inserting new pieces when the board gets set up, we also need to take care of movement and pieces attacking each other. This will be explained in the next section.
Whenever we receive a click on a
Square
and therefore call MovePiece
in GameState
, we need to update the PieceEntity
instead of directly moving the corresponding GameObject
. The movement of the Piece
will then happen via the PropertyChanged
notifications as we saw earlier.Before actually moving the
PieceEntity
, we do need to check if there is already a PieceEntity
at the desired position and if so, destroy it. To find a PieceEntity
at the newPosition
and also to find the PieceEntity
that needs to be moved from oldPosition
to newPosition
, we can use queries on the pieceEntities
collection (3).By querying the collection (calling
Filter
), we can look for one or multiple RealmObject
s with specific characteristics. In this case, we're interested in the RealmObject
that represents the Piece
we are looking for. Note that when using a Filter
we can only filter using the Realm properties saved in the database, not the exposed properties (Position
and PieceType
) exposed for convenience by the PieceEntity
.If there is an
attackedPiece
at the target position, we need to delete the corresponding PieceEntity
for this GameObject
(1). After the attackedPiece
is updated, we can then also update the movedPiece
(2).Like the initial setup of the board, this has to be called within a write transaction to make sure no other code is changing the database at the same time.
This is all we had to do to update and persist the position. Go ahead and start the game. Stop and start it again and you should now see the state being persisted.
The final step will be to also update our
ResetGame
button to update (or rather, wipe) the Realm
. At the moment, it does not update the state in the database and just recreates the GameObject
s.Resetting works similar to what we do in
Awake
in case there were no entries in the database—for example, when starting the game for the first time.We can reuse the
CreateNewBoard
functionality here since it includes wiping the database before actually re-creating it:With this change, our game is finished and fully functional using a local
Realm
to save the game's state.In this tutorial, we have seen that saving your game and resuming it later can be easily achieved by using
Realm
.The steps we needed to take:
- Add
Realm
via NPM as a dependency. - Import
Realm
in any class that wants to use it by callingusing Realms;
. - Create a new
Realm
instance viaRealm.GetInstance()
to get access to the database. - Define entites by subclassing
RealmObject
(or any of its subclasses):- Fields need to be public and primitive values or lists.
- A default constructor is mandatory.
- A convenience constructor and additional functions can be defined.
- Write to a
Realm
usingrealm.Write()
to avoid data corruption. - CRUD operations (need to use a
write
transaction):- Use
realm.Add()
toCreate
a new object. - Use
realm.Remove()
toDelete
an object. Read
andUpdate
can be achieved by simplygetting
andsetting
thepublic fields
.
With this, you should be ready to use Realm in your games.
If you have questions, please head to our developer community website where the MongoDB engineers and the MongoDB community will help you build your next big idea with MongoDB and Realm.