Welcome to our fifteenth blog post!
In this one, we aim to stray a bit away from showing off gameplay to instead show off how some of our game’s code works, specifically with regards to Abilities.
If you’d like to ask any sorts of questions with regards to how it was implemented or anything like that, we’d definitely be open to sharing with you. This post will predominantly cover the basics of what-does-what in the game.
All of our data interpretation begins with our methodology of storing the data: JSON.
JSON allows for a very convenient and easy way to not only store large amounts of data, but also conveniently write new data and, most importantly, access that data in a quick and reasonable fashion. The core of our load times when booting up the project are map generation and data-loading, so with JSON, we can get it as fast as it can possibly be interpreted and stored, ready for the game to call upon as needed. As a result, the game loads quite quickly, even though its quite literally handling thousands and thousands of lines of code.
To begin discussing integration, we should first take a step back and talk about our Default Data. Every Ability in the game is comprised of parts: Damage, Element, Range, Area-of-Effect, and so forth. With that being said, there are quite literally 100-200+ different Abilities alone in the game as it currently stands, and dozens of various variables to invoke. And this is all plausible in a convenient manner via one core element of data interpretation: default data (as shown in the image above). Before we begin reading the JSON, we first run through and establish every entity as having this default data. And, from there, as we read the data, we merely update the entity with its correct information. Such as, a powerful fireball spell would have high damage and long range. But a buffing spell would not need any damage. With the default data, we can conveniently comprise every entity based on its unique information, and let the code handle setting non-important data necessary to run the Ability properly.
This leads us into our desired data- each Ability is unique, and therefore must be treated as such. We want the game to read the unique data and do desired things with it, but treat the non-important data as nonexistent.
Take our image example above- our Great Fireball spell has some key features we want to address with this particular Ability. Its damage, its Area-of-Effect, and its Target-Types are all critical pieces of the Ability. The Damage part is self-explanatory: deal damage to those hit by the spell. However, it gets mixed up with its first alternative feature: its AOE. This establishes that it should strike in an area of a particular Radius denoted. Then, the next tag might come as a bit confusing: its Target-Type. You’ll notice two Target-Types listed: one above, just saying “Enemy”: True, and one within the AOE bracket, denoted “SplashAffectsWhatTargetType”. You may be reading the “SplashAffectsWhatTargetType” and thinking its self-explanatory: this is what the AOE effects. But then you must ask: what does “TargetType” mean, with Enemy set to true? Well, it means that this spell can only be cast upon Enemies, which in turn means that when a unit’s AI is functioning, or player is attempting to cast said spell, they must target an Enemy for it to function adequately. So, Great Fireball is a spell which can only be cast upon a target of type Enemy, but its AOE can effect anything from enemies to allies to even the caster itself. However, that’s not the full truth of the spell: it also utilizes “GroundCast”. What this means is that the player does not have to just click upon an Enemy to cast the spell, they can also click upon the ground, and the spellcaster will cast the Spell there. GroundCast is a unique feature predominantly only useful for the player, but there are some various unique ‘tricks’ to it that we developed to make it usable by enemies as well, and we’ll talk about those in a little bit.
Next, let’s look at a slightly different ability: Lifeless Spirit Poison. This Ability has a different parameter: “StatusEffectApplied”. It calls a Status Effect named Poison. This parameter is quite a unique one as it is calling a different set of JSON data to retrieve an element called Poison, and then applying that to the target as they are hit by the Ability. It also inflicts Mana Damage, which harms the target’s mana rather than their health.
With some of those Abilities shown and briefly discussed, its time to talk a bit more about the coding side of their integration.
Each parameter has a function, and its up to us as the programmers to determine what this function does.
Our Damage variable is quite simple- as the Unit is hit by an Ability, have the value of Damage subtract from their health (or, if the Damage is negative, use it to heal them).
Next is AOE: AOE is a bit strange as its design has changed quite a bit since it was first introduced. Originally, no spell would be considered AOE, it simply hit the target. But, as we developed the game and made things more unique, AOE now quite literally is enabled for every Ability. This was due to a major gameplay change done a while back: instead of targeting and tracking the target Unit, we target where they were standing when the Ability was launched, giving an opportunity to dodge the spell. Through code, we can conveniently make it so all Abilities utilize AOE, and the ones that explicitly list out their AOE data override the default instance. With AOE, we’re determining, upon spell landing, a specific area around where the spell landed do we apply ALL of the Ability’s effects (as if anything standing in that area were the ones struck by the spell). Since it applies all effects, we can do things like utilize another variable, “ConstructObject”, that spawns in a particular unit. And we can combine this with both AOE and setting our TargetTypes to “Corpse” to make it so all Corpses in a particular area are returned to life. There’s a lot of creative uses behind this code.
Who and what we hit are dependent upon the TargetType, which lists out what effects what. We do this based on the type of the Unit casting the spell based on the type of Unit whom was hit by the spell. If two PlayerUnits are hit by a spell that only effects Enemies, none of them are effected. But if a PlayerUnit casts a spell upon an Enemy, that enemy is effected, and vice-versa, its just a simple check of typing. Where things get more complicated are with Neutral units: no AI will ever trigger on a Neutral, every action has to be deliberate, and that’s been accounted for.
GroundCast simply means, upon clicking to cast a spell, are we allowed to cast it on the ground? If there is a Unit we can cast upon standing there, we’ll prioritize casting on the Unit. But if not, we’ll cast on the ground tile. This also has a bit of a unique integration: to make it work with non-Player AI, we made it so that if all TargetTypes are false that any non-Player Unit will attempt to cast said spell on a nearby tile at random. This is important for creating other unique spells like Dodge, which makes it so enemies teleport to a random nearby location upon spellcast (more heavily displayed with the Chimaera fight, as shown here), or with our various summoning spells to summon creatures.
Lastly, with our StatusEffectsApplied, you’ll notice its not just a single object but an array of them. Meaning, you can have multiple Status Effects be applied upon casting a single spell. Status Effect integration is possibly the most complicated- on spell hit, we go into our Status Effect database, grab out the desired element, and then put it onto the unit for them to now be effected by (effects range from harmful to helpful and anywhere inbetween). Status Effects are, quite literally, a whole other world of effects on their own, just like Abilities are.
We hope you enjoyed this rather brief insight into some of our coding practices, and thank you for viewing our post! Support and interest for the project has been rapidly growing ever since we began posting here, and we're incredibly grateful for all the wonderful feedback so far! We hope this project interests you as much as we love developing for it, and please look forward to more updates coming in the very near future!
If you’re brand new, consider checking out our trailer and overall description of the game here.