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

Started by
12 comments, last by AaronWizardstar 7 years ago

All my components that can effect a character reference an ID which just says what entity it's effecting aka character. So it can be the caster or target. Though as they stand now, only just the one target. Just makes life easier and the designer less likely to harass me ;-)

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

Advertisement

OK, so your components don't reference a character directly, but the character's ID. Nonetheless it sounds like effects are hardcoded to manipulate either the spell's current caster or the spell's current target (maybe both? or the ability to choose whether to manipulate the caster or the target?). In other words there isn't something else telling the effect what character to work on while the spell is running.

No, it can only take one ID, at this time but adding another ID in a struct is not a big deal. So it can only effect that entity (entities for me are ID's only uint32_t currently but I do a using EntityID = std::uint32_t so I can change at anytime and it just works). Spell systems have no idea who they're working on, just the ID. They have access to it's health as input if needed etc. I somewhat based my ECS off of the DoomRL (DRL now) code that is open sourced.

For me a Component System only works on as few components as possible. The vast majority of mine work on one alone as it should be in my opinion, and without checking them all out they might only be a 1:1 right now. Spells are a bit different in that they need to tie several unrelated components together but they only hold reference ID's to the components and timing info on when to trigger their use. So say when the vanish component triggers first, making our caster vanish. when it finishes and there is a listener it generates an event and our spell system being the listener gets it. It then knows, okay step 1 is done, activate the lightning bolt component, and a destination component. When the destination system see's it has a listener for this event (upon arrival) it fires off the event and the spell then removes the bolt/destination components and activates the reappear component and we're done so it kills itself off too.

Also when an entity "dies", in this case say an enemy, I don't destroy it (death animations start though). Say it dies on frame 20, all my entities have an (bool) is_alive variable which would be set to false when they're killed. Then on frame 21 any unresolved projectiles do not "hit" something that is gone. At the beginning of frame 22 though I cleanup all dead entities from frame 20. By that time all systems what were targeting that enemy have figured out it's is_alive is false and do whatever they need to do to handle that case.

Apparently it doesn't want to embed an image from dropbox... https://www.dropbox.com/s/dxem3elsautghve/Entity.png?dl=0

So that is my entity struct, simple. The handle is made up of two parts, the ID itself taking up 24 or 28 bits, I forget now and the second is the version number of this handle. That way if something old is holding onto an old handle it won't have the correct version number and the newer Entity won't be referenced by whatever system has an expired handle.

I'm tired, hopefully that made sense :)

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

I'm still working out how to handle my spells, so I decided to make mockups of the actual spells I'm planning and break down how they work.

For context, my game looks like this.

n2zmW9p.png

Realistically my game's actual animation will be limited and consist of shuffling actor icon sprites around.

Attack

kD88Yr1.gif

"Spells" in my game are really any kind of combat ability, so I'd be counting regular attacks as a spell. Still, beyond just applying damage to the target I wanted some animation, primitive as it is.
  • Let Y be the point halfway between the caster sprite and the target sprite
  • Over X seconds move the caster sprite to Y
    • This doesn't change the caster actor's true tile position
  • Apply damage to the target
    • This should be a reusable effect, handling what happens when the actor dies and such. It also takes a vector to handle the recoil bounce, in this case taking the vector from the caster to the target.
  • Over X seconds move the caster sprite back to its original position
Explosive Bolt

IazAfed.gif

Everyone likes area of effect explosion spells right? This spell targets any tile within range of the caster and has an area of effect that damages all enemies within it.
  • Show a projectile sprite moving from the caster to the target tile at a speed of X units/second
  • For each "layer" of the area of effect (tiles grouped by their distance from the target)
    • Show a spell field sprite over each layer tile
    • For each enemy actor on a tile in the current layer
      • Apply damage to the actor
    • Proceed to the next layer in X seconds
I decided to get fancy by having the explosion spread out from the target instead of hitting every enemy inside the AOE at the same time. So when the spell is running I'll need to work out what tiles to hit at what time interval.

Throw

a9fRKKv.gif

I was going to give warriors a "push" ability but decided that was boring. Now warriors will get to pick up adjacent enemies and throw them. Also, the warrior here is throwing the skeleton against a wall for extra damage...
Throw actually takes two targets. One for the adjacent enemy and a second for the throw target. But lets assume I already know how to handle multiple targets for a spell and work out how the spell runs.
  • Let A be the point halfway between the caster sprite and the target 1 actor sprite
  • Let B be the point Bi units above the caster sprite along the Y axis. This is where the target 1 actor sprite will be after it's been picked up.
  • Let C be the point Ci units above the caster sprite along the Y axis. This is where the caster sprite will be when it throws the target 1 actor.
  • Let D be the point Ci units above B along the Y axis. This is where the target 1 actor sprite will be at the start of the throw.
  • Over E seconds move the caster sprite to A
  • Simultaneously (the caster is picking up the target 1 actor):
    • Over F seconds (less than E) rotate the target 1 actor sprite to 90 degrees
    • Over E seconds move the caster sprite to its original position
    • Over E seconds move the target 1 actor sprite to B
  • Simultaneously (the caster is "jumping" to make the throw):
    • Over G seconds move the caster sprite to C
    • Over G seconds move the target 1 actor sprite to D
  • Simultaneously (the caster finally throws the target 1 actor):
    • Over G seconds move the caster sprite to its original position
    • Rotate the target 1 actor sprite to the angle along the vector from D to the target 2 tile
    • Start moving the target 1 sprite to the target 2 tile with a speed of H units/second
  • When the target 1 actor sprite reaches the target 2 tile
    • Apply damage to the target 1 actor
    • If the target 1 actor is still alive
      • If the target 2 tile is empty
        • Set the target 1 actor's tile to the target 2 tile
      • Else
        • Determine the true landing tile and make it Z
        • Rotate the target 1 sprite to 180 degrees (upside down)
        • Over J seconds move the target 1 sprite to tile Z
        • Apply damage to the target 1 actor (again)
        • Set the target 1 actor's tile to Z
      • Reset the target 1 actor sprite's rotation
How to implement all these

I was thinking of composing my spells out of hopefully reusable "effects" because the spells themselves seem to be built from common base actions like moving an actor's sprite between two points or displaying a hit sprite. But for a lot of spells these "base" actions require information that seems like they'd have to be worked out at run time. I mean, in Throw the target enemy actor's sprite gets moved between up to four points. This makes breaking spells up into interchangeable "effects" difficult, especially if I'd want to compose spells in a visual editor.

The suggestions in this thread so far has boiled down the idea that a spell's effects being able to access the spell's caster and target(s) should be enough (I'd add effects being able to access the spell's area of effect too). agleed also added a suggestion that sounded like effects could store temporary values in the main spell to be used later.

With that in mind, this is how I imagine spells made from composable effects would work in my game.
  • Spells have a caster, one or more targets, and an area of effect for each target.
  • Spells have a list of effects. When started, a spell runs its effects sequentially.
  • Effects themselves may have child effects. When an effect is finished it runs its children sequentially.
  • Most effects have a time component, and may also have a speed component like for projectiles.
  • Spells have a dictionary. Effects may read from and write to this dictionary.
  • That means there may be special "calculator" effects for writing data needed by other effects to the spell's dictionary.
  • Effects will typically have some kind of enum property that sets whether it manipulates or is relative to the caster or one of the targets.
Here are some common effects and their settings
  • MoveActorSprite
    • Moves an actor's sprite between two positions that are relative to any combination of the caster's position and the target positions. So the start position could be set to be relative to the caster while the end position could be set to be relative to target 1, or vise versa.
    • The actor to move is affect either the caster or one of the target actors.
  • ShowSingleSprite
    • Shows a single sprite over a position relative to the position of either the caster or one of the targets.
  • ShowProjectileSprite
    • Shows a sprite moving between two positions that are relative to any combination of the caster's position and the target positions.
  • DamageActor
    • Damages an actor. Set to affect one of the targets (or maybe even the caster).
Then there are some more specialized effects.
  • StoreEmptyAdjacentTile
    • Given either the caster or one of the targets, gets an adjacent empty tile and stores it in the spell's dictionary under "StoredTile" (key can be customized).
  • SetActorTileToStoredTile
    • A companion to StoreEmptyAdjacentTile. Set to affect either the caster or one of the targets, set's the actor's tile position to the value under "StoredTile" (key can be customized) in the spell's dictionary.
So, the Throw spell would start something like MoveActorSprite{ actor = caster; start = [caster, (0,0)]; end = [target 1, (0, -16)] }. The step where the target 1 actor is thrown would be represented by MoveActorSprite{ actor = target(0), start = [caster, (0, 16)], end = [target(1), (0, 0)] }. If the thrown actor hits a wall, the effects StoreEmptyAdjacentTile{ tile = target(1); storekey = "StoredTile" } and SetActorTileToStoredTile{ actor = target(0); storekey = "StoredTile" } are used.

Though I'm still not sure how to handle the branching logic, like checking whether the thrown actor hit a wall in the first place. Nor am I sure about how to handle the explosion spread effect in the Explosive Blast spell.

I'm actually starting to wonder if I'd be better off abandoning my effect composition idea and just writing each spell in code individually.

This topic is closed to new replies.

Advertisement