0% found this document useful (0 votes)
38 views

Week 10 Lab - Gotta Cache em All V1.01

Uploaded by

siqilao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
38 views

Week 10 Lab - Gotta Cache em All V1.01

Uploaded by

siqilao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

FIT2096 - Games Programming

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!

This Lab is Assessed


Labs 10 - 11 make up a component of Assignment 2b. You must complete this
lab and submit it by the due date in Week 14. More information about the
assessment can be found under the Assessments section on Moodle.

Learning outcomes this week:

● Understand how to utilise saving and loading with Unreal

● Consider variables that are needed to be stored in a save game

● Examine how level streaming is set up in Unreal

● Load and Unload levels using C++


FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!

1. Cloning our Git Repository


The first thing we need to do is clone the local repository of our code from last week’s lab. If you are
working on your own device and already have the repository, make sure you fetch the latest changes!

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.

And that’s it! Let’s get into the code!

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;

void AddEXP(int EXPToAdd);


Here we have three different variables, two to represent the amount of EXP we currently have as well
as how much it takes to get to the next level, and the other variable being a multiplier to represent
how much our stats and EXP amount goes up when we level up. Lastly we have a function to add
EXP to our character.

Make sure to Right Click and Generate a Definition for our new function!

Head into BeatEmUpCharacter.cpp and add the following inside of AddEXP:


CurrentEXP += EXPToAdd;
while(CurrentEXP > EXPToLevel)
{
CurrentEXP -= EXPToLevel;
EXPToLevel *= IncreaseMultiplier;
PunchDamage *= IncreaseMultiplier;
MaxHealth *= IncreaseMultiplier;
CurrentHealth = MaxHealth;
}
InGameUI->UpdateValues();

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!

2. Adding Progression (Contd.)


We need to actually make our enemies give the player some EXP now once they die, so open up
Enemy.h and add the following to a public section:
UPROPERTY(EditAnywhere)
int EXPAmount = 20;
This will mean that we need 3 enemies by default to level the player up for the first time, but feel
free to tweak these values, it’s your game after 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!

Open up InGameUI.h and add the following to a public section:


UPROPERTY(meta = (BindWidget))
UProgressBar* EXPBar;
After this, head into InGameUI.cpp and add the following to UpdateValues
EXPBar->SetPercent((float)Player->CurrentEXP / Player->EXPToLevel);

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!

3. Creating our SaveGame Object


Now that we’ve got something to save, let’s actually create our SaveGame object!

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.

MAKE SURE EACH OF THESE VARIABLES IS A UPROPERTY()!


5
That’s actually it for the save game class, now we actually need to write the logic for saving and
loading, as well as the supporting infrastructure.
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!

4. Supporting Saving and Loading - GameInstance


To be able to save and load, we’re going to need a Pause Menu, a Load button on our Main Menu, a
GameInstance, and additions to our GameMode.

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:

bool bLoadSave = false;


That’s it! This is the only variable we need to transition across levels for saving!

Head back into Unreal and go to Project Settings, then under Maps & Modes, change the Game
Instance Class to be BeatEmUpGameInstance.

5. Supporting Saving and Loading - Game Mode Alterations


The next thing we actually need to do is write some logic to actually save the variables we need as
well as load them in.

Open BeatEmUpGameMode.h and add the following to a public section:


UPROPERTY(EditAnywhere)
TSubclassOf<AEnemy> EnemyClass;

void Load(UBeatEmUpSaveGame* LoadedGame);


void Save(UBeatEmUpSaveGame* SaveGame);
UFUNCTION()
void PostBeginPlay();
Here we’ve got a reference to our Enemy’s class so we can spawn them in, and then a couple of
functions to be called when we load and save our game, both having a reference to our save game.
Lastly we make a function that we’re going to call when the world finishes loading.

Make sure to Generate Definitions and Add Includes for Enemy and BeatEmUpSaveGame!

Head in to BeatEmUpGameMode.cpp and add the following to BeginPlay():


GetWorld()->OnWorldBeginPlay.AddUObject(this, &ABeatEmUpGameMode::PostBeginPlay);
Here we take the OnWorldBeginPlay event which gets called once all of the actor’s BeginPlay
functions are done (including this one) and bind the PostBeginPlay function we just made to it. We
do this so that when we load our game, all of our actors have loaded in before we begin messing 6
with them.
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!

5. Supporting Saving and Loading - Game Mode Alterations (Contd.)


Next, add the following to Save:
ABeatEmUpCharacter* PlayerCharacter = Cast<ABeatEmUpCharacter>(GetWorld()->GetFirstPlayerController()->GetPawn());

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!

5. Supporting Saving and Loading - Game Mode Alterations (Contd.)


Next, we’re going to add the following to Load:
ABeatEmUpCharacter* PlayerCharacter = Cast<ABeatEmUpCharacter>(GetWorld()->GetFirstPlayerController()->GetPawn());

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();

for(int i = 0; i < LoadedGame->EnemyLocations.Num(); i++)


{
AEnemy* SpawnedEnemy = Cast<AEnemy>( GetWorld()->SpawnActor(EnemyClass));
if(SpawnedEnemy)
{
SpawnedEnemy->SetActorLocationAndRotation(LoadedGame->EnemyLocations[i], LoadedGame->EnemyRotations[i]);
SpawnedEnemy->CurrentHealth = LoadedGame->EnemyCurrentHealths[i];
SpawnedEnemy->MaxHealth = LoadedGame->EnemyMaxHealths[i];
if(LoadedGame->EnemyRagdollStates[i])
{
SpawnedEnemy->Ragdoll();
SpawnedEnemy->GetCharacterMovement()->GravityScale = 0;
SpawnedEnemy->GetMesh()->SetWorldLocation(LoadedGame->EnemyMeshLocations[i], false, nullptr, ETeleportType::TeleportPhysics);
SpawnedEnemy->GetMesh()->SetAllPhysicsLinearVelocity(LoadedGame->EnemyMeshVelocities[i], true);
}
}
}
Another big chunk of code, but this time we’re just doing the opposite, instead of saving our
variables from our player and enemies states, we’re instead setting our player and enemies states to
be the variables from our loaded save game. The only thing here is if they were ragdolling, we make
sure to make them ragdoll, then set their mesh’s location and their velocity, plus their gravity scale
on their movement to be 0 so their capsules don’t fall through the floor and despawn.

Don’t forget includes for CharacterMovement and CapsuleComponent!.

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.

6. Supporting Saving and Loading - Pause Menu


Let’s add a simple pause menu so we can actually pause our game, and save it!

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!

6. Supporting Saving and Loading - Pause Menu (Contd.)


Head into PauseMenu.cpp, we’re going to add the following to the NativeOnInitialized:
ContinueButton->OnClicked.AddDynamic(this, &UPauseMenu::ContinueButtonClick);
SaveButton->OnClicked.AddDynamic(this, &UPauseMenu::SaveButtonClick);
QuitButton->OnClicked.AddDynamic(this, &UPauseMenu::QuitButtonClick);
Here we bind each of our functions to each of our buttons, that’s it!

Next, add the following to ContinueButtonClick:


UGameplayStatics::SetGamePaused(GetWorld(), false);
GetWorld()->GetFirstPlayerController()->SetShowMouseCursor(false);
GetWorld()->GetFirstPlayerController()->SetInputMode(FInputModeGameOnly());
RemoveFromParent();
First here we call the SetGamePaused function from UGameplayStatics (Don’t forget an Include!)
to actually un-pause our game, then we turn the cursor off and switch back to putting the game in
focus, then we remove the pause menu from our screen.

After that, add the following to SaveButtonClick:


UBeatEmUpSaveGame* SaveGameInstance = Cast<UBeatEmUpSaveGame>(UGameplayStatics::CreateSaveGameObject(UBeatEmUpSaveGame::StaticClass()));
if(SaveGameInstance)
{
ABeatEmUpGameMode* BeatEmUpGameMode = Cast<ABeatEmUpGameMode>(GetWorld()->GetAuthGameMode());
if(BeatEmUpGameMode)
{
BeatEmUpGameMode->Save(SaveGameInstance);
UGameplayStatics::SaveGameToSlot(SaveGameInstance,"TestSave", 0);
}
}
Here we create a SaveGame by calling the CreateSaveGameObject function from
UGameplayStatics(wow this is a really helpful library!). We then check to see if that succeeded, then
get our game mode. If that is our BeatEmUpGameMode, we then call the save function, passing in
our new SaveGame, and actually call the SaveGameToSlot function to save this into our file directory
so we can access once the game is closed.

Don’t forget includes for BeatEmUpSaveGame and BeatEmUpGameMode!


Update Regarding Regenerating Project Files
Throughout semester, whenever we wanted to regenerate our project from
scratch, we would delete all of our files in our project except for the Config,
Content, and Source folders as well as the .uproject file. However, when we are
working with saves, these get stored in the Saved folder as .sav files, so if you
are dealing with Save files, make sure not to delete these files otherwise your 10
saves will be gone!
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!
6. Supporting Saving and Loading - Pause Menu (Contd.)
Lastly, add the following to QuitButtonClick:
UKismetSystemLibrary::QuitGame(GetWorld(), GetWorld()->GetFirstPlayerController(), EQuitPreference::Quit, true);
This just quits the game, not really much to talk about here.

That’s it for this class, though we need to actually alter our character class in our to let us pause!

Open up BeatEmUpCharacter.h and add the following to a public section:


UPROPERTY(EditAnywhere)
UInputAction* PauseAction;
UPROPERTY(EditAnywhere)
TSubclassOf<UPauseMenu> PauseMenuClass;
UFUNCTION()
void PauseGame();
Here we create a couple of UPROPERTYs for our Pause Input Action and our Pause Menu Class, and
finally a function to actually pause our game.

Make sure to generate a definition for our new function as well as add an include for PauseMenu!

Heading into BeatEmUpCharacter.cpp, add the following to SetupPlayerInputComponent with


the other bindings:
EnhancedInputComponent->BindAction(PauseAction, ETriggerEvent::Started, this, &ABeatEmUpCharacter::PauseGame);

This is the exact same thing we’ve been doing all semester, this binds our pause action to our pause
game function.

Lastly here, add the following to PauseGame:


if(!UGameplayStatics::IsGamePaused(GetWorld()))
{
UGameplayStatics::SetGamePaused(GetWorld(), true);
UPauseMenu* PauseMenu = Cast<UPauseMenu>(CreateWidget(GetGameInstance(),PauseMenuClass));
if(PauseMenu)
{
GetWorld()->GetFirstPlayerController()->SetShowMouseCursor(true);
GetWorld()->GetFirstPlayerController()->SetInputMode(FInputModeUIOnly());
PauseMenu->AddToViewport();
}
}
First here we check to see if the game is paused, and if it isn’t, we set the game to be paused (Don’t
forget an include for UGameplayStatics!). Then, we spawn in our PauseMenu, set our cursor and
input mode, and lastly actually add our pause menu to the screen! 11

That’s it for this code, head back to Unreal and hit Compile!
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!

6. Supporting Saving and Loading - Pause Menu (Contd.)


Now we’re back in Unreal, the first thing is to go to ThirdPerson->Input->Actions and add a new
Input Action named IA_Pause. After that, go up a folder to Input and then open IMC_Default. Add
a new action by clicking on the + button up the top, and then set this to be IA_Pause with the key
set to P.

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 to ThirdPerson->Blueprints and then open BP_ThirdPersonCharacter and set the


PauseGameClass to be BP_PauseGame and PauseInputAction to be PauseAction.

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!

7. Supporting Saving and Loading - Load Button on Main Menu


Here we’re going to add a button to our Main Menu to load our game… but as we’re using C++ and
our main menu doesn’t have any, we’ll instead add a C++ button to our non-C++ main-menu UI!

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.

Head into LoadButton.cpp and add the following to the Constructor:


OnClicked.AddDynamic(this, &ULoadButton::OnButtonClick);
This just binds the OnButtonClick to when this button is clicked, nice and simple!

Next, add the following into OnButtonClick:


if(UGameplayStatics::DoesSaveGameExist("TestSave", 0))
{
UGameplayStatics::OpenLevel(GetWorld(), MapToOpen);
UBeatEmUpGameInstance* GameInstance = Cast<UBeatEmUpGameInstance>(GetGameInstance());
if(GameInstance)
{
GameInstance->bLoadSave = true;
}
}
Here we check to see if our SaveGamecalled TestSave (the same one we made earlier) exists, and if
it does, we open the level we set in MapToOpen, then lastly get our game instance and set
bLoadSave to true, so it knows to actually perform our Save function from our GameMode.

Make sure to add includes for GameplayStatics and BeatEmUpGameInstance!

That’s it here, jump back into Unreal and hit Compile.

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!

8. Level Streaming! (Contd.)


You’ll notice if you hit play, you can’t see the floor that we put in, that’s because we haven’t told it to
load yet! Let’s do that by adding in a trigger volume to load in our level to act as a secret loading
screen!

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!

Make sure to Generate a Definition and add an include for BoxComponent!

Head into LevelTrigger.cpp and add the following to the Constructor:


TriggerVolume = CreateDefaultSubobject<UBoxComponent>(TEXT("Trigger Volume"));
RootComponent = TriggerVolume;

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!

Next, add the following to BeginPlay:


TriggerVolume->OnComponentBeginOverlap.AddDynamic(this, &ALevelTrigger::StartLevelStreaming);

Here we bind our StartLevelStreaming function to our TriggerVolume’s OnComponentBeginOverlap


event so our function gets called when something enters the trigger volume.

15
FIT2096: Games Programming
Week 10: Gotta Cache ‘Em All!

8. Level Streaming! (Contd.)


Lastly, add the following to StartLevelStreaming:
if(Cast<ABeatEmUpCharacter>(OtherActor))
{
if(bLoadLevel)
{
UGameplayStatics::LoadStreamLevel(this, LevelToLoad, true, true, FLatentActionInfo());
}
else
{
UGameplayStatics::UnloadStreamLevel(GetWorld(), LevelToLoad, FLatentActionInfo(), true);
}
}

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!

Make sure to add includes for BeatEmUpCharacter and GameplayStatics!

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.

Open BP_LevelTrigger then set the LevelToLoad to be LevelToStream.

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!

That’s everything for this week!

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.

The rubric for this week is shown below:

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

1 Lab tasks have been successfully completed

1.5 Saving/Loading started for at least one own mechanics/features

2 Saving/Loading completed for at least one of own mechanics/features

17

You might also like