Animation graphs#
An animation graph is a state machine of clips. Each state plays a clip, or a blend of clips picked by parameters, and transitions move from state to state when their conditions hold. A script only sets the parameters.
A graph is a .animgraph file, and you play it like a clip: point an Animation at it and load it.
Build one in the Graph tab of the animation editor, or write
the file by hand.
Playing a graph#
A body that idles, walks and runs by its speed, and jumps:
--!strict
-- server
local world = game.world
local body = world:find("npc") :: Character
local graph = world:add("Animation", { name = "locomotion", animationId = "res://animations/hero.animgraph" }) :: Animation
local track = body.animator:loadAnimation(graph)
track:play()
game.stepped:connect(function()
local v = body.velocity
track:setParameter("speed", vec3(v.x, 0, v.z).magnitude)
local state = body.humanoid.state
track:setParameter("grounded", state ~= "jumping" and state ~= "falling")
end)
body.humanoid.jumping:connect(function(started)
if started then track:trigger("jump") end
end)
local whoosh = body:add("Sound", { soundId = "res://sounds/jump.ogg" }) :: Sound
track.stateChanged:connect(function(state)
if state == "jump" then whoosh:play() end
end)setParametersets a number or a boolean,triggerfires a trigger once, andgetParameterreads one back, or its default.- Parameters are the track's attributes, so a server's values reach every player. A
LocalScriptsets them on a track it loaded itself. track.stateis the state it is in, andstateChangedfires with the name each time it enters one. Both sides run the graph, so each keeps its ownstate.
The rest of the track works as for a clip:
weight,speed,priority,mask,blend,fadeTimeandretargetIddo what they do on any track.timePositionandlengthare those of the state it is in.- Markers and events fire from the clip that weighs the most in the state.
The .animgraph file#
The graph the script above plays:
{
"version": 1,
"entry": "move",
"parameters": {
"speed": { "type": "number", "default": 0 },
"grounded": { "type": "bool", "default": true },
"jump": { "type": "trigger" }
},
"states": {
"move": {
"blend": "1d",
"parameter": "speed",
"motions": [
{ "clip": "res://animations/idle.anim", "at": 0 },
{ "clip": "res://animations/walk.anim", "at": 4 },
{ "clip": "res://animations/run.anim", "at": 8 }
]
},
"jump": { "clip": "res://animations/jump.anim", "loop": false },
"fall": { "clip": "res://animations/fall.anim" }
},
"transitions": [
{ "from": "move", "to": "jump", "when": [{ "parameter": "jump" }], "fade": 0.1 },
{ "from": "jump", "to": "fall", "fade": 0.2 },
{ "from": "any", "to": "move", "when": [{ "parameter": "grounded", "value": true }], "exit": 0.5, "fade": 0.2 }
]
}| Field | ||
|---|---|---|
parameters |
optional | name to type, number, bool or trigger, and a default |
states |
state name to what it plays. A state cannot be called any |
|
entry |
optional | where the graph starts; the first state when left out |
transitions |
optional | tried in order each tick; the first that can go is taken |
States#
| Field | ||
|---|---|---|
clip |
the .anim it plays, for a state of one clip |
|
blend |
optional | 1d or 2d for a blend space, clip when left out |
parameter |
for 1d, the number parameter the blend reads |
|
parameters |
for 2d, the two number parameters, [x, y] |
|
motions |
each with a clip and an at: a number on a 1d line, [x, y] in a 2d space |
|
speed |
optional | how fast it plays, 1 when left out |
speedParameter |
optional | a number parameter the speed is multiplied by |
loop |
optional | true or false. Left out, a clip state loops when its clip does and a blend always loops |
position |
optional | where the editor draws it. The engine reads past it |
Blend spaces#
- A
1dblend mixes the two clips either side of the parameter's value, and holds the first or last past the ends. - A
2dblend weighs every clip by how close the point is to it, so the weights change smoothly as the point moves between them. A point on a clip plays that clip alone.
The clips of a blend are kept in step. They run through their loops together, at a speed between theirs by weight, so a walk and a run blended put their feet down at the same time.
Transitions#
| Field | ||
|---|---|---|
from |
the state it leaves, or any for every state but its own to |
|
to |
the state it enters | |
when |
optional | conditions that must all hold, see below |
exit |
optional | how much of the state must have played first, 1 being once through. Left out, it can go at any time |
fade |
optional | seconds the two states blend for, 0.2 when left out |
A condition has a parameter, a test and a value. The test is >, <, >=, <=, == or
!=, and == when left out. A trigger needs only its parameter, and the transition that takes it
uses it up.
- A transition with no
whenand noexitgoes when its state ends, so it only fits a state that does not loop. - A trigger fired while no transition wants it waits until one does.
