Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Limnage · Jun 05, 2020 at 12:47 PM · componentinheritancearchitecturemodularspells

Ability System with Modular Targeting: having trouble finding a good way to map between input components and effect components

I'm trying to make an ability system with modular targeting/inputs for a turn-based RPG game (using Unity)

What I mean by that is that most ability systems I've found use an inheritance hierarchy where there are different sub-classes for each different type of targeting, e.g. SingleTargetAbility, PBAoEAbility, VectorAbility, etc.

However it seems like this structure lacks flexibility or results in a huge, unwieldy number of subclasses. For example, say you made single target abilities, like Holy Smite and vector (choose a direction radiating out from your character) abilities, like Flame Lance. Now let's say you want to make a Push ability, where the player selects a unit, the selects a direction radiating out from that unit in which to push it. With the inheritance hierarchy method, it seems like you'd have to make a new class, SingleTargetThenVector ability to fit this use case. And so forth for any other combination of inputs.

Instead, I tried to make a system where an ability can have any number of player inputs (i.e. targeting, but also including stuff like "choose one of 3 options"), and any number of effects (like damage, push, heal, etc), and then the inputs are mapped to each effect.

This is what I came up with (abbreviated to include only the important parts):

 public class AbilityInfo : ScriptableObject
 {
     public string abilityName = "New Ability";

     [SerializeField]
     public string id = System.Guid.NewGuid().ToString();

     public List<AbilityInputInfo> inputInfos;

     public List<AbilityEffect> abilityEffects;
 }

 public abstract class AbilityInputInfo
 {
     public abstract string uiPrefabName { get; set; }

     public abstract void promptInput(Ability ability);
 }

 public enum AbilityInputSource : int
 {
     Caster = -1,
     First = 0,
     Second = 1,
     Third = 2
 }

 public class AbilityEffect
 {
     public List<AbilityInputSource> playerInputSources;

     public EffectInfo info;
 }

 public class Ability : MonoBehaviour
 {
     public GameObject caster { get { return gameObject; } private set { } }

     public AbilityInfo info { get; private set; }

     private List<System.Object> _inputs;

     public static void Create(GameObject unit, AbilityInfo info)
     {
         Ability ability = unit.AddComponent<Ability>();
         ability.info = info;
     }
     public void execute()
     {
         if (info.inputInfos.Count > 0)
         {
             _inputs.Clear();
             Events.OnAbilityInput += onPlayerInput;
             info.inputInfos[0].promptInput(this);
         }
     }

     public void onPlayerInput(object input)
     {
         _inputs.Add(input);

         if (_inputs.Count == info.inputInfos.Count)
         {
             Events.OnAbilityInput -= onPlayerInput;
             executeEffects();
         }
         else if (_inputs.Count > info.inputInfos.Count)
         {
             Debug.LogError($"Too many inputs for {info.abilityName}: expected {info.inputInfos.Count}, got {_inputs.Count}");
         }
         else
         {
             info.inputInfos[_inputs.Count].promptInput(this);
         }
     }

     private void executeEffects()
     {
         foreach (AbilityEffect abilityEffect in info.abilityEffects)
         {
             List<System.Object> effectInputs = new List<object>();
             foreach (int inputSource in abilityEffect.playerInputSources)
             {
                 if (inputSource == (int)AbilityInputSource.Caster)
                 {
                     effectInputs.Add(caster);
                 }
                 else
                 {
                     effectInputs.Add(_inputs[inputSource]);
                 }
             }
             abilityEffect.info.execute(effectInputs);
         }
     }
 }

 public abstract class EffectInfo
 {
     public void execute(List<object> inputs)
     {
         //Some shared logic
         _execute(inputs);
     }

     protected abstract void _execute(List<object> inputs);
 }

 public class MoveEffectInfo : EffectInfo
 {
     protected override void _execute(List<object> inputs)
     {
         GameObject unit = (GameObject)inputs[0];
         Vector3Int targetPos = (Vector3Int)inputs[1];
         Movement movement = unit.GetComponent<Movement>();
         movement.move(targetPos);
     }
 }

 public class GroundSelectInputInfo : AbilityInputInfo
 {
     public override string uiPrefabName { get { return "GroundSelectInputUi"; } set { } }

     [SerializeField]
     int range = 5;

     public override void promptInput(Ability ability)
     {
         GameObject gameObject = Utility.InstantiatePrefab(uiPrefabName, GameObject.FindGameObjectWithTag("Canvas"));
         GroundSelectInputUi ui = gameObject.GetComponent<GroundSelectInputUi>();
         ui.initialize(ability.caster.position, range);
     }
 }


There are a few issues with this though:

  • The mapping from input to effect is not type-safe.

Because there are many different types of inputs to effects (units, positions, etc.), I just use "object" as a catch-all and then plugging those into the execute of each effect, and then inside the effects casting to the correct type. However, if I input the mapping wrong in the editor (put 2 instead of 1), then it will be an execution error.

  • Distinguishing between dynamic player inputs and static inputs (like "caster" or "caster's position") is really janky.

I do it through an enum that represents both static inputs and indices to the dynamic inputs, but that seems really janky. For example, right now the indices only go up to three. I'd have to add FOUR, FIVE, SIX, etc. as I have abilities with more inputs.

  • Passing the inputs back to the ability is done through a brittle event system.

Because the input is done through Unity UI, I use an asynchronous callback event system and register a listener in the Ability object. However, the same listener is used for all AbilityInputs, so it's possible that the Ability could receive input from a different UI than it expected (if the UI had a bug and sent multiple onPlayerInput events, for example), and the system wouldn't recognize that.

I can't seem to find a way around these problems, but it seems like there must be a way to make an ability system with modular targeting, although I haven't seen any examples online.

Anyone know how these issues can be solved?

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

0 Replies

· Add your reply
  • Sort: 

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

131 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Game architecture best practices 3 Answers

Composition: Multiple actions on death 1 Answer

An OS design issue: File types associated with their appropriate programs 1 Answer

Callbacks in component based design 1 Answer

Inheritance and Component Types in C# 2 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges