My custom game engine currently uses XML to specify combat animations. It's a little gross.
<AbilityAnimation>
<Name>sliceanddice</Name>
<Paths>
<Path PerTarget="true" Speed="0.025" PathTarget="caster" Type="linear" Animation="run">current;target.melee</Path>
<Path PerTarget="true" Speed="0.025" PathTarget="caster" WaitOnTargetAnimation="True" Type="static" Animation="swing">target.melee</Path>
<Path PerTarget="true" Speed="0.025" PathTarget="caster" WaitOnTargetAnimation="True" Type="static" Animation="swing">target.melee</Path>
<Path Speed="0.025" PathTarget="caster" Type="linear" Animation="run">current;caster.center</Path>
</Paths>
</AbilityAnimation>
The code for this is getting hard to maintain and I'm starting to feel the squeeze on its capability without massively building it out. That said, I recently implemented a very nice API in the Lua scripting system for in-game cinematics. I'm starting to wonder if I should just scrap this AbilityAnimation thing and move scripted animations to just Lua scripts.
Here's a snippet of code to run a short in-game scene. Assume “doodad” is the game object the player clicked on to run this function.
function Levels.Towntown.OpenBarrel(doodad)
local co
co = coroutine.create(
function(doodad)
local nothik = UnitGet("Ethan")
local barrelPos = DoodadGetPosition(doodad)
UnitFace(nothik, barrelPos)
coroutine.yield(AsyncUnitText(nothik, "Dad? Is that --"))
coroutine.yield(AsyncCustomText("Mysterious Voice", "I SWEAR TO GOD IF YOU POKE YOUR HEAD IN HERE ONE MORE TIME!"))
coroutine.yield(AsyncUnitText(nothik, "SORRY! SORRY! SORRY!"))
local w = UnitGetWanderer(nothik)
local playerPos = UnitGetPosition(nothik)
local barrelPos = DoodadGetPosition(doodad)
local origWalk = MovementSetWanderSpeed(w, 0.25)
local dir = VectorGetUnit(barrelPos, playerPos)
playerPos = VectorMultAdd(playerPos, dir, 8)
coroutine.yield(AsyncWandererPathTo(w, playerPos))
UnitFace(nothik, barrelPos)
coroutine.yield(AsyncUnitText(nothik, "(L'il bitch...)"))
MovementSetWanderSpeed(origWalk)
end
)
ThreadCreate(co, coroutine.resume(co, doodad))
end
The Async functions return a wait token that is passed to coroutine.yield. The engine creates a separate Lua state when calling ThreadCreate, making this wait possible, as it doesn't run on the main Lua state. The wait token returned is signaled by the engine, which resumes calling its version of Lua threads when the wait action is finished. I don't see any downsides to this (yet?) and it seems like a very powerful way to strictly time multiple actions. Any wait tokens that are not waited on are destroyed when the thread is closed so they don't leak. (For those interested, the wait tokens are stored in a map by lua_State.)
That said, I think I want to use this for combat animations. Something like this would be replace the xml above (forgive the inconsistent tabbing between code blocks):
function CastSliceAndDice(action)
local co
co = coroutine.create(
function(action)
local caster = ActionGetCaster(action)
local targets = ActionGetTargets(action)
local currentPos = UnitGetPosition(caster)
local targetPos
for target in ipairs(targets) do
targetPos = UnitGetPositionMelee(target)
coroutine.yield(AsyncPathLinear(caster, current, targetPos))
coroutine.yield(AsyncUnitAnimation(caster, "swing"))
currentPos = targetPos
end
coroutine.yield(AsyncPathLinear(caster, currentPos, UnitGetBattlePosition(caster)))
end
)
ThreadCreate(co, coroutine.resume(co, action))
end
But, I'm open to suggestions. I have no idea how games do this kind of thing normally, but I'm more inclined to say it's just whatever is already used for in-game cinematics .