Effects that depend on runtime information in composition-based spell system

Started by
12 comments, last by AaronWizardstar 6 years, 12 months ago
I'm designing a spell system. While I don't have a full ECS system available, I want to use a composition-based approach as much as possible. For instance a spell's targeting and area of effect would be defined by Range components with modifier components like TargetEnemyActors or TargetNeedsLineOfSight.
What a spell does would be defined by Effect components, which determine both graphics and game state changes. So Effects would include animating a projectile from a start location to an ending location, or damaging a certain actor. Effects would likely have a time or delay value for animation, or be able to be paired in parent-to-child chains to make effects follow one another. I'm wondering though about things certain Effects could only know at runtime. At runtime a spell would know its caster, target, and area of effect, but I'm wondering if that's enough for all Effects without making the Effects themselves more complex.
Take a fireball spell. It'd have an effect for launching a fireball sprite from the caster to the target (AnimateProjectileFromCasterToTarget...?) followed by an effect for damaging the target actor (DamageTargetActor). Though if I wanted fireball to be an area of effect spell, DamageTargetActor would need to be modified to damage all actors in the spell's area of effect (...DamageActorsInAreaOfEffect?). And maybe I'd want fireball to be able to damage the caster's allies, but have another area of effect spell that only damages enemies, so now DamageTargetActor needs an extra flag.
Now imagine a Ball Lightning spell, where the caster launches a bolt at an enemy and after it hits more bolts are launched from the target enemy to other enemies. The spell would need to determine at runtime what enemies are near the target enemy. Would this be inside some other kind of Effect, that would dynamically generate extra Effects to represent the new bolts following the initial bolt? Is this even a good path to go on for my spell system?
Advertisement

Well first off, your ECS like system is your own as there is no global standard. I personally more closely follow T-Machine's usage idioms myself, it's not exactly like his either. I want the simplest components I can get. Where I cannot break it down any further, then build up entities from there.

To me your Effect sounds way to complex, unless that is the Entity portion of the ECS system? You were not very explicit about that. I was actually designing a spell system for our ECS on Friday, so at least how I want it done is still fresh in my head. I only laid out a fireball spell as I did not have a lot of time. Your component names are way to complex for me, DamageActorsInAreaOfEffect, instead, RadiusDamage

Entity is composed of the following components:

- Position

- Direction

- Velocity -- It spawns moving at it's maximum velocity

- Max Distance -- it flames out when it reaches this, or it can be set to zero and goes until it collides

- Animate -- in this case it has a list of sprites to loop through, each update it changes to the next

- Damage -- It has a radius variable, if it's > 0.0 it's area damage, if it's 0.0 all damage is applied to what it collided with, along with a fall off variable. Currently I also have a damage type variable for fire, ice, blunt, etc. I might split then up into a radius damage and damage component though.

My goal is to have as few components as possible and use them in the most ways I can to simplify my system code. It's meant to give designers, non-programmers and easier time in making new things without dragging a coder away from their task. But if you have a billion components you've just defeated the purpose I believe.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

Your component names are way to complex for me, DamageActorsInAreaOfEffect, instead, RadiusDamage

I say "area of effect" instead of "radius" since the area might not be a circle, like with a ray spell that hits multiple enemies in a line (my game is tile-based).

Entity is composed of the following components

<snip>

Does Entity represent a whole spell? I'm not sure how well I can apply this system to myself since your example seems primarily geared for projectile spells or basic damage spells. I'm trying to come up with a system that can handle several kinds of spells like teleporting spells, spells that alter terrain, ally buff spells, and so on.

Well first off, your ECS like system is your own as there is no global standard. I personally more closely follow T-Machine's usage idioms myself, it's not exactly like his either. I want the simplest components I can get. Where I cannot break it down any further, then build up entities from there.

I brought up ECS and my lack of such really because the engine I'm using is more like a scene tree. A Spell would be a node and effects would be child nodes. So it's not like an ECS, but composition is still present.

My system can handle teleporting spells, the fireball example was the entity created when firing a fireball only. Any other spell would compose some of the same or completely different components. You can even make Damage (for direct damage to one thing only), DamageRadius and DamageSquare, etc. We're doing a grid based game so it's slightly different, radius is enough for us. I might make a DamageGrids component so we can do so many grids in each direction or something, if we come up with a use case for it.

With components you have to think differently about the composition of something. Then break it down to the most reusable parts possible. I have a general rule that my components won't have more than x variables, so far that rule is < 6. I'm willing to break it under very specific situations of course, I don't believe in absolutes for this sort of thing. An entity will be made up of any number of components mind you. I don't care.

But I can see having DamageTeleport (when it hits an enemy it teleports them else where). Or even Teleport as a form of movement. Either re-appearing regularly then disappearing, moving and reappearing. All should be possible with minimal work depending on the engine; never really looked at godot to be honest. Then altering terrain would be DamageTerrain, or AlterTerrain if you prefer that nomenclature, where maybe no damage is given to things in the area but it deforms the ground around them. You can attach a ModifyStatsOnCollision component if you want. Then have a variable for Friend/Foe and which stat (just one) and how much to alter it by. If you want to have 4 stats changed on your allies, attach 4 of these components is how I would do it.

Again, everyone is different. Having used something like ECS long before it was even a name but I still find issue at times breaking things down. I often write it once, use it for a bit then sub-divide it.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

Sorry, I'm still having trouble understanding how your system works and how I'd adapt it to my game.
I'm worried I put too much emphasis on the "ECS" notion. I'm not really using ECS, but since my engine represents everything as nested nodes it implies some kind of composition approach.
If it helps, my game is turn based as well as tile based.
You talked about an Entity a lot. This is the whole spell right? Or the visible part of the spell? A magic bolt basically, that applies its effect when it reaches the target? Hence you said that different effects like teleport, terrain altering, and buffs would be represented by different "damage" components?
And I was confused by your teleport spell examples. So you had an idea that the "teleport self" spell could be represented by a form of movement, though I'm unsure how such a spell would be composed while fitting in with how you seem to be doing the rest of your spells.
For instance, I was imagining a teleport spell which moves the caster to the target tile, and a wind spell which moves a target actor away from the caster. I guess the wind spell is where you'd use a "DamageTeleport" component in the spell (though, how would you set it up to make it move the target away from the caster specifically)? Maybe the teleport spell would use some kind of "DamageTeleportSelf" component?

Not sure how much use this is if you want a hardcore 'component'-ized spell system, but in Idle Raiders (and its successor Second Run, you can look it up on Kongregate if you want to play it) we don't solve this generally, instead there's a whole bunch of (re-usable) hard coding which is working fairly well. I think we have close to a hundred different spells in the actual game now (playable by people, and more added on a regular basis) and we haven't really encountered major issues.

Our spells are lists of actions (basically, functions that get called one after the other, with delays between them). We have a fireball spell in our game, and that has the actions "projectileFX" (for the graphical effect of launching the projectile, that also computes how long it's going to take), "projectileSound" (for sound effect), followed by the action "fireDamage" (for dealing damage).

When spells are constructed they are being given generic options (string-value pairs), in this case stuff like the file name for the projectile, the damage modifier, speed of the projectile, etc... We also have an "Ice Shard" spell, that could just be implemented as the Fireball spell but with different options (for various gameplay reasons, it's an entirely separate spell in our system though).

If we wanted to add an AOE component at the end of your spell, there are two ways we could do it. We could again hard-code it (maybe leave it out by disabling it with an option when the spell is constructed), and just add an "AOEFireDamage" action in addition or instead of the single target fireDamage action. The second way would be via our 'passive ability' system. All abilities can trigger other abilities using various gameplay rules. We could just create a generic "aoedamage" spell that is triggered by the fireball spell. As a practical example there's an "Ignite" passive skill that triggers a (burning) damage over time effect on the target every time a Fireball crits. Oh yeah, there's also an actual AOEDamage ability that can be used by warriors to have a chance to "cleave" their melee attacks, that works like this.

This works in a data-driven approach, too. All these 'actions' are created at runtime anyways (it's Javascript so it's easier to do this there, but in C++ they would just be function pointers that always have the same type, or if you want to get fancy, instances of classes derived from a SpellAction base class), so it's not a problem to cobble together an editor that assembles new spells from these basic actions.

There's one major upgrade we could and would like to make to this system, which is to have skills be an action-tree instead of an action list. With action trees (so a single action can branch out into multiple follow-up actions, or multiple branches can join back into one), it would be easier to have effects where you need to track multiple instaces of something that has previously been started by a different action.

For example, a random spell could say spawn three different projectiles that travel around for a bit, which would mean the action tree branches out into three paths, and at the end there would be a connecting action node that waits for all three projectiles to arrive at their target before doing something. Or the spell launches a random amount of projectiles, and each of them does something different (random?) when it reaches the target. That kind of stuff would be a lot easier to handle in terms of code if the "random things" that happen at the end can refer to a parent chain of action nodes, instead of having to find whatever they need within the linear list of actions that is there now.

We haven't done that yet mostly because we haven't encountered any serious use cases where we couldn't just (again) hard code around the problem. It sounds dirty but complicating the system needs to be a productivity win (less time spent creating the same things for the game), which we don't see at the moment.

Our spells are lists of actions (basically, functions that get called one after the other, with delays between them). We have a fireball spell in our game, and that has the actions "projectileFX" (for the graphical effect of launching the projectile, that also computes how long it's going to take), "projectileSound" (for sound effect), followed by the action "fireDamage" (for dealing damage).

Yeah, this was what I had in mind when I was talking about "effect components" earlier.

My main question is like this: You have a projectileFX action for showing a sprite moving from one location to another. It's simple enough to set a projectileFX action's sprite and speed when designing a spell that uses it. When the spell is cast the projectileFX action also needs to know the start and end points of its sprite. Do projectileFX actions assume the start is always the spell caster while the end is always the spell target? If not, how do you give the action the correct points when the spell is running?

Also, your passive ability system (basically, an action in a spell can be set to trigger another spell?) and your ideas for action trees are similar to ideas I've had for my own system, like the ball lightning spell that starts as one lightning bolt then spawns other lightning bolts. Though this gets back to my earlier question of how actions access necessary runtime information, like setting up the start and end points of the secondary lightning bolts. Also, I'm not sure my engine would be able to handle joining actions in an action tree so that'll be something I'll miss out on.

The projectileFX action internally calls a function in our graphical effects system. That function is hardcoded to take raw game-world positions, and what the projectileFX action does is take the casting entity and the target, get the positions of those and hands it over to that FX function. If we wanted to shoot the projectile not at the target, but somewhere else, we'd add a different kind of action (or just an option to projectileFX) that doesn't take the target entity's position, but whatever else you need.

I should add that all over the code, we have cases where 'variables' are really treated as 'this is either a variable or a function' (inspired by functional programming languages), so we can do stuff like changing the target position given to that FX function at runtime (for example, if we want a projectile to track a target, instead of flying to the same place when the target moves), or change the 'arrival time' variable which the spells use to know when to trigger damage.

The lightning bolt thing wouldn't require a tree or anything like that. In our linear action list system, I would just give the thing [n] iterations of a [lightningFX, lightningSFX, damage] action triple, where n is the number of times you want it to jump. Every time the lightningFX is used, it writes the location of the target to some internal variable of the spell that later executions of lightningFX reference. We have a bunch of tracking stuff like that in our spells, too. We have a spell that spawns rotating orbs around a caster, who shoot particles regularly. Since the positions of those always change and they are spawned at runtime, we just add those particle effect objects into a 'spawnedParticleEffects' array that every spell has, and every time that spell shoots a projectile, it picks a random entry out of that array as a source location.

Sorry, I'm still having trouble understanding how your system works and how I'd adapt it to my game.
I'm worried I put too much emphasis on the "ECS" notion. I'm not really using ECS, but since my engine represents everything as nested nodes it implies some kind of composition approach.
If it helps, my game is turn based as well as tile based.
You talked about an Entity a lot. This is the whole spell right? Or the visible part of the spell? A magic bolt basically, that applies its effect when it reaches the target? Hence you said that different effects like teleport, terrain altering, and buffs would be represented by different "damage" components?
And I was confused by your teleport spell examples. So you had an idea that the "teleport self" spell could be represented by a form of movement, though I'm unsure how such a spell would be composed while fitting in with how you seem to be doing the rest of your spells.
For instance, I was imagining a teleport spell which moves the caster to the target tile, and a wind spell which moves a target actor away from the caster. I guess the wind spell is where you'd use a "DamageTeleport" component in the spell (though, how would you set it up to make it move the target away from the caster specifically)? Maybe the teleport spell would use some kind of "DamageTeleportSelf" component?

As an aside I did not read agleed's responses or yours to him. Don't have time at this moment.

Yeah, I figured my teleport spell would be a little confusing that night, but I as already in bed :) I envisioned it as a "thing" (say a fireball) that cloaks while it moves then reappears. I'm working on a semi-turn based (after x seconds a turn auto happens) and it's tile/grid based. I'm not the designer, just a mere peasant programmer so I don't come up with the spell ideas, clearly. Also, yes the entity is the whole spell. In my case it's a struct with a std::bitset that says what components it has. It can have multiple components also, the system that holds all of that type of component (some systems have 2-3 very small components that are similar) will figure out if it has multiple and handle that automatically. So you just attach component after component without worry.

For your idea of a spell that moves the spell caster them self to a new location, with my system would not be all that difficult (though far harder than a fireball or lightning effect). You clearly have an animation period after a turn executes (or whatever you call it). I would spawn an entity for the spell with all the requisite components. Mine is VanishComponent, DestinationComponent (moves you so much per turn), SpritesComponent (not Sprite as it has multiple images, it handles timing and changing frequency etc, this can give it a shimmer effect as it moves for instance, or nothing if you don't want that). Upon arrival at it's destination the movement system will automatically raise an event announcing it to any listeners. I have an EventListenerComponent (I use compile time hashed names to speed things right up and broadcast hashed names) which causes the ReappearComponent (think that's it's name, but it is what it does. It undoes what Vanish did).

As "simple" as all that is, there is your spell via my system(s). My ECS system is getting an addition of entities being attached to entities (linked list with children). Thankfully it's just two pointer to get things started that I need to store per Entity. I also don't have a ton of them; I envision < 2000 per level.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

The projectileFX action internally calls a function in our graphical effects system. That function is hardcoded to take raw game-world positions, and what the projectileFX action does is take the casting entity and the target, get the positions of those and hands it over to that FX function.

OK, so projectileFX does assume the start is the caster and the end is the target. By extension all actions are implemented such that they are aware of all the runtime data they need themselves, like a damage action would always damage the target unit or the units in the spell's area of effect?

If we wanted to shoot the projectile not at the target, but somewhere else, we'd add a different kind of action (or just an option to projectileFX) that doesn't take the target entity's position, but whatever else you need.

This position parameter would have to be relative to either the caster's position or the target's position though, right? You wouldn't give the action just a constant position?

The lightning bolt thing wouldn't require a tree or anything like that. In our linear action list system, I would just give the thing [n] iterations of a [lightningFX, lightningSFX, damage] action triple, where n is the number of times you want it to jump. Every time the lightningFX is used, it writes the location of the target to some internal variable of the spell that later executions of lightningFX reference. We have a bunch of tracking stuff like that in our spells, too. We have a spell that spawns rotating orbs around a caster, who shoot particles regularly. Since the positions of those always change and they are spawned at runtime, we just add those particle effect objects into a 'spawnedParticleEffects' array that every spell has, and every time that spell shoots a projectile, it picks a random entry out of that array as a source location.

So in addition to a set of actions (and the locations of the caster and target, and the area of effect) spells have semi-generic storage properties the actions can write to and read from?

Though I'm still wondering about the ball lightning spell. You said you'd have the set of actions that represent a single bolt repeated for the number of secondary bolts you'd want to appear. By using spell's storage properties (I assume) you'd be able to keep track of the target of one bolt to be used as the start of the next bolt. Now after the first bolt I need the secondary bolts to launch at enemies within a certain distance to the original target. I'm guessing before starting a secondary bolt I could have an action that works out the next enemy, storing its location in one of the spell's storage properties to use as the target for the next bolt?

For myself, assuming I'm getting your spell storage properties idea right, I'd use a single dictionary in the spell. And if actions store values in that dictionary I could expose properties in them to control what key strings they use.

For your idea of a spell that moves the spell caster them self to a new location, with my system would not be all that difficult (though far harder than a fireball or lightning effect). You clearly have an animation period after a turn executes (or whatever you call it). I would spawn an entity for the spell with all the requisite components. Mine is VanishComponent, DestinationComponent (moves you so much per turn), SpritesComponent (not Sprite as it has multiple images, it handles timing and changing frequency etc, this can give it a shimmer effect as it moves for instance, or nothing if you don't want that). Upon arrival at it's destination the movement system will automatically raise an event announcing it to any listeners. I have an EventListenerComponent (I use compile time hashed names to speed things right up and broadcast hashed names) which causes the ReappearComponent (think that's it's name, but it is what it does. It undoes what Vanish did).

OK. Similar to my earlier question to agleed, are VanishComponent, DestinationComponent, and ReappearComponent implemented to always be affecting the caster, or are they made aware of which actor they're affecting in some other way?

This topic is closed to new replies.

Advertisement