Gameplay Ability System in a data oriented approach

Hello everyone,

a Gameplay Ability System goal is to be a highly flexible framework for building the types of abilities and attributes that you might find in an RPG or MOBA title. You can build actions or passive abilities for the characters in your games to use, and status effects that can build up or wear down various attributes as a result of these actions, additionally you can implement “cooldown” timers or resource costs to regulate the usage of these actions, change the level of the ability and its effects at each level, activate particle, sound effects, and more. The Gameplay Ability System can help to design, implement, and efficiently network in-game abilities from as simple as jumping to as complex as your favorite character’s ability set in any modern RPG or MOBA title.

I’m trying to design a Gameplay Ability System using DOTS to get full compatibility with DOTS projects and achieve high performance implementation by reusing similar tasks as much as possible.

the problem is that it is quite complex to port an OOD system to DOD.

This is how a simple Fireball implementation look like.

I’m aware that things can get more reactive and performant here by replacing some systems with an Event System like @tertle 's Package , an event system would ensure that events are produced and consumed in the same frame and prevent structural changes, which I will do once I have an MVP.

I’m very undecided on the approach to handle custom behaviors.
As you can see when OnAbilityCollisionsEventSystem is first run from the projectile hitcollision event, a new entity is created to represent an explosion using ComponentDatas and custom data.
but the second run of OnAbilityCollisionsEventSystem, new task entities are created to handle attribute modifications (health…).

The two solutions I see now are:

  1. Create different Handlers per Ability. (OnFireballProjectileCollision, OnFireballExplosionCollision …)
  • this approach seems very boilerplate, not scalable and not maintainable.
  1. Create functions per Ability event, which will have the same method signature but different behaviors, this can be done by passing a NativeArray of Bursted FunctionPointers and the capacity will index the correct one.

Any suggestions are welcome.
Thanks

@WAYNGames .

Hello,

Thanks for the ping ;).

I would decompose an ability has follow:

  1. the effects: what happens when the ability is triggered (play SFX/VFX, spawn entity, apply damage)

  2. the restrictions: what prevent an ability to trigger the effects (cool down, range, player class,…)

  3. the costs: this is a combination of effect and restriction; you can cast if you don’t have enough mana but if you do you will definitely consume mana (this is mainly to avoid design error where an ability could check for a restriction and forget to apply the resource consumption effect)

That’s it that is your ability.

Then additional information is for the effect themselves:

  1. time/duration: is it an instant effect? over time effect (oneEnable/everyXsec/onDisbaled?)

  2. target(s): is it a single target effect or a multi target effect (AOE/Party members/…)

  3. when: does it apply as soon as the user hit the action button or after a delay (after cast time elapsed/…)

  4. static data: thing that can be set at edit time and only read at run time like damage type (physical/magical/…) or the power scaling of the effect. if it’s an overtime effect, how often does it tick, …

if you split it and organize it well it should be possible to define one ‘application’ system per effect and one trigger system per effect/trigger type.

Then it’s a matter of composing thing to make your ability.

That is at least the design approach I’m trying to go for.

Implementation wise things may changes when 0.50 drops but for now I rely on native stream to avoid has much structural change as possible, blob assets for the static read only data, and maps to handle the N…N nature of the effect application (several player can hit several target and both set overlaps).

On the authoring side I try to make thing has simple as possible for the game designer and has flexible has possible while designing (for rapid iteration) and delivering too (with the help of addressable) all the while trying to limit any potential stupid mistake that could lead to a day of hair pulling debug to figure out you forgot to check a box…

I have not had much time recently to work on my package has I would have like but it hasn’t left my mind either.

I’ve set aside some time for that as part of my 2022 resolutions, and hopefully I won’t have to start from scratch with 0.50.

thank you for answer :slight_smile:
I haven’t had much time lately, but I’ll try to see your package and how you’re trying to solve the problem.

I thrown together a quick “sequence diagram” to try an explain roughtly how I do things.
Hopefully it will at least help a little understanding my spagetti code :stuck_out_tongue:

For now the package has simple direct effect, base constraint (but the constraint system is not flexible/expendable enougth to my taste) and basic UI integration, it don’t handle effect over time and AoE.
I also don’t handle spanwing new entities, I need to figure an elegant way to spawn an entity based on a prefab that works well with subscenes, addressable and don’t require a lot of setup…
If all goes to plan I should be able to change an ability “at runtime” through adressable if the code part don’t change.
From changing the damage dealt to swaping to a new VFX or adding an effect to an ability.
Latest showcase video

Thank you very much, it will definitely help to understand your package quickly.

I’m having some trouble understanding this part. Can you give examples of what “custom behaviors” would mean, and rephrase what the problem is with the second run of OnAbilityCollisionsEventSystem?


One thing I’d say on the topic of Ability systems in general, is that specific code-based approaches to abilites have grown on me as opposed to a “mix & match modular components for everything” approach. I’m not sure if your solution is going towards the former or the latter, but it’s just my two cents. Basically what I like to do now is a library of static AbilityUtilities that I can use to build abilities easily in code, while still having all the power of writing specific behaviour for specific abilities. Sometimes things get so specific with abilities that tailor-made solutions end up being clearer & much less trouble than fully generic code-less solutions imo

For a fireball ability:

A ProjectileAbilitySystem would do:

  • if (AbilityUtilities.CheckUseInput(in abilityInput))

  • if (AbilityUtilities.CheckCooldownFinished(in ability))

  • AbilityUtilities.SpawnPrefabWithOwner(entity, projectileAbility.ProjectilePrefab)

  • AbilityUtilities.OnAbilityUsed(ref ability)

And a ProjectileSystem would do:

  • move with velocity

  • AbilityUtilities.RaycastForHittables()

  • if hit found…

  • AbilityUtilities.TryApplyDamage(onEntity, projectile.Damage)

  • AbilityUtilities.SpawnPrefabWithOwner(entity, projectile.OnHitPrefab) // explosion prefab

  • Destroy(self)

And a ExplosionSystem would do:

  • AbilityUtilities.CheckSphereForDamageables()

  • for each hit…

  • AbilityUtilities.TryApplyDamage(onEntity, projectile.Damage)

  • Destroy(self) in x seconds

This is a very simple case that would lend itself well to being done in a generic way, but other abilities would be hell to implement just in pre-made ability-agnostic modular components. Like a “MindControl” ability where:

  • you click on a target unit and now you control it instead of yourself
  • very specific conditions are checked to see if you can control the target unit. Not just based on unit type/team, but also based on what the unit is doing currently
  • and your character follows that controlled character, but with a slower zombie-like walk
  • and the mindcontrol link can break under very specific conditions
  • and the mana is consumed based on very specific rules that take into account a complex combination of factors
  • and a % of the damage applied to the controlled unit is transferred to you as well, and that % grows over time
  • and once the mindcontrol link breaks, both units are stunned for a duration that depends on how long the mindcontrol was active & the source of the breakage
  • etc…

Not saying it can’t be done in a fully generic way, but I do think a generic approach will make everything unbelievably complicated as opposed to just writing that ability in code directly. A generic approach would also make debugging much harder, and introduce a lot of potential new bugs for combos of components/conditions that weren’t planned to work together or for changes that unexpectedly broke other abilities.

It may seem like the code-based approach is less designer-friendly, but in practice you’ll still be able to handle the large majority of abilities with just a few pre-made components/systems either way (most abilities will be spawning a prefab, or targeted buffs/effects, or area buffs/effects, etc…). For the more complex abilities however, the code-based approach will give you all the control you need and will simplify your architecture a whole lot

However I do think it’s a good idea to make the concept of Stats & StatModifiers into a carefully-crafted generic system, that your hand-made abilities use to apply their effects

Thank you very much for your answer :slight_smile:

I believe this problem has already been solved by Unreal’s Gameplay Ability System (GAS), which allows all abilities to communicate and manage their states through Tags and attributes in multi-phases.

In my opinion, one of the strengths of the DOD, is to divide large tasks into small units of work and group similar units from different contexts to perform the same tasks linearly, in parallel and at large scale, which exacly what’s posing problems right now.

In the example provided above, the projectile collision event system “OnAbilityCollisionEventSystem” is executed whenever a CollisionEvent is triggered via a hit detection. it can be a projectile raycasting on its path or an explosion distance point query.
The complexity with this approach is that the same system will run for many abilities, while some of them might have the same event handling behaviour but others not.
for example, a fireball would explode on its first impact but a magic arrow will continue its journey and hit all enemies in its path.
these two abilities have to use the same systems for their behaviours in common. (Propulsion, RayCast Hit Detection, Attributes Modifications, add/remove Tags …)
At first glance, I thought maybe I could use different combinations of components to split these different behaviours into different systems, but this will definitely increase the number of systems, which is not a good way to go.

at the moment i plan to create function interfaces for Abilities Events, this way all Abilities can create/reuse BurstCompiled functions which will be passed through NativeArray<FunctionPointer> and keep all logic in a single system.

To make things more designer-friendly, maybe at some point I’ll add a way to reuse/create event-handling code through visual scripting.
this is quite difficult at the moment as there is no way to use DOTS packages in assetstore visual scripting tools and AFAIK Unity’s Visual Scripting ECS package does not support creating static functions for BurstCompiled FunctionPointers usage.

I agree but one should 'ot prevent the other.
The main idea is to be able to compose your ability. The effect could be as simple as changing a stat or much more complex like you exemple in which case I would go with a simple effect that add would add a component (or store in a system map or something) to the controling and controller entity and then make dedicated system that apply the expected behavior for mind controlled entities essentially coding the mind control effect with reactive systems that would share the damage, change the camera/input target when the mind control start and apply the stun when it stops.

The point is the aim is not to replace the coder part all together but to give tools for a designer to make most of the work by composing effect into abilities. If it becomes too complex and the designer can’t make the ability or it ends up killing the performance then he can request the dev to make a dedicated effect that will do it in a simpler, more effective way.

@WAYNGames
I read your package, it’s still very early and missing some features but it’s pretty good :slight_smile:
in my case it’s a different path because I’m based on Unreal’s GAS, which dynamically resolves abilities interactions (Cancel if, Temporary Disabled if…) in a data-driven approach.
my plan is making all abilities based on Generics and Compositions.
Netwoking (Prediction, Reconciliation and Lag Compensation ) must be supported by defaut as every Ability and it’s Actions are represented by Ghost Entities.

updated plan.

Hello again,
knowing that we have to pass a different data structure containing (damage, AOE radius or projectile distance…) which will be used from these Bursted FunctionPointers Callbacks. (OnInit, OnCollision, OnComplete, OnDestroy)
what would be the correct way to access the metadata component?

  1. store data in an IElementBuffer as a byte array and reinterpret the structure for read/write?
  2. store the data in a generic componentdata (Metadata) and use the DynamicComponentHandle for access?
  • this approach will have some cons as it will ensure that abilities instances will be separated into different chunks and it will require additional codgen for DynamicComponentHandles management.

Do you guys have a preference between these 2 options or even better do you have an alternative?

Thank you

What I’ve been doing is something like this:
https://discussions.unity.com/t/846269

Essentially; codegen a bufferElement that’s a union struct of all the structs it could assume the form of, and codegen switch statements that can convert it to the right type and call a function on it. This solution is also an alternative to function ptrs at the same time, but I haven’t done any real tests to compare the performance of the two approaches (switch-statement vs function ptr). I know function ptrs have some limited burstability and apparently cannot take native collections as parameter (which I need), so I avoided them from the start

I do think it’ll perform better than the deserializable byte array approach

it’s a good alternative for what I’m looking for.
I guess in a metadata case some components have to create fake methods to satisfy the interface implementation and it’s the developer’s responsibility to never call them, right?

this is also a good idea, I guess for that I have to create a structure for each ability, which is not a problem because most of the structs will not contain any data but only the logic as functions override.
but how well can the Polymorphic Components handle hundreds of switch cases?


Edit:
my plans for the ability system are all based on composition and reusibility, even for function pointers callbacks.
My plan was to make these events call a list of functions pointer indexes to encourage logics reuse and minimize coding for designers. so they don’t need to write code that already exists, but just index it with an Enum.
but i dont see any way to do it with the Polymorphic Components approach.

So you need designers to create some kind of “Ability” prefab/data, and define a list of effects (via enum) that this ability has? And I suppose that based on the selected enum, the inpector must show the editable parameters specific to that effect type?

Not sure if “Ability” and “Effect” means the same thing for both of us, but in my case an “Ability” is a collection of modular “Effects”, and an “Effect” is a modification operation on a stat

The way I handle this is basically… lots of codegen. For each effect struct that is part of a polymorphComponent, I generate an authoring version of it. Then I generate a custom editor for Abilities, that will allow the inspector to expose only the parameters of the selected effects when changing the effect enum value. Then I generate the code that converts this Ability editor to ECS: for each effect that is part of the ability, add it on the ability entity as a polymorphic buffer element. And then there’s more codegen to be able to define stat types for your game, and make it so that a designer can just say “this effect affects the Strength stat” and the system will know how to resolve that efficiently at runtime

Codegen is really the only way I found to make a system like this that’s designer-friendly but also makes no runtime performance sacrifices. It really makes me hope that Unity will eventually release a user-friendly Source Generators package, because I feel like codegen is really crucial to solving certain problems in DOTS

Effects means Attributes operations (Health, Mana …)
I’m using terms that I’ve learned from Unreal’s Gameplay Ability System. :wink:

+1 on that

Yes this seems the best way to make it generic for the Designer .

How do you guys sync Abilities across the network?

My current plan is to do a synchronization based on the NetCode package by having every ability and its actions represented by ghost entities, which will be automatically synchronized across all Clients.

I plan to set all abilities and actions as owner predicted and fully interpolated for other clients.

Server :

  • Apply Movement Prediction on Projectiles…,
  • Apply HitDetection on Projectiles/AOE…,
  • Apply Game Effects,
  • Dont Apply CUEs (VFX, Animations…)

Owner Predicted Client (Ability Spawner) :

  • Apply Movement Prediction on Projectiles…,
  • Apply HitDetection on Projectiles/AOE…,
  • Dont Apply Game Affects,
  • Apply CUEs (VFX, Animations…)

Interpolated Client :

  • Dont Apply Movement Prediction on Projectiles…,
  • Dont Apply HitDetection on Projectiles/AOE…,
  • Dont Apply Game Effects,
  • Apply CUEs (VFX, Animations…)

this way, all projectile movement predictions errors will be automatically playback and corrected.
I still have to implement a server side system to validate predicted spawns of abilities and actions from clients in order to avoid cheating and mispredictions.

@lclemens I tagged you here to get a comments/advices from you because you already made your own ability/skill system :slight_smile:
the design above was done very quickly, so it may be wrong at some points.

As I stated in this [comment]( Comparing different approaches for Events in DOTS page-2#post-8069081), for the next few weeks I will be focusing on the Gameplay Ability System, so gathering as much information as possible will give me a good start.

I plan to create a Gameplay Ability System that is heavily inspired by Unreal’s one, but in a data oriented approach.

my goal is to create a package that can :

  • help artists easily build Abilities/Skills mostly based on composition.
  • synchronize abilities across the network by default using Netcode Package.
  • Abilities are represented by ScriptableObjects which can easily be created/modified without the need to republish the game on mobile platforms.
  • which benefits from DOTS Performance / Clean design.
  • Interactive Abilities by design, eg. a thunder spell deals more damage to Soaked players…

Some concepts are used :

  • Attributes to manage resources/capabilities… (Health, Mana, Speed … )
  • Tags to manage States. (Stunned, Untargetability, Burning, Soaked, Frozen, Fear, Silenced, Invulnerable …)
  • Events that can contain any Shared/Unique behaviour(s)

The whole concept is still incomplete for the moment.

@Opeth001 I think your approach looks similar to mine. The basic philosophy is to use composition of various tags and data on an “ability entity”. These two high-level discussions were invaluable to me and I studied them in detail:

I didn’t use any fancy or odd concepts - everything I used falls within the ordinary ECS programming paradigm that a beginner DOTS programmer would know. No codegen. I didn’t even use tertle’s events.

In those reddit descriptions and in my implementation, each ability has a DynamicBuffer where ImpactData is just Entity target and float3 hitPos. Whenever any system decides it wants to create an impact, it appends an impact to that buffer via ecb. Then in the next frame all the systems can react to that event (display pfx, play sounds, apply damages, etc) by checking if the impact buffer is not empty. As I mentioned in @PhilSA 's thread, I used Approach C for accumulating multiple damage modifiers, speed modifiers, etc in one frame, so each enemy has a DynamicBuffer - so there are also systems checking if the modifier buffers are empty and applying those modifiers. There is a system that clears the buffer at the end of each frame.

In addition to the “impact” event, each ability also has a boolean trigger state so that any systems can react during the frame whenever a trigger event occurs. And of course, at any given time systems can operate on the ability (like performing linear movement for a bullet, applying physics gravity, etc). So in summary - there are two events that systems are interested in: trigger and impact.

On every ability I have a TriggerValidityData, which is important because it accounts for all the variables that might modify when the ability is allowed to trip the trigger event. There is a system that continually updates this based on does the targeter have a target, is the turret/weapon pointing in the right direction, etc. In the case of a player triggered ability, the criteria would also include if the player clicked an ability button or pressed an ability hotkey.

One difference is that all of my abilities are only for NPCs… My “player” doesn’t really have abilities yet, but I think they would be similar with the only major difference being that they would require additional input from the user-input system when determining if the ability can be used or not. And they would specify an icon for the HUD.

A second difference is that I don’t use netcode, so I don’t know if my system is compatible with networking or not.

The last difference is that I used prefabs instead of scriptable objects, which I may convert to scriptables at some point (should be fairly easy if I can find the time). I guess the reason I used prefabs at the time is because some of my abilities have models with multiple child objects that need to be positioned appropriately. For example, a sniper shot ability has a muzzle flash and a beam flash (see screenshot below)

The one problem I had with the concepts defined in two reddit posts is that they call for creating an ability whenever it is time to “fire” and then destroying it when it is done. Well I tried that, and it got ugly for a few reasons:

  • Some abilities need some persistence. For example, one ability I have is a laser beam that fires whenever its targeter chooses a valid target. Well it’s firing at say 4 per second, causing damage each time, but it also has a beam that needs to be constantly enabled and adjusting to the target as long as a target exists - and it needs audio engange/disengage/loop sounds to be played. Also, what if you want passive abilities like the ability to heal any nearby friendlies over time, or boost their stats?
  • It was counter-intuitive to set the cooldown-interval (or fire rate) on the “spell-caster or weapon” instead of embedding it with the rest of the stats on the ability. The designer would have to look in two places.
  • It doesn’t support things like a projectile that can go out and impact an enemy, and then move to another one (like a mini-drone or something).

So the simple solution was to embed the cooldown interval into the ability itself. That solved many issues, however I then had the issue of handling projectile abilities that need to destroy themselves. The simple solution was to make a new component to spawn a new ability when the trigger event happens. So a cannon turret has a persistent “CannonSpawner” ability that spawns "CannonBullet"s (which destroy themselves on impact).

8070809--1042877--upload_2022-4-22_13-23-2.png

It works great! With just with that one mechanism, I can make all kinds of crazy things like abilities that spawn multiple bullet abilities on launch, abilities that explode on impact and fire out other abilities, etc.

One last thing - splash damage was a bit difficult to implement for abilities that destroy themselves on impact. I ultimately fixed that with proper system ordering. If you’re curios about how that was solved I can go into more detail.

It’s outside of the scope of the topic, but I also discovered a pretty easy way to handle over-time effects (like damage-over-time, slowdown-over-time, etc. After one failed attempt, I’m pretty happy with my final solution and it’s also very extensible and easy to add new over-time effects.

As for performance, ECBs are used a decent amount, but I don’t see any reason why my approach would have any big performance hits. I haven’t benchmarked, but I see no obvious reason why it wouldn’t scale to many hundreds of thousands of NPCs with a few abilities. I suspect the limiting factor would be having too many particle effects, but they are pooled game objects and are started in the main thread. There are a few places where CDFE is used like when fetching a particular targeter, getting a target’s position, or maybe to flash a muzzle, but they are relatively rare. Nearly all of the systems act directly on the ability without getting parameters via CDFE.

Overall I’d say that the most difficult thing I’ve built in my game so far was this ability system. It’s weird because for so little code, it sure was tough coming up with a workable design that is flexible enough to handle lots of future abilities! Ultimately, I think its success can be attributed to using the composition principal. Unlike the traditional OOP approaches to skill/spell/ability systems, my system doesn’t have a big huge behemoth structure with a GUI that has a zillion options. Recreating that would have taken me years. Instead, there are a bunch of little systems all reacting to abilities built via composition and as a result, it’s actually very little code. I see no reason why I couldn’t use this framework to build a mind-control ability.

To keep it interesting, here are some screenshots of a cannon turret that fires bullets that cause splash damage and burn-over-time damage. Keep in mind that this composition could be done via scriptable objects instead of prefabs.

Anyhow… That’s just a summary of how I pulled it off. I’m sure there is lots of room for improvement.