Animation

Skinned meshes

A glTF model's skins come in as skeletons and its animations as clips. Both are parts of the model, named like Hero.gltf#Skeleton/0 and Hero.gltf#Animation/1.

An arm with a skinned mesh renderer and an animator

Every field is listed in Components.

Dragging an animated model into the scene sets up a SkinnedMeshRenderer and an Animator on the right entity.

Playing one clip

With no controller, an Animator loops its Clip. That is enough for a spinning fan or an idle character.

Animator controllers

A controller is a .cairnanimator file: parameters, states that each play a clip, and transitions between them. Write one with the animator.write command:

{
  "command": "animator.write",
  "path": "Assets/Animators/Hero.cairnanimator",
  "data": {
    "Parameters": {"Speed": 0, "Grounded": true},
    "Start": "Idle",
    "States": [
      {"Name": "Idle", "Clip": "Assets/Models/Hero.gltf#Animation/0", "Loop": true},
      {"Name": "Walk", "Clip": "Assets/Models/Hero.gltf#Animation/1", "Loop": true,
       "Events": [{"Time": 0.4, "Function": "OnFootstep"}]}
    ],
    "Transitions": [
      {"From": "Idle", "To": "Walk", "When": "Speed > 0.1 and Grounded", "Fade": 0.2},
      {"From": "Walk", "To": "Idle", "When": "Speed <= 0.1", "Fade": 0.2}
    ]
  }
}

It is shown on several lines here to be readable. In a command file it is one line.

  • Transitions are tried in the order listed, and the first whose condition holds is taken.
  • "From": "Any" leaves any state.
  • "AtEnd": true waits for the clip to finish.
  • Fade is the time, in seconds, to blend from the present pose.
  • An event calls a function by name on the entity's scripts when playback passes its time.

Then set it as the Controller of an Animator component. The file formats page shows the file it writes.

Driving it from a script

Lua
TypeScript
function Hero:OnStart()
	self.Animator = self.Entity.Animator
end

function Hero:OnUpdate()
	self.Animator:Set("Speed", self.Controller:GetVelocity():Length())
end

function Hero:OnFootstep()
	Audio.PlayOneShot(self.StepSound, 0.3)
end

animator:Play("Jump", 0.1) goes to a state at once, whatever the transitions say.

Tweens

For motion that needs no skeleton, a tween takes a value to a target over time:

Lua
TypeScript
Tween.To(self.Entity, "Position", Vec3(0, 3, 0), 2, "EaseInOut")
Tween.To(light, "Intensity", 0, 0.5, "Linear", function() Log.Info("dark") end)

It works on an entity's Position, Rotation and Scale, and on any number or Vec3 of a component.

Good to know

  • Animation plays in play mode and in the game. While editing, a mesh is shown as modelled.
  • Skinning is done on the CPU, which suits a handful of characters. Crowds would need it moved to the GPU.