Search Unity

Feedback There's no good way to build an Ability/Skill system with Unity tools

Discussion in 'Scripting' started by Limnage, May 10, 2020.

  1. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    I've been researching all the resources I could find on implementing ability/skill systems in Unity, and I've come to the conclusion that there's no way to properly design and implement one using Unity tools.

    Here's the fundamental problem: a proper ability system will build abilities out of modular components like targeting, effects, etc.

    For example, take a look at DotA 2's ability system: https://developer.valvesoftware.com...ools/Scripting/Abilities_Data_Driven_Examples

    Abilities are built with nested components like modifiers, events,and actions. You define an event block, and then define the various actions that will occur on that event.

    However, Unity does not have tools to create a system with nested components that can be modified in the editor.

    The Unity tool best suited for an ability system seems to be ScriptableObjects. For example, this is an official Unity tutorial on how to create abilities using ScriptableObjects: https://learn.unity.com/tutorial/cr...h-scriptable-objects#5cf5ecededbc2a36a1bd53b7

    Instead of defining modular components, like different targeting types, that can be plugged into abilities freely, that tutorial creates an inheritance hierarchy, with a base Ability class and each type of targeting being a separate derived class.

    This seems like a horrible idea that does not extend well at all. In that example code, each ability defines its actions (e.g. applying damage) explicitly in the derived class. What about when there are multiple different types of actions (applying damage, applying debuffs, stunning, pushing, teleporting, etc.) that you want to mix and match with different targeting? Then the inheritance tree quickly breaks down and becomes unmanageable.

    The ideal solution would be to be able to define nested ScriptableObjects inside of ScriptableObjects. The problem is that Unity does not handle this well. If you try to add the nested SOs as subassets, then they don't get properly removed and deleted and you can end up with a bunch of orphaned assets. This basic functionality that is required for any modular ability system does not seem to be properly supported by Unity for some reason.

    If you simply create all nested SOs as separate assets, then you get a bunch of horrible redundancy and wasted manual effort. For example, imagine if you have a targeting modules that you define as SOs. Then, you'd have to create multiple SOs representing stuff "single target, range 5, enemies only". You'd need a single SO for every combination of parameters, which seems absurd.

    You also can't use simple Serializable classes, because Unity's serialization doesn't support polymorphism.

    So in conclusion it seems like there isn't actually any way to implement a modular, component-based ability system with Unity's tools. Every example I've seen using an inheritance hierarchy or just enumerates every single possible attribute an ability could have and requires you to set the unused ones to null. Both systems quickly break down as the complexity and number of abilities grows.
     
  2. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    Since I expect this thread will get attract lots of different responses, maybe you could take a few minutes to elaborate what you consider a "Unity tool".

    Does .net and C# count as Unity tools, or do you require a more specific framework? What level of automation are you looking for? Drag and drop to build a system with the complexity of Dota? Or would scriptable objects be acceptable "if only they allowed nesting"?

    I don't mean to come across harsh, but with blanket statements like "there isn't actually any way to implement a modular, component-based ability system with Unity's tools", you're likely to get an earful unless you are a bit more specific.

    To be clear, I'm inclined to agree with you, especially given the kinds of skill systems I've seen puttered around the forums. So I await the responses of those much more experience with baited breath.
     
    DungDajHjep, akuno, Nad_B and 5 others like this.
  3. khrysller

    khrysller

    Joined:
    Mar 14, 2019
    Posts:
    125
    following this I am interested
     
  4. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    By "Unity Tool" I meant specifically the systems built by the Unity team like ScriptableObjects and serialization. Of course you can define your own json/xml like syntax for defining abilities and build your own parser for it with C# (and in fact I've seen many people do that when making games in Unity), but at that point your system doesn't really have anything to do with Unity and Unity isn't helping you to create the system in any way.

    Compared to something like Unreal's Gameplay Ability System (https://docs.unrealengine.com/en-US/Gameplay/GameplayAbilitySystem/index.html) this seems very inconvenient.
     
  5. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,536
    uhh.. do you even script bro?
    you can literally do everything you said here, just make a goddamn custom class.

    I stopped reading halfway through because it stinks of a "no magic button to do everything i want in a second with 0 bugs" type threads..
     
  6. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    That was kind of a dramatic and confusing explanation of your problem.

    But let me suggest a few directions you could try going, with various tradeoffs:


    Option #1: Replace Polymorphism with Parameterization

    Instead of making "deal damage" and "stun" and "push" into subclasses, create one class with an enum-type variable that says which of those things it's going to do. Add a [Serializable] attribute to SpellEffect, and then your Spell class can have a List<SpellEffect> field and it will serialize fine.

    Note that you could still delegate the actual functionality to a bunch of different subclasses at runtime, if you want. The function SpellEffect.Cast() might just look up the appropriate class to use based on the effect-type field and then call that. (Probably using either a giant switch statement or a dictionary look-up.)

    The main downside is that the list of parameters that modify how the effect works has to be the same for all effect types, since they're all stored in the same data structure. e.g. you might have a field like "float intensity" that on a damage spell determines how much damage it does but on a stun determines how long the stun lasts and on a push determines how far (or how much force). You may end up with one or two "extra" parameters that are ignored by most effect types, and you may find some concepts challenging to represent.

    However, if it's important to you that the people editing spells in your project have a nice clear UI to work with, you could write a custom editor to change how these parameters are displayed depending on the currently-selected effect type.


    Option #2: All-in-One Spells

    Instead of making one class that does damage, and another that stuns, and a third that pushes, make a single class that does everything. Every single spell in the whole game has a field for how much damage it does, and a separate field for how long it stuns, and a separate field for how far it pushes the target. Most of those fields may be "zero" on most spells, but they're always there if you need them.

    Obviously, this could get unwieldy if you have a large number of specialized effects. But there are many computer games where this could be totally practical. And a few wacky special cases that are only used by one spell (or one family of spells) can still be handled by separate classes as long as they don't need to be combined with all the other effects in the game.


    Option #3: Metamagic

    Instead of trying to cram 5 effects into one spell, you could create spells that cast other spells (or at least, SpellEffects that cast other SpellEffects--you don't necessarily need the children to have their own mana cost, range, etc.). Maybe you just have a field on every spell for the spell to cast next (which will be null a lot of the time), or maybe you have "metaspell" as a specific spell subtype and it contains a list of all the spells it should cast.

    In fact, this is such a powerful feature that you may want to consider implementing it even if you use one of the other listed options.

    You seem to be concerned about the fact that you might delete a "parent" spell and forget to delete its "child" spells that are no longer needed. Personally, I don't see how this is any different from the risk that you might forget to delete the parent spell when you change the wizard's spell list so that they can no longer cast it. You should be able to mostly eliminate this issue through good workflow and disciplined file organization, but if you're really worried you can write a script that will scan all of your assets for orphans.

    This also has the advantage that a single subeffect could be used by multiple spells, if you want. Maybe one class has a signature effect that's used as a component of multiple abilities ("deal 5 damage and then stealth", "teleport 3 squares and then stealth", "apply stealth to all allies within 5 tiles", etc.). If you use metamagic, then if you ever need to tweak that effect, you can make your changes in one place and they'll automatically apply everywhere.


    Option #4: Split the Data and Interface

    It sounds like you've got one Unity option that can serialize your data, but doesn't manage it the way you want, and another Unity option that can mange your data the way you want, but can't serialize it properly. You could bridge the two with a custom editor that makes your data look like one thing but secretly transforms it into something else behind-the-scenes so that it can be saved properly.


    Option #5: Make Your Own Editor

    This doesn't have to be fancy. It might just be a spreadsheet with some simple macros that you export as a CSV and then have your game read when it launches.
     
  7. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    Right, you can create your own json/xml format and create your own c# parser, but you wouldn't be using ScriptableObjects or any of the Unity tools that they seem to intend people to use for Ability systems based on their tutorials.
     
  8. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    Thanks for the reply.

    Option 1 and 2 are certainly possible, but they seem to fall into the "enumerate every possible attribute in a god class" anti-pattern (for option 1 there's a SpellEffect god class and for option 2 there's a Spell god class). Although you can reduce the number of attributes at the risk of introducing complexity, bugs, and need for additional code by making the attributes generic (e.g. parameter1, parameter2), and then using those parameters to mean different things based on an enum switch or something similar, it still seems like the system would fall apart once you get to a large number of different effects with different parameters (some of them floats, some of them ints, some of them enums, etc. etc.). It seems like something like the DotA 2 ability system or any large scale game with lots of abilities would make this system quickly unusable.

    Option 3 (metamagic) is ideal. This is the system that I think most production scale ability systems like DotA 2 use, i.e. making individual effects modular so that they can be plugged into various abilities. As you point out, you can have a spell effect that can be used by various parent spells.

    The problem here is that specifically there doesn't seem to be a good way to do this with Scriptable Objects. Let's say you have a StealthEffect class that inherits form SpellEffect that inherits form ScriptableObject. StealthEffect (or any SpellEffect) will probably need some specific parameters, like stealth duration or fade time.

    If you manually create StealthEffect SO assets for each combination of parameter values (like one for stealth duration or 2 and fade time of 5, another one for stealth duration of 1 and fade time of 3, etc.), then it quickly becomes very messy. Ideally you just want the parent Spell ScriptableObject asset to contain all of its own information, so you'd want to be able to define a DamageAndStealth spell that contains a StealthEffect and a TeleportAndStealth spell that contains a StealthEffect, each with their own parameters.

    This thread shows some of the difficulties with that, though: https://forum.unity.com/threads/scriptableobject-skill-system-how-to-better-organize.539400/ when you create these child component effects as subassets of the parent spell SO, then you get lots of orphan assets if you change it or undo in the editor. If you just have one spell hold all of its own parameters, then this isn't a problem, but there doesn't seem to be a clean way to do this with ScriptableObjects.

    Option 4 and 5 are going down the path of creating your own custom editor and format and ignoring stuff like ScriptableObject. This seems to be the best path if you want a well-designed ability system, but it means ignoring the tools like ScriptableObject that Unity seems to intend to be used for this exact use-case.
     
  9. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    To the best of my knowledge, option 1 is how WarCraft 3 worked. In fact, basically any of the games I played growing up that had some kind of customer-accessible editor appeared to use this system.

    You can argue that this was a kludge that people did because better tools weren't available. But since you are presently complaining that better tools are not available, maybe you should consider it.


    Uh...no? Aren't you just putting all the parameters into one god object like you objected to above?

    The whole point of treating the second effect as its own encapsulated spell is that you don't need to put its damage, duration, etc. into the parent spell. The stealth class is the class that already knows what parameters are required for stealth, so you enter the parameters there, not into the meta-spell that invokes it.

    If you put those parameters into the parent instead, you'd effectively be using option 1 instead of option 3. (And incidentally would destroy the advantage of having a single subspell that can be used by multiple parents but rebalanced in just one place.)


    I don't think ScriptableObject is intended for this exact use case. Unity noticed that people were creating MonoBehaviours just to hold data when they didn't actually need stuff like a Transform or an Update function, and ScriptableObjects are meant to replace those. They aren't the result of someone asking "how should someone make an ability system in Unity?", they are the result of someone asking "can I get something that I can edit like a MonoBehaviour but that doesn't actually have to be part of a scene?"
     
    Aethenosity, hassanali4 and Bunny83 like this.
  10. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    What I mean is that the subspells/subeffect would be their own classes, but they would have parameters that are specified by each individual spell that uses them. For example, you wouldn't want to restrict it so that every spell that uses stealth must have the same stealth duration, right? So you'd need each spell to include the StealthEffect component, but then specify the variables that differ for each spell, like duration or fade time.

    So something like:


    Code (CSharp):
    1. public class Ability : ScriptableObject
    2. {
    3.     public string abilityName = "New Ability";
    4.  
    5.     [SerializeField]
    6.     List<Effect> abilityEffects;
    7. }
    Code (CSharp):
    1. [System.Serializable]
    2. public class StealthEffect : Effect
    3. {
    4.     float stealthDuration;
    5. }
    The problem is that you can't do this because Serializable doesn't work in the editor with polymorphism.

    You could try making StealthEffect inherit from ScriptableObject instead, but then you'd run into the aforementioned problems with ScriptableObjects.

    Warcraft 3 was made 20 years ago, and design and technology has progressed since then. The old style is not as flexible as a modular system, and it seems like a game engine should have tools should support that.

    Unity themselves seem to think that ScriptableObjects are the answer, since they have an official tutorial for ScriptableObject abilities: https://learn.unity.com/tutorial/cr...h-scriptable-objects#5cf5ecededbc2a36a1bd53b7

    The problem is if you can't do nested ScriptableObjects cleanly or do polymorphism with serializables, then you can't really use the Unity editor to make your ability system unless you customize it heavily, at which point you could just write your own parsing and syntax. It's just disappointing when Unreal not only gives you tools to make your own ability system properly, but straight up gives you a working and well designed Gameplay Ability System that you can use out of the box.
     
  11. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    One big advantage of options 1 and 2 is that they make it possible to construct a single table that lists all of the spells in your entire game, with each spell being 1 row. It is true that this places notable structural limits on how your spells can be built, but it brings some organizational advantages. Depending on how crazy spells get in your particular game, those organizational advantages might outweigh the lost flexibility.

    (Remember that flexibility is only valuable if you actually use it. You may want to start by making an informal list of all the types of spells you actually plan to put into your game and seeing how much flexibility you actually need to support those spells.)

    In that particular example, I was explicitly assuming that you would want "stealth" to be the exactly same in every case. This means you can make changes in one place that apply to all spells, and also allows you to describe the spells very compactly (e.g. in tooltips).

    If you want different parameters in every case, then you need a separate class instance to use as the follow-on for each "parent" spell, rather than a single instance that is incorporated by reference into multiple parents.

    Technically, you'd probably make Effect inherit from ScriptableObject, and StealthEffect would still inherit from Effect.

    But yes, if you want "metamagic" (spells that cast other spells), and if you have chosen to use ScriptableObjects for your spell data, then obviously subspells that you incorporate by reference should also be ScriptableObjects.

    When you are asking for subspells to be serialized directly inside of the parent spells, you are really saying that you don't want to use metamagic at all, in the sense that you don't want subspells to be incorporated by reference. You don't want them to be standalone objects that can be reused by multiple parents; you want them to be modules that are built into a single parent.

    And it's fine if you prefer that as an organizational scheme, but let me just point out that in terms of what the system can actually DO, you are asking for a less flexible system. Even if Unity supported the exact thing that you're wishing for, some games should use separate ScriptableObjects anyway, because they allow reuse in a way that your proposed system does not.


    Now, as far as "the aforementioned problems"...

    Your most recent post only complains that this is "messy" (which is subjective, but for whatever it's worth, I think the mess should be pretty minimal if you use sensible conventions for naming and organization of your assets).

    You earlier seemed worried about orphans, but I addressed that in my first reply and you didn't follow up.

    Searching for anything else you've said...

    Rereading your first post, I see mention that you think this creates a lot of redundant data entry. I'm pretty sure that's totally false. If any spell in your game uses some particular combination of parameters, then that particular combination of parameters has to be entered somewhere--that's true in any system. This is just entering them in a different place. If you want a spell that does 5 poison/sec for 10 seconds, then you might enter "5 poison/sec for 10 seconds" into the base spell, or you might enter "5 poison/sec for 10 seconds" into a separate ScriptableObject, but either way it's the same data being entered.

    Anything else?

    I have the impression you mostly just don't like the UI. Fine, you are allowed to not like the UI.
     
    Aethenosity likes this.
  12. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Honestly speaking, it just read like @SparrowsNest has described it.

    Yes, there is no out-of-the-box or magic button solution. And opinions may vary, but an ability system is nothing you can generalize in such a way that it satisfies all needs. Because everyone does things differently, and that's a thing many of us like about Unity. Perhaps there will be a feature like that in the future, perhaps they're just going to buy & integrate one? Noone knows. They surely don't want to provide incomplete and inefficient tools, they already messed things up with all their networking systems.

    If you want it to be modular as you describe, there needs to be some hidden system that does everything for you and your specific idea of a modular ability system.
    And here we are with the DOTA 2 example. I'm not familiar with it, but it's already titled as data-driven, so there'll be system that parses all of that and executes a very generic routine in order to execute what's declared.

    It's more or less a type of declarative approach, as it's generally a separation of the data and the systems that process the data. At some point, the data has to be pre-defined, entered, generated or calculated, as shown in the DOTA 2 examples.

    And here's the thing. You said you'd need to create tons of SO instances in order to create every possible combination. I mean well, if you look at the DOTA example, you will need to define those input values as well, in that specific way of course. You then have text data assets I guess?!

    So what's the deal here... If you want something like that, you'll create a system that (at least) consists of behaviour-driven modules which process data blobs that you feed in.

    Scriptable Objects are just a tool, like @Antistone already mentioned. They do not exist to tackle this exact type of problem, they are not the solution for the problem. Instead, they are just a tool which you can strategically use to build a system which then tackles the problem.

    It's true that you might need to put some effort in it, because there is no built-in solution. But it's definitely possible, and there are infinite ways to approach such systems.
     
    Aethenosity and SparrowGS like this.
  13. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    I think you're misunderstanding the problem with using ScriptableObjects for a modular ability system.

    Here are the options you have with Unity:

    1. Make the sub Effects inherit from ScriptableObject, and create a separate asset for each sub effect.

    So if you have a Spell called DamageAndStealth with two subeffects, Damage and Stealth, then you need to create a DamageAndStealthDamageEffect asset and a DamageAndStealthStealthEffect asset. These SO assets represent parameterized instances of Damage and Stealth modules, respectively. They are only used in one spell (you don't want to reuse effects because then if you change the values they would change multiple spells, which is not what you want in this case). There's no reason for them to be separate assets. In terms of the work flow, the extra manual effort I mentioned is NOT data entry, it's the creation/deletion/linking of these assets in the editor.

    2. Make the sub Effects inherit from ScriptableObject, but create them as sub-assets of the main spell asset.

    This requires some extra editor coding. The problem with this method is that you end up with orphan assets. I'm not talking about run-time memory management. What I mean is if you undo, remove a spell effect, or do almost any change in the editor, you'll end up with stranded assets. If you try this path out, you quickly realize it's not worth the headache.

    Sure, if you want every effect to have the same effect parameters, then the ScriptableObject system works perfectly for you now because you just use the same Effect SO reference for all spells. However, the majority or large-scale games will want specific effect parameters per spell. Most of the time you don't want to restrict your spells to all do the same damage, have the same stealth time, etc.

    The ideal set-up would be to have a single SO asset per spell, and then you can plug in effects like Stealth or Damage and change their parameters on the spell asset itself in the editor.

    If Unity supported polymorphism in their editor serialization, this would be possible. However, they don't, so doing any ability system in the Unity editor becomes a huge headache.

    You seem to think this is just my personal UI preference. That is not the case. Show me any Unity ability framework that uses the Unity editor and ScriptableObjects and doesn't have a modular system, and I'll be able to easily point out how it would fundamentally unable to support many common RPG abilities or would lead to a lot of suboptimal workflow and wasted manual effort (like a huge list of unused parameters cluttering the editor or requiring the creation of dozens of unneccessary assets for a single spell).
     
  14. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    Since 2019.3 there are some polymorphic serialization improvements. Though probably not the way you want them to be.
    It would be nice if you'd have a Scriptable Object Spell with a
    List<Effect>
    where
    Effect
    is just a plain C# object.
    When you define a
    IncreaseHealth : Effect
    that you'd be able to add this to the
    List<Effect>
    and be able to serialize it's properties. Like
    float Value;


    You could do that with 2019.3, but you'd have to write your own Editor which adds the specific type of Effect through code.
    So if you want to add the IncreaseHealth you'd have to write an inspector that will execute
    Effects.Add(new IncreaseHealth())
    to add it as an instance to the list. Unless I'm missing something.
    It would be nice if you could just drag drop the inherited effect script onto the list so it creates an instance for you. But that doesn't work.

    Potion with Health Effect.png

    Debug console.png


    So yeah polymorphism seems possible but still somewhat limited with the inspector tools you have out of the box.
    But it does serialize.

     
    Limnage likes this.
  15. QJaxun

    QJaxun

    Joined:
    Sep 30, 2016
    Posts:
    24
    Check out Odin Serializer. Using the SerializeReference and InlineEditor attributes makes it easy to do this sort of stuff.
     
    Ony, lclemens and Bunny83 like this.
  16. Sherbb

    Sherbb

    Joined:
    Feb 19, 2018
    Posts:
    1
    (1 year necro lol)

    This is true. With odin this thread is pretty easy. Without.....

    Anyways here is an example I have made:

    I have a system where the player gets UPGRADES like "do an aoe explosion when you catch something". Those upgrades are made up of a list of BEHAVIORS such as "play particles on x event", or "modify this stat on the player".

    Each BEHAVIOR inherits from a parent class that just has some basic data like the owned player, and a ton of virtual event methods. Methods like OnPlayerTakeDamage(), OnHitEnemy(enemy), OnCatchHammer().

    You can see how instead, for a spell, you could have methods like OnSpellStart(), OnFinishCasting(), etc.

    Anyways, here is how the Scriptable Object looks like:
    upload_2021-4-23_21-37-55.png
    In this example the UPGRADE will apply the AffectAttribute BEHAVIOR to the player immediatly, modifying their stats. Then the HammerParticles BEHAVIOR will add some vfx that only get triggered during certain events.

    With odin you can get a filtered list to create an instance of a polymorphic class.

    upload_2021-4-23_21-39-44.png

    To clean up the drop down and only get the classes you want, you gotta filter them like so:

    Code (CSharp):
    1.     [TypeFilter("GetFilteredTypeList")]
    2.     public List<MajorBoonBehavior> behaviors = new List<MajorBoonBehavior>();
    3.  
    4.     //filters boonscript drop down. taken from odin documentation
    5.     private IEnumerable<Type> GetFilteredTypeList()
    6.     {
    7.         var q = typeof(MajorBoonBehavior).Assembly.GetTypes()
    8.             .Where(x => !x.IsAbstract)                                          // Excludes BaseClass
    9.             .Where(x => !x.IsGenericTypeDefinition)                          
    10.             .Where(x => typeof(MajorBoonBehavior).IsAssignableFrom(x));         // Excludes classes not inheriting from BaseClass
    11.         return q;
    12.     }
    I think you might also need to inherit SerializedScriptableObject instead.

    Also beware: The data in the SO is not super safe. If you rename or re-namespace the BEHAVIOR class the data in the SO will be set to null.

    Hopefully some of this will be relevant to some people....
     
    Last edited: Apr 24, 2021
    Bunny83 and LiberLogic969 like this.
  17. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    Fifteen responses over nearly a year and I'm surprised nobody mentioned the asset store.

    There are plenty of packages that can help you set this up, or at least give you ideas of how to set yours up.

    And guess what? That's where DOTA-ish type stuff belongs.

    The LAST place I want custom arena crud is in the engine; I want the engine to focus on moving pixels and making sounds and gathering inputs seamlessly on all target hardware, not doing some DOTA2 clone.
     
  18. wqaetly

    wqaetly

    Joined:
    Oct 25, 2018
    Posts:
    7
    hi guys, i had developed a moba skill system based on behaviour tree, the skill configs can be exported to binary file and run on the server.
    It includes these features:
    • collider system based on box2d
    • visual node skill editor based on GraphView
    • BuffSystem, RecastNavSystem, NumSystem
    • State synchronization as network sync solution
    here are some screenshoots
    upload_2021-6-19_15-44-57.png
    upload_2021-6-19_15-45-10.png
    upload_2021-6-19_15-45-32.png

    The projects is absoutly free and open source,https://gitee.com/NKG_admin/NKGMobaBasedOnET
    welcome to star it, thanks!
     
    JoNax97 and mopthrow like this.
  19. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    Imo best way is to create your own system and use unity only as subscriber for events what happend in your custom class.
     
  20. ExtraCat

    ExtraCat

    Joined:
    Aug 30, 2019
    Posts:
    52
    Btw that official Unity tutorial is a really, really bad idea. When used for multiple characters, skills will start overriding each other on initialize. That says a lot about unity developers' capability to create working gameplay systems per se. They better stick to moving pixels indeed, like the other poster said.
     
    Theta_ likes this.
  21. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    It can be used for multiple characters, but that wasn't part of this tutorial. The tutorial was about Scriptable Objects. Creating abilities as asset files rather than hardcoding or creating a new component for every ability.

    if you cannot adapt this into something that works for multiple game objects, that says more something about you than Unity developers their capabilities.

    The component that uses the Scriptable Object asset can create an instance of the asset and then call Initialize.
    But becoming an instance means it is no longer shared between other objects. And in case of an ability, does it need to be shared? It completely depends on how you're trying to solve the problem.
     
    MartinMa_ and Kurt-Dekker like this.
  22. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    I think if you want whole, complex, ready to use,100% working assets just switch to Unreal engine.I love unity because you can make everyting almost from 0.
     
    Last edited: Sep 23, 2021
  23. ExtraCat

    ExtraCat

    Joined:
    Aug 30, 2019
    Posts:
    52
    As far as I'm concerned, their example is totally useless for coding a skills system due to severe, mentioned nowhere limitations. Yet here we are, OP actually considered using it as a base for his skill system.

    Either you provide a working example of using a system, or you provide none. You can't provide a broken, barely functioning example and call it a day, creating issues for uncountable amount of people trying to follow your example. Well, unless you don't care about what you do ofc.

    No wonder the video was delisted from YT by now.

    This thought comes to me more and more often, yeah. Not everyone has all time in the universe to create everything from zero.
     
    Last edited: Sep 24, 2021
  24. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    Thats true but isnt it boring just copy paste another fortnite,dota,valoratnt etc?

    For me it is same like make website with real code (Unity) or in some wysiwyg editor(Unreal engine).

    About blaming other systems created by someone else - if you dont like them, then create your own.And that is exactly what you can do easy in Unity and hard in Unreal engine.

    FYI - if you struggle edit some asset in Unity you will struggle 10x more with Unreal engine.You can check some video what differences re in C and C#.
     
    Last edited: Sep 24, 2021
    akuno likes this.
  25. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    You can make a skill system with Scriptable Objects. It's not impossible and the tutorial just shows you a way of how you can create them. Teaching you the basics. That in itself is a functioning prototype. Nothing is broken about that.

    You want it to work so that it can be handled by multiple game objects. But that's not part of the tutorial. However, you can change it to handle multiple Game Objects... It just requires some C# knowledge, but it isn't impossible.
    If a tutorial shows you how to build a soap box car, but you expect a fully functional Tesla. Then something is seriously wrong with your expectations. When they teach you math in a tutorial, don't expect them to teach you quantum physics as well.

    Tutorials are not a full fledged system. If you want that, go to the Asset Store, there's enough material on there.
    If you want something really specific that suits your needs, either hire someone to make this for you or learn C# to do it yourself.

    But don't go spreading nonsense about Unity devs being incompetent. Because they are not incompetent at all. If anything, it is your own incompetence. Learn C#, learn how the engine works and create a system that suits your needs. Just don't expect a fully fledged system from tutorials. That's not what tutorials are for.
     
  26. Boki83

    Boki83

    Joined:
    Apr 25, 2017
    Posts:
    1
    I so much agree with you!
     
    akuno likes this.
  27. juelzsantana123

    juelzsantana123

    Joined:
    Feb 28, 2019
    Posts:
    17
    To everybody whoever read this coment :: here is ability framework that i made for my game (it is not entry level, also botcaster and bottarget you have to figure out yourself. it only require to implement these methods that are in code below)
    =>


    Code (CSharp):
    1.  
    2.   public abstract class BotAbilityData : ScriptableObject
    3.   {
    4.     [field: SerializeField] public string _name { get; protected set; }
    5.     [field: SerializeField] public Sprite icon { get; protected set; }
    6.     [field: SerializeField] public Class usableBy { get; protected set; }
    7.     [field: SerializeField] public float range { get; protected set; } = 1f;
    8.     [field: SerializeField] public float cooldown { get; protected set; } = 1.8f;
    9.     [field: SerializeField] public float castTime { get; protected set; } = .5f;
    10.     [field: SerializeField, TextArea(3, 6)] public string description { get; protected set; }
    11.     [field: SerializeField] public BotAbilityBehaviour[] behaviours { get; protected set; }
    12.     [field: SerializeField] public AbilityTargeting Targeting { get; protected set; }
    13.     public abstract bool IsAvailable(Class usableBy, int level);
    14.   }
    15.   [System.Serializable]
    16.   public class BotAbility
    17.   {
    18.     public BotAbilityData data;
    19.     public float lastUsedTime;
    20.     public virtual bool IsInRange(float distanceToTargetSQR) => data.range * data.range >= distanceToTargetSQR;
    21.     public virtual bool IsReady() => lastUsedTime + data.cooldown < Time.time;
    22.     public virtual bool CanUseOn(BotTarget botTarget, BotCaster botCaster)
    23.       => data.Targeting.HasFlag(botTarget.GetRelationship(botCaster));
    24.     public void UpdateUsedTime()
    25.     {
    26.       lastUsedTime = Time.time;
    27.     }
    28.   }
    29.   [System.Serializable]
    30.   public class BotCast
    31.   {
    32.     public bool isFailed { get; private set; }
    33.     public bool isFinished { get; private set; }
    34.     public float totalTime { get; private set; }
    35.     public float elapsedTime { get; private set; }
    36.     public Action<float> onUpdate { get; private set; }
    37.     public Action onSuccess { get; private set; }
    38.     public float progress => Mathf.Clamp01(elapsedTime / (totalTime + 0.0001f));
    39.     public BotCast(float totalTime, Action<float> onUpdate, Action onSuccess)
    40.     {
    41.       isFailed = false;
    42.       isFinished = false;
    43.       elapsedTime = 0f;
    44.       this.totalTime = totalTime;
    45.       this.onUpdate = onUpdate;
    46.       this.onSuccess = onSuccess;
    47.     }
    48.     public void Update(float delta)
    49.     {
    50.       if (isFailed || isFinished) return;
    51.       elapsedTime += delta;
    52.       if (elapsedTime > totalTime)
    53.       {
    54.         Finish();
    55.         return;
    56.       }
    57.       if (onUpdate != null)
    58.         onUpdate.Invoke(elapsedTime);
    59.     }
    60.     public void Interrupt()
    61.     {
    62.       isFailed = true;
    63.     }
    64.     private void Finish()
    65.     {
    66.       isFinished = true;
    67.       onSuccess?.Invoke();
    68.     }
    69.   }
    70.   public enum Class
    71.   {
    72.     none = 0,
    73.     knight = 1 << 0,
    74.     rogue = 1 << 1,
    75.     paladin = 1 << 2,
    76.     archer = 1 << 3,
    77.     marksman = 1 << 4,
    78.     wizard = 1 << 5,
    79.     all = ~(~0 << 4)
    80.   }
    81.   public enum AbilityTargeting
    82.   {
    83.     none = 0,
    84.     self = 1 << 0,
    85.     friendly = 1 << 1,
    86.     hostile = 1 << 2,
    87.     all = ~(~0 << 4)
    88.   }
    89.   public enum AbilityUseStatus { none, isActive, isInterrupted, isFinished }
    90.   public abstract class BotAbilityModifier : MonoBehaviour
    91.   {
    92.     public string _name;
    93.     public Sprite icon;
    94.     public abstract void ApplyModifier(BotCaster caster, BotTarget target);
    95.     public abstract void RemoveModifier(BotTarget target);
    96.   }
    97.   public abstract class BotAbilityBehaviour : MonoBehaviour
    98.   {
    99.     [field: SerializeField] public BotAbilityModifier[] modifiers { get; protected set; }
    100.     public BotAbilityData data { get; protected set; }
    101.     public BotCaster caster;
    102.     public BotTarget target;
    103.     public AbilityTargeting targeting = AbilityTargeting.none;
    104.     public bool IsActive { get; protected set; }
    105.     public virtual void Init(BotAbilityData data, BotCaster caster, BotTarget target)
    106.     {
    107.       this.data = data;
    108.       this.caster = caster;
    109.       this.target = target;
    110.     }
    111.     public abstract void StartBehavior();
    112.     public abstract void UpdateBehavior(float delta);
    113.     public abstract void StopBehavior();
    114.     public virtual bool IsTriggered(BotCast botCast) => IsActive == false && botCast.isFinished && !botCast.isFailed;
    115.   }
    116. }
    117.  
    118.  
     
    Last edited: Mar 3, 2023