Welcome back.
Now we're going to be creating a menu system for saving and loading the game.
And this is going to involve some new technology, a new plugin for using the mVVM software architecture
pattern.
And this project was initially created in Unreal Engine 5.2.
And as soon as 5.3 came around, it broke the project.
So there were changes made to the mVVM plugin between 5.2 and 5.3.
Now, the changes required to make this compatible with 5.3 are going to be outlined in this video.
They're not huge changes.
It's not a huge refactoring of the system by any means.
It's just a couple additional pieces of code that we have to add.
But this video is going to outline those changes.
And as this video was created after the course was completed, it's going to be all about making those
fixes to upgrade this to be compatible with 5.3 from the point of view of the finished project.
So the answers are all here in this video.
So if you're using 5.3 and above and you're following along with the creation of this menu system and
you're getting blueprint runtime errors, well, the answers are all in this video.
And you're going to come back to this video to figure out how to fix them.
And this is just something that we have to deal with with rapidly changing software, especially when
using plugins that are in the beta stage.
So the good news is we do have a system that works in 5.3.
There are just a couple of things that need to be changed from the original version of the project.
So the best thing to do is to create this system, follow along with the videos and as the errors pop
up, refer to this video to see why those errors pop up and how to fix them in order to make the system
work in 5.3.
And it's a really cool system.
We can make a new game, we have save game slots here, and we can enter our name to create a new slot.
We have a default map for our very first dungeon.
Of course, we're going to be able to save our most recently reached map and our level, and we're going
to later learn how to load in all of our abilities and stats.
We can select a slot and we can try to delete it.
We get an are you sure menu.
We can cancel that operation.
We can actually delete it from the Are you Sure menu.
We can now have a vacant slot.
We can enter our name again.
We can choose the new slot, select it, press play, and then we can actually start a game.
So it's quite a nice system that we have created.
And we just need to make sure that it works with 5.3.
So as you're developing it, if you run into blueprint runtime errors, jump back over to this video
and watch it to see the changes that you have to make.
Hello, everybody.
Welcome.
This is Stephen from the future here.
And I have my aura project in front of me.
And this project initially was created in Unreal Engine 5.2.
And in 5.3, some changes have been made to the ViewModel plugin, and those changes have somewhat broken
the project as we created it.
So some changes need to be made to the project to make it work in Unreal Engine 5.3.
So this video is going to go over those changes and talk about why those changes need to be made.
So I have my aura project, and this is the exact project that is on the GitHub repo.
Now I'm about to upgrade this project to Unreal Engine 5.3, as it's currently a 5.2 project.
So what I'm going to do is simply switch Unreal Engine version to 5.3.
I'm going to click okay.
It's now going to generate the project files, and we'll go ahead and open up the project as is and
see the problems.
And we'll go ahead and fix those issues.
So I'm going to go ahead and just open the project.
I'll go ahead and rebuild the aura modules now.
And I have my project loaded up and we're now in Unreal Engine version 5.3.
So let's see what's broken in 5.3 in our project and let's see how to fix it.
I'm going to press play.
I'm going to go to the play button and it looks like I have something saved here in the first slot.
I'm going to go ahead and just remove that now.
As soon as I hit stop, I see the errors that have been generated.
So we're going to talk about those.
But let's go ahead and just open up our saved folder and go to save games.
There's that load slot zero.
I'm going to go ahead and just remove that.
And now we'll start with no saved games.
So we have no games saved here.
I'm going to try to click plus to create a new game and I can't.
It's broken.
So let's go ahead and see what those errors are.
They're actually blueprint runtime errors.
Now what they say is access none trying to read property BP load screen view model.
So this is related to the view model.
And we got those errors when I tried to click on new game.
So the new game button pressed function accessed none for the view model.
And before we even did that the enter name and the vacant slots also had these errors where we got none
trying to read property calling this function set BP load slot view model.
If I click on that we're going to get this function set BP load slot view model.
Now this is an auto generated function that gets created when you add a view model to any given widget.
In our case the load slot enter name widget has a view model.
If we go to the designer tab, we can see that it has its view models here, and it has a load slot
view model.
And we gave it the load screen view model.
Now the reason that function was accessing none is because we were unable to successfully set the view
model for this particular widget manually.
If we select our load slot view model, we'll see that its creation type is manual, which means that
that view model is created somewhere externally, and the view model should be set manually for this
widget.
And we're doing that in the widget switcher.
So when we try to set the view model manually for this, we're failing to do so.
Now if we go to that widget switcher it's our WB load slot.
Widget switcher we have a function on this class.
It's called initialize slot.
And what this function is doing is it's calling find load screen view model.
This accesses the load screen view model and it's calling our blueprint callable function get load slot
view model by index.
And those load slot view models are being created.
We're creating them in C plus.
Plus we can see where that's happening if we open up our project in our IDE.
We're actually creating those load slot view models right here in initialise load slots in the load
screen view model.
Here in UVM load screen, we're using new object to create those load slots.
So they are being created and we are successfully retrieving them here.
Each time the widget switcher tries to set the load slot view model on its child widgets the widgets
in the widget switcher.
Here it does have a valid load slot view model.
By this point, we can prove that by getting the name of that load slot view model, we can call get
object Name and we can use a print string, node, print string, and print the name of that view model.
So if I go ahead and play test the game by pressing play and going to this menu, we see those load
slot view models.
They are valid and they have been created.
So that's not the problem.
The problem is that we're failing to set the load slot view model here.
So why is that?
Why are we failing?
If we go to our message log and we take a look at these errors where we're accessing none and follow
them to this function, the set BP load slot view model.
This is where we're failing.
We're actually not able to set that view model.
And that's this function here.
Set BP load slot view model.
Why is that failing.
Well the answer is tied to the change in 5.3.
And it's because we have a view model assigned to these widgets without any field notifies bound on
those widgets.
So we can go to the load slot, enter name here and we can check out here.
In the designer, we can see that we do have a couple of view models assigned, but for the load slot
view model it's creation type is manual, and we're failing to manually assign it because there are
no field notifies bound to any widgets on load slot enter name.
We can click on view bindings and see that there are no bindings for this view model.
So the fix is quite simple.
We have to have at least one field notify bound on this widget, and we can basically bind really any
of them as load slot view model has a few field notifies depending on where you are in the course.
As you watch this video, you may not have field notifies just yet, but we can easily add a field notify
to this view model class in C plus plus and make sure that any widgets that have this view model assigned
which are using the manual creation type, at least bind to one field notify on that class.
So we need to add a field notify to this class.
So what I'm going to do is close the editor.
And I'm going to go back to that class.
The load slot view model mVVM load slot.
Here's the CPP.
And here is the H file.
And we just need a field notify that we can bind to.
And it could be really anything.
We could even turn load slot name into a field notify.
Now if we do that, if we turn load slot name into a field notify, we're now going to need to create
a getter and setter for it, just like our other field notifies down here.
But that's not a big deal.
We can do that.
So what I'm going to do is take load slot name.
I'm going to control X to cut it out of the public section.
And I'll go ahead and just move it down to the private section with our other field notifies.
And I'll go ahead and also give it the same view property that the other field notifies have.
That way it's now a field notify.
And with getter and setter.
In the Uproperty macro we need public getters and setters, which means anywhere in our project where
we're accessing load slot name as if it's a public variable that's now going to need to access the public
getter for it that we're about to create.
No big deal.
We can do that.
So let's create the getters and setters first.
The setter.
It's going to be void set load slot name.
And this is an F string.
It's going to take in an f string called in load slot name.
And we'll go ahead and generate the definition.
And we have to make sure that if this is a field notify that we're using our macro mVVM, set property
value.
And that will broadcast that field notify when it changes via this setter function.
So the first input is the name of that field notify variable load slot name.
And the second is the value that we're going to broadcast.
It's going to be the input to this setter function.
And again don't worry about those red squiggles.
This is going to compile just fine.
This is one of the few things that Ryder doesn't recognize but it's not an error.
So we have the setter.
Let's make that getter as well.
It's going to return an F string.
It's going to be get load slot name.
It'll be const and it'll simply return load slot name.
So we've now turned this load slot name into a field notify.
Now this will create errors as we're making this a private variable now.
So anywhere where we're trying to access this as if it were public that's going to need to change.
We're going to need to call the public getter now instead.
And then we need to decide when we want to actually broadcast the load slot name as well.
So first let's try to compile and find all those errors where we're accessing load slot name when it's
inaccessible.
Okay.
So here they are.
The first one.
We'll double click on it.
And that's going to be in the game mode where we're accessing load slot name.
Here.
All we need to do is call the public getter instead.
So we're going to say get load slot name.
That's a function call.
We'll go ahead and replace the variable access with the getter.
Now we'll do that here.
We'll do that here.
So that fixes three of those errors already.
And if I scroll down to where I see a red line here, I can see the next violation of this where we're
trying to access it here.
We'll change that to the getter as well.
Now in my error log, we can see that we're making this violation in the game mode base.
We fixed those but we're also trying to access it from mVVM load screen.
Let's go to that.
And we see that in initialize load slots we're actually setting these load slots accessing that public
variable which is now private.
So the way that we can set it is now through that setter we created which will actually broadcast that
field notify.
So any widgets that bind to it will receive that string value.
So this time instead of replacing it with the getter, we're going to replace it with the setter set
load slot name.
We're going to set the value of the load slot name like that.
So we're going to do that for all three load slots.
We're going to replace load slot name with set load slot name calling the function now.
So there's the second one.
Here's the third one.
And now we can just quickly try to compile and see if that fixes that issue.
Looks like we're trying to do it in mVVM load screen elsewhere.
Here it is.
We're trying to access it in order to set the load slot name on the game instance.
We're going to replace that with the get load slot name there.
And if I look at the scroll bar, there are a few other instances of this.
We're going to change this from load slot name to get load slot name in these cases as well.
We're just replacing this with the getter as that is now a private variable.
Let's try to compile again.
I'm just going to run in debug mode and we have a successful compile.
Now what we can do is we can go to all of those widgets that have the view model load slot, enter name
is one of them.
They all have the load slot view model with creation type set to manual.
As soon as we bind to one of the field notifies on it, we'll now successfully be able to set that view
model manually, so we'll start with the load slot enter name, but I'm going to get all three of them
open.
We have enter name we have taken I'll get that one open as well.
And we have vacant I'll get that open as well.
And we'll make sure that each of these has at least one field notify bound.
Now the take in already does.
If we go to view binding, we'll see that we're binding to the player name map value level value.
So that one isn't violating that.
But we'll just go ahead and add a text widget to each of these and bind to the load slot name anyway.
And then we'll have something that shows the load slot name.
And for a package build we can just hide that widget.
So let's do it.
We're going to go to load slot enter name.
And I'm just going to find a text.
I'm going to add a text widget to this.
I'm going to bring it on over to the vertical box.
And I'm going to stick it right there in the vertical box.
We can have it on the top.
Now I placed it just under vertical box.
But notice if I hover my mouse cursor here there are two spaces here.
That blue line has an arrow to the left pointing up and an arrow to the left pointing down.
If I do the one pointing up, it's going to be right here on the vertical box.
And if I set its vertical alignment to bottom, it's going to be down there.
Now this is just to get a field notify bound here.
So I'm going to make the text kind of small.
And I'll set its color and opacity to something like magenta and change the font.
I'm going to change it from bold to light.
So it's nice and light.
I'm going to set its size to something smaller like 12.
So we just have text there.
I'm going to set this text to by default say load slot name.
And I'm going to change its name from text to text underscore load slot name I'm going to check is variable
on it.
And I'm going to bind it to a field notify.
So I'm going to go to view bindings.
And because I have it selected if I click the plus here it's going to add it.
For this particular widget I'm going to bind its text property.
Select that I'm going to use a conversion function.
So conversion functions it's going to be two text from string.
And I'm going to choose the field notify on load slot ViewModel.
And I'm going to choose Load slot name I'm going to select that.
And now we have at least one field notify bound to load slot ViewModel.
So I'm going to compile and save that.
And I'm going to go over to the taken slot which we know already has field notifies bound.
But I'm going to just add a text widget and bind it to load slot name anyway.
And this will be a sort of debug for us that we can check to see the load slot name and make sure it's
correct.
So I'm going to add a text to this one.
I'm going to add it to the vertical box route.
I'm going to hover so that that blue arrow is pointed upwards.
So it's right there.
It's not going to mess up the padding or alignment of anything.
And I'm going to get that new text block.
I'm going to rename it to text load slot name.
I'm going to check is variable.
I'm going to set vertical alignment to bottom.
I'm going to set its color to a magenta color, change its font to light and a size of 12.
And now we have this one.
I'm going to go ahead and just create a binding to that.
So I'm going to click View Bindings.
Add a new binding I'm going to choose its text property.
I'm going to set it to a conversion function to text from string and choose on load slot view model
the load slot name.
And now this one is bound.
I'm going to compile and save that.
And then I'll go to the vacant widget.
So here in the vacant widget I'm going to find text.
I'm going to put it right here on the vertical box using that up arrow so it doesn't mess up my padding.
And I'll go ahead and select that rename it to text underscore load slot name check is variable.
Change it to a magenta color.
Change its font to light with a size of 12.
Set its vertical alignment to bottom so its down there and we'll go ahead and bind it.
View bindings.
We'll add the binding.
We'll choose its text property, and we'll use a conversion function to text from string and choose
on the load slot view model our load slot name.
We'll select that and now it's bound.
And now I'm going to go ahead and save all.
I'm going to press play.
And I'm going to go to the load menu.
And we see load slot zero, load slot one and load slot two.
As we did broadcast this field notify from C plus plus in mVVM load screen in the initialize load slots.
That's because we called the setter which results in the broadcast via that you mVVM macro.
And if we click plus it works.
If we enter a name, it works.
If we select a slot, we can select it, but it looks like our widgets aren't quite properly working,
so we'll need to see what's broken there.
But if I hit stop, we've taken care of lots of errors.
Now we still have one.
We access none.
Trying to read property BP load screen view model.
This is different.
This is not the load slot.
View model.
That's the load screen view model here in assign slot selected.
And that's on WB load screen.
So if we click on assign slot selected we'll see here that the BP load screen view model isn't valid
here.
We need to see why.
So here on the load screen widget WB load screen.
If we click on load screen view model we're using view model property path here.
So why are we not getting a valid view model here.
Right here on assign slot selected.
Why isn't it valid by this point.
Well let's see we're in event blueprint initialize widget.
And we can just check to see if this is a valid view model.
At this point we can use is valid and we can hook up is valid to what it was doing.
But we can use the is not valid.
We could print a string here and we can say.
No valid load screen view model.
And let's see if we get that print string here.
Let's go and press play.
It says no valid view model.
So that's not valid okay.
So where are we setting the Wbhp load screens view model.
We know that it's set by property path.
Let's go to the HUD folder and open load screen HUD.
So we see here that we're creating our load screen widget.
And we're calling add to viewport and then calling blueprint initialize widget.
So let's see what happens if we just replace this BP load screen view model with our getter find load
screen view model.
Let's see if we can get it from that.
Let's see if it's valid.
We'll go ahead and hook that up.
We'll hook it up to the is valid check and we'll hook it up to the binding here okay.
So now we can test everything.
We can take a slot and delete it okay.
So the delete isn't working.
And delete button pressed.
If we go to it we're using BP load screen view model here.
We're going to replace this with our find load screen view model.
And we'll use it straight from that.
And that way we shouldn't be using something that isn't valid.
Let's give that a try.
We'll select a slot.
We'll delete it delete.
It works.
Let's create a new slot.
Select it delete it.
It works.
Let's create a new slot with a name.
Select it.
Let's click play okay.
So play isn't working either.
And that must mean that that view model isn't going to be valid at that point either.
We'll just go ahead and just find load screen view model at that point so we can go ahead and play test
again.
Select it press play and we're able to play.
And we're loading all of our properties.
We're preparing shaders.
That's why we can't see that fireball yet.
Now we can see those effects.
And it looks great.
Now does it ever become valid.
Well we can check that.
We can check an event tick.
We can take that BP load screen view model, get BP load screen view model.
And we can check is valid in tick and see if it ever does become valid.
Let's go ahead and just print a string.
If it is valid we'll say valid view model.
And if it's not valid let's print not valid view model or no valid view model.
Let's go ahead and press play okay.
So it never actually becomes valid.
We can go ahead and remove event tick there.
So why isn't it actually being set.
Well let's see what happens if we create a field notify and bind to it.
Here I'm going to go ahead and close down the engine and go to mVVM load screen.
And we're going to add a field notify.
I can just steal one from our mVVM load slot.
And an NVMe load screen.
We'll just go ahead and add a new field notify.
And this can be simple.
Let's make it an INT 32.
And we'll just call it num load slots.
This way we can see visually how many slots we have.
And we'll create public getters and setters for it.
We'll have a void set num load slots that takes in int 32 called in num load slots, and we'll create
an int 32 returning getter get num load slots, const return num load slots.
And we'll generate the function definition.
And we'll go ahead and steal this macro.
We'll call that macro using num load slots and setting it to in num load slots.
Now we have a field notify and we can simply broadcast that field notify right up here.
After we initialize load slots we'll call set num load slots.
And we'll go ahead and get our load slots map.
And call num on it.
That'll give us the number of elements in that map.
Now that we have a field notify, let's bind to it.
We'll go ahead and open up our asset editors, and we'll just add a text widget up here at the top.
We'll call this text underscore num slots.
And I'll just go ahead and wrap this in a horizontal box.
And we'll add another text to this.
And the first one is just going to say num slots.
We'll go ahead and make it smaller.
Let's make it 16 with light font.
And then our text num slots will make it light font 16 as well.
We'll give it a magenta color and it's going to say num slots.
I'll make my horizontal box a little bigger and I'll take my num slots.
Check is variable and we'll view bindings.
We'll add a binding for it.
We'll bind to its text value.
And we're going to use a conversion function to text this time from integer.
And for the value we're going to bind it by clicking on the chain icon and selecting the load screen
view model and choose NUM Load Slots.
We'll have a minimum number of digits and a maximum number of digits.
We have some extra properties to set for an integer.
So now that that is bound, we have a binding to our load screen view model.
Let's see if we're successfully creating and setting our widget with the property path.
We'll just check right away.
In Event Blueprint Initialize widget, we'll get our BP load screen view model.
We'll get object name and we'll see if it's a valid name or if it's none.
So we'll print that string.
And we'll see what happens here.
So I'll press play press play again.
And we have a valid load screen view model.
We can select a slot.
We can press play and we can play the game.
So even if we're using a property path creation type we still are going to need at least one field notified
bound.
And I don't know if we even looked but let's press play.
We see num slots is three because we have three slots and we broadcast that value.
So WB load screen now has some debug text there for us.
So the moral of the story is that in 5.3 if you have a view model you need at least one field notify
bound to it.
So that's something that is now enforced.
Otherwise we won't be able to properly set the view model based on that creation type.
Now we don't have to leave this debug text.
We can hide it.
We can take its opacity and set it down to zero.
If we don't want to see that text in the game, that's not a big deal.
It's not going to cause us a whole lot of overhead so we can just go into our load slot, enter name,
get that text, set its opacity down to zero, we can get our load slot taken.
Same thing.
We'll hide that text and our load slot vacant.
We'll hide that text as well.
And that way each of these has a property.
It's updated, but we don't have to actually see it.
We're going to go ahead and we'll compile all of these make sure they're all compiled.
Now we don't see those invisible widgets.
So I'm going to go back to load slot taken and remove that event tick that was actually spamming the
screen.
We can now make sure that other print string is there and it works as well.
So now we will have a load screen view model that's valid on our load screen.
So we don't need to print the name of it anymore.
We know it's valid and we also don't need to call find load screen view model every time.
We can just get BP load screen view model and use that instead of calling that getter every time.
I'm going to copy that.
And we'll paste it here as well.
We'll undo that little Band-Aid.
And looks like I forgot to hook it up to the binding there.
Now we're good.
We can press play, select a slot, we can delete it.
We can create a new slot, select it, press play.
We can play the game.
Things appear to be working.
We can press the X.
We can go ahead and quit and come back.
Make a new slot.
Select it delete it.
Cancel delete delete.
New slot.
First dungeon, select it, press play and we can play.
So everything works now.
And now our project is working in Unreal Engine 5.3, so everything works.
And we are now compatible with the latest Unreal Engine version.
And our beautiful save system and menu system is all working properly.
I'm going to go ahead and just delete those slots so that I can commit this to the GitHub.
And you can see the project in a working state in all its glory.
Now one last thing before we wrap up that I want to point out is that there is a warning.
That says Detected Orphan Sound, which is not a one shot music.
Main menu.
Remember our main menu music is looping and sounds which are not one.
Shots need to have audio components or they risk being orphaned.
Well, we can easily fix that.
Let's go back to our main menu and we can check out where we're playing that sound that's going to be
in our actor here.
We can browse to this asset.
It's in blueprints main menu.
It's BP or a main menu.
We're calling Play Sound 2D.
And this sound is getting orphaned.
So instead of play sound 2D we're going to spawn sound 2D instead.
This returns an audio component and we can store that.
So it's not orphaned.
It won't be garbage collected.
And we can set it to music main menu by browsing to it and just setting it there.
We'll go ahead and set the sound.
We'll promote it to a variable and call this main menu music.
And that way we're storing that and then it shouldn't be orphaned.
And that'll take care of that issue.
We'll compile save that.
And we took care of that warning.
And we still get the sound it plays.
We get our music.
Everything looks great.
And one last error we have is this one input mode UI only attempting to focus non focus widget.
And we see that that is in our load menu.
Let's take a look at that.
And we're setting input mode UI only here we're trying to focus on a non focusable widget.
So we don't have to focus on that.
We can just remove that not pass in anything for widget to focus.
And we can go back to our main menu map, press play, go to our load menu.
And we should still have the ability to use our menu and everything works fine.
So that takes care of that issue okay.
So that's going to wrap up this video.
We've now converted the project and the mVVM system to work properly in 5.3.
That was a good learning exercise.
And I do apologize that the 5.2 version wasn't compatible with 5.3 until we made this fix.
Now that we know how to fix it, we know a little bit more about how the mVVM system works, and it's
a pretty nice system, and this is just what you have to deal with.
With rapidly evolving and changing software, it's always going to be updating.
Changing things become deprecated, and you have to make sure your software is compatible with the latest
version.
That's just part of development.
So that's going to be something that we always have to deal with in our careers as developers.
So excellent job and I'll see you soon.