WEBVTT

00:06.810 --> 00:07.710
Welcome back.

00:07.710 --> 00:13.320
So now that we have data structures for saving data, it's time to actually save it.

00:13.320 --> 00:16.620
So we need to figure out how to save our world state.

00:16.650 --> 00:21.660
Now we're going to go on to our game mode base to create save and load functions.

00:21.660 --> 00:24.450
And this is just one solution.

00:24.450 --> 00:27.030
You don't have to save data from the game mode.

00:27.030 --> 00:28.920
You can do it from other classes.

00:28.920 --> 00:31.020
Some people like to create subsystems.

00:31.020 --> 00:33.450
And that's something I encourage you to look into.

00:33.450 --> 00:39.570
If you're interested, you can make a save game subsystem we're just going to use or a game mode base

00:39.570 --> 00:40.470
for this.

00:40.470 --> 00:46.170
And I'm going to put a brand new function here in our game mode base for saving the world.

00:46.830 --> 00:54.060
It sounds kind of funny, but we're just going to call this function Save World State.

00:54.060 --> 01:02.790
And I like this function to take in a new world by pointer called world, as this depends on what world

01:02.790 --> 01:05.220
we're currently in when we save the world.

01:05.220 --> 01:08.310
So let's go ahead and generate this definition.

01:08.310 --> 01:11.460
And let's think about how we're going to save the world.

01:11.460 --> 01:16.980
Now first of all, remember our data structures are going to identify the world by its name.

01:16.980 --> 01:24.420
If we go to load screen save game, we'll see that we have a T array of saved maps, F save map, and

01:24.420 --> 01:25.650
the F saved map.

01:25.650 --> 01:30.060
Data structure has an F string called map asset name.

01:30.060 --> 01:36.480
We need to know what the map is for a given world in our save world state function.

01:36.480 --> 01:38.070
So how do we get that?

01:38.070 --> 01:42.810
Well, first we can take the world and we can call get map name.

01:43.110 --> 01:48.480
But get map name is going to have some characters prefixing it.

01:48.480 --> 01:54.570
We call this the streaming levels prefix, and these characters must be removed if we want the true

01:54.570 --> 01:55.620
map name.

01:55.620 --> 02:00.960
So what we can do is make a non-const f string called world name.

02:03.630 --> 02:09.960
And then we can remove those characters from the beginning that are automatically prepended to it.

02:09.960 --> 02:13.650
So we can take world name dot remove from start.

02:13.650 --> 02:18.540
This is just an f string utility function and we're going to remove those characters.

02:18.540 --> 02:22.560
We can get those because they're called streaming levels prefix.

02:22.560 --> 02:27.540
Once we remove those from the start we now have the actual name of the world.

02:27.540 --> 02:29.640
This is that map asset name.

02:29.640 --> 02:34.530
So once we've done that we can actually get our save slot data.

02:34.530 --> 02:41.520
Now to get our save game data according to which slot we're using, we need our game instance as the

02:41.520 --> 02:45.960
Or game instance has the load slot name and the load slot index, right.

02:45.960 --> 02:48.930
So we can get you or a game instance.

02:48.930 --> 02:55.890
We can call this aura GI for aura game instance, and we'll cast to aura game instance.

02:55.890 --> 03:01.230
So you or a game instance here we're going to cast the result of get game instance.

03:01.230 --> 03:04.560
So here we have an aura game instance.

03:04.560 --> 03:05.730
We can check it.

03:05.730 --> 03:06.810
It should never be null.

03:06.810 --> 03:08.760
We can just check aura GI.

03:08.910 --> 03:15.660
Now once we have the game instance we can check to see if we have valid save game data.

03:15.660 --> 03:24.690
We can say if you load screen save game we'll call this save game equals get save slot data.

03:24.870 --> 03:31.020
We can get the save slot data using the load slot name and index from aura GI.

03:31.020 --> 03:41.700
So aura GI, the game instance has the load slot name, and aura GI also has the load slot index.

03:41.700 --> 03:46.920
So if this pointer is valid, we now have our save game data.

03:46.920 --> 03:51.690
Now we're ready to start saving things to our save game data.

03:52.200 --> 03:59.790
Now the first thing I want to do is take that save game object and see if it has the current world already.

03:59.790 --> 04:08.580
Because remember load screen save game has a t array of saved maps and each one has the map asset name.

04:08.580 --> 04:09.060
Right.

04:09.060 --> 04:16.290
And if we're going to take this saved maps array and add to it, we need to see if it already contains

04:16.290 --> 04:19.080
an F saved map for the current world name.

04:19.080 --> 04:20.670
So we're going to check that.

04:20.670 --> 04:24.600
We're going to say if not save game.

04:24.720 --> 04:29.190
And we're going to call that has map function passing in the world name.

04:31.030 --> 04:37.900
If not has map, then that means we haven't saved data for this particular world yet, and we need to

04:37.900 --> 04:42.610
add a saved map to our saved games array of saved maps.

04:42.610 --> 04:48.040
So we're going to make a new F saved map called New saved map.

04:49.410 --> 04:51.540
And we need to add to this.

04:51.540 --> 04:54.330
We need to take new saved map.

04:54.330 --> 04:58.800
First of all, we can set the map asset name because we have world name right?

04:59.340 --> 05:02.190
And then we can take our save game object.

05:03.290 --> 05:10.880
Take it saved maps array and add our new saved map to it.

05:11.390 --> 05:18.530
So we're adding an F saved map to our saved maps array and the save game object for this slot name and

05:18.530 --> 05:19.370
index.

05:19.370 --> 05:21.500
This is all for the first time.

05:21.500 --> 05:23.570
Now what do we do after that?

05:23.570 --> 05:29.690
Because so far we've just added a new f saved map with an empty array actor, right?

05:29.690 --> 05:33.110
An empty array of F save actors?

05:33.110 --> 05:41.210
Well, we need to populate that array of saved actors with any actors in our world that we wish to save.

05:41.210 --> 05:46.670
Now, how are we going to distinguish which actors we should save and which actors we shouldn't?

05:46.760 --> 05:54.170
A perfectly fine way to distinguish between actors we should save and actors we shouldn't is to implement

05:54.170 --> 05:55.460
an interface.

05:55.460 --> 05:58.070
Now, interaction doesn't have any interfaces.

05:58.070 --> 06:05.750
We haven't created any for saving the game or for distinguishing actors that should be saved actors.

06:05.750 --> 06:07.880
So we should create a new interface.

06:07.880 --> 06:10.460
Let's go ahead and compile and launch the editor.

06:10.460 --> 06:14.510
And what I'd like to do while I'm here in the editor is quite quick.

06:14.510 --> 06:21.830
I'd like to go to C plus plus classes or a public interaction and make a new interface class simple.

06:21.830 --> 06:25.010
Let's scroll all the way down to unreal interface.

06:25.010 --> 06:26.030
Click next.

06:26.030 --> 06:31.280
We'll put this in interaction and we'll call this save interface.

06:31.280 --> 06:35.270
We'll create the class and close the editor right back down.

06:35.450 --> 06:36.290
That's it.

06:36.290 --> 06:41.360
And we can check all the actors in the world to see if they implement the save interface.

06:41.360 --> 06:47.030
Now, so far, no actors implement the save interface, but there's one that I can think of that I'd

06:47.030 --> 06:48.860
like to have this interface.

06:48.860 --> 06:50.600
That's my checkpoint class.

06:50.600 --> 06:57.590
Why don't I just go over to checkpoint dot H and implement the interface here we can say public, I

06:57.620 --> 07:03.170
save interface and of course that header is auto included right there.

07:03.500 --> 07:06.350
So now our checkpoint implements the interface.

07:06.350 --> 07:09.890
And we know that our checkpoints are something we can save.

07:09.890 --> 07:12.470
So back to our A game mode base.

07:12.470 --> 07:17.720
Let's get all the actors and filter out any that don't have that interface.

07:18.380 --> 07:26.270
Now what I'd like to do first is I'd like to take my save game object and call the function I created

07:26.270 --> 07:33.200
called Get Saved map with name passing in world name, because this is going to give me the F saved

07:33.200 --> 07:35.720
map that contains that array.

07:35.720 --> 07:40.850
And each time I save the world state, I'm going to want to empty this array.

07:40.850 --> 07:42.800
I'm going to want to clear it on out.

07:43.460 --> 07:47.240
So what I'll do first is I'll make a copy of it.

07:47.240 --> 07:56.300
I'll say F saved map, saved map, and I'll get this f saved map as a copy and I'll empty the copy.

07:56.300 --> 07:58.850
I'll say saved map dot saved actors.

07:58.850 --> 08:04.670
Remember, it's an F saved map which contains a saved actors array.

08:04.670 --> 08:06.380
I'm going to empty that array.

08:07.060 --> 08:09.940
Now it may already be empty, but it may not.

08:09.940 --> 08:11.980
So I'm going to make sure to clear it out.

08:11.980 --> 08:14.470
I'm going to say clear it out.

08:14.650 --> 08:16.480
We'll fill it in.

08:18.410 --> 08:20.030
With actors.

08:20.360 --> 08:24.500
Now I'm going to say quote and unquote actors, right.

08:24.500 --> 08:28.940
Because we're filling it in with f saved actors, right?

08:28.970 --> 08:31.250
Not real actors, not pointers.

08:31.250 --> 08:32.720
We can't save pointers.

08:32.720 --> 08:33.290
Now.

08:33.290 --> 08:39.050
The way we're going to do this is we're going to iterate over all the actors in the world.

08:39.050 --> 08:42.290
That's okay to do it once in a while.

08:42.290 --> 08:44.990
Like every time we reach a checkpoint, that's totally fine.

08:44.990 --> 08:51.740
In fact, we most likely won't even feel a glitch or anything unless there are lots of actors.

08:51.740 --> 08:57.170
And I mean a lot of actors doing this in C plus plus should be totally fine.

08:57.170 --> 09:03.710
But if you wanted to optimize this, the way to do it would be to store an array of actors that you'd

09:03.710 --> 09:11.810
like to save and add actors to that array each time you change their state somehow, and then iterate

09:11.810 --> 09:16.790
over that smaller subset of actors instead of all the actors in the world.

09:16.790 --> 09:23.690
That being said, it's not going to be that performance intensive to just iterate over all the actors

09:23.690 --> 09:24.080
in the world.

09:24.080 --> 09:29.060
I'm just pointing out that you could optimize it so that you don't iterate over all of them.

09:29.480 --> 09:32.060
So how do we iterate over all of them?

09:32.420 --> 09:40.790
Well, we can do a for loop and we can use an iterator, an F actor iterator that can take in a world.

09:40.790 --> 09:45.650
So we do it by saying f actor, iterator it.

09:45.680 --> 09:49.490
Now f actor iterator needs an include.

09:49.490 --> 09:56.480
We're going to include it and its engine utils dot h that contains the f actor iterator.

09:56.480 --> 10:04.430
We can create one here in our for loop passing in the world, because an f actor iterator can take in

10:04.430 --> 10:05.390
the world.

10:05.390 --> 10:09.380
And what we'll do for this for loop is we'll say it.

10:10.260 --> 10:11.580
Plus plus it.

10:11.610 --> 10:18.300
This is the syntax to iterate over all the actors in the world via the f actor iterator.

10:18.300 --> 10:25.710
Now the f actor iterator is going to contain a pointer to an actor for each iteration of this for loop.

10:25.710 --> 10:32.850
And to get that actor, we say actor star actor equals dereferenced iterator.

10:32.850 --> 10:35.550
And of course it's a actor, right?

10:35.760 --> 10:38.850
There's an A in front of all a actor types.

10:39.060 --> 10:43.740
Now this for loop will loop over all the actors, literally every single one.

10:43.740 --> 10:45.210
And we can filter them out.

10:45.210 --> 10:48.930
First of all, if it's pending kill, we don't want it.

10:48.930 --> 10:53.610
So if it's not valid, we can say not is valid actor.

10:55.120 --> 10:58.960
That's going to be a condition to not do anything.

10:58.960 --> 11:07.300
But we'll also have an Or here and say not actor implements because we want to see if it implements

11:07.300 --> 11:09.970
the you Save interface.

11:13.010 --> 11:16.670
Now, if either of these is true, we can simply continue.

11:16.670 --> 11:21.350
Continue will say skip the rest of this iteration of the for loop.

11:21.350 --> 11:24.320
Go to the next actor and test that one.

11:24.830 --> 11:33.440
So if we reach down beneath this line, then we've found a valid actor that implements the save interface.

11:34.400 --> 11:45.680
Now, what we can do in this case is we can make a new f saved actor called saved actor, and we can

11:45.680 --> 11:49.340
start to fill this saved actor struct in with valid data.

11:49.340 --> 11:53.720
We can take saved actor dot actor name.

11:54.140 --> 12:01.130
And if we want to get an actor name that will be distinguishable from other actors of the same type

12:01.130 --> 12:05.810
in the world, then we can use actor get f name.

12:08.000 --> 12:12.800
And we'll be safe to use that now, if we like, we can save the transform.

12:12.800 --> 12:16.520
We can say saved actor dot transform.

12:17.460 --> 12:19.110
Equals actor.

12:19.230 --> 12:20.850
Get transform.

12:21.510 --> 12:22.830
If you'd like to save that.

12:22.830 --> 12:26.580
My checkpoints aren't movable and I don't want to set their transforms.

12:26.580 --> 12:31.200
But just as an example, you could save the transform.

12:31.200 --> 12:38.670
And now comes the part where we were talking in the last video about how you can serialize the data

12:38.670 --> 12:41.550
in an actor into bytes.

12:41.550 --> 12:44.610
Remember when we created the saved actor?

12:44.610 --> 12:50.700
We created bytes, a t array of uint eights, in other words, an array of bytes.

12:50.700 --> 12:57.270
And we said that we can serialize variables from this actor if they have the save game specifier.

12:57.270 --> 13:00.000
Well, first of all, let's see what that looks like.

13:00.000 --> 13:04.620
Let's go to our checkpoint and give our checkpoint a variable.

13:04.620 --> 13:06.720
We'll make it a public variable.

13:06.720 --> 13:13.290
Let's say bool be reached and set it to false and give it a uproperty.

13:13.290 --> 13:18.330
Now reached as in this checkpoint has been reached already, right?

13:18.330 --> 13:22.800
Let's say if this boolean is reached, we want to save that data.

13:22.800 --> 13:24.240
We want it to be remembered.

13:24.240 --> 13:27.630
So next time we go to the world, it's already glowing.

13:27.630 --> 13:29.280
It's already deactivated.

13:29.280 --> 13:32.610
We can't overlap with it anymore and it's lit up.

13:32.700 --> 13:39.720
Now, to be able to serialize this variable, we give it save game as a uproperty specifier.

13:39.720 --> 13:47.340
And yes, you can still give it other specifiers like blueprint read only for example, and edit anywhere.

13:47.340 --> 13:53.940
Anything you like, but save game is what makes it serializable with the method we're about to use.

13:54.120 --> 14:01.560
So now that checkpoint has a boolean with save game, let's go about serializing that data.

14:02.420 --> 14:08.630
Now, the first thing we're going to do is create something called a memory writer.

14:08.630 --> 14:11.900
An f memory writer.

14:11.900 --> 14:14.420
We're going to call this memory writer.

14:16.180 --> 14:20.980
And we're going to initialize it by passing in that byte array.

14:20.980 --> 14:24.970
We're going to take saved actor dot bytes and pass that in.

14:24.970 --> 14:27.400
Now what is a memory writer.

14:27.400 --> 14:31.090
Let's right click on it and go back to its definition.

14:31.090 --> 14:33.100
We'll go to Declaration or Usages.

14:33.100 --> 14:35.950
And it takes us to memory writer dot H.

14:36.250 --> 14:44.410
Now it says memory writer and memory writer 64 are implemented as derived classes rather than aliases,

14:44.410 --> 14:46.870
so that forward declarations will work.

14:46.870 --> 14:56.110
And it shows that f memory writer is a public t memory writer 32 t indicates a template, and f memory

14:56.110 --> 15:01.090
writer is simply a t memory writer, using 32 as the template argument.

15:01.090 --> 15:08.290
And if you were wondering if this was pertaining to the size, notice there's an F memory writer 64,

15:08.290 --> 15:12.940
which is using T memory writer passing in 64 instead of 32.

15:12.970 --> 15:17.020
Now we can right click on T memory Writer and we can go to that one.

15:17.020 --> 15:19.630
We see that it's still here in memory writer.

15:19.630 --> 15:26.020
And the comment says archive for storing arbitrary data to the specified memory location.

15:26.020 --> 15:29.830
Now we have experience with archives already in.

15:29.830 --> 15:39.100
Net serialize we've experienced the FR archive type T memory writer is a template class with an index

15:39.100 --> 15:45.010
size, and it's based on or derived from F memory archive.

15:45.010 --> 15:47.830
Now notice it has a static assert at compile time.

15:47.830 --> 15:53.440
It asserts that the index size is either 32 or 64, and it says only.

15:53.440 --> 15:58.900
These are supported 32 bit index size and 64 bit index size.

15:58.900 --> 16:01.510
Now what's an F memory archive?

16:01.510 --> 16:05.860
Well, if we go to the definition of this one it's an F archive.

16:05.860 --> 16:09.640
It's just a child class of the F archive class.

16:09.640 --> 16:13.450
It's a base class for serializing arbitrary data in memory.

16:13.930 --> 16:20.260
Now usually structs have F's, but it turns out that the f skive and the F memory archive.

16:20.260 --> 16:22.120
Well, it looks like they're classes.

16:22.120 --> 16:23.710
It doesn't matter though.

16:23.710 --> 16:26.140
They're used for serializing data.

16:26.140 --> 16:28.120
So we can use an archive.

16:28.120 --> 16:33.130
We can use a memory writer to archive our saved actors bytes.

16:33.250 --> 16:39.880
Basically, what we're going to do is take this saved actor dot bytes this array.

16:39.910 --> 16:45.550
Stick it into memory writer so that the memory writer has a reference to it.

16:45.550 --> 16:55.540
And then we're going to use an F object and name as string proxy archive I'm going to call this archive.

16:57.120 --> 17:04.350
And initialize this with our memory writer, and it also takes a bool called b in load.

17:04.350 --> 17:10.860
If find fails, which says indicates whether to try and load a raft object.

17:10.860 --> 17:14.190
If we don't find it, we can pass true in for that.

17:15.850 --> 17:21.640
Okay, so what's this other archive we know f memory writer is a descendant of the F archive.

17:21.640 --> 17:25.990
But what's an f object and name as string proxy archive?

17:25.990 --> 17:34.030
Well, it's another archive, but it's designed to serialize a you object and f names if we have them.

17:34.030 --> 17:41.590
So we can serialize an actor, for instance using f object and name as string proxy archive.

17:41.590 --> 17:45.670
I would like to use this to serialize my actor.

17:46.440 --> 17:49.020
And what I can do is take my archive.

17:51.120 --> 17:52.590
And there's a boolean.

17:52.620 --> 17:55.770
R is savegame.

17:57.000 --> 18:02.520
We're going to set that to true because we're serializing this through a save game object.

18:02.520 --> 18:08.400
So the archive here can be set to be compatible with a save game object.

18:08.400 --> 18:14.280
And once we have this we can take our actor and call serialize on it.

18:14.280 --> 18:18.360
Actors have a serialize function that takes an archive argument.

18:18.360 --> 18:21.390
And we know that our archive is an F archive.

18:21.570 --> 18:28.110
We can pass that in, and serialize is going to serialize the actor into the archive.

18:28.110 --> 18:33.180
Now, once we're done with that, we can take our saved map and remember what our saved map is.

18:33.180 --> 18:36.060
It's our f saved map that we created up here.

18:36.510 --> 18:42.450
So it has its own saved actors array and we can add to it.

18:42.450 --> 18:48.210
We can even add unique because we overloaded the double equals operator for the f saved actor.

18:48.210 --> 18:51.000
And we can put our saved actor in here.

18:51.000 --> 18:53.730
We can add that to the array uniquely.

18:53.730 --> 18:58.380
So what does all this have to do with the saved actor?

18:58.380 --> 19:03.120
Well, we pass in the saved actors bytes into this memory writer.

19:03.120 --> 19:05.610
And remember, the memory writer is right here.

19:05.610 --> 19:09.360
And it does have the capability to take in a byte array.

19:09.360 --> 19:14.400
Now it's going to take that byte array and it's going to write to the memory in that byte array.

19:14.400 --> 19:20.160
That byte array exists in our saved actor, which we're adding to our saved map.

19:20.160 --> 19:24.330
And this we plan on adding to our save game object.

19:24.330 --> 19:31.800
So once we're done with this whole for loop, we can then take that saved map we created and we can

19:31.800 --> 19:39.840
find on our save game object which F saved map has this world name and we can replace it.

19:39.840 --> 19:44.760
We know that the saved maps has an F saved map with this world name.

19:44.760 --> 19:46.230
We made sure of that here.

19:46.230 --> 19:51.900
Either it didn't have one and we added one, or it does have one, and we're going to replace it.

19:51.900 --> 20:06.240
We're going to say for f saved map reference, we'll call this map to replace in save game saved maps.

20:07.200 --> 20:08.700
And we'll say if.

20:09.480 --> 20:12.630
Map to replace dot map.

20:12.630 --> 20:15.810
Asset name equals world name.

20:18.120 --> 20:26.310
If that's the case, we're going to take map to replace and set it to saved map, because we've just

20:26.310 --> 20:31.560
now filled that in with fresh new data, and we're going to clear out the old data.

20:31.560 --> 20:35.820
We can do this because we're using a reference here, a non-const reference.

20:35.820 --> 20:38.430
So we're going to replace that data right here.

20:38.430 --> 20:44.340
Now that our save game object has fresh new data, we can actually save the game.

20:44.340 --> 20:51.660
So after this for loop we can use you gameplay statics save game to slot.

20:52.890 --> 21:01.530
Passing in the save game and the slot name and slot index, which we know we have thanks to our game

21:01.530 --> 21:09.090
instance, so we can copy our game instance, load, slot name and index and we can paste them in right

21:09.090 --> 21:09.660
there.

21:10.410 --> 21:16.920
So now we have a function to save all the actors in the world, at least those that have implemented

21:16.920 --> 21:20.970
the save interface, which are just the beacons for now.

21:20.970 --> 21:24.180
But we have the ability to serialize their data.

21:24.180 --> 21:32.580
So now we know that we're serializing any variables that contain the save game Uproperty specifier.

21:32.700 --> 21:35.940
The question now is how do we load that data?

21:35.940 --> 21:38.880
Well, we'll be doing that in the next video.

21:38.880 --> 21:40.380
So stay tuned.

21:40.380 --> 21:45.540
We'll create a load world state function and save and load our data.

21:46.050 --> 21:47.040
I'll see you soon.
