Unofficial Pokemon Board Game – Data Models

There are several more models we will need to create before we can start even a basic game. Pokemon, Players, and the Game model itself are all a required part of the setup flow. Technically other data models will need to be created during setup as well, but at a minimum to get the game up and running we will need these three. In addition, each of these models will be provided with a factory class to help create and configure them.

Enums

Some of the fields held by our upcoming models use a special data type called an enum. There are placeholder scripts for each located in the Scripts/Enums directory, where each type has a file by the same name.

public enum Genders {
	Male,
	Female
}

public enum Poses {
	Front,
	Back
}

I’ve shown both of the new enums we will need for this lesson here. Both are used when determining what picture to show to represent a Pokemon. Most of the Pokemon are gender neutral and will use the same picture regardless. Some have a “-F” or “-M” postfix on the sprite and in those cases I need some way to consistently know which one we want to load.

The pose comes into play when we want to specify whether we are looking at a Pokemon from the front or from the back. I use this in the setup screen as a sort of visual toggle on which Pokemon you have selected to be your buddy. The selected Pokemon will face you while the others face away. In addition, the battle screen has a sort of perspective to it as if you are the trainer standing behind your Pokemon and giving it orders. Because of this you will always see your Pokemon from behind, but will always see the opponent’s pokemon from the front.

Data Models

There are a handful of models to add in this lesson. There are already placeholder scripts for each located in the Scripts/Model directory, where each class has a file by the same name.

Unlike the models we created earlier, these models are not designed with an ECS architecture. There are pros and cons of both architectures, but in this case not using it can simplify some things, such as a nice bonus of being immediately serializable and visible within the Unity Inspector window. I could always create custom editor scripts to get my ECS models to appear, but it requires no extra work for these.

Pokemon

Update the script located at Scripts/Model/Pokemon.cs with the following:

using UnityEngine;
using System;
using ECS;

[Serializable]
public class Pokemon {

	public static readonly double[] multiplierPerLevel = new double[] { 0.094, 0.135137432, 0.16639787, 0.192650919, 0.21573247, 
		0.236572661, 0.25572005, 0.273530381, 0.29024988, 0.306057377, 0.3210876, 0.335445036, 0.34921268, 0.362457751, 
		0.37523559, 0.387592406, 0.39956728, 0.411193551, 0.42250001, 0.432926419, 0.44310755, 0.453059958, 0.46279839, 
		0.472336083, 0.48168495, 0.4908558, 0.49985844, 0.508701765, 0.51739395, 0.525942511, 0.53435433, 0.542635767, 
		0.55079269, 0.558830576, 0.56675452, 0.574569153, 0.58227891, 0.589887917, 0.59740001, 0.604818814, 0.61215729, 
		0.619399365, 0.62656713, 0.633644533, 0.64065295, 0.647576426, 0.65443563, 0.661214806, 0.667934, 0.674577537, 
		0.68116492, 0.687680648, 0.69414365, 0.700538673, 0.70688421, 0.713164996, 0.71939909, 0.725571552, 0.7317, 
		0.734741009, 0.73776948, 0.740785574, 0.74378943, 0.746781211, 0.74976104, 0.752729087, 0.75568551, 0.758630378, 
		0.76156384, 0.764486065, 0.76739717, 0.770297266, 0.7731865, 0.776064962, 0.77893275, 0.781790055, 0.78463697, 
		0.787473578, 0.79030001 
	};

	public static int MaxLevel { get { return multiplierPerLevel.Length - 1; }}
	public const int MaxEnergy = 100;

	public Genders gender;
	public int entityID;
	public int fastMoveID;
	public int chargeMoveID;
	public int hitPoints;
	public int maxHitPoints;
	public int energy;
	public int level;
	public int attackIV;
	public int defenseIV;
	public int staminaIV;

	public string Name { get; set; }
	public Entity Entity { get; set; }
	public Move FastMove { get; set; }
	public Move ChargeMove { get; set; }
	public SpeciesStats Stats { get; set; }
	public Evolvable Evolvable { get; set; }

	public float HPRatio { get { return (float)hitPoints / (float)maxHitPoints; }}
	public float EnergyRatio { get { return (float)energy / (float)MaxEnergy; }}
	public float LevelRatio { get { return (float)level / (float)MaxLevel; }}

	public float CPM { get { return (float)multiplierPerLevel[level]; }}
	public float Attack { get { return (Stats.attack + attackIV) * CPM; } }
	public float Defense { get { return (Stats.defense + defenseIV) * CPM; }}
	public float Stamina { get { return (Stats.stamina + staminaIV) * CPM; }}
	public int CP { get { return Mathf.Max(10, Mathf.FloorToInt(Mathf.Sqrt(Attack * Attack * Defense * Stamina) / 10.0f)); }}
}

Note that there is a “[Serializable]” tag on this class – I use this so that the class can be viewed in Unity Inspector windows. Ultimately the Flow Controller will hold a reference to an object hierarchy containing Pokemon, and this setting will make sure I can debug the values or even “cheat” by tinkering with values while debugging if desired.

Note that only the instance fields are actually going to be serialized. Other instance properties such as a Pokemon’s cached reference to its Entity, and calculated properties such as the “HPRatio” (hit point ratio) will be ignored. Furthermore the “static” and “const” fields and properties of a class will also not ever be serialized. Note that all of this is also true when serializing to JSON – which we will do later to persist our game.

The static array of values “multiplierPerLevel” is a multiplier used in combat calculations so that a level stat has an influence on the other stats. Ideally, I wouldn’t hard code these values into the class like this, but might instead provide some sort of asset that could be updated, perhaps even a settings URL that the app could hit for balance data. For now, this was the easiest way to get up and running. I found the table of values here which included both level and half level stats. I allowed this array to control the max level of a pokemon by its own array length. I found the max energy stat in a few places which I have already linked to in previous lessons. A lot of the other calculated properties, such as how to calculate the CP level I found here.

Player

Update the script located at Scripts/Model/Player.cs with the following:

using System.Collections.Generic;
using System;

[Serializable]
public class Player {
	public string nickName;
	public int tileIndex;
	public int destinationTileIndex;
	public int pokeballs;
	public int revives;
	public int potions;
	public int candies;
	public List<Pokemon> pokemon = new List<Pokemon>();
	public List<string> badges = new List<string>();
}

Hopefully each of these fields are pretty self explanatory. The “nickName” is set during the Setup flow where a player can choose to input a custom name or use the default, such as “Player 1” if they don’t provide a value. The “tileIndex” indicates the index on the board’s tiles of where the player is located. The “destinationTileIndex” is where the player is headed – for example, if the game has just begun and our player rolls a 3, then his current “tileIndex” will be 0 while his “destinationTileIndex” will be 3. I’ll use these values to determine whether or not the “Journey” flow has completed or not.

There are several stats for a rudimentary inventory system: “pokeballs”, “revives”, “potions”, and “candies” where each holds the count of that item that the player possesses. “Pokeballs” are needed to catch any wild pokemon you encounter, “revives” are needed to restore any pokemon which has been KO’d, “potions” restore pokemon hit points, and “candies” are used for evolution.

Finally there are two lists: “pokemon” and “badges”. The “pokemon” list is the list of pokemon that makes up the trainer’s team. You start with a single pokemon and can collect more by capturing them after a random encounter battle. The “badges” list holds the names of whatever gym types you have defeated. It starts out empty and grows as you gain victories. The name of the type of the gym is used to load the image of the badge from resources. The game system can also use the count of this list to determine if a player has won the game or not.

Game

Update the script located at Scripts/Model/Game.cs with the following:

using System.Collections.Generic;
using System;
using UnityEngine;

[Serializable]
public class Game {
	public List<Player> players;
	public int currentPlayerIndex;
	public Player CurrentPlayer { get { return players[currentPlayerIndex]; }}
}

Note that a “Game” is marked as Serializable just like the “Player” and “Pokemon” are. This means that the entire hierarchy of data contained here can be displayed in the Unity Inspector window, and that it can also be serialized to or deserialized from JSON with a single call.

The game ties together all of the important information about a session of our app into one model. It holds everything which would be necessary to save and restore a game session later if necessary. Note that by design, I always save the game at the beginning of each player’s turn – this is a common pattern and can be seen in games such as “Mario Party”. This is easier than needing to be able to persist at “any” state and can also exclude things like a battle or its related models from needing to be saved.

If ALL of the data had been ported over to the ECS architecture, saving “anywhere” at “anytime” would be much simpler, but even then you might decide it is a better experience to save at special events like I am doing now anyway. For example, nothing in the battle screen currently indicates whose turn it is. If multiple players happened to have acquired the same pokemon, then it might be hard to remember whose battle was in progress once restoring a game. In contrast, when I save at the beginning of a turn, I can restore to the beginning of a turn where we get that nice handy alert window explicitly telling us whose turn it is now.

Factories

There are multiple common approaches to initializing an object. For some fields you can use an initializer at the same time you define the field. I used this in a few places, usually around any field that held a generic “List” because I like to be able to simply start adding objects to the list without having to remember to create it first. Other times you might want something a little more complex. For example I assign a random gender to Pokemon as well as random starting IV stats (I dont actually know what the IV stands for, but all the guides refer to it…) from 0 to 15. This could be accomplished by including a “constructor”, and that would be a perfectly fine approach.

In this project, I created something called “factory” classes that are responsible for creating instances and handling setup for me. One benefit of this approach is greater control. A constructor is always called (although you may have more than one constructor with overloaded parameters), but I am not obligated to use my factory’s creation method in any way. When I need new Pokemon to be generated, then I am happy to use a Factory to create them, but when loading a Pokemon from a save file, I dont need to go through the initial setup of picking a gender and random stats – I simply want to assign the ones that were already saved.

In addition, if the creation of an asset required knowledge of other resources that I didn’t want to have tightly coupled, then the factory can provide a nice way to separate that link. The model could still be easily reusable and the factory could be an optional extra bit which may or may not be reusable.

Another pattern is failable initializers. Perhaps you want to use a factory to attempt to create an instance of a class with certain parameters. If the parameters fall within invalid ranges, you might prefer to return “null” instead of an incomplete or malformed instance. Constructors wont allow this, but a factory method could. I’m not making use of any of these advanced features in this project, but I thought I would point them out just in case it helps you understand the value in this pattern.

There are a handful of factories to add in this lesson. There are already placeholder scripts for some of them located in the Scripts/Factory directory, where each factory has a file by the same name.

Pokemon Factory

You will need to create this script and then add the following:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ECS;

public static class PokemonFactory {

	public static Pokemon Create (Entity entity, int level = 0) {
		Pokemon pokemon = new Pokemon();
		pokemon.gender = UnityEngine.Random.Range(0, 2) == 0 ? Genders.Male : Genders.Female;
		pokemon.attackIV = UnityEngine.Random.Range(0, 16);
		pokemon.defenseIV = UnityEngine.Random.Range(0, 16);
		pokemon.staminaIV = UnityEngine.Random.Range(0, 16);
		pokemon.SetEntity (entity);
		pokemon.SetMoves ();
		pokemon.SetLevel (level);
		pokemon.hitPoints = pokemon.maxHitPoints;
		return pokemon;
	}
}

Here we create and return a Pokemon with some special setup going on. This function takes two arguments, an “Entity” and a second optional argument called “level”. Optional arguments use a default value (in this case ‘0’) if the method is called with that argument missing.

To assign the gender, we roll a random number – the Range method uses an exclusive upper bound which means that the number here will only be ‘0’ or ‘1’, so basically I have an equal chance of getting either gender. There are three ‘IV’ stats which are also randomly generated from ‘0’ to ’15’ (because like before ’16’ is an exclusive upper bound). These bonus stats allow a little variation even among the same type of Pokemon.

Then I start doing something that looks like I am calling instance methods on the Pokemon for “SetEntity”, “SetMoves”, and “SetLevel”. However, if you look at the Pokemon class there are no instance methods whatsoever. The methods here are some new extension methods and are defined in another class. In this case I created a separate class called the PokemonSystem for providing these functions – we will show that in a bit. Finally, after assigning the level (which will also adjust the max hit points based on the level), I update the hit points stat to be the same as the max hit points. The Pokemon is now fully configured and can be returned.

Player Factory

You will also need to create this script and then add the following:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class PlayerFactory {
	public static Player Create () {
		var player = new Player ();
		player.pokeballs = 5;
		player.revives = 1;
		player.potions = 1;
		return player;
	}
}

This factory could have easily been implemented as a “constructor” on a player, or even using field initialization values. It is especially flexible because I am simply assigning constant values to each of the fields. It is always possible that I would need more control in the future, and I had already started using the factory pattern with some of my other classes, so I simply continued the pattern here. Beyond creating and returning a player instance, it also assigns default values to some of the player’s inventory. Feel free to tweak the starting values to your liking.

Game Factory

Update the script located at Scripts/Factory/GameFactory.cs with the following:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class GameFactory {
	public static Game Create (int playerCount) {
		Game game = new Game ();
		game.players = new List<Player> (playerCount);
		for (int i = 0; i < playerCount; ++i) {
			var player = PlayerFactory.Create ();
			game.players.Add (player);
		}
		return game;
	}

	public static Game Create (string json) {
		Game game = JsonUtility.FromJson<Game> (json);
		foreach (Player player in game.players) {
			foreach (Pokemon pokemon in player.pokemon) {
				pokemon.Restore ();
			}
		}
		return game;
	}
}

This version makes the best use of the factory pattern so far. There are two approaches used to create the game instance, one is a new game created based on a player count, and the other is created based on previous saved game data. In order to fully restore a game, some additional setup work is required on the Pokemon, but currently the game model doesn’t have any coupling to that class. By keeping the setup in this factory class instead of as a constructor in the Game class, I can maintain a more loosely coupled project.

It is worth pointing out that one of the benefits of an ECS architecture is the ability to overcome the “impossible” problem of creating object hierarchies with object relationships already in place. If the Pokemon class was implemented using ECS I wouldn’t have to “Restore” these objects after instantiating them. In this case I want to cache the references to some of those database objects for later.

Systems

There is only one “system” to add in this lesson, and I am only providing a partial implementation which was necessary for the factory code to compile plus a couple methods we will need for the setup flow. There are already placeholder scripts for the systems as well. You will find them located in the Scripts/Systems directory, where each class has a file by the same name.

Pokemon System

Even though the Pokemon data model wasn’t implemented using the ECS architecture, I can still use some of the general concepts for it as well. I created a system specifically to operate on instances of this class. However, the way I implemented these methods as extensions makes it feel more like an MVC architecture anyway. I still don’t know how my peers would feel about this – I like it, but I am open for feedback.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ECS;
using SQLite4Unity3d;

public static class PokemonSystem {

	public static void SetLevel (this Pokemon pokemon, int level) {
		pokemon.level = level;
		float start = (float)pokemon.Stats.stamina / 2f;
		float stop = pokemon.Stats.stamina;
		float value = EasingEquations.Linear (start, stop, pokemon.LevelRatio);
		pokemon.maxHitPoints = Mathf.RoundToInt(value);
	}

	public static void SetEntity (this Pokemon pokemon, Entity entity) {
		pokemon.Name = entity.label;
		pokemon.entityID = entity.id;
		pokemon.Entity = entity;
		pokemon.Stats = entity.GetSpeciesStats();
		pokemon.Evolvable = entity.GetEvolvable();
	}

	public static void SetMoves (this Pokemon pokemon) {
		List<Move> moves = pokemon.Entity.GetMoves();
		List<Move> fastMoves = new List<Move>();
		List<Move> chargeMoves = new List<Move>();

		foreach (Move move in moves) {
			if (move.energy > 0)
				fastMoves.Add(move);
			else
				chargeMoves.Add(move);
		}

		if (fastMoves.Count == 0 || chargeMoves.Count == 0) {
			Debug.LogError("Missing moves");
			return;
		}

		pokemon.FastMove = fastMoves[Random.Range(0, fastMoves.Count)];
		pokemon.ChargeMove = chargeMoves[Random.Range(0, chargeMoves.Count)];

		pokemon.fastMoveID = pokemon.FastMove.id;
		pokemon.chargeMoveID = pokemon.ChargeMove.id;
	}

	public static void Restore (this Pokemon pokemon) {
		var connection = DataController.instance.pokemonDatabase.connection;
		var entity = connection.Table<Entity> ()
			.Where (x => x.id == pokemon.entityID)
			.FirstOrDefault();
		pokemon.SetEntity (entity);
		pokemon.FastMove = connection.Table<Move> ()
			.Where (x => x.id == pokemon.fastMoveID)
			.FirstOrDefault();
		pokemon.ChargeMove = connection.Table<Move> ()
			.Where (x => x.id == pokemon.chargeMoveID)
			.FirstOrDefault();
	}

	public static Sprite GetAvatar (this Pokemon pokemon, Poses pose = Poses.Front) {
		return GetAvatar(pokemon.Entity, pokemon.gender, pose);
	}

	public static Sprite GetAvatar (Entity entity, Genders gender, Poses pose) {
		string file = entity.id.ToString("000");
		string folder = pose == Poses.Front ? "PokemonFronts" : "PokemonBacks";
		string fileName = string.Format("{0}/{1}", folder, file);
		Sprite sprite = Resources.Load<Sprite>(fileName);
		if (sprite == null) {
			string extension = gender == Genders.Male ? "-M" : "-F";
			fileName = string.Format("{0}/{1}{2}", folder, file, extension);
			sprite = Resources.Load<Sprite>(fileName);
		}
		return sprite;
	}
}

The “SetLevel” method does more than simply updating the “level” value itself. As a Pokemon experiences this growth, the max hit points stat will also be incremented. The code there may look a little complex, but what I am doing is using one of my “easing equations” from my animation library to interpolate between two values. In this case the “curve” is just a simple linear equation that goes from half of the Pokemon’s stamina stat, to the full value of the Pokemon’s stamina stat. The distance along that curve that I want to grab a value from is the percentage of the levels that can be obtained. In other words, a Pokemon’s max hit points will be equal to half its stamina when it is initially created with a level of ‘0’. When it reaches the max level, its hit points will be the same as its stamina stat.

Whenever I set the “Entity” of a Pokemon, you could think of it as if we are determining what prototype to use as the basis for creating it. I am referring to a reference from the the Pokemon database which is part of the earlier ECS architecture. I assign the “id” of the entity (which is serializable and from which I can restore the reference at a later date) as well as save the reference to the entity object itself. From the entity I can also get references to several of the “components” that I also want to cache at this point.

As I create or evolve a pokemon, I will pick a single fast move and charge move from among the potential moveset to assign. I do this by grabbing all of the moves, then looping through them and categorizing them based on whether they give you energy or take it away. Once they are sorted, I pick at random from the lists accordingly. Like before I assign the “id” of the move as well as the move itself, so that I can more easily save and restore the object later.

After we have loaded a saved game, there are some properties on a Pokemon that were not serialized and which will need to be reconnected. I use the “Restore” method for this. Using the id’s which we did save, it is pretty trivial to grab the references to the objects again and cache them for later use.

Finally, this system provides an easy way for us to associate a picture (the avatar) with a Pokemon. Generally, I will use an extension method on the Pokemon itself, because then we will always maintain the same gender setting. When a player is picking his buddy, I haven’t actually created the Pokemon yet, so I made this an overloaded method. During setup I will simply pass along the Entity and pre-determined pose and gender settings.

Summary

In this lesson we created three important data models which are required as part of a minimal setup for our game. In addition, we spent some time introducing the factory as an architectural pattern and then created factories for each of our new models. Finally I showed a couple of enums and a partial implementation of one of the systems which was used by a factory so that everything would at least compile.

Don’t forget that there is a repository for this project located here. Also, please remember that this repository is using placeholder (empty) assets so attempting to run the game from here is pretty pointless – you will need to follow along with all of the previous lessons first.

4 thoughts on “Unofficial Pokemon Board Game – Data Models

  1. Hey Jon, great work! Always a good read.

    I like what you did with the Pokemon “System”, I think having the methods be extensions of the Pokemon class makes for cleaner code. I also think it’s a nice way to bridge the gap between the Pokemon data model and the entity systems. It’s hard to say whether or not that will cause any problems in the future, but, knowing the scope of this project, I doubt it will be problematic.

    And as I understand it, the Pokemon class is more like an interface, sitting on top of the ECS information, right?

    1. Thanks for the feedback Nathan, glad you also enjoyed the approach I took. I was thinking the same thing that it helped it feel more uniform with the other ECS code.

      Regarding the Pokemon class, I suppose in some ways you could see it like an interface, but the specific collection of components and stats, etc is really just a more complex model that is unique to itself. It has more content and purpose than merely to provide access to the underlying components.

      1. Hmm, I think I see now. I re-read the paragraph about SetEntity, and now I see that the ECS Pokemon data is prototypical (name, type, moves, etc), and the Pokemon class has the specifics for unique instances of Pokemon in the game.

        Neat.

Leave a Reply

Your email address will not be published. Required fields are marked *