One of the core building blocks of making
games is data. On its own, data is nebulous and unconnected. But when you structure it into something that
tells you the relationship between it and what it represents, you give that data meaning. Here’s an example of some structured data
in Unity. This class represents an item with a name,
a description, and a price. This one represents a shop with a name, an
amount of gold, and a supply of products. And this one represents a player with an amount
of gold and an inventory. Both the shop and player classes have a list
of items, but I’ve used different variable names to better describe what each list actually
represents. This is how we assign meaning to our data,
by organizing it all into distinct classes which are known as data structures. Once you’ve defined your data structures
you can assign even more meaning through the use of objects. Objects add behaviour to your data structures
and make them easier to understand and work with. Here’s an example of an object that encapsulates
the usage of items. We can use it to perform common operations,
like checking whether or not the player can afford a particular item or gathering all
of the items that are cheaper or more expensive than a particular amount. Another use case for objects is persistence
as is the case for the repository pattern. The repository pattern is a design pattern
that’s used to decouple your game code (or business logic) from the data access layers
in your application. In other words, it completely separates the
source of your data from the usage of your data. The way it works is that you define objects,
called repositories, that are responsible for creating, reading, updating, and deleting
data from an arbitrary source. These are known as CRUD operations and the
source could be a text file, a database, or even an API that’s hosted on the web. To access this source, repositories use an
abstraction which is generally called a data context. The data context is what decides where the
data comes from, and you can implement more than one to access multiple streams of data. Let’s take a look at an example of the repository
pattern in Unity. But first, if you’re interested in content
like this, then sign up for our monthly Level 2 Game Dev newsletter. Level 2 is all about helping you develop the
skills needed to take your game dev hobby or career to the next level. Once a month, we’ll send you an email with
curated content that’s designed to help push you on your game dev journey and help
you keep your finger on the pulse of what’s going on in the industry. If that sounds good to you, visit the link
in the description to sign up now! Now, as with all design patterns, there are
many ways to implement the repository pattern. And there are even frameworks that do all
of the heavy lifting for you (which I don’t necessarily recommend). But for the sake of simplicity, here’s a
simple implementation that I created for this video. We’ll start with the GameData class. In order to contextualize our data, we need
to encapsulate which data structures we want to persist, which is exactly what this class
represents. For now, we’re just gonna store a list of
players and a list of shops. Simple as that. Next, we need a context for that data. The DataContext class keeps a record of the
game’s data. It’s abstract because we don’t know exactly
where the data is coming from or how to save new data that gets generated by the user. That’s why there are two abstract methods
that need to be implemented: Load and Save. To make things easier, DataContext has one
implemented method called Set that we can use to grab a subset of the game data based
on a type, using C# generics. For this video I’ve implemented a single
context which is able to grab and store data in a JSON file. Here’s what it looks like. It basically uses Unity’s built-in JsonUtility
to serialize and deserialize data to and from a json file which you can define in the editor. Finally we have the base Repository class
that each of our individual repositories will derive from. In a nutshell, it implements all of the CRUD
methods I mentioned earlier using an instance of DataContext that could change depending
on where we want a particular repository to source it’s data from. It uses C# generics as well to make it easier
to implement a repository for each data structure in our model, as we can see here with our
two implementations: the Shops and Players classes. So that’s the nuts and bolts of my implementation
of the repository pattern. Let’s switch over to Unity and see it in
action. In the scene, I’ve got a UI for the shop
that’s being driven by the ShopWindow class. We’ll take a look at that in a moment. When I press play we can see the UI gets populated
with items. Both the shop and player’s gold is displayed,
as well. I can purchase items by pressing the buy button. Doing so will update the UI to show items
being removed from the shop, the shop gaining gold, and the player losing the gold that
was spent. Now let’s stop and restart the scene. Look at that: all of my purchases have persisted
thanks to our repositories. Let’s take a look at the ShopWindow class
to see how it’s all wired up. More specifically, let’s look at the mechanics
of the Buy method. Buy gets called when we press the buy button,
and it’s where all of our persistence operations take place. First it grabs an instance of the player and
the shop using a set of IDs. For this demonstration, both IDs are set via
some public fields. But in a real implementation, they’d probably
be injectected when the player interacts with a shopkeeper NPC. The player and shop instances are each retrieved
from their respective repositories, which are wired up to use the Json data context. Next it does a couple of checks to make sure
that the item exists in the shop and the player has enough gold. And then it subtracts the price of the item
from the player’s gold before finally sending an updated version of the player to the repository
and saving the transaction. The same is done for the shop. Although the code might look a little different
in production, this is pretty much how you’d use the repository pattern in your own code. It makes working with data much easier. Plus it’s flexible enough to support changes
to your data sources and can scale to support as many data structures as you need to persist. However there are a couple of problems that
we still need to address. Let’s imagine that this code was a little
more complicated. Maybe purchasing an item means we need to
update a quest objective. Or collecting a certain amount of items triggers
an achievement. Anything that could happen in response to
the player gaining a new item. It could happen out of band as a response
to an event, or in-line right here in the Buy method. But what would happen if, in the middle of
everything, an exception was thrown and, as a result of this exception, the shop
repository never got saved? Well the answer is simple: your data would
be out of sync and there would be no way to recover. This is an issue of data concurrency. Data concurrency is the ability to allow multiple
parts of your application to affect multiple transactions within a single database. Simply put, data concurrency allows many different
parts of your application to access the same data all at the same time. To facilitate this, you’d need a way to
queue up or log changes to your data in memory before writing all of those changes to the
actual source. Which, in our case, is a JSON file. However the repository pattern wasn’t intended
to solve this issue on it’s own. So we’ll need something else to help us
out. But before we look at that, there’s one
other problem that we need to solve. If we peek into the Save method of our base
Repository class and then follow it all the way down to our JSON implementation of the
DataContext class, we can see that it’s responsible for writing data directly to the
source. Every time it gets called it has to open up
a data stream and push new data into the actual JSON file. If you’ve worked with data access before,
you’ll know that opening streams like this is very expensive. Now of course the time it takes to open a
JSON file that lives on my SSD is pretty negligible, but imagine if our data source lived in the
cloud and could only be accessed via a slow API call. If I wasn’t careful, this would become a
performance bottleneck very quickly. So how do we solve these two problems: that
of data concurrency and performance bottlenecks caused by data access? Luckily, both of these problems occur commonly
enough that an entire pattern was created to solve them. That pattern is called “Unit of Work”. A Unit of Work maintains a list of objects
affected by a business transaction and coordinates the writing out of changes and the resolution
of concurrency problems. The idea is that the Unit of Work keeps track
of every change that happens and writes them all at the same time when you tell it to. The shop example is a little trivial because
it only makes changes to two objects and only calls the Save method twice, but I think it’ll
serve to illustrate the point. First, we’ll create a class called UnitOfWork. UnitOfWork will be responsible for coordinating
all of our persistence logic so it’ll need a reference to the DataContext and all of
the repositories. And since we’ll need access to those repositories,
we’ll expose them through a couple of public properties. Finally, let’s add a Save method that delegates
to the data context. And that’s it. Now we can put our UnitOfWork to work in the
ShopWindow class. We’ll start adding a reference. Then we’ll replace each instance of a repository
with a pass through call to the UnitOfWork. And instead of calling save on each repository,
we’ll simply call save on the UnitOfWork. Finish with a little clean up and voila! That’s all there is to it. Our problems are solved and the code still
works with very minimal changes Persistence presents an interesting challenge
for game developers. Your data must maintain its integrity so your
players have the experience that you designed. And jankiness during gameplay as your code
attempts to open and close the same data source over and over again will only make your players
turn away. Thankfully, the repository pattern with the
unit of work presents tried and tested solutions for both of these problems. But this isn’t the only approach to saving
data for your games. Let me know what you think about these patterns
or if you save data in a completely different way in the comment section below. I’d love to hear all about it. If you enjoyed this content, be sure to sign
for the Level 2 Game Dev Newsletter. And of course, like this video and subscribe
to Infallible Code. Thanks for watching. As always, I’ll catch you in the next video.