Animation

Display supports playing Animations.

Define an animation

Animations can either be frame-based, or use a custom function. Here’s a frame-based animation:

local on = { index = "!", color = prism.Color4.YELLOW }
local off = { index = " ", color = prism.Color4.BLACK }
spectrum.Animation({ on, off, on }, 0.2, "pauseAtEnd")

0.2 is the amount of seconds to play each frame for. It could also be a table of times, { 0.2, 0.2, 0.2, 0.2 }, or by ranges, { ["1-2"] = 0.5, ["3-4"] = 0.25 }. The final parameter tells the animation what to do when it loops. This can either be a function (which accepts the animation instance and the number of loops), or the string name of a function on Animation.

Animations can be registered to the spectrum.animations registry:

local on = { index = "!", color = prism.Color4.YELLOW }
local off = { index = " ", color = prism.Color4.BLACK }
spectrum.registerAnimation("Exclamation", function()
   return spectrum.Animation({ on, off, on }, 0.2, "pauseAtEnd")
end)

Individual frames can also be functions that accept a Display, x, and y:

local on = { index = "!", color = prism.Color4.YELLOW }
local off = { index = " ", color = prism.Color4.BLACK }
local function putAround(display, x, y)
   display:putSprite(x + 1, y, "!", on)
   display:putSprite(x - 1, y, "!", on)
   display:putSprite(x, y + 1, "!", on)
   display:putSprite(x, y - 1, "!", on)
end
spectrum.registerAnimation("Exclamation", function()
   return spectrum.Animation(
      { putAround, off, putAround },
      0.2,
      "pauseAtEnd"
   )
end)

For more complex animations, a function that accepts the elapsed time and the display can be used. Other parameters passed to the constructor are ignored.

spectrum.registerAnimation("Projectile", function(owner, targetPosition)
   --- @cast owner Actor
   --- @cast targetPosition Vector2
   local x, y = owner:expectPosition():decompose()
   local line = prism.Bresenham(x, y, targetPosition.x, targetPosition.y)

   return spectrum.Animation(function(t, display)
      local index = math.floor(t / 0.05) + 1
      display:put(line[index][1], line[index][2], "*", prism.Color4.ORANGE)

      if index == #line then return true end

      return false
   end)
end)

Tip

Make sure to return true when the animation is over.

Play an animation

To play an animation, Level:yield() an AnimationMessage. There are a few options here. You can play an animation at an actor’s position:

level:yield(prism.messages.AnimationMessage {
   animation = spectrum.animations.Exclamation(),
   actor = kobold
})

Note

If an actor is passed to a custom animation, Display:overrideActor() will be called when the animation starts, and Display:unoverrideActor() will be called when it ends.

Or at a position:

level:yield(prism.messages.AnimationMessage {
   animation = spectrum.animations.Exclamation(),
   x = position.x,
   y = position.y
})

If an actor is passed, the x and y are relative to the actor’s position:

level:yield(prism.messages.AnimationMessage {
   animation = spectrum.animations.Exclamation(),
   actor = target,
   y = -1
})

Animations can force the LevelState to wait for them to finish playing:

level:yield(prism.messages.AnimationMessage {
   animation = spectrum.animations.Exclamation(),
   actor = target,
   y = -1,
   blocking = true
})

Or they can be skippable by passing skippable = true, though you will have to decide when you want animations to be skipped by calling Display:skipAnimations(), e.g. on a key press or mouse click.

Drawing animations

The Display will draw animations automatically, so long as you are using a LevelState and using Display:putSenses(). Otherwise, you will have to call Display:update() and Display:putAnimations() yourself.