Online Game Balancing tool #1 – Base Class

balance_screen

After first playable demo I have encountered lot of problems with ShooterTutorial gameplay. I would like to fix them all but first  I need to have a way to quickly iterate balance of the game. In this tutorial you will learn:

  • how to change base weapon and player variables over internet,
  • how to have backup offline values,

I will do this using C++ but it will be very easy!

This Tutorial has been created using Unreal Engine 4.10
Make sure you are working on the same version of the engine.

Theory

As always – first some theory.

We are living in great times for indies. Normally when dealing with cloud stuff I would need to have team of programmers doing backend for online communication. It’s costly and risky task the same as doing your own game engine. Today you can do this by yourself without any budget in your indie basement and it’s matter of minutes.

Why you should store your game data in cloud in first place? The answer is simple: do iterate faster. You don’t need to push new builds with new balance – which can take time (publishing game take some time on stores and there is always risk: will all players update their builds?) you can just change them in cloud and they will be updated for all clients. You can go even further with this – create custom patterns, weapons, perks (I will do custom perks in ShooterTutorial) in cloud. I was doing lot of this stuff when doing mobile games – they can be changed entirely without updating the build.

If you learn how to do this and having “how this can be iterated faster?” question in mind you will end up with better game as you will spend less time on pipelines and more on actual fun game.

Every professional game developer have this question in mind during whole production. You shouldn’t be different.

Backend

Remember how I did online leaderboards using Playfab? It turns out that guys from Playfab are really helpful and they have lot’s of possibilities in their backend. I will use Playfab for storing my game data. Before moving forward please read online leaderboards tutorial to get the basics.

Later on we will use some UE4 reflection code to create custom perks but in this Tutorial let’s focus on basics.

Why C++?

Getting base variables from Playfab cloud can be done easily with Blueprints. I will use C++ instead because I would like to create custom curves and perks in later tutorials and they can be done in C++ only.

Another thing is I would like to motivate you guys to learn UE4 C++. I’m learning it by myself and it’s awesome. For things that I’m doing in ShooterTutorial you don’t need to be C++ guru. If you learn how to use UE4 C++ your game will have less issues as it’s harder to “break things” in C++ than in Blueprints. You will gain new skill that’s needed for game development: programming knowledge. It will help you a lot in your daily tasks and you should invest your time to learn it.

I know how this sounds. Why you should learn C++ if you can create whole game in Blueprints? Yes you can but you are limited to what is exposed to Blueprints. For my opinion if you know Blueprints already learning UE4 C++ is another step of learning the engine.

Using PlayFab in C++

If you are after creating online leaderboard tutorial you should have PlayfabSDK installed in your Plugins folder. If you haven’t added any C++ code to your project please go to Menu – Level Selection – Base Data in C++ tutorial which will learn you how to add C++ code to your project. If you already have Visual Studio installed and you have added custom C++ code to your project you can move forward.

I have PlayFabSDK installed and Blueprints can now use PlayFab custom nodes. I would like to have possibility to use PlayFabSDK (BlueprintSDK) in C++ as well. To do this you need to open YOURPROJECTNAME.Build.cs file and add some modules: Playfab, Json, JsonUtilities.

// Fill out your copyright notice in the Description page of Project Settings.

using UnrealBuildTool;

public class ShooterTutorial : ModuleRules
{
	public ShooterTutorial(TargetInfo Target)
	{
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Playfab", "Json", "JsonUtilities"});

		PrivateDependencyModuleNames.AddRange(new string[] {"Playfab", "Json", "JsonUtilities"});

        // Uncomment if you are using Slate UI
        // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

        // Uncomment if you are using online features
        // PrivateDependencyModuleNames.Add("OnlineSubsystem");
        // if ((Target.Platform == UnrealTargetPlatform.Win32) || (Target.Platform == UnrealTargetPlatform.Win64))
        // {
        //		if (UEBuildConfiguration.bCompileSteamOSS == true)
        //		{
        //			DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam");
        //		}
        // }
    }
}

Recompile your game after doing this. I have added Json and JsonUtilities modules as well as they will be used in later Tutorials for custom perks.

Creating BalanceData

Now create new C++ class extending from Object named BalanceData.

It will contain all of the variables you want to get from Playfab. Open BalanceData.h and add all of the variables.

#include "Object.h"
#include "ShooterTutorial.h"
#include "PlayFabJsonObject.h"  //if you haven't added Playfab module including this will cause compile error
#include "BalanceData.generated.h"
/**
 * 
 */
UCLASS(BlueprintType, Blueprintable)
class SHOOTERTUTORIAL_API UBalanceData : public UObject
{
	GENERATED_BODY()
public:

	/*Player*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Player")
		float  PLAYER_HEALTH = 100.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Player")
		float  PLAYER_ARMOR = 25.0f;
	
	/*Drone*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Drone")
		float  DRONE_HEALTH = 5.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Drone")
		float  DRONE_DAMAGE = 10.0f;
	/*Trooper*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Trooper")
		float  TROOPER_DAMAGE = 25.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Trooper")
		float  TROOPER_HEALTH = 50.0f;
	/*Soldier*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|FutureSoldier")
		float  FUTURESOLDIER_DAMAGE = 3.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|FutureSoldier")
		float  FUTURESOLDIER_MELEE_DAMAGE = 50.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|FutureSoldier")
		float  FUTURESOLDIER_HEALTH = 50.0f;

	/*Ninja*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Ninja")
		float  NINJA_MELEE_DAMAGE = 15.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Ninja")
		float  NINJA_HEALTH = 25.0f;

	/*Marine*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Marine")
		float  MARINE_DAMAGE = 3.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Marine")
		float  MARINE_HEALTH = 25.0f;

	/*Weapons - Pistol*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Pistol")
		float  PISTOL_DAMAGE = 14.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Pistol")
		float  PISTOL_DAMAGEMOD_MIN = 0.5f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|PIstol")
		float  PISTOL_DAMAGEMOD_MAX = 1.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|PIstol")
		float  PISTOL_CRIT_MOD = 2.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|PIstol")
		float  PISTOL_CRIT_CHANCE = 10.0f;

	/*Weapons - Rifle*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Rifle")
		float  RIFLE_DAMAGE = 14.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Rifle")
		float  RIFLE_DAMAGEMOD_MIN = 0.7f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Rifle")
		float  RIFLE_DAMAGEMOD_MAX = 1.2f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Rifle")
		float  RIFLE_CRIT_MOD = 3.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Rifle")
		float  RIFLE_CRIT_CHANCE = 10.0f;

	/*Weapons - Shotgun*/
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Shotgun")
		float  SHOTGUN_DAMAGE = 14.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Shotgun")
		float  SHOTGUN_DAMAGEMOD_MIN = 0.4f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Shotgun")
		float  SHOTGUN_DAMAGEMOD_MAX = 0.7f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Shotgun")
		float  SHOTGUN_CRIT_MOD = 2.0f;
	UPROPERTY(BlueprintReadOnly, Category = "BalanceData|Shotgun")
		float  SHOTGUN_CRIT_CHANCE = 10.0f;
};

As you can see I’m declaring default values which will be used for offline mode.

Now I need to get the data somehow. To do this I will create new function that want’s Response from PlayFab:

	
UFUNCTION(BlueprintCallable, Category = "BalanceData")
void UpdateData(UPlayFabJsonObject* Response);

I will pass Response using Blueprints after logging in.

Now open BalanceData.cpp and make sure you add declaration of this function:


#include "ShooterTutorial.h"
#include "BalanceData.h"
#include "Runtime/JsonUtilities/Public/JsonObjectConverter.h"  // this will be used in next Tutorial as we will be creating structure using JSON data
	
void UBalanceData::UpdateData(UPlayFabJsonObject* Response)
{
	if (Response)
	{

	}
}

At this stage try to compile your game. If compiled without errors you can move forward.

Basically UE4 macros can create weird compiling issues and you should often check if your game is compiling. If not – you need to use Google and search for the issue. Most of them comes from text formatting when using macros (UFUNCTION, UPROPERTY) make sure you copy them without any spaces. I have found Google is the part of learning UE4 C++ so take your time!

Now just get number field from Response:

	
void UBalanceData::UpdateData(UPlayFabJsonObject* Response)
{
	if (Response)
	{
		// get default weapons and player variables values
		PLAYER_HEALTH = Response->GetNumberField(FString("PLAYER_HEALTH"));
		PLAYER_ARMOR = Response->GetNumberField(FString("PLAYER_ARMOR"));
		DRONE_HEALTH = Response->GetNumberField(FString("DRONE_HEALTH"));
		DRONE_DAMAGE = Response->GetNumberField(FString("DRONE_DAMAGE"));
		TROOPER_DAMAGE = Response->GetNumberField(FString("TROOPER_DAMAGE"));
		TROOPER_HEALTH = Response->GetNumberField(FString("TROOPER_HEALTH"));
		FUTURESOLDIER_DAMAGE = Response->GetNumberField(FString("FUTURESOLDIER_DAMAGE"));
		FUTURESOLDIER_MELEE_DAMAGE = Response->GetNumberField(FString("FUTURESOLDIER_MELEE_DAMAGE"));
		FUTURESOLDIER_HEALTH = Response->GetNumberField(FString("FUTURESOLDIER_HEALTH"));
		NINJA_MELEE_DAMAGE = Response->GetNumberField(FString("NINJA_MELEE_DAMAGE"));
		NINJA_HEALTH = Response->GetNumberField(FString("NINJA_HEALTH"));
		MARINE_DAMAGE = Response->GetNumberField(FString("MARINE_DAMAGE"));
		MARINE_HEALTH = Response->GetNumberField(FString("MARINE_HEALTH"));
		PISTOL_DAMAGE = Response->GetNumberField(FString("PISTOL_DAMAGE"));
		PISTOL_DAMAGEMOD_MIN = Response->GetNumberField(FString("PISTOL_DAMAGEMOD_MIN"));
		PISTOL_DAMAGEMOD_MAX = Response->GetNumberField(FString("PISTOL_DAMAGEMOD_MAX"));
		PISTOL_CRIT_MOD = Response->GetNumberField(FString("PISTOL_CRIT_MOD"));
		PISTOL_CRIT_CHANCE = Response->GetNumberField(FString("PISTOL_CRIT_CHANCE"));
		RIFLE_DAMAGE = Response->GetNumberField(FString("RIFLE_DAMAGE"));
		RIFLE_DAMAGEMOD_MIN = Response->GetNumberField(FString("RIFLE_DAMAGEMOD_MIN"));
		RIFLE_DAMAGEMOD_MAX = Response->GetNumberField(FString("RIFLE_DAMAGEMOD_MAX"));
		RIFLE_CRIT_MOD = Response->GetNumberField(FString("RIFLE_CRIT_MOD"));
		RIFLE_CRIT_CHANCE = Response->GetNumberField(FString("RIFLE_CRIT_CHANCE"));
		SHOTGUN_DAMAGE = Response->GetNumberField(FString("SHOTGUN_DAMAGE"));
		SHOTGUN_DAMAGEMOD_MIN = Response->GetNumberField(FString("SHOTGUN_DAMAGEMOD_MIN"));
		SHOTGUN_DAMAGEMOD_MAX = Response->GetNumberField(FString("SHOTGUN_DAMAGEMOD_MAX"));
		SHOTGUN_CRIT_MOD = Response->GetNumberField(FString("SHOTGUN_CRIT_MOD"));
		SHOTGUN_CRIT_CHANCE = Response->GetNumberField(FString("SHOTGUN_CRIT_CHANCE"));
	}
}

And that’s all in C++! I will add to this function more advanced stuff in next Tutorial. For now let’s just focus on base values.

Configuring PlayFab

Now each of this variables need to be added to PlayfabTitle Data.

titledatainplayfab

Make sure they have the same name as in Response->GetNumberField(FString(“DRONE_DAMAGE“));

Using BaseData in Blueprints

Create new Blueprint extending from BalanceData named BP_BalanceData. Open it and add one Custom Event named Update with one Play Fab Json Object Reference input:

bp_balancedata

Now I need to login to PlayFab and pass response to UpdateData function.

If you have created online leaderboards you should know how to login to Playfab using Blueprints. After logging we will get Title Data and pass it to BP_BalanceData.

Open ShooterGameInstance and add new Dispatcher named OnDownloadedBalanceData with one input: BalanceData Reference.

Now find the place when you are logging and after logging get title data and pass it to BalanceData:

gettitledata

This is really simple. I’m getting Title Data from PlayFab, decode it so it will have JSON structure and then pass it to the UpdateData function in BP_BaseData so the values will be updated from Response.

Now we need to connect with OnDownloadedBalanceData dispatcher to actually update the variables that are in BalanceData object.

Getting Data – Player

Let’s start from player. Open GameplayCharacter and find BeginPlay event.

GameplayCharacter_getbalancedata

And that’s all in player. If data will come from PlayFab OnBalanceDataDownloaded event will fire.

Getting Data – Weapons and Enemies

With weapons it’s more complicated as we have couple of them.

Open BP_BaseWeapon and create new custom event named GetBalanceData. It will be empty in this class but weapons will overwrite it.

Now find Begin Play and bind on OnDownloadedBalanceData:

BaseWeapon_OnBalanceDataDownloaded

Now go to BP_Weapon_Pistol and overwrite GetBalanceData event.

Pistol_GetBalanceData

You should get the idea. You can update rest of the weapons using this method. The same comes with enemies. Just use the same method in BP_BaseEnemy.

Final Result

You can see changing pistol damage is changing from cloud.

Hope I motivated you to play with cloud solutions in your game. In next tutorial we will do more advanced stuff – create custom balance curve and perks that can change variables that don’t need to be declared earlier.

Creating ShooterTutorial takes a lot of my free time.
donateIf you want you can help me out! I will use your donation to buy better assets packs and you will be added to Credits /Backers page as well. Implementing game is taking time but writing about it is taking much more effort!

Leave a Reply