Add Animator

This commit is contained in:
mars 2023-04-19 23:05:25 -04:00
parent 78b09b35de
commit 9dc81e02e3
2 changed files with 102 additions and 28 deletions

89
animator.lua Normal file
View File

@ -0,0 +1,89 @@
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

View File

@ -1,4 +1,5 @@
local Animation = require "animation"
local Animator = require "animator"
local Atlas = require "atlas"
local Font = require "font"
local MultiMenu = require "multimenu"
@ -31,38 +32,31 @@ function love.load()
local leafRangerSprites = love.graphics.newImage("assets/leaf-ranger.png")
local leafRangerAtlas = Atlas(leafRangerSprites, 288, 128)
local function sprites(row, len)
local function sprites(row, len, on_finish, next)
local start = (row - 1) * 22 + 1
return Animation:new_spanned(leafRangerAtlas, start, start + len - 1)
local anim = Animation:new_spanned(leafRangerAtlas, start, start + len - 1)
return { animation = anim, on_finish = on_finish, next = next }
end
LeafRangerAnimations = {
local states = {
idle = sprites(1, 12),
run = sprites(2, 10),
hurt = sprites(16, 6),
death = sprites(17, 20),
hurt = sprites(16, 6, "goto", "idle"),
death = sprites(17, 19, "stop"),
}
LeafRanger = Animator(states, "idle")
local options = {}
for name, anim in pairs(LeafRangerAnimations) do
for name, state in pairs(states) do
options[#options + 1] = {
text = name,
callback = function()
LeafRanger.animation = anim
LeafRanger.step = 0
LeafRanger.frame = 1
end,
callback = function() LeafRanger:setState(name) end,
}
end
Menu = MultiMenu(BlockyFont, options)
LeafRanger = {
animation = LeafRangerAnimations.idle,
step = 0,
frame = 1,
}
end
function love.draw()
@ -70,22 +64,13 @@ function love.draw()
Menu:draw()
BlockyFont:draw("Hello, world!")
LeafRanger.animation:draw(LeafRanger.frame, 0, 0)
LeafRanger:draw(0, 0)
push:finish()
end
function love.update(dt)
LeafRanger.step = LeafRanger.step + dt
if LeafRanger.step > LeafRanger.animation.frames[LeafRanger.frame].duration then
LeafRanger.step = 0
LeafRanger.frame = LeafRanger.frame + 1
if LeafRanger.frame > #LeafRanger.animation.frames then
LeafRanger.frame = 1
end
end
LeafRanger:update(dt)
end
function love.keypressed(key)