evil-jrpg/animator.lua

90 lines
2.4 KiB
Lua

local Object = require "lib/classic"
---@alias AnimationFinishBehavior
---| '"loop"' Loop the animation over from the beginning.
---| '"stop"' Freeze the animation on the last frame.
---| '"goto"' Go to another state.
---@class State
---@field animation Animation The animation frames for this state.
---@field on_finish? AnimationFinishBehavior What to do after this state's animation finishes. Defaults to "loop".
---@field next? string The next state to go to in the case of "goto" on_finish.
local Animator = Object:extend()
---Creates a new animator.
---@param states { [string]: State } A dictionary of all states in this animator.
---@param first_state string The first state of this animator.
function Animator:new(states, first_state)
---The states in this animator.
self.states = states
---The current state.
self.state = first_state
---The timestep of the current frame.
self.step = 0
---The current frame in the current animation.
self.frame = 1
--- The next state to progress to.
self.next_state = nil
end
function Animator:update(dt)
local state = self.states[self.state]
local frames = state.animation.frames
--Step the current timestep and return if the frame hasn't changed.
self.step = self.step + dt
if self.step <= frames[self.frame].duration then
return
end
--Step the current frame and return if there are more.
self.step = 0
self.frame = self.frame + 1
if self.frame <= #frames then
return
end
--We've reached the end of the current state.
if self.next_state then
self.state = self.next_state
self.frame = 1
self.next_state = nil
elseif state.on_finish == "goto" then
self.state = state.next
self.frame = 1
elseif state.on_finish == "stop" then
self.frame = #frames
elseif state.on_finish == "loop" or state.on_finish == nil then
self.frame = 1
else
error("Invalid state on_finish " .. state.on_finish)
end
end
function Animator:draw(x, y)
local state = self.states[self.state]
state.animation:draw(self.frame, x, y)
end
---Sets the state of this animator.
---@param state string The name of the state to set.
---@param queue? boolean If true, queues the state change for after the current state.
function Animator:setState(state, queue)
if not self.states[state] then
error("Invalid state " .. state)
elseif queue then
self.next_state = state
else
self.state = state
self.step = 0
self.frame = 1
end
end
return Animator