Week 10 Lab - Gotta Cache em All V1.01
Week 10 Lab - Gotta Cache em All V1.01
WEEK 10 LAB
GOTTA CACHE ‘EM ALL!
Welcome to the Week 10 lab. This week we’re going to be jumping into both saving and loading, as
well as level streaming!
For this week, we’re going to start off with creating a simple progression system with some EXP,
then add the ability to save and load our game, complete with a pause menu and load button on the
main menu! After this, we’ll add a little bit of level streaming to our game to show you how to get it
working in case you wanted to use it for your assignments!
To clone our repository, first open up GitHub Desktop and skip the login screen, and enter your
name and Monash email address in the next screen.
Then, click the Clone a repository from the Internet button and then switch to the URL tab. Head to
the BeatEmUp project we made on GitLab last week, and click the Clone dropdown button and copy
the Clone with HTTPS link to the clipboard. Paste this into GitHub Desktop in the Repository URL
textbox, then set the Local Path to wherever you would like to clone the repository to. Hit the Clone
button.
If you are on the lab machines: We have to clone the repository to the actual machine we are
working on, instead of the Monash network drives. There a couple of places we can choose from, but
we suggest creating a new folder named Git inside of your Downloads folder. We create a new folder
because the folder we clone to has to be an empty one.
This will open up an Authentication failed window, enter your authcate as the Username and your
Personal Access Token we created earlier as your Password. Then hit Save and retry. If you have
forgotten your Personal Access Token, you can create another one with the instructions on Page 8 of
the Week 0 Supplementary.
2
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
2. Adding Progression
The first thing we’re going to do is add something more we can save, and we’ll do this in the form of
levelling up and EXP.
Open up Rider and add the following to a public section inside of BeatEmUpCharacter.h:
UPROPERTY(EditAnywhere)
int CurrentEXP = 0;
UPROPERTY(EditAnywhere)
int EXPToLevel = 50;
UPROPERTY(EditAnywhere)
float IncreaseMultiplier = 1.5;
Make sure to Right Click and Generate a Definition for our new function!
First we actually add to our CurrentEXP, and then we level up in a while loop. The reason for the
while loop is because if somehow we gain a bunch of EXP at once, we want to level up multiple
times rather than just once. We increase the amount of EXP we need to level up, as well as the
amount of damage we do, how much health we have, and we also give the player a full heal. Lastly
we refresh the UI so that our health updates.
As we never made our max health change, we need to add that to UpdateValues inside of
InGameUI.cpp:
3
MaxHealthText->SetText(FText::FromString(FString::SanitizeFloat(Player->MaxHealth)));
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
Open up Enemy.cpp and add the following to StopRagdolling, inside of the if statement before
the Destroy():
Player->AddEXP(EXPAmount);
The last thing we want to do is make it so we can actually see our EXP, so let’s add an EXP bar to
our UI!
All we’re doing here is the exact same thing as our health bar, by converting our EXP to a
percentage.
That’s it for the code (at least for now). Head back into Unreal and hit Compile but DON’T HIT
PLAY YET as it will crash! We need to add our EXPBar to our UI first!
Open up BP_InGameUI and add a Vertical Box with a Progress Bar inside of it to the Canvas
Panel. Rename the Progress Bar to EXPBar.
Click on the Vertical Box and under Anchors, select the very bottom left while holding down Shift
+ Control (Shift + Command for Mac). Next, change the Position X to be 200, the Offset Top to
be 100, and the Size X to be 250.
Lastly, select the EXPBar and change it’s colour to whatever you would like, I chose green.
Your UI should now look the same as the image to the right.
That’s it for now, give it a go and hit play, after killing an enemy, you should see your EXP go up, and
when you kill 3 of them, your health and damage should go up!
4
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
This is pretty simple, and exactly the same as all the other classes we have made across semester.
Head into the C++ Classes folder inside of the Content Drawer and Right Click to Create a New
C++ Class.
Go to All Classes and then find the SaveGame class to use as our parent class. Hit Next and then
name it BeatEmUpSaveGame and hit Create Class.
Once it finishes creating, open up Rider and then open the newly created BeatEmUpSaveGame.h
file, and then add the following two variables to a new public section:
UPROPERTY()
FString SaveSlotName;
UPROPERTY()
uint32 UserIndex;
These two variables, SaveSlotName and UserIndex, are the only two things we need to actually save
our game, and we’ll be using them later when actually saving our data to a file.
Next, we need to actually store all the things we want to save, such as the following player variables:
FVector PlayerPosition;
FRotator PlayerRotation;
float PlayerCurrentHealth;
float PlayerMaxHealth;
float PlayerCurrentEXP;
float PlayerEXPToLevel;
float PlayerPunchDamage;
On top of that, add the following for our enemy variables:
TArray<FVector> EnemyLocations;
TArray<FRotator> EnemyRotations;
TArray<float> EnemyCurrentHealths;
TArray<float> EnemyMaxHealths;
TArray<bool> EnemyRagdollStates;
TArray<FVector> EnemyMeshLocations;
TArray<FVector> EnemyMeshVelocities;
We store our enemy variables as arrays as we will likely have many different enemies that we need to
store the variables of, so this will let us do that.
First, let’s deal with a GameInstance. A GameInstance is a object that stays valid during the entire
game, no matter what level we’re on, and it’s a fantastic way to bring variables across multiple levels.
First, open the Content Drawer, go to the C++ Classes and Right Click to create a New C++
Class, this time, go to All Classes and select GameInstance as the Parent Class and name it
BeatEmUpGameInstance.
Once Rider is open, add the following to a new public section of BeatEmUpGameInstance.h:
Head back into Unreal and go to Project Settings, then under Maps & Modes, change the Game
Instance Class to be BeatEmUpGameInstance.
Make sure to Generate Definitions and Add Includes for Enemy and BeatEmUpSaveGame!
SaveGame->PlayerPosition = PlayerCharacter->GetActorLocation();
SaveGame->PlayerRotation = PlayerCharacter->GetActorRotation();
SaveGame->PlayerCurrentHealth = PlayerCharacter->CurrentHealth;
SaveGame->PlayerMaxHealth = PlayerCharacter->MaxHealth;
SaveGame->PlayerPunchDamage = PlayerCharacter->PunchDamage;
SaveGame->PlayerCurrentEXP = PlayerCharacter->CurrentEXP;
SaveGame->PlayerEXPToLevel = PlayerCharacter->EXPToLevel;
TArray<AActor*> Enemies;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AEnemy::StaticClass(), Enemies);
for(int i = 0; i < Enemies.Num(); i++)
{
AEnemy* CurrentEnemy = Cast<AEnemy>(Enemies[i]);
SaveGame->EnemyLocations.Add(CurrentEnemy->GetActorLocation());
SaveGame->EnemyRotations.Add(CurrentEnemy->GetActorRotation());
SaveGame->EnemyCurrentHealths.Add(CurrentEnemy->CurrentHealth);
SaveGame->EnemyMaxHealths.Add(CurrentEnemy->MaxHealth);
if(CurrentEnemy->GetMesh()->GetCollisionProfileName() == "Ragdoll")
{
SaveGame->EnemyRagdollStates.Add(true);
}
else
{
SaveGame->EnemyRagdollStates.Add(false);
}
SaveGame->EnemyMeshLocations.Add(CurrentEnemy->GetMesh()->GetComponentLocation());
SaveGame->EnemyMeshVelocities.Add(CurrentEnemy->GetMesh()->GetComponentVelocity());
}
Wow this is a big chunk of code! However even though there’s a lot here, it’s actually some really
simple logic.
We first get our PlayerCharacter and then set all of the save game variables we declared to be the
variables from this player. We then get all of the enemies in the world using UGameplayStatics
function GetAllActorsOfClass (Don’t forget an include here!) and then we loop through each of
them, adding their variables to each of the arrays in our save game object. We also check to see if
they’re currently ragdolling, and if they are, we set their ragdollstate to true, and if not, false.
7
That’s it for saving, we just need to store all of the variables from our save game, next is loading!
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
PlayerCharacter->SetActorLocationAndRotation(LoadedGame->PlayerPosition, LoadedGame->PlayerRotation);
PlayerCharacter->PunchDamage = LoadedGame->PlayerPunchDamage;
PlayerCharacter->CurrentHealth = LoadedGame->PlayerCurrentHealth;
PlayerCharacter->MaxHealth = LoadedGame->PlayerMaxHealth;
PlayerCharacter->CurrentEXP = LoadedGame->PlayerCurrentEXP;
PlayerCharacter->EXPToLevel = LoadedGame->PlayerEXPToLevel;
PlayerCharacter->InGameUI->UpdateValues();
Now that the load function is done, we actually need to load the game so this function gets called!
But before that, add the following line to the end of StopRagdolling inside of Enemy.cpp:
GetCharacterMovement()->GravityScale = 1; 8
All we’re doing here is just setting our gravity scale back to 1 so our enemies don’t float off!
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
5. Supporting Saving and Loading - Game Mode Alterations (Contd.)
Lastly, add the following to PostBeginPlay:
UBeatEmUpGameInstance* GameInstance = Cast<UBeatEmUpGameInstance>(GetGameInstance());
if(GameInstance)
{
if(GameInstance->bLoadSave)
{
UBeatEmUpSaveGame* LoadedGame = Cast<UBeatEmUpSaveGame>(UGameplayStatics::LoadGameFromSlot("TestSave",0));
if(LoadedGame)
{
Load(LoadedGame);
}
}
}
Less of a chunk of code here, here we get our game instance (Don’t forget an include!), we then check to see if we’re supposed to be loading our save, as
we might just be starting a new game and we don’t want to load our game if we haven’t hit the load button. If we are supposed to be loading, we call the
actual load function from UGameplayStatics and fetch the loaded game with the name “TestSave”, if that succeeds, call the load function.
That’s it for the code for this part, but we need to actually set our enemy class in the gamemode. Head back into Unreal and then hit Compile. Once it’s
done, Right Click on BeatEmUpGameMode and click Create Blueprint Class Based On and save it in Blueprints named BP_BeatEmUpGameMode.
Open up this new blueprint and set the Enemy Class to be BP_Enemy, then open the ThirdPersonMap under ThirdPerson->Maps, then change the World
Settings GameMode Override to be BP_BeatEmUpGameMode.
Add a new C++ Class, go to All Classes and this time inheriting from UserWidget, and name it PauseMenu.
Once Rider is open, add the following to a new public section in PauseMenu.h:
virtual void NativeOnInitialized() override;
UPROPERTY(meta = (BindWidget))
UButton* ContinueButton;
UPROPERTY(meta = (BindWidget))
UButton* SaveButton;
UPROPERTY(meta = (BindWidget))
UButton* QuitButton;
UFUNCTION()
void ContinueButtonClick();
UFUNCTION()
void SaveButtonClick();
UFUNCTION()
void QuitButtonClick();
We first override NativeOnInitialized to act as our begin play so we can bind our functions to our buttons, then we have a reference to 3 buttons, continue,
save, and quit, and then we make functions for each of those buttons.
9
Make sure you add an include for the Button and also Generate Definitions!
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
That’s it for this class, though we need to actually alter our character class in our to let us pause!
Make sure to generate a definition for our new function as well as add an include for PauseMenu!
This is the exact same thing we’ve been doing all semester, this binds our pause action to our pause
game function.
That’s it for this code, head back to Unreal and hit Compile!
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
Next, go into the UI folder and Right Click then select User Interface->Widget Blueprint, make
sure you choose PauseMenu under All Classes. Name the new widget BP_PauseMenu and then
open it.
This one is going to be pretty simple, first add a Canvas Panel, then add a Vertical Box inside of
this.
Click on the Vertical Box and then set its Anchor to be the bottom middle option (the one that
goes from the top to bottom of the middle of the screen). Lastly here set Size X to be 500 and the
Offset Top and Offset Bottom to be 100.
Next, add 3 Buttons with a Text inside of each. Select all three buttons, then set the Left and
Right Padding to be 50, and the Top and Bottom Padding to be 100.
Change the names of the each of the buttons to ContinueButton, SaveButton, and QuitButton
respectively by pressing F2 (Fn + F2 on laptops/Macs). Lastly, change the Text inside each of the
buttons to be Continue, Save, and Quit respectively.
That’s it here!
Go and have a test! Hit Play and pause your game, you’ll notice that you can hit continue to continue
playing as well as quit to quit the game, you can also save which looks like it’s doing nothing, but it’s
actually adding a save game to BeatEmUp->Saved->SaveGames that we can use for loading later…
speaking of which, let’s add loading!
Go to the C++ Classes folder and Create a New C++ Class, go to All Classes and select Button
as the Parent class, hit Next and then name this LoadButton.
12
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
7. Supporting Saving and Loading - Load Button on Main Menu (Contd.)
Once Rider opens, open LoadButton.h and add the following to a public section:
ULoadButton();
UFUNCTION()
void OnButtonClick();
UPROPERTY(EditAnywhere)
FName MapToOpen;
Here we’ve got a constructor so we can bind our OnButtonClick to our button (ourselves) and then a
reference to the name of the map we want to open when we click on the button.
Make sure you Generate Definitions for both of our new functions.
Open up the UI folder and then open BP_MainMenu and then drag a LoadButton (under Common)
in between the Start and Controls button in the Hierarchy.
Click on this LoadButton and set the Padding to be 50. Lastly, set the Map To Open to be
ThirdPersonMap. Lastly, drag a Text element into the LoadButton and change it to Load.
That’s everything for saving and loading! You should be able to start the game from your Main Menu, 13
hit Start, play around for a bit, hit P to pause the game, hit the Save button, then Quit, go back to the
Main Menu, and hit load and it should load back to where you left off!
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
8. Level Streaming!
Here we’re going to add in a level which we can load into our main level at runtime! We do this so
we don’t need long load screens and obvious transitions for our players!
First, up the top left, go to File->New Level and then select Basic. Make sure to Save this new level
as soon as it opens under Levels and name it LevelToStream.
Here all we’ve got is a super big floor and some lights, we only want the floor here as our main level
has lights! In the Outliner, delete everything except for floor.
That’s all we’ve got to do for the moment for this level!
Open back up the main level under ThirdPerson->Maps->ThirdPersonMap and then once that’s
open, on the right side of the screen under the World Settings, under World Partition Setup, tick
Disable World Partition.
Then, scale down one of the walls and then duplicate it over to the other side as well, making an
opening in the wall. Then make a pathway out of the map, with a turn after a while like the image to
the right. This is where we’re going to place our new map!
Then, up the top of the screen, go to Window->Levels and then when that’s done, right click on
Persistent Level and then select Make Current. Then, open the Content Drawer and then drag
our LevelToStrea level into the PersistentLevel dropdown. This will add our level onto the origin
of our level, but that’s not where we want it.
Position it at the end of the L shape like shown in the image below:
14
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
Go to the C++ Classes folder, then add a new C++ Class that inherits from Actor named
LevelTrigger.
Once Rider opens, open LevelTrigger.h and add the following to a public section:
UPROPERTY(EditAnywhere)
UBoxComponent* TriggerVolume;
UPROPERTY(EditAnywhere)
FName LevelToLoad;
UPROPERTY(EditAnywhere)
bool bLoadLevel;
UFUNCTION()
void StartLevelStreaming(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 BodyIndex, bool bSweep, const FHitResult& Hit);
Here we start with some variables for our actual hitbox as well as a reference to the level we want to
load, then we have a boolean which represents the name of the level we want to load in. Lastly we
have our OnOverlap function we want to call when our player overlaps with this trigger volume!
Here we do the same thing as always, initialise our components and set our rootcomponent to be
our trigger volume. Make sure to set bCanEverTick to false as we don’t need this actor to tick!
15
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
We first check to see if we overlapped with our player, and if so, we load in the level if it’s a load type
trigger, and unload it if it isn’t. That’s it!
Head back into Unreal and hit Compile. Once it finishes, Right Click on our new LevelTrigger
class and Create a Blueprint Based on it, place it in the Blueprints folder, and name it
BP_LevelTrigger.
Place two of these new BP_LevelTriggers in the world, near the end of the first bit of the L shape
like the image on the right and scale them up to cover a decent area so the player can’t dodge
around it, that’s how the speedrunners win! Make sure the one closest to the Streamed Level has
bLoadLevel ticked and the one closest to the Main Level has bLoadLevel unticked!
That’s everything! Give it a go and you should see the level load in as you enter the second box and
see it despawn when you enter the first box!
If this were a proper game that we wanted to hide this from the player, we’d put up walls so we
couldn’t see the level until we turn the corner, so it seems like one cohesive and joint level. However
for these labs, I wanted to show you how it loads in at runtime!
16
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
9. Additional Tasks
For this week the additional tasks don’t require any pitching, you just need to get Saving and
Loading working with at least one of your mechanics/features.
Mark Explanation
0 Student was absent or has not attempted lab tasks
0.5 An attempt has been made at the lab tasks but they are unfinished
17