12
\$\begingroup\$

I am designing for a game that consist of characters who have unique offensive skills and other abilities such as building, repairing, etc. Players can control multiple of such characters.

I'm thinking of putting all of such skills and abilities into individual commands. A static controller would register all of these commands into a static command list. The static list would consist of all the available skills and abilities of all the characters in the game. So when a player selects one of the characters and click on a button on the UI to cast a spell or perform an ability, the View would call the static controller to fetch the desired command from the list and execute it.

What I'm not sure of however if this is a good design given that I'm building my game in Unity. I am thinking I could have made all the skills and abilities as individual components, which would then be attached to the GameObjects representing the characters in the game. Then the UI would need to hold the GameObject of the character and then execute the command.

What would be a better design and practice for a game that I am designing?

\$\endgroup\$
3
  • \$\begingroup\$ Seems good! Just throwing this related fact out there: In some languages, you can even go as far as making each command a function to itself. This has some awesome advantages for testing, since you can easily automate input. Also, control rebinding can be done easily by reassigning a callback function variable to a different command function. \$\endgroup\$
    – Anko
    Commented Jan 5, 2013 at 15:03
  • \$\begingroup\$ @Anko, what about the part where I have all the commands put into a static list? I'm worried that the list may get huge and every time when when a command is needed, it has to query the huge list of commands. \$\endgroup\$
    – xenon
    Commented Jan 5, 2013 at 15:31
  • 1
    \$\begingroup\$ @xenon You are very unlikely to see performance issues in this part of the code. As far as something can only happen once per user interaction it would have to be very computation intensive to make a noticeable dent in the performance. \$\endgroup\$ Commented Jan 5, 2013 at 18:56

2 Answers 2

18
\$\begingroup\$

TL;DR

This answer goes a little crazy. But it's because I see you're talking about implementing your abilities as "Commands," which implies C++/Java/.NET design patterns, which implies a code-heavy approach. That approah is valid, but there's a better way. Maybe you're already doing the other way. If so, oh well. Hopefully others find it useful if that's the case.

Look at Data-Driven Approach below to cut to the chase. Get Jacob Pennock's CustomAssetUility here and read his post about it.

Working With Unity

Like others have mentioned, traversing a list of 100-300 items is not as big a deal as you might think. So if that's an intuitive approach for you, then just do that. Optimize for brain efficiency. But the Dictionary, as @Norguard demonstrated in his answer, is the easy no-brainpower-required way to eliminate that problem since you get constant-time insertion and retrieval. You should probably use it.

In terms of making this work well within Unity, my gut tells me that one MonoBehaviour per ability is a dangerous path to go down. If any of your abilities maintain state over time a they execute, you'll need to manage that a provide a way to reset that state. Coroutines alleviate this problem, but you're still managing an IEnumerator reference on every update frame of that script, and have to absolutely make sure you have a sure-fire way to reset abilities lest incomplete and stuck-in-a-state-loop abilities quietly start screwing up the stability of your game when they go unnoticed. "Of course I'll do that!" you say, "I'm a 'Good Programmer'!". But really, you know, we're all objectively terrible programmers and even the greatest AI researchers and compiler writers screw stuff up all the time.

Of all the ways you could implement command instantiation and retrieval in Unity, I can think of two: one is fine and won't give you an aneurysm, and the other allows for UNBOUNDED MAGICAL CREATIVITY. Sort of.

Code-Centric Approach

First is a mostly-in-code approach. What I recommend is that you make each command a simple class that either inherits from a BaseCommand abtract class or implements an ICommand interface (I'm assuming for the sake of brevity that these Commands will only ever be character abilities, it's not hard to incorporate other uses). This system assumes that each command is an ICommand, has a public constructor that takes no parameters, and requires updating each frame while it is active.

Things are simpler if you use an abstract base class, but my version uses interfaces.

It's important that your MonoBehaviours encapsulate one specific behavior, or a system of closely-related behaviors. It's okay to have lots of MonoBehaviours that effectively just proxy out to plain C# classes, but if you find yourself doing too may update calls out to all sorts of different objects to the point where it's starting to look like an XNA game, then you're in serious trouble and need to change your architecture.

// ICommand.cs
public interface ICommand
{
    public void Execute(AbilityActivator originator, TargetingInfo targets);
    public void Update();
    public bool IsActive { get; }
}


// CommandList.cs
// Attach this to a game object in your loading screen
public static class CommandList
{
    public static ICommand GetInstance(string key)
    {
        return commandDict[key].GetRef();
    }


    static CommandListInitializerScript()
    {
        commandDict = new Dictionary<string, ICommand>() {

            { "SwordSpin", new CommandRef<SwordSpin>() },

            { "BellyRub", new CommandRef<BellyRub>() },

            { "StickyShield", new CommandRef<StickyShield>() },

            // Add more commands here
        };
    }


    private class CommandRef<T> where T : ICommand, new()
    {
        public ICommand GetNew()
        {
            return new T();
        }
    }

    private static Dictionary<string, ICommand> commandDict;
}


// AbilityActivator.cs
// Attach this to your character objects
public class AbilityActivator : MonoBehaviour
{
    List<ICommand> activeAbilities = new List<ICommand>();

    void Update()
    {
        string activatedAbility = GetActivatedAbilityThisFrame();
        if (!string.IsNullOrEmpty(acitvatedAbility))
            ICommand command = CommandList.Get(activatedAbility).GetRef();
            command.Execute(this, this.GetTargets());
            activeAbilities.Add(command);
        }

        foreach (var ability in activeAbilities) {
            ability.Update();
        }

        activeAbilities.RemoveAll(a => !a.IsActive);
    }
}

This works totally fine, but you can do better (also, a List<T> isn't the optimal data structure for storing timed abilities, you might want a LinkedList<T> or a SortedDictionary<float, T>).

Data-Driven Approach

It's probably possible that you can reduce your ability's effects down into logical behaviors that can be parameterized. This is what Unity was really built for. You, as a programmer, design a system that then either you or a designer can go and manipulate in the editor to produce a wide variety of effects. This will great simplify the "rigging" of the code, and focus exclusively on execution of an ability. No need to juggle base classes or interfaces and generics here. It will all be purely data driven (which also simplifies initializing command instances).

The first thing you need is a ScriptableObject that can describe your abilities. ScriptableObjects are awesome. They're designed to work like MonoBehaviours in that you can set their public fields in Unity's inspector, and those changes will get serialized to disk. However, they're not attached to any object and don't have to be attached to a game object in a scene or instantiated. They are the catch-all data buckets of Unity. They can serialize basic types, enums, and simple classes (no inheritance) marked [Serializable]. Structs can't be serialized in Unity, and serialization is what allows you to edit the object fields in the inspector, so remember that.

Here's a ScriptableObject that tries to do a lot. You can break this out into more serialized classes and ScriptableObjects, but this is supposed to just give you an idea of how to go about doing it. Normally this looks ugly in a nice modern object oriented language like C#, since it really feels like some C89 shit with all those enums, but the real power here is that now you can create all sorts of different abilities without ever writing new code to support them. And if your first format doesn't do what you need it to do, just keep adding to it until it does. So long as you don't change field names, all your old serialized asset files will still work.

// CommandAbilityDescription.cs
public class CommandAbilityDecription : ScriptableObject
{

    // Identification and information
    public string displayName; // Name used for display purposes for the GUI
    // We don't need an identifier field, because this will actually be stored
    // as a file on disk and thus implicitly have its own identifier string.

    // Description of damage to targets

    // I put this enum inside the class for answer readability, but it really belongs outside, inside a namespace rather than nested inside a class
    public enum DamageType
    {
        None,
        SingleTarget,
        SingleTargetOverTime,
        Area,
        AreaOverTime,
    }

    public DamageType damageType;
    public float damage; // Can represent either insta-hit damage, or damage rate over time (depend)
    public float duration; // Used for over-time type damages, or as a delay for insta-hit damage

    // Visual FX
    public enum EffectPlacement
    {
        CenteredOnTargets,
        CenteredOnFirstTarget,
        CenteredOnCharacter,
    }

    [Serializable]
    public class AbilityVisualEffect
    {
        public EffectPlacement placement;
        public VisualEffectBehavior visualEffect;
    }

    public AbilityVisualEffect[] visualEffects;
}

// VisualEffectBehavior.cs
public abtract class VisualEffectBehavior : MonoBehaviour
{
    // When an artist makes a visual effect, they generally make a GameObject Prefab.
    // You can extend this base class to support different kinds of visual effects
    // such as particle systems, post-processing screen effects, etc.
    public virtual void PlayEffect(); 
}

You could further abstract the Damage section into a Serializable class so you could define abilities that deal damage, or heal, and have multiple damage types in one ability. The only rule is no inheritance unless you use multiple scriptable objects and reference the different complex damage configuration files on disk.

You still need the AbilityActivator MonoBehaviour, but now he does a little more work.

// AbilityActivator.cs
public class AbilityActivator : MonoBehaviour
{
    public void ActivateAbility(string abilityName)
    {
        var command = (CommandAbilityDescription) Resources.Load(string.Format("Abilities/{0}", abilityName));
        ProcessCommand(command);
    }

    private void ProcessCommand(CommandAbilityDescription command)
    {

        foreach (var fx in command.visualEffects) {
            fx.PlayEffect();
        }

        switch(command.damageType) {
            // yatta yatta yatta
        }

        // and so forth, whatever your needs require

        // You could even make a copy of the CommandAbilityDescription
        var myCopy = Object.Instantiate(command);

        // So you can keep track of state changes (ie: damage duration)
    }
}

The COOLEST part

So the interface and generic trickery in the first approach will work fine. But in order to really get the most out of Unity, ScriptableObjects will get you where you want to be. Unity is great in that it provides a very consistent and logical environment for programmers, but also has all the data entry niceties for designers and artists you get from GameMaker, UDK, et. al.

Last month, our artist took a powerup ScriptableObject type that was supposed to define behavior for different kinds of homing missiles, combined it with an AnimationCurve and a behavior that made missiles hover along the ground, and made this crazy new spinning-hockey-puck-of-death weapon.

I still need to go back and add specific support for this behavior to make sure it's running efficiently. But because we made this generic data description interface, he was able to pull this idea out of thin air and put it into the game without us programmers even knowing he was trying to do it until he came over and said, "Hey guys, look at this cool thing!" And because it was clearly awesome, I'm excited to go add more robust support for it.

\$\endgroup\$
1
  • \$\begingroup\$ As someone who implemented the first option because it mentally made sense and is easy to understand because "abilities are a tangible thing (class of "Ability") and I can give them to a player to use (command)", what are the bullet point advantages and disadvantages of each? I actually couldn't find a good advantage of the second way in your writeup besides "unity likes serialized objects and it lets other non-programmers on the team make stuff easier". In fact, one huge advantage of the first way is that you could dynamically assign abilities on the fly (think respeccing in WoW). \$\endgroup\$ Commented Jul 26, 2020 at 4:44
3
\$\begingroup\$

TL:DR -- if you're thinking about stuffing hundreds or thousands of abilities into a list/array that you'd then iterate through, every time there's an action called, to see if the action exists and if there's a character who can perform it, then read below.

If not, then don't worry about it.
If you're talking about 6 characters/character-types and maybe 30 abilities, then it's really not going to matter what you do, because the overhead of managing complexities might actually require more code and more processing than just dumping everything in a pile and sorting...

Which is exactly why @eBusiness suggests that you're unlikely to see performance issues during event-dispatch, because unless you're trying really hard to do so, there's not a lot of overwhelming work here, compared to transforming the position of 3-million vertices on-screen, etc...

Also, this is not the solution, but rather a solution for managing larger sets of similar issues...

But...

It all comes down to how big you're making the game, how many characters share the same skills, how many different characters/different skills there are, right?

Having the skills be components of the character, but having them register/unregister from a command-interface as characters join or leave your control (or get knocked out/etc) still makes sense, in a very StarCraft sort of way, with hotkeys and the command card.

I've had very, very little experience with Unity's scripting, but I'm very comfortable with JavaScript as a language.
If they allow it, why not have that list be a simple object:

// Command interface wraps this
var registered_abilities = {},

    register = function (name, callback) {
        registered_abilities[name] = callback;
    },
    unregister = function (name) {
        registered_abilities[name] = null;
    },

    call = function (name,/*arr/undef*/params) {
        var callback = registered_abilities[name];
        if (callback) { callback(params); }
    },

    public_interface = {
        register : register,
        unregister : unregister,
        call : call
    };

return public_interface;

And it might be used like:

var command_card = new CommandInterface();

// one-time setup
system.listen("register-ability",   command_card.register  );
system.listen("unregister-ability", command_card.unregister);
system.listen("use-action",         command_card.call      );

// init characters
var dave = new PlayerCharacter("Dave"); // Character Factory pulls out Dave + dependencies
dave.init();

Where the Dave().init function might look like:

// Inside of Dave class
init = function () {
    // other instance-level stuff ...

    system.notify("register-ability", "repair",  this.Repair );
    system.notify("register-ability", "science", this.Science);
},

die = function () {
    // other clean-up stuff ...

    system.notify("unregister-ability", "repair" );
    system.notify("unregister-ability", "science");
},

resurrect = function () { /* same idea as init */ };

If more people than just Dave have .Repair(), but you can guarantee that there's only going to be one Dave, then just change it to system.notify("register-ability", "dave:repair", this.Repair);

And call the skill by using system.notify("use-action", "dave:repair");

I'm not sure what the lists you're using are like. (In terms of the UnityScript type-system, AND in terms of what's going on post-compilation).

I can probably say that if you've got hundreds of skills that you were planning on just stuffing into the list (rather than registering and unregistering, based on which characters you currently have available), that iterating through a whole JS array (again, if that's what they're doing) to check a property of a class/object, which matches the name of the action you want to perform, is going to be less performant than this.

If there are more-optimized structures, then they're going to be more performant than this.

But in either case, now you have Characters who control their own actions (take it a step further and make them components/entities, if you wish), AND you have a control system which requires a minimum of iteration (as you're just doing table-lookups by name).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .