diff --git a/animator.lua b/animator.lua new file mode 100644 index 0000000..66baeca --- /dev/null +++ b/animator.lua @@ -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 diff --git a/main.lua b/main.lua index b4fab18..3f9b65f 100644 --- a/main.lua +++ b/main.lua @@ -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)