WEBVTT

00:06.930 --> 00:08.250
Welcome back.

00:08.460 --> 00:15.780
Now, if I go back to load menu and press play, and let's just say I want to clear out all my slots

00:15.780 --> 00:17.880
and make a new slot.

00:19.080 --> 00:20.070
And load it up.

00:20.070 --> 00:21.600
I have a fresh game now.

00:21.600 --> 00:26.250
Now, every time I reach a checkpoint, I'm going to save my progress.

00:26.250 --> 00:26.820
Right?

00:26.820 --> 00:29.730
And that's indicated visually by the glow.

00:29.730 --> 00:36.930
But if I step off of this, yes, we're deactivating the overlap sphere, so I can't reactivate it.

00:36.930 --> 00:41.610
But as soon as I exit and come back, yes, I've saved my progress.

00:41.610 --> 00:45.720
But when I press play, it comes back, not activated.

00:45.720 --> 00:47.490
It's not glowing anymore.

00:47.790 --> 00:55.140
And this is because while we are saving character variables such as attributes and abilities, we're

00:55.140 --> 00:56.700
not saving the world state.

00:56.700 --> 01:02.310
We're not saving these actors and the states that they're currently in or that they should currently

01:02.310 --> 01:03.150
be in.

01:03.150 --> 01:09.900
So that's what the rest of this section is about saving the world state and how we can go about doing

01:09.900 --> 01:10.440
that.

01:10.740 --> 01:15.690
Now, saving actors is less trivial than you might think.

01:15.780 --> 01:24.510
You may think that perhaps you could go into your save game object load screen save game, and just

01:24.510 --> 01:28.410
add an array of actors here, or perhaps even a single actor.

01:28.410 --> 01:30.570
Let's just say you tried that.

01:30.570 --> 01:37.230
You added a t array of a actors called saved actors, and you went about saving those and attempting

01:37.260 --> 01:37.890
to load them.

01:37.890 --> 01:44.310
Well, that's not going to work, because an array of pointers is an array of memory addresses, and

01:44.310 --> 01:50.910
there's no guarantee that the next time you launch your game that those actors will occupy the same

01:50.910 --> 01:52.410
memory addresses.

01:52.410 --> 01:56.730
They might and you might get what looks like working behavior.

01:56.730 --> 02:04.680
Coincidentally, that's if you close your game and relaunch it again right away and haven't given your

02:04.680 --> 02:10.560
operating system any chance to run any other tasks that might take up that memory.

02:10.560 --> 02:18.180
Sure, you might load back into the same world, and you may load the previous state of those actors

02:18.180 --> 02:22.080
because you just happen to be referencing that same memory.

02:22.080 --> 02:26.640
But that would be a coincidence, and that's undefined behavior.

02:26.640 --> 02:27.420
Overall.

02:27.420 --> 02:30.690
We can't actually save actor pointers.

02:30.690 --> 02:37.650
It's just not something we can do just due to the nature of pointers being addresses and memory and

02:37.650 --> 02:44.550
memory shifts around memory changes, and your actors aren't guaranteed to occupy the same memory location

02:44.550 --> 02:46.230
every time you run the game.

02:46.230 --> 02:50.640
For that reason, we don't save actor pointers, we save data.

02:50.880 --> 02:54.390
So what we have to do is have an array.

02:54.390 --> 03:01.380
If we're going to have an array of our saved actors, we need an array of saved actor data.

03:01.380 --> 03:08.700
Anything that we wish to save that once we load back up, we can fill in those actors with that data

03:08.700 --> 03:16.200
at that point, so much like we have an F saved ability, we need an F saved actor or something similar.

03:16.200 --> 03:21.690
A struct that determines what data we save for a given actor.

03:21.690 --> 03:23.670
So I'm going to make a struct for this.

03:23.670 --> 03:25.440
I'm going to say struct.

03:26.260 --> 03:26.950
F.

03:27.370 --> 03:29.080
Saved actor.

03:29.740 --> 03:31.930
I'm going to make this a u struct.

03:33.770 --> 03:35.450
With a generated body.

03:37.320 --> 03:42.030
And we need to determine what exactly we need to save per actor.

03:42.060 --> 03:45.630
Now, this could be any number of things and it depends on your game.

03:45.780 --> 03:51.390
But one of the things that we need is a way to identify these actors.

03:51.390 --> 03:57.990
And if the actors that you're saving and loading are persistent, such as our checkpoints, their names

03:57.990 --> 03:59.430
should remain constant.

03:59.430 --> 04:01.740
So that's one of the ways we can identify them.

04:01.740 --> 04:04.860
So we're going to have an F name called actor name.

04:04.950 --> 04:10.410
This is what we'll use to identify our saved actor.

04:10.410 --> 04:12.900
And I'm going to make sure that this is initialized.

04:12.900 --> 04:15.510
So we'll give it an empty f name value.

04:15.630 --> 04:22.710
Now some of your actors that you're saving and loading may have a transform or in other words, a location

04:22.710 --> 04:24.600
rotation and scale.

04:24.600 --> 04:27.870
And if you'd like to save that transform, you'd put it here.

04:27.870 --> 04:31.230
You'd say F transform, transform.

04:32.610 --> 04:38.940
And you could give it an empty f transform by default and give this a uproperty these all need to be

04:38.940 --> 04:41.460
you properties, by the way.

04:41.670 --> 04:47.670
Now you could add any number of other variables here, such as gameplay tags like our saved ability

04:47.670 --> 04:57.690
has, but there's also a way to serialize an actor and save any number of its member variables, provided

04:57.690 --> 05:02.790
that those member variables are marked with a specific U property specifier.

05:03.180 --> 05:10.440
That specifier is savegame, and we'll see how that works, and the way that you can serialize that

05:10.440 --> 05:17.310
data on the actor for all variables marked with this specifier is to have a t array of bytes.

05:17.310 --> 05:23.010
Now we can represent a byte which is eight bits of data as a u int eight.

05:23.010 --> 05:32.460
Because a u int eight is an unsigned eight bit integer and a t array of u, int eight is an array of

05:32.460 --> 05:33.090
bytes.

05:33.090 --> 05:37.890
We can call this bytes, and we can give it a u property.

05:37.890 --> 05:46.710
And I'm going to give this a comment that says contains serialized, not really contains, but it is

05:46.710 --> 05:51.390
serialized variables from the actor.

05:52.530 --> 05:58.590
Only those marked with save game specifier.

05:58.740 --> 06:01.020
So that's what bytes is going to contain.

06:01.020 --> 06:05.160
Now we'll learn how to serialize those bytes very shortly.

06:05.880 --> 06:13.140
Now because we created an f save actor, it's often useful to be able to add these to an array using

06:13.170 --> 06:13.950
Add Unique.

06:13.950 --> 06:21.180
And we've seen that you can't use Add unique on a struct unless it has an overload for the double equals

06:21.180 --> 06:21.900
operator.

06:21.900 --> 06:22.830
And we've seen that.

06:22.830 --> 06:25.800
To do that outside the struct we have to use inline.

06:25.800 --> 06:36.390
So we're going to say inline bool operator double equals and we say const f saved actor reference left.

06:36.390 --> 06:39.450
That will be on the left hand side and const.

06:40.340 --> 06:43.250
F saved actor reference, right?

06:43.520 --> 06:46.040
That's for the right hand side operand.

06:46.820 --> 06:50.750
And we're just going to return left dot actor name.

06:50.750 --> 06:52.850
I'd like to compare by actor name.

06:52.850 --> 06:59.960
And if it's the same actor name as right dot actor name I'm considering these two structs equal.

06:59.960 --> 07:08.840
So the double equals operator will just compare their actor names and then return true if those names

07:08.840 --> 07:09.770
are equivalent.

07:09.800 --> 07:16.250
Now that we have an F saved actor, we may wish to have an array of actors that we'd like to save.

07:16.250 --> 07:23.690
But in this game project we may be traveling to other levels, other maps, which means that an array

07:23.690 --> 07:29.540
of actors that are saved for one map make absolutely no sense in another map.

07:29.540 --> 07:32.840
So I need a way to distinguish between maps.

07:32.840 --> 07:40.940
I need some kind of data structure that can store an array of these saved actors, plus additional information

07:40.940 --> 07:43.040
such as the name of the map.

07:43.040 --> 07:48.170
For that reason, I'm going to make another struct called F saved map.

07:48.170 --> 07:56.180
Now this will be like f saved actor and that it's going to be saved to disk, but it's going to represent

07:56.180 --> 08:02.150
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

08:02.150 --> 08:08.900
it the generated body all U structs need, as well as a couple variables that it will contain, the

08:08.900 --> 08:11.570
first of which is going to be a map name.

08:11.570 --> 08:15.800
Now, I'm not talking about the user facing text that we see in the widget.

08:15.800 --> 08:18.770
I'm talking about the actual name of the asset.

08:18.770 --> 08:21.170
I'm going to identify it by name.

08:21.170 --> 08:27.890
I'm going to use an F string for this and call it Map Asset name, and give it an empty f string value

08:27.890 --> 08:30.470
by default and a u property.

08:30.470 --> 08:37.430
So an f saved map can be identified by its map asset name, and then it can contain an array of all

08:37.430 --> 08:39.170
of its saved actor data.

08:39.170 --> 08:41.990
These f saved actor structs.

08:41.990 --> 08:50.540
So I'm going to make a t array that stores f saved actors called saved actors and give this a U property

08:50.540 --> 08:51.320
as well.

08:51.320 --> 08:54.830
So we'll have an array of saved actor data.

08:54.890 --> 08:59.030
This is what I'd like to have in my saved game object.

08:59.030 --> 09:05.060
And because we can have any number of maps that we've been to that we may wish to save actors for,

09:05.060 --> 09:08.660
I'm going to have an array of f saved maps.

09:08.660 --> 09:16.010
So down into my load screen save game all the way down at the bottom, just under saved abilities,

09:16.010 --> 09:24.830
I'm going to have a T array of F saved maps, and I'm going to call this saved maps.

09:24.830 --> 09:29.840
So we're going to be saving data per map, and they will exist in this array.

09:29.840 --> 09:36.140
Now if we have an array of f saved maps and each f saved map has an array of its own, this could get

09:36.140 --> 09:38.330
quite confusing quite quickly.

09:38.330 --> 09:44.960
So for that reason, I'd like to make a couple of useful utility functions, one of which I would like

09:44.960 --> 09:51.710
to return a specific saved map based on the name of that map or the world name.

09:51.710 --> 10:01.340
So I'm going to make a function that returns an f saved map copy called get saved map with map name,

10:01.550 --> 10:06.920
and this can take a const f string reference called map name.

10:07.580 --> 10:14.570
So this function is going to actually be quite simple, but having it will make things easy for us,

10:14.570 --> 10:16.910
so we don't have to have a bunch of for loops.

10:16.910 --> 10:22.070
In our other logic, all I want to do is loop through my saved maps.

10:22.070 --> 10:33.200
So I'm going to say for f saved map, map and saved maps, this could be an f saved map const reference

10:34.550 --> 10:37.400
so as to make this more efficient.

10:37.400 --> 10:38.840
Avoiding copies.

10:38.840 --> 10:47.270
And I'm going to say if map dot map asset name equals map name, the name we're passing in to this function.

10:47.270 --> 10:54.740
If the names are equivalent, I can simply return this map, but otherwise I can return an empty saved

10:54.740 --> 10:57.230
map and f saved map.

10:57.830 --> 10:58.850
Temporary.

10:58.850 --> 11:05.150
Now, if we're calling this function and we're getting a value whether we get a valid map or not, well,

11:05.150 --> 11:07.310
how are we going to know that this is a valid map?

11:07.310 --> 11:10.400
Or if we're just returning the empty F saved map?

11:10.400 --> 11:15.680
Well, it would be nice to have a function that can return a boolean if we actually have a map with

11:15.680 --> 11:16.820
a specific name.

11:16.820 --> 11:22.760
So I'm going to make a bool returning function called has map, and this will take also a const f string

11:22.760 --> 11:25.580
reference called map name.

11:26.060 --> 11:32.330
So we can check to see if our saved data has a map with this specific map name.

11:32.330 --> 11:33.140
Yet.

11:33.350 --> 11:38.840
That's useful to know, because if we're trying to save data, we need to know if there's an array of

11:38.840 --> 11:39.500
actors.

11:39.630 --> 11:41.130
In the save data or not.

11:41.130 --> 11:44.670
So this is going to be similar to the function above.

11:44.670 --> 11:48.510
It's just going to loop over our saved maps.

11:48.510 --> 11:51.750
It's going to check the names against the name passed in.

11:51.750 --> 11:54.360
And if we get a match, we're going to return true.

11:54.360 --> 11:57.720
And if we don't get a match, we're going to return false.

11:58.290 --> 12:03.990
So with that, we have a couple of utility functions that will be useful for us.

12:04.290 --> 12:11.340
So now that we have some data structures designed to save some data, we have our saved actor and a

12:11.340 --> 12:14.010
saved map which has an array of saved actors.

12:14.010 --> 12:16.230
And we have an array of saved maps.

12:16.230 --> 12:18.570
How are we going to save the world state?

12:18.570 --> 12:21.180
How are we going to save the data in our world?

12:21.180 --> 12:22.410
That's the next step.

12:22.410 --> 12:25.980
So we're going to need some functions to save that data.

12:25.980 --> 12:27.750
And we'll do that next.

12:27.750 --> 12:35.100
Now before wrapping up, we do need to realize that map name is already a member variable on our save

12:35.100 --> 12:35.730
object.

12:35.730 --> 12:42.390
So we do need to make sure these input parameters are or not using that same name.

12:42.390 --> 12:48.600
I'm going to change them to in map name in both cases here for both of these, and make sure that we're

12:48.600 --> 12:51.120
changing these to in map name.

12:51.270 --> 12:59.850
In the functions, be careful to replace both usages so as not to use the member variable map name.

12:59.850 --> 13:03.780
So with that we should now have code that can compile.

13:04.530 --> 13:11.010
So in the next video we're going to create some functions that we can use to actually save this data

13:11.010 --> 13:13.650
to disk when saving the world state.

13:14.010 --> 13:14.910
I'll see you soon.
