There is one last “resource” we need to gather – the data for our database. I saved this for its own step, because I wanted to take a bit more time to explain the “how” and “why” of the layout of the data. It has a certain pattern to it which is based off of something called an “Entity Component System” architecture.
Entity Component System (ECS)
I recently started working with this pattern while making my Zork project. If you already followed along with that project or are already familiar with Adam Martin’s take on this architecture then you can feel free to skip ahead to the next stuff.
Because I am beginning with the assumption that you are already familiar with Unity, I don’t feel I will need to elaborate too much on the idea of ECS. It has a lot in common with Unity and some would say that Unity is an ECS. Anyway, following is my super brief over-simplification of the concept:
Entity
The first part of the architecture is the entity. Conceptually, you can think of this as something like a GameObject in Unity – by itself it doesn’t really “do” anything, it is just a container for components. With this pattern, you create complex objects by the components which make it up.
Now for the differences – an entity is implemented as nothing more than a unique id (like an integer data type). It is basically a key used in a database, and that’s it. The key will be used in the mapping of relationships to components.
Component
The next part of the architecture is the component. You probably already have a good idea of what this is. In Unity, it would be any class that inherits from MonoBehaviour. Actually, the MonoBehaviour class inherits from another class which is, not surprisingly, called Component.
The difference this time is that most Unity developers store both data and behavior in their components. A component should be nothing more than a structure of data – it should be able to be stored as a table in a database.
System
The system has no directly matching concept in Unity, although there is nothing stopping you from having used them. A system is really just a single class which acts upon the data in a component or collection of components. Any behavior (method) that you might previously have put in a component should actually go here instead.
Implementation
I have already included a starter database file in our project as well – it could be a good template to use for doing this again in the future. Take a look at the “Pokemon.db” file in the “StreamingAssets” folder. It is empty except for the definitions of a few important tables:
The “Enity” table contains columns for an “id” and “label”. The “id” IS the entity as I mentioned earlier. It is just a key in a database. For every object in your game you would create a new row in this table. The “label” is intended to be for debug purposes only and gives a quick idea of what that “id” and its components are intended to represent. Following is the class implementation:
public class Entity { [PrimaryKey, AutoIncrement] public int id { get; set; } public string label { get; set; } }
The “Component” table contains an “id”, “name” and “description”. I like to think of this table as a way to index my other tables – each of which represents what I think of as an “actual” component. The “name” is the name of one of those tables, and the description gives you an idea of what that component is for. I don’t actually use the description in the implementation of the code for any reason, but it might be handy for later reference in case you forget why you created something. There will be one row in this table for each TYPE of component you have in your game. The tables that the row points to will hold the instances of those components and the data specific to its own kind. Following is the class implementation:
public class Component { [PrimaryKey, AutoIncrement] public int id { get; set; } public string name { get; set; } public string description { get; set; } }
The “EntityComponent” table is what holds the relationship between an entity and its component(s). For each component instance that needs to be attached to an entity instance, you will have one new row in this table. The table definition includes an “id”, “entity_id”, “component_id”, and “component_data_id”. The “id” (as always) allows you a way to point to a specific row of this table. The “entity_id” holds the “id” of a row in the “Entity” table. The “component_id” holds the “id” of a row in the “component” table, and finally the “component_data_id” holds the “id” of a row in yet another table – one which you should be able to find by way of the data that the “Component” table tells you. Following is the class implementation:
public class EntityComponent { [PrimaryKey, AutoIncrement] public int id { get; set; } public int entity_id { get; set; } public int component_id { get; set; } public int component_data_id { get; set; } }
I realize that all might be a bit confusing. Feel free to read it a few times if you need to, but I will include an example that will hopefully help to clear everything up.
Database Setup
For this project I wanted to ease my way into using SQLite, so the only thing I stored in the database was information regarding the Pokemon. Note that these are treated almost like “prefabs” or “prototypes” because the data held here is not unique to an instance of a kind of Pokemon. In other words, any “Bulbasaur” the game instantiates would use the same base stats, evolution costs, etc. as all of the other “Bulbasaurs”.
Let’s take a look at the three default tables I inlcluded first. In order to match what I created, the “Entity” table should be populated so that there is one row per type of Pokemon. From “Bulbasaur” to “Dragonite”, etc. each gets a new row.
I created each Pokemon as an entity because I want to “describe” them based on a collection of components. In theory I could have created a single table with all of the information relevant to any given Pokemon, but in practice, most systems don’t need all of that data, they only need small bits of the data. For example, when I decide what Pokemon to spawn in a random encounter, I want to know the weighted chance from ALL of the Pokemon, but I don’t want to have to load the entire database into memory either. If I am not spawning a “Dragonite” at that point, then I don’t benefit from knowing its attack strength or move set, so it is beneficial to be able to break it down.
The game I created only requires a few components, each of which describes some aspect of a Pokemon:
- SpeciesStats: This component holds the base stats such as attack, defense, and stamina and what type(s) a Pokemon is classified as.
- Evolvable: This component indicates what other Pokemon an entity can evolve into, after paying the candy cost. Not all Pokemon have this compoennt.
- Encounterable: This component indicates the chance that a given Pokemon can appear. Not all Pokemon will have this component either, such as Legendary pokemon like Mew.
- Move: This component holds the information of an attack such as its power or energy cost. By attaching one or more as a component to a Pokemon we can define its set of abilities.
Go ahead and create a row in the “Component” table for each of these listed components. Then, we will need to create the “Component Data” table for each. Feel free to do so manually or programmatically, based on the following classes which represent them:
public class SpeciesStats { public const int ComponentID = 1; [PrimaryKey, AutoIncrement] public int id { get; set; } public string name { get; set; } public int typeA { get; set; } public int typeB { get; set; } public int maxCP { get; set; } public int attack { get; set; } public int defense { get; set; } public int stamina { get; set; } } public class Evolvable { public const int ComponentID = 2; [PrimaryKey, AutoIncrement] public int id { get; set; } public int entity_id { get; set; } public int cost { get; set; } } public class Encounterable { public const int ComponentID = 3; [PrimaryKey, AutoIncrement] public int id { get; set; } public double rate { get; set; } } public class Move { public const int ComponentID = 4; [PrimaryKey, AutoIncrement] public int id { get; set; } public string name { get; set; } public int type { get; set; } public int power { get; set; } public double duration { get; set; } public int energy { get; set; } }
Note that in each of the classes above I have a “ComponentID” – this will be the same as the “id” in the “Component” table – if you added them in a different order, you should modify this accordingly. This field is quite convenient when one needs to perform fetches.
Once you have the tables configured you will need to start populating them with data and then adding the entity component rows to connect them. In the image below, you can see a few which show the connection between the first five Pokemon entities (based on the entity_id column) with the Species Stat component (based on the component_id column) and which stat to use (based on the component_data_id column):
There is a certain “art” to figuring out how you want to model you data, not only for the convenience of entering it initially, but also for maintaining it, and allowing the greatest flexibility with it. Consider the “Move” component as one example. I was easily able to show a Pokemon’s move set simply by adding the moves I wanted a Pokemon to be able to use as components to the Pokemon’s entity itself. While this was a simple solution, a more complex battle system might have benefitted from the Move being attached to its own entity. Then I could attach additional components to the new move’s entity – some might allow status ailments to be inflicted upon the target, or give some other benefit to the user. Some, like a Ditto’s ability to transform, may not actually apply any damage at all and are drastically different in implementation than a normal attack move. Being able to connect additional components to a custom Move entity would make all of this a LOT easier to account for. If I went this route, I would not attach the Move as a component on the Pokemon entity. I might instead create some alternate entity that had components marking a collection of other entities. The Pokemon could then get a reference to this collection entity as its move set.
In addition to the component tables listed above, I added two additional tables for “Type” and “TypeMultiplier”, and they (for better or worse) didn’t follow the ECS pattern. Like the alternate implementation of a “Move”, the “Type” could potentially have been added to its own entity, and the type multipliers (the strength or weakness of one type against another) could have been added as additional components to the same entity. Ultimately, my reasoning was just that it was simpler not to need the Entity and that my simple game wouldn’t grow any more complex, so I was fine with simply looking up the information I needed manually.
The additional tables should use the following setup:
public class Type { [PrimaryKey, AutoIncrement] public int id { get; set; } public string name { get; set; } } public class TypeMultiplier { [PrimaryKey, AutoIncrement] public int id { get; set; } public int attack_type_id { get; set; } public int defend_type_id { get; set; } public double value { get; set; } }
Finding the Reference Data
Hopefully by now you understand what kind of data needs to be added, why it is structured like it is, and also how to add it – either manually through the use of an editor like SQLite Browser, or programmatically by random generation or by parsing data you find somewhere else. All of the data I used was based off of Pokemon Go. Here are a couple of links you might find valuable while trying to rebuild a working database for yourself:
- The Silph Road – well laid out data of all of the Pokemon names, numbers, base stats, etc.
- Game Master – a decoded and categorized json file covering just about everything you could want to know.
- Spawn Rates – a table of the spawn chance of all of the Pokemon based on data from 10,000 spawns.
- Pokemon Go moves list – a list of the pokemon and their moves with a DPS rating.
- Quick Moves & Charge Moves – an alternate list of the moves in Pokemon Go, with a time for each attack listed separately from the DPS.
If you google for a little while you will see that there are a ton more than the ones I have listed. Hopefully you will find something you like working from. Good luck!
Generating Data
If gathering the real data is too tedious, you can always generate your own data procedurally. We covered how to create and insert data into tables in the previous lesson, so you can always create something random using the same means. Afterward you can choose to polish the data a bit so it feels more balanced, but at least having the data will allow you to play the game and get a feel for it. It might be nice to at least generate data within the same ranges as the values I used in my prototype, so here are a few values to begin with:
- Entity: I used 149 different pokemon in the prototype.
- Encounterable rate: ‘0.0011’ to ‘15.98’
- Evolvable: there are 72 pokemon which can evolve. Some of the evolved pokemon can evolve again. You may want to make sure that evolving always goes to a pokemon with stronger stats. The cost ranged from ’12’ to ‘400’ (although only 1 pokemon exceeded ‘100’).
- Move power: ‘0’ to ‘120’, duration: ‘0.4’ to ‘5.8’, energy (for charge moves): ‘-20’ to ‘-100’, energy (for quick moves): ‘4’ to ’15’
- Species Stats attack: ’29’ to ‘271’, defense ’44’ to ‘323’, stamina ’20’ to ‘500’. maxCP can be calculated based on the other stats.
- Type: there are 18 different types.
- Type Multiplier: ‘0.8’ for weak against, ‘1.25’ for strong against
Summary
In this lesson I gave a brief introduction of the Entity Component System (ECS) architecture. I then discussed how that architecture was implemented with the database that provided all of the Pokemon data. I showed how to structure the tables and classes that work with those tables, and finally provided links that can help you populate those tables with data. Even if you don’t use the “real” Pokemon Go data, you will need to populate the database with something before you can continue with the rest of the project – the database on the repository will remain empty, although the classes will be updated accordingly.
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.
Great tutorial, but I got a question. I cant understand the TypeMultiplier class, what is attack_type_id and defend_type_id and what value is value?
The “TypeMultiplier” is a table that will be used in damage algorithms related to the “Type” of a move and Pokemon using the move. The type refers to stuff like Flying, Water, Bug, etc. Some types are strong against others and some types are weak against others. So, I represent these relationships in the “TypeMultiplier” table where the “attack_type_id” is the “id” of a “Type” that is used in the attack, and the “defend_type_id” is the “id” of a “Type” that is defending against the attack. The “value” is a multiplier that can either cause the damage algorithm to do more damage or less damage. For example, a value of “1.25” means that the attacking type is strong against the defending type and will do more damage. If you see “0.8” then it means the attacking type is weak against the defending type and will do less damage. Does that make sense? You will see this used in the Battle Setup lesson (part 13).
Yeah It does make sense now.
Thank you for taking the time to explain it to me.
Hi,jon
I’m trying to learn ecs from this tutor.
If possible, please upload only file pokemon.db. I can’t anything without it.
The reason I didn’t include the pokemon data was because I am not sure about copy-right laws regarding those stats. I did however try my best to demonstrate how you could create your own stats or obtain a copy of them on your own. Also, one of the users on my forum shared some code demonstrating how to populate the database:
http://theliquidfire.freeforums.net/thread/52/database-population-code
I hope that helps!
Thank you very much ,Jon
I will try it.