Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Seeking good way to define techs/skills in TRPG.

Discussion in 'Scripting' started by LMan, Jul 28, 2015.

  1. LMan

    LMan

    Joined:
    Jun 1, 2013
    Posts:
    493
    I'm looking for a designer-friendly way to define and store a library of special attacks in the context of a Tactical RPG.

    All Techs follow the same line of action- as I've attempted to illustrate on the following, hastily drawn diagram. (I don't actually know how to draw logic flow charts, apologies if I used some wrong shapes/arrows.)
    Tech Logic Flow.png

    All techs have to have information on how they display a selection grid- shape, size, pattern, ect:


    BUT they have to be flexible enough to add individual behavior- for instance an arrow attack range might increase when used on a high elevation, whereas a magic spell range would ignore elevation.

    My current system uses scriptable objects to keep a bunch of common variables, and keep a list of Effects. On execution of the tech, script would just grab only the values it needs and interpret each effect according to it's type.
    Custom Editor.png
    This is rather clunky as there is too much un-needed info that is stored with each tech, and it doesn't support adding any additional behaviors later on. I'm looking for a more flexible approach.

    An idea I had was to assign behavior methods to delegates, to be executed on an event call. It would look like so in the inspector.
    ideaCustom Editor.png

    This would allow me to define very specific methods of behavior, but not have to code similar behavior multiple times.

    The problem then would be defining specific parameters for those individual methods- for instance if an arrow attack required a minimum range parameter that no other move required, how would I go about revealing and setting it through the inspector? Preferably those parameters would appear under the Event inspector when the appropriate delegate was assigned.

    Would such a thing be possible? Is there an infinitely simpler way?
    My apologies if this isn't very clear- questions are welcome. Thank you for your time and advice! -Luke
     
  2. Ness

    Ness

    Joined:
    Oct 1, 2012
    Posts:
    182
    I'm not sure if I understand you 100%, but it seems to me that you are trying to make The Skill Generator Software :] which imo is not necessary. I would just add a "minimum range" and a "terrain height mod" variables and allow them to be applied to any skill. If you apply it to a melee skill while "gamedesigning" this is a mistake to be corrected, but not a big deal.
     
  3. tedthebug

    tedthebug

    Joined:
    May 6, 2015
    Posts:
    2,570
    If some variables are terrain based could those variables be stored on each terrain piece & passed across as needed to fill in formula required for some effects?

    So have standard formula for melee attacks, ranged attacks etc then pass in modifiers based on the character class, terrain etc. this may let you make base classes that you can inherit from & expand/modify as needed.
     
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    What about using GameObject prefabs instead of ScriptableObjects? I find it easier to implement component-based design with GameObjects. You can compose skills by putting a number of small, single-purpose scripts on a GameObject. The scripts can use interfaces to define their purposes. For example, you could compose a Fireball skill by putting scripts like this on a GameObject:
    • Skill - Coordinates all the other scripts.
    • StatRequirement : ISkillRequirement - To use this skill, Mana must be above 50.
    • InventoryRequirements : ISkillRequirement - To use this skill, the character must have a Magic Wand.
    • TerrainTargeting : ISkillTargeting - Allows player to target an area of terrain.
    • CostEffect : ISkillEffect - When used, consume 40 Mana.
    • DamageEffect : ISkillEffect - When used, apply 60-70 damage.
    • SpawnEffect : ISkillEffect - When used, spawn a fire particle effect prefab.
    That's just a hypothetical example. But this approach makes it easy to define new skills and tweak them. For example, you could remove the TerrainTargeting script and add a UnitTargeting script instead to target individual units, or add a PartyRequirements script to require that a specific party member is present to be able to use the skill, etc.

    The Skill script could do the following:
    1. Go through all ISkillRequirements. If they're all met (say there's a function ISkillRequirement.IsMet), allow the character to use the skill.
    2. Find the ISkillTargeting component. Use it to specify a target.
    3. Execute all ISkillEffects when the character uses the skill.
     
    Kiwasi, JoeStrout and LMan like this.
  5. RockoDyne

    RockoDyne

    Joined:
    Apr 10, 2014
    Posts:
    2,234
    Seems a tad bit excessive though, especially to look up things like requirements. Most of this is pretty easy to simplify down to generic datatypes. Effects can be merged into a unified form, same with cost, and put into lists. The two hardest parts are targeting and animations, which more often than not require unique settings. In theory those could be done with other scriptable objects, but finding a nice "designer friendly" method to all of this is going to be a lot of work.
     
    LMan likes this.
  6. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,533
    That's what I like about GameObjects and ScriptableObjects. (I suppose ScriptableObjects would work just as well in this case.) Everything goes through the same inspector as all the usual Unity stuff, so it's fairly designer-friendly.

    The requirements thing might seem a bit excessive, but it's really flexible. It would certainly be possible to add a single ISkillRequirements script that has all your stats, and use that for everything. But then if you had a one-off skill that had some crazy requirements -- specific quest states, specific locations, specific time of day on the realtime system clock, whatever -- you could add them without having to modify any existing code. I like to design things in the smallest functional blocks. It makes them easier to understand, check for correctness, and reuse.
     
    LMan likes this.
  7. LMan

    LMan

    Joined:
    Jun 1, 2013
    Posts:
    493
    I have yet to use interfaces in a big system- that kind of modular flexibility sounds very much like what I had in mind. Thanks so much!
     
  8. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    First thing that popped into my mind was a custom inspector that can hide and display values as needed. Perhaps a reflection based system that can check the methods implemented on a specific object and dynamically create and add the properties you need, probably with a dictionary.

    But if you haven't tried interfaces yet that might be a better place to start.

    Reflection is cool and awesome, but often overkill.
     
  9. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,848
    I did something like this very recently. My boys and I are (just for fun) making a video game version of Firefly: The Board Game (we're calling it Firefly: The Board Game: The Video Game). So we needed a way to represent cards like these:



    We settled on a design very much like what Tony described:

    1. Class Test works with interfaces ITestRequirement and ITestResult; it first checks all the ITestRequirements to make sure the test is even applicable in the current situation...
    Code (CSharp):
    1.    public bool CanApply(PlayerData player) {
    2.         foreach (ITestRequirement requirement in GetComponents<ITestRequirement>()) {
    3.             if (!requirement.CanApply(player)) return false;
    4.         }
    5.         return true;
    6.     }
    ...and then applies all the results:

    Code (CSharp):
    1.     public bool ApplyResults(PlayerData player, int rollTotal=1) {
    2.         bool anyApplied = false;
    3.         foreach (ITestResult result in GetComponents<ITestResult>()) {
    4.             if (result.Apply(player, rollTotal)) anyApplied = true;
    5.         }
    6.         return anyApplied;
    7.     }
    2. We then define a bunch of simple components that are the different sorts of results (or requirements) that can be attached to a test. Most of the results are parameterized with the die roll they apply to.

    3. A card type is defined with another class (NavCard) that defines the sprite to show for the card, and either has the Test and requirement/result components right on it (when there are no options), or has two child objects called "Option1" and "Option2" (each with a Test and requirement/result components on it).


    The cool thing is that one component can be both a requirement and a result, when that makes sense. For example, that "Crazy Ivan!" option above has Pilot and Mechanic requirements (this would be two RequireCrewTag components), and then says "Spend 1 Fuel" — which uses this class:
    Code (CSharp):
    1. public class ResultSpendFuel : MonoBehaviour, ITestRequirement, ITestResult {
    2.     public int minRoll = -999;
    3.     public int maxRoll = 999;
    4.  
    5.     public int howMuchFuel = 1;
    6.  
    7.     public bool CanApply(PlayerData player) {
    8.         return player.fuel >= howMuchFuel;
    9.     }
    10.  
    11.     public bool Apply(PlayerData player, int rollTotal) {
    12.         if (rollTotal < minRoll || rollTotal > maxRoll) return false;
    13.         player.fuel -= howMuchFuel;
    14.         if (player.fuel < 0) player.fuel = 0;    // (just in case!)
    15.         return true;
    16.     }
    17. }
    This component implements ITestRequirement, so if you don't have enough fuel to begin with, then the UI won't even let you choose this option. But then if you do, it subtracts that much fuel when it is applied.

    This is working really well for us — it turns out that fairly small number of requirement and result classes are needed to define all the cards in the game, and then they're just combined in various ways.
     
    LMan, tedthebug and TonyLi like this.
  10. LMan

    LMan

    Joined:
    Jun 1, 2013
    Posts:
    493
    Just thought I'd share my results! Thanks for the advice, all!



    ITargeting takes care of laying down range grids.
    IRequire checks if the conditions are right to execute the skill
    IEffects are executed in the order they appear as components, and they only execute after the preceding effect has completed.


     
    JoeStrout and TonyLi like this.