1
\$\begingroup\$

I am trying to make a component-based ability system for my new turn based game that I am working on. I not sure with the architecture and the approach I am following.

So here my idea:

Every Ability is a Prefab.
Four Ability Prefabs will be attached to every Character, those are their abilities.

An Ability consists of an AbilityController.cs. This will have the name and some basic data for the ability. Then I will attach one or more AbilityTargetMode.cs. Here I can pick a target mode (enemySingle, enemyAll, enemyRandom, AllySelf, ...) and attach one or more AbilityComponent.cs to every AbilityTargetMode. AbilityComponents are things like "Do Damage", "Apply Poison", and so on.

The idea behind this is that I can build complex abilities:

Do damage to a random enemy and poison him, then heal self, then apply buff to all allies.

The structure for an ability would be:

-- Controller
---- TargetMode: EnemySingle
------ Component 1: Damage
------ Component 2: Poison
---- TargetMode: Self
------ Component 1: Heal
---- TargetMode: AlliesAll
------ Component 1 : Buff

And so on..

But I'm not sure about the whole system. Is it something that sounds right? Should I overthink something?

And another problem that I have is that I do not know how to attach my Components and my target mode to my Controller/Prefab. Is it better to use scriptable objects? What are your thoughts on the system?

######EDIT######

So yesterday i got my system working. At least a prototype for it. Here is a picture what i can do with it in the inspector:

Editor for my ability system

Cool thing about it is, that i can make Components like "DoDamage" or "ApplyPoison". Then chain them and compose my custom abilitys. One ability can have more effects. Like a chain of effects. Damage one Enemy > heal one ally > buff all other allys

Its not optimal. I really would like to have something like this with scriptabla objects, but i was not able to pull it off. Is this system okay how i did it?

My game is turn based. The game is based around the combat and around a lot of characters. thats why i want an easy ability composer.

I have 3 codes:

  1. AbilityController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AbilityController : MonoBehaviour
{
    public string abilityName;
    public AbilityTargetMode[] targetModes;

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            for(int i = 0; i < targetModes.Length; i++)
            {
                targetModes[i].Execute();
            }
        }
    }

}
  1. AbilityTargetMode
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public enum TargetMode { EnemySingle, EnemyAll, EnemyRandom, AllySingle, AllySingleOther, AllyAll, AllyAllOther, AllyRandom, AllyRandomOther, AllySelf}

[System.Serializable]
public class AbilityTargetMode
{
    public TargetMode targetMode;

    public AbilityComponent[] abilityComponents;

    private Character[] targets; 

    public void Execute()
    {
        CastAbilityComponentsOnTarget();
    }

    private void CastAbilityComponentsOnTarget()
    {
        for (int i = 0; i < abilityComponents.Length; i++)
        {
            abilityComponents[i].CastAbilityComponent(targets);
        }
    }

    private void SelectTargets()
    {
        switch (targetMode)
        {
            case TargetMode.EnemySingle:
                break;
            case TargetMode.EnemyAll:
                break;
            case TargetMode.EnemyRandom:
                break;
            case TargetMode.AllySingle:
                break;
            case TargetMode.AllySingleOther:
                break;
            case TargetMode.AllyAll:
                break;
            case TargetMode.AllyAllOther:
                break;
            case TargetMode.AllyRandom:
                break;
            case TargetMode.AllyRandomOther:
                break;
            case TargetMode.AllySelf:
                break;

        }
    }

}

  1. AbilityComponent
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class AbilityComponent : MonoBehaviour
{
    public virtual void CastAbilityComponent(Character[] _targets)
    {
        //do stuff
    }
}
\$\endgroup\$
11
  • 1
    \$\begingroup\$ That looks like a job for ScriptableObjects, rather than prefabs, if you're never instantiating them as scene objects. But other than that, I'd generally advise against using the Q&A here to ask internet strangers to vet your ideas. Instead, try implementing your ideas first. If if works as you planned, great, you're done, and you never had to wait for input from us in the peanut gallery. 😉 If it doesn't work, then you'll have a clear problem that you can ask how to solve. \$\endgroup\$ – DMGregory Feb 25 at 18:27
  • \$\begingroup\$ Thank you. Yeah, your right but i maybe didnt described my problem well. I am sitting around for hours trying to figure out how to exactly do this best. i would have delivered some code but here is my problem: If i use Scriptable Objects they are Assets with a fixed value each. But then if i have an asset "Damage" i can not pick the value for it. and i cant add scripts to an scriptable object and adjust the values there. On the other side if i do everything with prefabs and scripts it is maybe not the right way. \$\endgroup\$ – Powski Feb 25 at 19:09
  • \$\begingroup\$ A prefab also has a fixed value each, no? So just like you can create more prefabs with different values, you can create more SOs with different values. \$\endgroup\$ – DMGregory Feb 25 at 22:39
  • \$\begingroup\$ @Powski use ScriptableObjects for things that don't change during gameplay in this case. From what you have described the ScriptableObjects would hold behaviour and actions for ability as well as other info like maybe formulas to calculate the damage or effect length etc. But for dynamic things they should be stored elsewhere, most likely not as ScriptableObjects created in editor, but as objects that are created dynamically, then use these in combination with ScriptableObjects to achieve the desired result. \$\endgroup\$ – Candid Moon _Max_ Feb 26 at 1:55
  • \$\begingroup\$ @Powski I can see why you may want to use Prefabs, if you want to have starting values for objects and to be able to modify them it sounds great until you have full hierarchy bloated with created GameObjects. Some people have been using Instantiate on ScriptableObjects to copy them, but it doesn't copy children scriptable objects as references to those are copies which is good by design. I would use ScriptableObjects in my project if these were objects that I wanted to copy in runtime and use as a base, but being able to edit them in editor. \$\endgroup\$ – Candid Moon _Max_ Feb 26 at 2:16
0
\$\begingroup\$

Problems with this implementation:

  1. The first most obvious problem with your implementation is with AbilityTargetMode. You will run into a problem that one class implements a lot of logic and will become bloated, hard to manage. Basically it doesn't follow the Single Responsibility principle. The main culprit is switch statement. Not only there is this problem, but the issue with adding new types of targets, while game is in development it's really hard to predict what kind of types you are going to need in the future, thus making you add more and more code to this class.

  2. It is limited in aspect of conditions. (Imagine you have a water spirit in your party, it neglects any water based damage, how would you define here if it takes this damage or not? This can of course be handled by passing damage information when actually doing damage to a target. But what if there are other conditions that do not depend on what character knows? Like "Swamp Blessing"[if you are in swamp location - restore 20hp to all allies], how would you handle such a case? It is not a passive skill of a character, nor this is something you want to have in your ability code - because again it will become a mess once you have a few hundred conditions.) Casting of a single AbilityComponent should be able to be under conditions.

  3. It uses prefabs which will work fine, but it could be harder to manage if ability is complicated. The main issue with referencing, it would be hard to tell if you are referencing the correct component, as well as assigning them would be quite tedious. ScriptableObjects are a much better fit.

Possible solution:

I understand that this is link a based answer, but there are 11 scripts and fitting them here would make this answer quite long. If that is necessary to copy them here, let me know, I will do that. But this link will be up as long as this account here exists anyway.

Component-based ability system for turn-based rpg

There is a scene with an example, so I recommend downloading and playing with it because it may look complicated, but it's pretty easy in essence.

[TEST Ability] has a button.

And I apologise in advance for my over modular design, but it's intended to be scalable solution for different types of games. Also, some of the stuff can be used for other systems.

Future possible improvements:

  1. Maybe, condition per action instead or both?
  2. Selection of target providers can be improved by writing a custom inspector that will find them all and show you options to choose from, or using Addressables, though, I would not suggest going with this if you are not fully comfortable in Unity yet, it's a complicated beast and it serves a different purpose, having it for editor stuff is probably an overkill.
  3. FunctionalConditionsGroup is definitely not the best way to handle this, but it's an ok way. A better solution is possible I think, but I would need to work with it to see what works and what not.
\$\endgroup\$
3
  • \$\begingroup\$ Thank you very much for the answer. I will download it and will have a look :) \$\endgroup\$ – Powski Mar 3 at 15:32
  • \$\begingroup\$ I had a look and your system looks really cool so far. I see that there is a problem with my target system. The thing is that i started working on my system in the meanwhile. In the last few days i managed to do it with scriptable objects. While mine system is not as clean as yours (i m a beginner i would say.. and also only doing it as a hobby at the moment) it works so far. Would be a lot of work for me to change it all. I think i will implement some stuff inspired by your targeting system in the future. :) \$\endgroup\$ – Powski Mar 3 at 16:31
  • \$\begingroup\$ @Powski thanks, glad to hear that it can be useful to you :) \$\endgroup\$ – Candid Moon _Max_ Mar 3 at 19:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.