Welcome back.
Now, if I go back to load menu and press play, and let's just say I want to clear out all my slots
and make a new slot.
And load it up.
I have a fresh game now.
Now, every time I reach a checkpoint, I'm going to save my progress.
Right?
And that's indicated visually by the glow.
But if I step off of this, yes, we're deactivating the overlap sphere, so I can't reactivate it.
But as soon as I exit and come back, yes, I've saved my progress.
But when I press play, it comes back, not activated.
It's not glowing anymore.
And this is because while we are saving character variables such as attributes and abilities, we're
not saving the world state.
We're not saving these actors and the states that they're currently in or that they should currently
be in.
So that's what the rest of this section is about saving the world state and how we can go about doing
that.
Now, saving actors is less trivial than you might think.
You may think that perhaps you could go into your save game object load screen save game, and just
add an array of actors here, or perhaps even a single actor.
Let's just say you tried that.
You added a t array of a actors called saved actors, and you went about saving those and attempting
to load them.
Well, that's not going to work, because an array of pointers is an array of memory addresses, and
there's no guarantee that the next time you launch your game that those actors will occupy the same
memory addresses.
They might and you might get what looks like working behavior.
Coincidentally, that's if you close your game and relaunch it again right away and haven't given your
operating system any chance to run any other tasks that might take up that memory.
Sure, you might load back into the same world, and you may load the previous state of those actors
because you just happen to be referencing that same memory.
But that would be a coincidence, and that's undefined behavior.
Overall.
We can't actually save actor pointers.
It's just not something we can do just due to the nature of pointers being addresses and memory and
memory shifts around memory changes, and your actors aren't guaranteed to occupy the same memory location
every time you run the game.
For that reason, we don't save actor pointers, we save data.
So what we have to do is have an array.
If we're going to have an array of our saved actors, we need an array of saved actor data.
Anything that we wish to save that once we load back up, we can fill in those actors with that data
at that point, so much like we have an F saved ability, we need an F saved actor or something similar.
A struct that determines what data we save for a given actor.
So I'm going to make a struct for this.
I'm going to say struct.
F.
Saved actor.
I'm going to make this a u struct.
With a generated body.
And we need to determine what exactly we need to save per actor.
Now, this could be any number of things and it depends on your game.
But one of the things that we need is a way to identify these actors.
And if the actors that you're saving and loading are persistent, such as our checkpoints, their names
should remain constant.
So that's one of the ways we can identify them.
So we're going to have an F name called actor name.
This is what we'll use to identify our saved actor.
And I'm going to make sure that this is initialized.
So we'll give it an empty f name value.
Now some of your actors that you're saving and loading may have a transform or in other words, a location
rotation and scale.
And if you'd like to save that transform, you'd put it here.
You'd say F transform, transform.
And you could give it an empty f transform by default and give this a uproperty these all need to be
you properties, by the way.
Now you could add any number of other variables here, such as gameplay tags like our saved ability
has, but there's also a way to serialize an actor and save any number of its member variables, provided
that those member variables are marked with a specific U property specifier.
That specifier is savegame, and we'll see how that works, and the way that you can serialize that
data on the actor for all variables marked with this specifier is to have a t array of bytes.
Now we can represent a byte which is eight bits of data as a u int eight.
Because a u int eight is an unsigned eight bit integer and a t array of u, int eight is an array of
bytes.
We can call this bytes, and we can give it a u property.
And I'm going to give this a comment that says contains serialized, not really contains, but it is
serialized variables from the actor.
Only those marked with save game specifier.
So that's what bytes is going to contain.
Now we'll learn how to serialize those bytes very shortly.
Now because we created an f save actor, it's often useful to be able to add these to an array using
Add Unique.
And we've seen that you can't use Add unique on a struct unless it has an overload for the double equals
operator.
And we've seen that.
To do that outside the struct we have to use inline.
So we're going to say inline bool operator double equals and we say const f saved actor reference left.
That will be on the left hand side and const.
F saved actor reference, right?
That's for the right hand side operand.
And we're just going to return left dot actor name.
I'd like to compare by actor name.
And if it's the same actor name as right dot actor name I'm considering these two structs equal.
So the double equals operator will just compare their actor names and then return true if those names
are equivalent.
Now that we have an F saved actor, we may wish to have an array of actors that we'd like to save.
But in this game project we may be traveling to other levels, other maps, which means that an array
of actors that are saved for one map make absolutely no sense in another map.
So I need a way to distinguish between maps.
I need some kind of data structure that can store an array of these saved actors, plus additional information
such as the name of the map.
For that reason, I'm going to make another struct called F saved map.
Now this will be like f saved actor and that it's going to be saved to disk, but it's going to represent
a map and all the actors we've saved for that map, it's going to be a U struct, and I'm going to give
it the generated body all U structs need, as well as a couple variables that it will contain, the
first of which is going to be a map name.
Now, I'm not talking about the user facing text that we see in the widget.
I'm talking about the actual name of the asset.
I'm going to identify it by name.
I'm going to use an F string for this and call it Map Asset name, and give it an empty f string value
by default and a u property.
So an f saved map can be identified by its map asset name, and then it can contain an array of all
of its saved actor data.
These f saved actor structs.
So I'm going to make a t array that stores f saved actors called saved actors and give this a U property
as well.
So we'll have an array of saved actor data.
This is what I'd like to have in my saved game object.
And because we can have any number of maps that we've been to that we may wish to save actors for,
I'm going to have an array of f saved maps.
So down into my load screen save game all the way down at the bottom, just under saved abilities,
I'm going to have a T array of F saved maps, and I'm going to call this saved maps.
So we're going to be saving data per map, and they will exist in this array.
Now if we have an array of f saved maps and each f saved map has an array of its own, this could get
quite confusing quite quickly.
So for that reason, I'd like to make a couple of useful utility functions, one of which I would like
to return a specific saved map based on the name of that map or the world name.
So I'm going to make a function that returns an f saved map copy called get saved map with map name,
and this can take a const f string reference called map name.
So this function is going to actually be quite simple, but having it will make things easy for us,
so we don't have to have a bunch of for loops.
In our other logic, all I want to do is loop through my saved maps.
So I'm going to say for f saved map, map and saved maps, this could be an f saved map const reference
so as to make this more efficient.
Avoiding copies.
And I'm going to say if map dot map asset name equals map name, the name we're passing in to this function.
If the names are equivalent, I can simply return this map, but otherwise I can return an empty saved
map and f saved map.
Temporary.
Now, if we're calling this function and we're getting a value whether we get a valid map or not, well,
how are we going to know that this is a valid map?
Or if we're just returning the empty F saved map?
Well, it would be nice to have a function that can return a boolean if we actually have a map with
a specific name.
So I'm going to make a bool returning function called has map, and this will take also a const f string
reference called map name.
So we can check to see if our saved data has a map with this specific map name.
Yet.
That's useful to know, because if we're trying to save data, we need to know if there's an array of
actors.
In the save data or not.
So this is going to be similar to the function above.
It's just going to loop over our saved maps.
It's going to check the names against the name passed in.
And if we get a match, we're going to return true.
And if we don't get a match, we're going to return false.
So with that, we have a couple of utility functions that will be useful for us.
So now that we have some data structures designed to save some data, we have our saved actor and a
saved map which has an array of saved actors.
And we have an array of saved maps.
How are we going to save the world state?
How are we going to save the data in our world?
That's the next step.
So we're going to need some functions to save that data.
And we'll do that next.
Now before wrapping up, we do need to realize that map name is already a member variable on our save
object.
So we do need to make sure these input parameters are or not using that same name.
I'm going to change them to in map name in both cases here for both of these, and make sure that we're
changing these to in map name.
In the functions, be careful to replace both usages so as not to use the member variable map name.
So with that we should now have code that can compile.
So in the next video we're going to create some functions that we can use to actually save this data
to disk when saving the world state.
I'll see you soon.