Search Unity

Design Patterns for Enabling Skills/Abilities

Discussion in 'Scripting' started by S3dition, Apr 1, 2019.

  1. S3dition

    S3dition

    Joined:
    Jan 6, 2013
    Posts:
    252
    Hey all. I've been puzzling over and researching what is a common design pattern for RPG type games. I've been wholly unsuccessful in de-mystifying this enigma.

    I full understand that people user either scriptable objects or components in conjunction with interfaces. However, what I haven't seen is a good modular way for calling the different abilities.

    For example, lets say I have some item modules on a ship or character that do something like thorns damage, amplify damage based on targets gold, deal extra damage based on weapon type vs armor type and heals the player for x of the damage dealt.

    As these abilities stack, I need a way to test for and execute them with the proper data in arguments. I might have a list of items and the items may or may not trigger on a given attack/hit. And if they do, they need different data passed through (enemy object, player object, weapon object, damage object, etc).

    I've tried using generics, but that dead ended for me. I don't want to have 40 method signatures when only a few may be needed per ability.

    So how do I ensure that the ability fires when expected and has access to the correct information.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    There's all sorts of ways to approach this problem, and I don't know your specific battle engine so couldn't say how to exactly best fit into your engine.

    But generally speaking... there's your basic requirements of your battle and that would be the arguments that you'd allow your modifiers to decorate.

    So for example... an attack is going to be made up of some simple data:

    Attacker
    Defender
    AttackInfo (which attack the attacker used)

    From this the rest should all be pullable from that information. If for whatever reason the modifier needs to know what armor the 'Defender' is wearing... well your Defender object should have a way to retrieve that information from it. Same goes for things like the weapon, this should be retrievable from the 'Attacker' object as what its equipped weapon is... or maybe it's part of the AttackInfo object (again, I don't know your specific battle system).

    Anyways, my point is you don't come up with 80 bajillion signatures for your functions/methods. That signature should be generalized/abstract. It's sole purpose is to inject the data contexts from which all this information is pulled from.
     
  3. S3dition

    S3dition

    Joined:
    Jan 6, 2013
    Posts:
    252
    That's kind of what I'm stabbing at. So if I have an interface with something like this:

    OnAttack(obj player, obj enemy, obj attackObj)

    That is simple enough to call. However, now suppose I also need to take into account a buff from commander on a ship that adds fires x missiles on a hit with 40% chance (just throwing skills out).

    Now we have to add that extra ability in - but how does it fire? Simply adding another argument won't work, because we don't know that this particular skill exists on the player.

    I'm actually trying to build this system, but the idea is that there can be a queue of skills built on any given object that could be activated. Think Diablo or Neverwinter Nights and the abilities stacked on items. NWN is actually the perfect case, as they can have offensive abilities on armor and defensive abilities on weapons - but its all called in the appropriate OnHit or OnAttack actions.
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    OK... I'll give you an example based on my own personal concept of a battle system. This isn't your battle system, this is just a generic battle system that contains buffs.

    Something like this (mind you this is written in notepad off hand... the design is not clean, the spelling could be off, it is intended as pseudo code):

    Code (csharp):
    1.  
    2. public interface ICombatEntity
    3. {
    4.  
    5.     float Health { get; set; }
    6.     Stats BaseStats { get; }
    7.     Stats ModifiedStats { get; }
    8.  
    9.     void AddModifer(IModifier modifier);
    10.     bool RemoveModifier(IModifier modifier);
    11.  
    12. }
    13.  
    14. public interface IModifier
    15. {
    16.  
    17.     void AdjustStats(ref Stats stats);
    18.  
    19. }
    20.  
    21. public struct Stats
    22. {
    23.     public float MaxHealth;
    24.     public float Defense;
    25.     /.. etc ../
    26. }
    27.  
    28. public interface IAttackLogic
    29. {
    30.  
    31.     void OnAttack(ICombatEntity attacker, ICombatEntity defender);
    32.  
    33. }
    34.  
    35. public class StandardComatEntity : ICombatEntity
    36. {
    37.  
    38.     #region Fields
    39.  
    40.     private float _health;
    41.     private Stats _baseStats;
    42.     private Stats _modifiedStats;
    43.     private List<IModifier> _modifiers = new List<IModifier>();
    44.  
    45.     #endregion
    46.  
    47.     private void ApplyModifiers()
    48.     {
    49.         var stats = _baseStats;
    50.         foreach(var mod in _modifiers)
    51.         {
    52.             mod.AdjustStats(ref stats);
    53.         }
    54.         _modifiedStats = stats;
    55.         _health = Mathf.Clamp(_health, 0f, _modifiedStats.MaxHealth);
    56.     }
    57.  
    58.     #region ICombatEntity Interface
    59.  
    60.     public float Health
    61.     {
    62.         get { return _health; }
    63.         set
    64.         {
    65.             _health = Mathf.Clamp(value, 0f, _modifiedStats.MaxHealth);
    66.         }
    67.     }
    68.  
    69.     public Stats BaseStats { get { return _baseStats; } }
    70.     public Stats ModifiedStats { get { return _modifiedStats; } }
    71.  
    72.     public void AddModifier(IModifier mod)
    73.     {
    74.         _modifiers.Add(mod);
    75.         this.ApplyModifiers();
    76.     }
    77.  
    78.     public bool RemoveModifier(IModifier mod)
    79.     {
    80.         if(_modifiers.Remove(mod))
    81.         {
    82.             this.ApplyModifiers();
    83.             return true;
    84.         }
    85.  
    86.         return false;
    87.     }
    88.  
    89.     #endregion
    90.  
    91. }
    92.  
    93. public class DefenseBuff : IModifier
    94. {
    95.  
    96.     public float DefenseAdjust;
    97.  
    98.     public void AdjustStats(ref Stats stats)
    99.     {
    100.         stats.Defense += this.DefenseAdjust;
    101.     }
    102.  
    103. }
    104.  
    105. public class ParalyzingLungeAttack : IAttackLogic
    106. {
    107.  
    108.     public float Damage;
    109.  
    110.     public void OnAttack(ICombatEntity attacker, ICombatEntity defender)
    111.     {
    112.         //paralyzing lunge attack does its damage plus the attackers attack minus the defender's defense, with a minimum of 5 no matter what
    113.         defender.Health -= Mathf.Max(5f, this.Damage + attacker.ModifiedStats.Attack - defender.ModifiedStats.Defense);
    114.         //it also applies a 'paralyze effect'
    115.         defender.AddModifier(new Paralyze(5)); //paralyze for 5 turns...
    116.     }
    117.  
    118. }
    119.  
    Mind you my 'paralyze' example doesn't really show that modifier, but it's just there to show general idea. Probably would want to add logic to block stacking of paralyze.

    With all this said, I'm not saying you should go with this design outlined here. I don't know your battle engine and what exactly you need. But this is a general starting point I came up with by giving myself the task of designing a general purpose battle system for myself. The general idea of this battle system being that you can "attack" and that the attacks are based on stats that can be "modified".

    Really though... I would have sat down and mapped out my battle system. Figured out the sorts of things my battle system needs to be able to do. Write it all out on paper. THEN I would start mapping out the rules into code from there.
     
    Last edited: Apr 1, 2019
  5. S3dition

    S3dition

    Joined:
    Jan 6, 2013
    Posts:
    252
    I like it. Im running a bit short on spare time, but I'm going to go through some paces and build out a design around this concept.