Sunday 2 June 2013

To effect or not to effect

    This post is going to talk about how the game uses a very generic form of script to provide effects or actuators on a wide variety of objects, performing a wide variety of effects, for a minimum of coding.

    Following on from the previous post where I discussed abstracting the myriad of actions available to the player, this discussion is about the same type of abstraction, but applied to a different game mechanic.

    Think, if you will, of a character in the game.  Let's say it's a smelly little goblin called Bog.  Bog, in the heat of battle, has been struck with a multitude of different magic spells - he is slowed down by a cold spell, he is on fire from that stray fireball, and his sword is currently heating up rapidly because of that damn fire witch!  Each of these effects require the code to change a different aspect of the character.  The slow effect needs to reduce movement, and the fire effect needs to apply damage over time.  The heating-up weapon means soon poor little Bog won't be able to keep hold of his sword and will have to drop it.  To code these up would normally be a bit of a pain in the backside, because they are three very different results, but there is a better way!

    Once again we turn to abstraction.  In the game, there is a class called StatusEffect, and it takes in a bunch of values from a data file that tell the effect class how to behave when applied to an object or character.  At first this sounds horribly complex, but when you consider the game is turn based, you realize that everything is just a series of events happening one after the other...


    Bog starts his turn, and immediately the code polls the active effect scripts attached to Bog, telling the scripts that a turn has started.  The 'On Fire' script has its 'damagePerTurn' value set to 20 and its 'damagePerTurnType' set to 'fire'.  This way the script engine knows that 20 fire damage should be applied during turn start events, and it does so.

    Now Bog wants to move somewhere, but he's being slowed by the cold spell.  When Bog gains control (after the fire damage has been applied) he calculates how far he can move based on his movement points, his character attributes (such as agility) and one more thing - the cold status effect active on Bog.  The cold effect has its 'modifyAttributeType' value set to 'movement' and its modifyAttributeMultiplier set to 0.5.  The code simply asks the active effect scripts for the modifiers to modify Bog's movement allowance by, causing Bog to have half movement.

    I've deliberately left the heating up weapon to last.  This is the fallback system - because not everything can be broken down into a few variables and appropriate event calls.  Instead the status effect script has entirely virtual functions, which allows me to subclass the effect script with custom behaviours.  Any behaviour I do not override runs the default behaviour based off of the data values.  This way I can create custom scripts that have common behaviour, but for certain event types have custom code executing. 

    These custom scripts know they are custom because one of the data values in the standard scripts is a simple boolean called 'useCustomScript'.  By using and abusing C#'s reflection abilities, the game is able to create unique instances of these scripts based off of strings in the data files, allowing for an automated system where custom scripts need the bare minimum of effort to be added into the game.

    So in our heating up example, the custom script would override the onTurnStart method, calling the base method first of course, then it would start heating up Bog's weapon.  After it reaches a certain heat value, it tells Bog to drop it.  A simple script, but one which needs custom behaviour because it needs to be able to tell Bog to drop his weapon, preferably while swearing.  By using polymorphism, there is no need to alter the way the effect script is activated, it uses the existing system and the virtual/override methods take care of the rest.
   
    So through a combination of simple abstraction and polymorphism, I have a system that can create a massive variety of effects with the bare minimum of effort.  Admittedly it was quite an undertaking setting everything up in the first place, but the end result is that I won't feel overwhelmed when it comes time to add the hundreds of different effects to the game.  Most of them will be nothing more than a text file with a few lines in it.

No comments:

Post a Comment