Appearance
Appearance
Wordle Project Part 1 - This guide continues the project developed on Tutorial: Game Logic. You can either build it following the guide or import the project from samples/WordleGameLogic
inside the SDK.
Game Configs overview - This tutorial focuses on Metaplay's Configs system. Taking a look at Setting Up Game Configs should be helpful towards completing this guide.
Game Configs (or just Configs) specify the game’s economy, configuration, and design data. It is typically defined in spreadsheet format like a CSV file and can be hosted on the cloud, like on a Google Sheets spreadsheet.
A Game Config Item (or just Item) is a single configurable set of data representing one specific type of in-game element. It corresponds to a single row on a given Game Config Library sheet. An Item could be, for example, an enemy and its health and damage or a shop-exclusive item bundle, its price, and contents.
A Game Config Library (or just Library) is a collection of Config Items, typically ones specified in the same spreadsheet.
Game Config Archives are binary files in which the SDK packages all the config data. Archives are the atomic unit of transfer to clients using CDN.
While following this guide, you'll set up a Configs system that works with dynamic values on a spreadsheet instead of hardcoded items. This way, your game's designers can edit and balance your game without needing to open Unity or edit the game's code directly.
Here's a quick layout of the steps ahead:
The source code for this project, as well as Tutorial: Game Logic and Tutorial: Cheat-Proof Gameplay is available as a Unity project in the Samples/Wordle
directory in the SDK.
You can switch between this and the previous guide's version of the project using the Tutorial
button on Unity's menu.
Use the Part 1: Game Logic
version as a base to start this project and code along or take a look at Part 2: Game Configs
for the final result.
Here are the solutions we defined before:
List<string> Solutions = new List<string> { "MODEL", "GAMES", "SERVE", "LOGIC" };
We'll remove this from the code and add the solutions to a CSV file, that we'll store inside a folder called GameConfigs
at the root of your Unity project, so that later the SDK can find it and build your Configs properly.
For the new implementation, we'll turn Solutions
into a Config Library. Each Library is composed of Items, each of which should contain at least an id and a value.
We need to tell the SDK what to look for so we can process the CSV file correctly and access the configs.
To do this, we'll create a library inside our game's SharedGameConfig
, as well as add the GetSolutionForRound
method we had before in PlayerSubmitGuess
. Don't forget to remove the original GetSolutionForRound
from PlayerActions.cs
!
public class SharedGameConfig : SharedGameConfigBase
{
[GameConfigEntry("Solutions")]
public GameConfigLibrary<int, SolutionInfo> Solutions { get; protected set; }
public string GetSolutionForRound(int roundIndex)
{
return Solutions[roundIndex % Solutions.Count].Word;
}
}
And so, you can add a corresponding C# class to your Config files:
// GameConfigLibrary requires a class that implements IGameConfigData<> to store its data
// even if it's a single field. Using it adds an extra layer of safety to the game and
// helps us make sure make sure no bad data is being used.
[MetaSerializable]
public class SolutionInfo : IGameConfigData<int>, IGameConfigPostLoad
{
[MetaMember(1)] public int Id { get; private set; } // Unique id of the solution
[MetaMember(2)] public string Word { get; private set; } // Word for solution
public int ConfigKey => Id;
void IGameConfigPostLoad.PostLoad()
{
// Validate the Word to avoid bad data leaking into the game
MetaDebug.Assert(!string.IsNullOrEmpty(Word), "SolutionInfo {0} has empty Word", Id);
MetaDebug.Assert(Word.Length == 5, "SolutionInfo {0} Word is not 5 characters", Id);
// Convert to uppercase as that is what the game logic expects
Word = Word.ToUpperInvariant();
}
}
Metaplay ships with a utility to build game configs quickly and easily, you can open the window using Metaplay/Game Config Build Window
. Make sure to select Custom FileSystemBuildSource
for the default source and Csv
for the file format.
Now, all you have to do is press build. We'll take care of converting the data and nicely packaging it into a compressed file.
This will automatically load the new configs into the Unity editor. If you happen to be running the game client in offline mode, it will also hot-load the changes into the running game. This is great for rapid iteration.
Metaplay SDK automatically updates the contents of SharedGameConfig
and recursively updates any references in the active PlayerModel
(or other models) to the updated Game Config Item classes.
The way to access the solutions from gameplay code remains similar, with the exception that instead of using GetSolutionForRound
, we'll now use GameConfig.GetSolutionForRound()
from the PlayerModel
.
GameConfig.GetSolutionForRound(RoundIndex);
Another thing to note is that reading the solutions from the Configs introduces the possibility of the solutions being changed while a player is between sessions. To account for that, we'll store the current solution once while initializing a fresh PlayerModel
and update it when we advance rounds, instead of checking the Configs directly in PlayerSubmitGuess
.
public class PlayerModel : ...
{
[MetaMember(209)] public string CurrentSolution { get; set; } = "";
...
protected override void GameInitializeNewPlayerModel(MetaTime now, ISharedGameConfig gameConfig, EntityId playerId, string name)
{
// Setup initial state for new player
PlayerId = playerId;
PlayerName = name;
if (CurrentSolution == "")
{
CurrentSolution = GameConfig.GetSolutionForRound(RoundIndex);
}
}
}
[ModelAction(ActionCodes.PlayerAdvanceRound)]
public class PlayerAdvanceRound : PlayerAction
{
public override MetaActionResult Execute(PlayerModel player, bool commit)
{
if (player.RoundStatus == RoundStatus.Playing)
return ActionResult.StillPlaying;
if (commit)
{
// Bump to next round & clear game state
player.RoundIndex++;
player.CurrentSolution = player.GameConfig.GetSolutionForRound(player.RoundIndex);
player.RoundStatus = RoundStatus.Playing;
player.CurrentWord = "";
player.GuessedWords.Clear();
player.GuessResults.Clear();
}
return ActionResult.Success;
}
}
We did it! Now you have a custom game data pipeline you can easily extend to fit whatever data your game needs. Try experimenting for a bit and then rebuilding the configs.
Right now, the main drawback we have is that our game still trusts the client with the solution. This leaves the game vulnerable to attempts to cheat or hack the game. This is addressed in Tutorial: Cheat-Proof Gameplay, the next tutorial in the series.
Besides that, many more useful features rely on the Configs system. Here are some of them!
MetaReward
classes. Doing this facilitates adding new items through Configs and making them visible to the LiveOps Dashboard. You can learn more about it in Implementing MetaRewards.MetaRewards
. Check out Getting Started with In-App Purchases for more information.PlayerSegments
to separate your player base into subsets and have features targeted to specific audiences. Take a look at Implementing Player Segments for details.And last but not least, a great way to make your team's workflow even more dynamic is to host the Config spreadsheets on a cloud service like Google Sheets. Here's a tutorial on how to get started: Google Sheets Integration.