Spell System with Inherited Classes

Hey all,

I am new to Unity and to get a grasp of things, i wanted to implement a simple spell system.
To do that:

  • I implemented base class “Magic” (which inherits from MonoBehaviour) and its inherited classes (Fire,Water,Air). These are in “Magic.cs”.
  • I also have an empty GameObject SpellFactory and script named SpellFactory.cs is attached to it.
  • I use “Magic activeMagic” to determine Player’s active magic in hand. I wanted to do this as decoupled as possible so that implementing new spells would be easier.

The problem is whenever I called activeMagic.cast(…) it calls base class’ method “cast()”. I identified that “spellFactory.addComponent<[Spell Type]>();” is the issue here but can’t find a way to use activeMagic correctly. Components are just added to SpellFactory GameObject on scene (this one is obvious :slight_smile: ). How can I use activeMagic to cast a spell? I don’t want to get all components and delete them before adding new component because it seems ugly.

  • “SpellFactory.cs” :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpellFactory : MonoBehaviour
{
    GameObject spellFactory;
    //Magic magicClass;
    Magic activeMagic;
    // Start is called before the first frame update
    void Start()
    {
        spellFactory = GameObject.Find("SpellFactory");
        activeMagic = spellFactory.AddComponent<Magic>();
        //magicClass = spellFactory.GetComponent<Magic>();
    }

    // Update is called once per frame
    void Update()
    {
       
    }

    public void CastSpell(float power){
        activeMagic.cast(power);
    }

    public void ChangeActiveMagic(int number){

        switch (number)
        {
            case 1:
                activeMagic = spellFactory.AddComponent<Fire>();
                Debug.Log("changed fire");
                break;
            case 2:
                activeMagic = spellFactory.AddComponent<Water>();
                Debug.Log("changed water");
                break;
            case 3:
                activeMagic = spellFactory.AddComponent<Air>();
                Debug.Log("changed air");
                break;
            default:
                activeMagic = spellFactory.AddComponent<Magic>();
                break;
        }
    }
}
  • “Magic.cs” :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Magic : MonoBehaviour
{
    protected float initialPower;
    // Start is called before the first frame update
    protected void Start()
    {
        initialPower = 0;
        Debug.Log("Magic!!! Power: " + initialPower);
       
    }

    // Update is called once per frame
    protected void Update()
    {
       
    }

    public int cast(float power){
        Debug.Log("NO");
        return 0;
    }

}

public class Fire : Magic
{

    new void Start()
    {
        initialPower = 5;
        Debug.Log("Fire! initialPower: " + initialPower);
    }

    public new int cast(float power){
        Debug.Log(" Fire spell casted! Power * initialPower = " + (initialPower * power));
        return 0;
    }


}

public class Water : Magic
{

    new void Start()
    {
        initialPower = 3;
        Debug.Log("Water! initialPower=" + initialPower);
    }

    public new int cast(float power){
        Debug.Log("Water spell casted! Power * initialPower = " + (initialPower * power) );
        return 0;
    }

}

public class Air : Magic
{

    new void Start()
    {
        initialPower = 7;
        Debug.Log("Air!");
    }

    public new int cast(float power){
        Debug.Log("Air spell casted! Power*initialpower = " + (initialPower * power) );
        return 0;
    }

}

Well you have two issues here:

First of all you should lookup how oop and inheritance work. You did not implement a virtual method in your base class therefore you can not override it properly in your derived classes. You used the new keyword which explicitly does “hide” the base implementation. This does not result in a virtual dispatch. This is the most fundamental basic concept about OOP, so you really need more background here ^^. In short make your cast method a virtual method and instead of using the new keyword, use the override keyword. Of course Start needs the same treatment.

Your second issue is that every MonoBehaviour that you actually want to attach to a gameobject has to be located in a seperate script and the script file name and the class name have to match. So you can not define your derived classes inside your Magic.cs file.

Finally I would highly recommend to stick to .NET standards where methods always start with a captial letter (PascalCase / UpperCamelCase and not lowerCamelCase)

I’m used to Python; so my C-family skills are rusty as we can see from here :smile:

About second issue: What if I want to add different kind of spells? For example, more advanced fire spells. Should I add them on “Fire.cs” and without an inheritance? With your fix, it worked as expected but still SpellFactory GameObject becomes huge with added Components :slight_smile:

A complex spell system is quite a challenge. From my personal experience with this, I recommend you to stay away from inheritance in Unity as much as possible. The engine focusses on the component pattern, which allows you to piece together your archetypes in the form of having GameObjects with MonoBehaviour components. An archetype is basically your inherited type in traditional C#, but without the need to actual create and hardcode that type, which is one major advantage of the component pattern.

Unity knows something called Prefabs. A prefab is a template for an object in your world, such as a spell. Instead of constructing and destructing your objects all the time, you would instead instantiate those prefabs and reference them in your object that manages the currently active spell. You already do this in SpellFactory, referencing a Magic component. However, instead of dynamically building your spell in ChangeActiveMagic(int), you could reference a prefab and instantiate the needed prefab instead. In other words, the number parameter wouldn’t determinate a C# type, but a prefab. If you change your spell, you either instantiate a new prefab instance from that identifier or you reference an already existing prefab instance if the player has a selection of spells displayed anyways.

Why use a GameObject for each spell and not just one script? Your spells are probably somewhat complex, including but not limited to things like cooldown, cast cost and so on. Your scripts might become very big and since you mention wanting to decouple, adding new components that each do exactly one thing after the single-responsibility principle allows you to keep an overview and have everything nicely sorted.

3 Likes

Yes, this is key: Unity is already a component architecture. Embrace it and forego inheritance. Inheritance also makes things extremely brittle if you are not really good at laying stuff out early on, whereas component architectures tend to be more-easily refactorable, in my experience.

1 Like

Thank you for detailed answer,much appreciated. The thing that does not click (which may caused by my lack of knowledge about prefabs ) is that do I need to create prefabs for every spell of every “element”(different prefabs even if they are the same element)? As far as I can read from linked Prefab page, answer is no; I just need to create Prefabs for elements(fire,water,air,earth) and then I can override their behaviours for different kind of spells in that element (for example fire tornado,fireball and fire wall). Do I get it right?

I would use scriptable objects instead of prefabs. A spell can be an asset. It can reference “component assets” with the specifics. Like a damage type asset, a target type asset, and so on. Then your game is fully data driven and it uses composition which makes it much easier to create new spells by mixing and matching the various data components. This also allows you to create a runtime spell editor where players can select the various components to create custom spells. Custom editor tools will probably also help a lot when creating your data.

Well, there is not a right and wrong really, however, what I would do is to create a prefab for each kind of spell, rather than for each damage type. The behaviour of your spell is likely defined by its components, so one would rather create a prefab that correctly describes a certain behaviour, such as creating a missile, spawning a minion or other stuff, then create prefab variants from that with the actual configuration needed for the spells. One such spell type and its prefab could be SpawnMinion, then variations of it would be RaiseZombie, SummonImp and so on.

If you created prefab variations after damage type, you would have to create the same logic again and again. Let’s say you have a prefab called Fire and you create a prefab variation from that called Fire Wall, but then you would create a prefab called Stone and for it a prefab variation called Stone Wall, you would now have to setup the logic for how a spell creates a wall twice, once for Fire Wall and once for Stone Wall.