Saturday, May 16, 2015

How I implemented my first spell in to my Unity based RPG

Today I'm going to do something I haven't done before: attempt to share my complete implementation of a feature (my Spell system) in my Rise of Dagon RPG (made in Unity 3D) including some source code!

I know of course I'm no bloody genius and my code hasn't any unique ideas in it - in fact if your a particularly great coder you can probably spot some things I've done poorly.  (hey if so drop me a note - I'd love to make improvements!)

But also I hope that a few people might find it interesting - maybe you haven't gotten in to the new Unity UI, maybe you are just starting out on an RPG and seeing how someone else went about it will help you think your way through yours?

Maybe my game's code is a train wreck - and this will be a great laugh for you?  Perhaps a great example of what not to do?

As I'm attempting to share the complete implementation - this is going to be long! So hey if your the TLDR type; pan through the screenshots and bail now!

Otherwise .. here we go!




So the first thing is I've already implemented a base inventory system where the player can drag items from their inventory into slots on the characters "paper doll" slots for armor, as well as to slots where they can equip items in the primary or secondary hand slots.

This system was created using Unity's new UI system that came out in Unity 4.6 and is implemented starting off with example code in the UI Examples project that Unity provides.

The drag and drop examples give a starting point that is interesting - it actually took me a little bit of time to figure out how to get drag and drop to actually work on the same slot!  The Unity provided as the provided example has separate 'draggable' items and 'droppable' areas.

Making them both work was in the end - simpler than I thought - but call me a bloody idiot it actually took me two weeks to figure it out as I headed the completely wrong direction by trying to combine the two scripts in to one!

The key ended up being that to have what appears to the user to be a single slot that is both drag and drop I needed to have two images!

The first is an outer 'container' image that was the 'droppable' image with an appropriate droppable script.  The second was a nested image as a child that acts as the icon with a draggable script attached (see screenshot if unclear).


At that point I had my way in -- all I needed to do was use the OnDrop() method and do what needed doing code wise and my inventory system was now working!

So - just a reminder - we're building up to the spell system by explaining how it had to fit inside of my inventory system here.

The inventory system has a class hierarchy that derives all the possible items from my BaseItem class.

BaseItem really isn't super important to the spell system except for one thing; and that is that eventually I went with making spells inherit from BaseItem so they could be handled like inventory items.

Of course there is one potential problem with doing this  - what if the player tries to put a Fireball spell on their helmet slot for instance?

Or a lightning bolt in their pants slot?

So to answer this I created a small class called EquipmentSlotRequirement which is used as a member of the BaseItem class to determine what slot each BaseItem can go in to:

EquipmentSlotRequirement code


 using UnityEngine;  
 using System.Collections;  
 public class EquipmentSlotRequirement {  
   public enum SlotRequirement  
   {  
     NONE,  
     ARM,  
     CHEST,  
     EAR,  
     FEET,  
     HANDS,  
     HEAD,  
     LEGS,  
     NECK,  
     PRIMARY,  
     RING,  
     SECONDARY,  
     WRIST,  
     GENERAL  
   }  
   public SlotRequirement equipmentSlotRequirement;  
   public bool meetsSlotRequirement(SlotRequirement requirement)  
   {  
     bool meetsRequirement = false;  
     if (requirement == equipmentSlotRequirement)  
     {  
       meetsRequirement = true;  
     }  
     return meetsRequirement;  
   }  
 }  

When the BaseItem is instantiated we set whatever slot requirement it has in code - in the case of a glove it would require a HANDS slot for example.

Then each equipment slot would also have a EquipmentSlotRequirement; and if the item being dragged in to it doesn't match the requirement:

 if (swapEquipment[fromSlotNum].thisSlotRequires.meetsSlotRequirement(equipSlots[destinationSlotNum].thisSlotRequires.equipmentSlotRequirement))  
 {  
 //do stuff  
 }  

Then the item in question can not be dragged in to the slot!

Well as I started adding Spells to the inventory my first inclination was to create a new ENUM type called "SPELL" and then add that as a requirement to drag to the players hands, then I realized that I had actually not put any requirements on the players hands so far - as I originally wanted you to be able to drag a potion, or a sword, or a wand, or a rock .. really anything to the hand slots right?

And then that glorious but somewhat rare moment when you realize that it already works hit me: spells could be dragged to the hands already because there is no requirement for hands!

But to make sure the spells could not wind up on your helmet slot or your arm, all I had to do was give them the 'NONE' enum slot type .. and thus it would never meet the requirement of specialized inventory slots!

I have no illusions that I was a master mind and with foresight designed my inventory well enough that the Spells would fit so nicely, but in this case I think I got a little cross between luck and just a bit of solid base design paid off.

And you'd think that would be it right?  But no there was still more work to be done to get my spells in to my inventory!

The next thing was I needed to make a class that stores what each of the 4 Characters spell books are, and when you choose to load the individual's spell book it has a reference to the UI component and pushes out the needed references according to which character  you have selected at the moment.

So as of the writing of this post  this class file is still under development as it is hard coded to load the spell book of character 3 with a Firebolt spell!  Still it does have the skeleton needed to complete the rest of work.

The createTestSpells() method is the "proof of concept" that shows the rest of the class works and that its just a matter of writing some helper code to swap the spell book out when a different character is selected, and then eventually load spell books according to each character loaded in the game rather than hard coding in a sample spell.

So lets take a quick look at the PlayerSpells class:


 using UnityEngine;  
 using System.Collections;  
 using UnityEngine.UI;  
 
 public class PlayerSpells : MonoBehaviour {  
  
 private PlayerBehavior playerBehavior;  
   private SpellPrefabs spellPrefabs;  
   public static int currentPlayerSpellsLoaded = 0;  
   public Sprite blanksprite;  
   public GameObject spellPanel;  
   public GameObject spellButton;  
 
  //spell icons and scripts  
   public Image[] spellImages = new Image[12];  
   public EquipableSpellOrAbilitySlot[] spellSlots = new EquipableSpellOrAbilitySlot[12];  
   public BaseSpell[] c1Spells;  
   public BaseSpell[] c2Spells;  
   public BaseSpell[] c3Spells;  
   public BaseSpell[] c4Spells;  
   public BaseSpell[] swapSpells;  
 
     // Use this for initialization  
      void Start () {  
     playerBehavior = FindObjectOfType<PlayerBehavior>();  
     spellPrefabs = FindObjectOfType<SpellPrefabs>();  
     // player spellbooks, and a temp swap spellbook  
     c1Spells = new BaseSpell[12];  
     c2Spells = new BaseSpell[12];  
     c3Spells = new BaseSpell[12];  
     c4Spells = new BaseSpell[12];  
     swapSpells = new BaseSpell[12];  
     //spellImages = new Image[12];  
     initSpells();  
      }   
public void initSpells()  
   {  
     // the reason we do the first one outside of the loop is it exists in the prefab alreaday while the other ones are clones  
     spellImages[0] = spellButton.transform.GetChild(1).GetComponent<Image>();  
     spellSlots[0] = spellButton.transform.GetComponent<EquipableSpellOrAbilitySlot>();  
     for (int x = 1; x < 12; x++)  
     {  
       GameObject newButton = (GameObject)Instantiate(spellButton, transform.position, transform.rotation);  
       newButton.transform.SetParent(spellPanel.transform, true);  
       spellImages[x] = newButton.transform.GetChild(1).GetComponent<Image>();  
       spellSlots[x] = newButton.transform.GetComponent<EquipableSpellOrAbilitySlot>();  
       newButton.name = "SPELL_0" + (x);  
     }  
     for (int x = 0; x < 12; x++)  
     {  
       spellImages[x].sprite = blanksprite;  
       spellSlots[x].slotnum = x;  
       //spellSlots[x].inventory = true;  
     }  
     // init these with blank items (later when players are loaded well put in the actual inventory items they have)  
     for (int x = 0; x < 12; x++)  
     {  
       c1Spells[x] = new BaseSpell();  
       c2Spells[x] = new BaseSpell();  
       c3Spells[x] = new BaseSpell();  
       c4Spells[x] = new BaseSpell();  
     }  
     // temp method to put some spells in the player books for testing purposes  
     createTestSpells();  
   }  
  
 public void createTestSpells()  
   {  
     // this is a debug / test method to create some test spells for game play testing only  
     c3Spells[0] = (BaseSpell)new FireBolt();  
     c3Spells[0].IconID = 80000;  
     c3Spells[0].ItemType = BaseItem.ItemTypes.SPELL;  
     c3Spells[0].minDamage = 3;  
     c3Spells[0].maxDamage = 10;  
     spellImages[0].sprite = spellPrefabs.firebolt.GetComponent<FireboltBehavior>().icon;  
   }  
 }  

Unfortunately I don't know your personal level of expertise (whomever is reading at the moment) so I have to try to walk the line between not getting overly simple to avoid alienating advanced coders, yet not get too technical to alienate newer or non-coders here so please bear with me as I attempt to walk some middle line here and explain what I think are the most important points rather than everything. Do feel free to leave comments if you have questions I'll do my best to answer.

At the top of the PlayerSpells class you see we have some basic class member variables that help us refer to the PlayerBehavior script that contains the information on the characters, and a reference to SpellPrefabs class which just has references to the Unity gameobject prefabs we will instantiate later when casting the spells.

Next up we have a few GUI references that refer to the UI canvas panel that the spells live on and a reference to a single button!

To jump ahead here, when I create the UI in the editor I only make one button but I make sure that the container (the panel) has a GridLayoutGroup component added to it. What this does is when I add more buttons via code, because the GridLayoutCroup is on the panel each additional button will automagically fit in according to the grid pattern laid out in the UI component.

This is a really nice touch of the 4.6 UI so I don't have to define these grids in code! 

You'll notice this work occurs in the initSpells()  method.

spellImages[0]  and spellSlots[0] get a reference to the first button I already created, and its EquipableSpellOrAbilitySlot script  (more on that later).  And after that we iterate of instantiating newButton's for the rest of the spell book.

Now were skipping back up to the class member variables again -- where you see the BaseSpell[] arrays being defined and a swapSpells[] array.

As noted above - the end implementation will need to load the actual spells that each character really has, this is where the spells will be loaded in to (c1Spells through c4Spells).   
A firebolt traveling down the dungeon corridor!
The swapSpells[] array will be used to place a reference to whichever character spells need to be loaded in to the UI as indicated by the currentPlayerSpellsLoaded member variable . 


So now we have a spell book for each character, and a swapSpells book to show the current one on the UI, and we also have a way to show spells on the UI and drag them to the players hands!

And you think we might be done? No, not nearly!

Next up we need a method of actually casting the spell. When the player clicks on that primary or secondary hand slot there are all sorts of things that might actually be in their hands right?

So at the moment I'm using this code to figure out what type of object the player is holding and what to do with it:


 public void PlayerOffHandAction(int player)  
      {  
     // the reason we need a primary hand and offhand action is because unity cant pass more than one parameter on a button method  
     // and we need to know both what player hit it, and which hand so we can grab what item was in the hand  
     BaseWeapon tempWeapon = new BaseWeapon();  
     BaseSpell tempSpell = new BaseSpell();  
     ITakeDamage target = WhoCanIHit();  
     if (playerInventory == null)  
     {  
       playerInventory = FindObjectOfType<PlayerInventory>();  
     }  
     if (player == 1)  
     {  
       // attempt a melee attack  
       try  
       {  
         tempWeapon = (BaseWeapon)playerInventory.c1Equipment[1];  
         meleeAttackSequence(target, tempWeapon);  
         //TODO implement weapon cooldowns  
       }  
       catch  
       {  
         // this catch is only here to prevent a cast exception occuring , nothing to do  
       }  
       //attempt a spell or ability attack  
       try  
       {  
         tempSpell = (BaseSpell)playerInventory.c1Equipment[1];  
         spellOrAbilityAttackSequence(target, tempSpell);  
         //TODO implement weapon cooldowns  
       }  
       catch  
       {  
         // this catch is only here to prevent a cast exception occuring , nothing to do  
       }  
     }  
     else if (player == 2)  
 {  
 // repeat for players 2-4  
 }  


So what's going on here is that firstly (not shown) the primary and secondary hand have a Unity Button script on them.

The button script fire's a method - depending on if its the primary or secondary/offhand hand.  In this case I'm showing the  PlayerOffHandAction() script , the only difference is that it references position 1 in the array rather than position 0.  0 being primary, 1 being secondary/offhand.

So if we click the button; Unity UI calls this method and passes me an int to know which character we are dealing with.  This is necessary because Unity's UI methods only accept 1 parameter as seen in the comment at the beginning.  If Unity button script could accommodate multiple parameters I would only need one method with two parameters which hand, and which player.

Interestingly right as I'm writing this I just thought of a way to refactor this to use 1 method for all the attack determinations that does in fact take two parameters!  What I would do is have the Unity buttons call helper methods that took one parameter, but those helper methods would then call the refactored method with two parameters (if that makes no sense to you don't worry about it - its not really important to this - but an interesting insight for intermediate and above coders).

Anyways - to continue here - we call the PlayerOffHandAction and it attempts to grab a target with the WhoCanIHit() method and then it goes on to make sure the PlayerInventory is not null, if so we grab a reference to it.

Then we try to cast whatever was in this hand to a (BaseWeapon), if the cast succeeds we know the item was some sort of melee weapon and we execute the meleeAttackSequence() method.

If the cast fails it will throw an exception so we catch it to avoid errors, but we don't really need to do anything else with that catch right now as it was expected - so we move on and try to cast the item in the hand to a (BaseSpell) , once again if the cast succeeds we execute a method - in this case the spellOrAbilityAttackSequence() method and proceed.

Also - if the cast fails, we catch the exception but don't do anything with it ; this might happen if the player is holding a quest item in their hands that has no useable actions - its expected to sometimes try to 'use' an item and it is not useable; and it won't cast down to a weapon or a spell.

And then finally we have further if/else iteration for player characters 2-4 that do the same thing.

Now lets step inside the spellOrAbilityAttackSequence() method:


   void spellOrAbilityAttackSequence(ITakeDamage damageable, BaseSpell spellUsed)  
   {  
     if (messageSystem == null)  
     {  
       messageSystem = FindObjectOfType(typeof(MessageSystem)) as MessageSystem;  
     }  
     if (damageable != null)  
     {  
     }  
       if (ActionRounds.playerTurn)  
       {  
         spellUsed.FireSpell();  
         ActionRounds.setPlayerTurn(false);  
       }  
   }  

This code is also a work in progress.  Eventually it will need to handle both projectile spells and what I'll call 'hitscan'  or instant hit spells.

In the case of a hitscan/instant hit spell the codeblock for  if(damageable != null) will be executed .. but the first spell I've implemented is in fact a project type of spell called FireBolt, so the null check is a code stub for the moment.

So remember in the previous bit of code we cast the object in the players hands to a BaseSpell, and then we called this bit of code with our target and the spell being used?

So in this method we are saying if it is in fact the players turn (and eventually check if its on cooldown) then we are using Object Oriented code here to do execute the FireSpell() method on the spellused object like so:   spellUsed.FireSpell() and then tell the ActionRounds (which I use to manage whose turn it is in a state machine) class that its no longer the players turn.

Next we are coming up on the actual spell classes themselves (ooooh exciting!!), but first lets have a brief discussion about the design before hand.

When designing the spell system I knew that I had two primary use cases in mind:

1) The first is Spells like Firebolt, Fireball, Lightning, Heal, and so forth.

2) The second is Abilities like Bash, Slam, Knockback, and so forth.

The thing is both of these categories could behave like a spell - an ability really is only an ability because I put it under the "abilities" tab rather than the "spells" tab.  I mean it would make sense to have a "Knockback" spell very easily right?

And then I got a little confused - do I make BaseAbility the base class, or BaseSpell the base class?

In one way I thought to myself , what does it matter? But then I thought to myself - well maybe it will matter - maybe one of these two is easier for me, or perhaps a better design that will make things 'just work out' better down the line.  

Remember earlier how I mentioned the spells naturally going in to the hand slots ended up 'just working out' earlier?

So if I were a more experienced coder there's probably some UML chart, or class design schematic exercise I should have done but in the end I went to bed and thought about it the next day until I got home in the evening and it seemed the right thing to do was make BaseAbility be the base class because Spells would likely need everything an Ability has - but an Ability doesn't need everything a Spell has.

For instance a Fireball needs a particle system as part of a game object that will be instantiated and that will fly around making the 'fire' visible to the player, but a Bash might have no visible component at all?

So with this in mind I came up with the following two classes:

BaseAbility class, extends BaseItem as mentioned earlier in this blog post because this lets it be handled in the players inventory system and go in to the hand slots.

Notice that BaseAbility is an abstract class, meaning it has methods that are not fully defined - and will have to be defined downstream in the implementing or concrete class.

 using UnityEngine;  
 using UnityEngine.UI;  
 using System.Collections;  
 public abstract class BaseAbility : BaseItem {  
   // all Spells, Skills, Special Abilities etc are handled as a BaseAbility  
   public bool passive;  
   // the minimum level to acquire or use this  
   public int requiredLevel = 0;  
   // the level at which it was cast at, sometimes a mulptiplier effect  
   public int casterLevel = 0;  
   public Sprite icon = null;  
   public GameObject itemObject = null;  
   public enum MagicPower  
   {  
     NONE,  
     HEALTH,  
     STAMINA,  
     STRENGTH,  
     DEXTERITY,  
     INTELLIGENCE,  
     SPIRIT,  
     FIRE,  
     EARTH,  
     WATER,  
     AIR,  
     POSITIVE,  
     NEGATIVE  
   }  
   public MagicPower magicPower;  
   public abstract void FireSpell();  
 }  

And then we have the BaseSpell class


 using UnityEngine;  
 using UnityEngine.UI;  
 using System.Collections;  
 public class BaseSpell : BaseAbility  
 {  
   public bool AOE = false;  
   public int damage;  
   CompassDirection directionCast;  
   public Location3 location;  
   public int minDamage;  
   public int maxDamage;  
   public override void FireSpell()  
   {  
     // actual implementation should be in the concrete spell that is cast rather than this  
   }  
 }  


As you see here in the BaseAbility class I have defined variables that I've labeled as MagicPowers and the abstract FireSpell() method - this is why I explained earlier about my thoughts about Abilities vs Spells and how they are similar and which should inherit from the other!

But as you see, this is not the end .. there is a concrete class at the end of this needed!  For my first spell test I decided to make a spell called FireBolt which is kind of a fireball lite spell, it is smaller and does damage to only one target.

So lets take a look at the FireBolt class!

 using UnityEngine;  
 using System.Collections;  
 using UnityEngine.UI;  
 [System.Serializable]  
 public class FireBolt : BaseSpell  
 {  
   public override void FireSpell()  
   {  
     SpellManager spellManager = UnityEngine.GameObject.FindObjectOfType(typeof(SpellManager)) as SpellManager;  
     FireSpellTypes.SpellsTypes fireBolt = FireSpellTypes.SpellsTypes.FIREBOLT;  
     spellManager.launchFireSpell(fireBolt);  
   }  
 }  

Wow, not much there to look at is there? In fact the only thing is the implementation of the FireSpell() method !

Its possible of course that other spells that are more complex will have more things going on but this is all that was needed for FireBolt and all that will be needed for some other spells like FireBall, Lighting Bolt and similar spells

So what's going on here is I need this NON Monobehavior class to talk to my SpellManager class so that it can instantiate an actual Firebolt gameobject in to the world.

I had thought this was going to be really hard because so far you may not have noticed but BaseItem, BaseAbility , BaseSpell, and FireBolt do not inherit from Monobehavior!

If your fairly experienced you are saying "no big deal" but for me this was a first time experience.. I wasn't sure if it was going to work but I attempted to do what I thought would probably work and I got lucky: it worked on the first try.

As it may be unclear what it was that I did to people new to programming or Unity what I did was

1) add  using UnityEngine;  import at the top of the class file
2) When adding code that refers to Monobehavior methods fully qualify what you are calling by adding UnityEngine. before it

So when I added the line :

 SpellManager spellManager = UnityEngine.GameObject.FindObjectOfType(typeof(SpellManager)) as SpellManager;  

I used that technique and I was able to grab the SpellManager component out of the GameObject in the scene in a non-Monobehavior derived class!

Again this might be old-hat for you veterans but for a self taught guy who has only been coding for 3 years I was jazzed when this worked!

The SpellManager simply instantiates a gameobject Firebolt where the player is and points it in the direction the player is facing.. and it travels forward until it hits a target and explodes.

I've stubbed out an enum for the different FireSpells that I hope to test out but you can see the FireBolt code in its working state below:

 using UnityEngine;  
 using System.Collections;  
 public class SpellManager : MonoBehaviour {  
   public GameObject firebolt;  
   private PlayerBehavior playerBehavior;  
   private PlayerMovement playerMovement;  
      // Use this for initialization  
      void Start () {  
     playerBehavior = FindObjectOfType(typeof(PlayerBehavior)) as PlayerBehavior;  
     playerMovement = FindObjectOfType(typeof(PlayerMovement)) as PlayerMovement;  
      }  
   public void launchFireSpell(FireSpellTypes.SpellsTypes _type)  
   {  
       playerBehavior = FindObjectOfType(typeof(PlayerBehavior)) as PlayerBehavior;  
       playerMovement = FindObjectOfType(typeof(PlayerMovement)) as PlayerMovement;  
     switch (_type)  
     {  
       case FireSpellTypes.SpellsTypes.FIREBOLT:  
         GameObject playerObject = GameObject.Find("playerPrefab");  
         // instantiate an actual Firebolt in the game at the proper start point  
         GameObject newFireBolt = Instantiate(firebolt, playerObject.transform.position,playerObject.transform.rotation) as GameObject;  
         FireboltBehavior tempSpell = newFireBolt.GetComponent<FireboltBehavior>();  
         //TODO tell it what direction to go by setting its compass direction  
         tempSpell.compassDirection = new CompassDirection();  
         tempSpell.compassDirection.currentDirection = playerMovement.compassDirection.currentDirection;  
         tempSpell.currentLocation = new Location3();  
         tempSpell.currentLocation.X = playerMovement.playerLocation.X;  
         tempSpell.currentLocation.Y = playerMovement.playerLocation.Y;  
         tempSpell.currentLocation.Z = playerMovement.playerLocation.Z;  
         // potentially initialize it with info about the caster level and damage range etc  
         tempSpell.alive = true;  
         break;  
       case FireSpellTypes.SpellsTypes.FIREBALL:  
         //dostuff  
         break;  
       case FireSpellTypes.SpellsTypes.FIRESTORM:  
         //dostuff  
         break;  
       case FireSpellTypes.SpellsTypes.METEORSTORM:  
         //dostuff  
         break;  
       default:  
         //dostuff  
         break;  
     }  
   }  
 }  

If there's anyone left reading at this point ...that is how I implemented my first Spell in The Rise of Dagon in Unity 3D!

Thanks for reading and let me know if you have any questions or comments, or even suggestions for code improvements are welcome as well!


No comments:

Post a Comment