Great post. I haven’t used Unity personally but it’s interesting to read about how people are using the built in tooling stuff.
Merry Christmas from Earl Grey... and a few more updates!
Earl Grey here, wishing you a most joyous holidays. My task for the day is to teach these beloved Tea Minions of mine how to properly carol. What plans do you have for the remainder of the year?
On behalf of 3 Halves Games, we wish you a Merry Christmas and a Happy New Year. Earl Grey, disguised slightly as Santa, has urged us to present all of you with a very brief holiday update on our progress. We will present an updated monthly roadmap, where we discuss our progress on our December 2019 goals, early January next year. As for what we will talk about today...
Authoring Battle Entity Data in Unity
Designing a scalable system for fast unit iteration
Our game, Sovereign Tea, is built with Unity 2018.4. The reason for using an older version of Unity is simple: 2018.4 has LTS, and none of the 2019 versions have that yet. 2019.3 has yet to be officially released, though at the time of this post it is in pre-release and downloadable via Unity Hub. We care a lot more about LTS (Long Term Support) for our editor than we do about the latest bleeding-edge features. That said, when Unity 2019 enters LTS we will consider switching over. That should happen sometime next year. Thankfully, the game is simple enough that we have no worries about upgrading versions should we decide to embark on that route.
The goal of the engineer is to provide the tools necessary for the designer to author data for the game. We want to have the data be as agnostic of its usage as possible, since data can be used in multiple different ways. For example, we may use the data for Sacrificial Tea to create the in-map battle entity, or we may use it to showcase an entry in our brewing menu. These are two different use cases: the latter represents UI and thus should not have any care about what the data it is being fed is, and the other is something that is useful in game. Not every field in our data for Sacrificial Tea is used in the UI, and that's fine, but by authoring one node in one place we can use it for multiple purposes. Let's take a look at how the unit is designed:
We use a custom editor script to represent our BattleEntityData in the inspector. This allows us a few nifty tools such as hiding certain fields if the requirements are not met, an easier-to-use reorderable array interface, preview icons, and more. Let's break down each field:
- Guid: This is an unchanging ID that we can use to reference the data from text files, including save files. Unity objects do have their own IDs that are used for serialization, but these IDs change between builds and thus cannot be relied upon for consistency in the event of patches or file updates. We expose tools to allow us to copy the ID for easy debugging, as well as to change the ID in the extremely rare event that we have an ID collision. That hasn't happened yet, however!
- Name Key, Description Key: Do pay attention to the fact that the word Key is used here. These don't represent the actual string in English, but rather they represent the key of what the string will be when used for lookups in our localization system. However, from the perspective of the editor, we simply show what the English field is to allow for easy iteration. Behind the scenes, it saves something like this:
We don't yet know what our final plan is for localization, and the languages shown above are simply placeholders used to confirm working of the system, but we have been paying close attention to all client-facing strings in our title with anticipation that this game might receive a translation one day. If you're interested in translating, do contact us either on Twitter, Facebook, or Discord and let's have a brief chat!
- Profile pic, prefab: These are references to the actual data used for visualizing the unit. The prefab has its own slew of unique settings, such as setting its height, where the ground plane should live relative to the unit, and animations. More on animations in our coming January 2020 update!
- Team: Self-explanatory, a unit belongs to a specific team. Technically this field isn't fully required, as we can say a unit should have no knowledge of its team, but this is one of the shortcuts we've taken to specify, in general, what the starting team should be. That starting team is not a guarantee, but merely a suggestion.
- HP: Believe it or not, this is the only stat that any unit innately has! Everything else is based on whether the unit has the applicable actions, and that leads us to our next part of this devlog...
We could author stats innately on objects. Other games do this and for their purposes it works. For example, some games might provide stats such as HP, Attack, Defense, Special Attack, Special Defense, and Speed. Our recent obsession with that particular referenced title, aside, we took a different approach: we want the stats to be directly associated with the actions that can be taken. Not every unit can attack, nor can every unit move. Additionally, all actions operate essentially the same in code:
- When you click a unit, check if the unit is valid to take that action
- Check the tiles around to see if the action is possible to take
- When you click another tile, check if that tile was among those where the action is possible
- Take the action if all the tests pass
Does initiating an attack versus moving a unit really differ that much in implementation? The answer is simple: No. To attack you ultimately need to select a unit (a tile) and then another unit (another tile), whereas to move you ultimately need to select a unit (a tile) and then another tile (ideally without a unit). This allows us to simply expose parameters for what the actions should do, to localize code specific to those actions to their own small classes (the classes themselves merely implementing methods such as ApplyAction) and then be done with the whole ordeal. The battle manager doesn't ultimately care what action you took, nor should it. This keeps our code very minimal and highly localized. Updating code and data for the movement action thus has no repercussion on the other actions, which greatly helps our sanity while developing the game.
You may notice we use color coding here. We have a separate scriptable object file where we define client-facing colors. The pastel blue and red you see are the actual colors that are used in the game's UI. For clarity and to assist our designers at evaluating units from afar, we use these same colors to color the background in the inspector.
The most exciting part of our gameplay comes from triggers. In game these are called Abilities. A trigger occurs when a condition is met. Sometimes the conditions are cyclical, like happening at the start of a unit's turn, but other times they require something else to occur. This is clear from Sacrificial Tea, which has a trigger that fires when it dies, On Death. Reading the above inspector, you should be able to tell exactly what the trigger will do:
On Death the unit will perform Action Damage targeting Coffee units on the Opposing Tile for a sum of 2 Damage.
An understanding of our game code is not required to understand how to author units for our game, and that is the ultimate goal for cooperation between an engineer and a designer. The engineer exposes the data fields and ensures the actions and triggers behave as designed, while the designer hooks the data up and authors content that makes for an interesting game.
This has been our framework for Sovereign Tea since the beginning of development and it has enabled us to very rapidly iterate and develop the levels and units of this fun little game.
That's it for our Christmas devlog! Be sure to follow us on Twitter, like us on Facebook, and join our Discord group! We'll have another devlog update soon into the new year. Unit then, we hope you have a wonderful holiday season. Looking forward to speaking with you all again in 2020!