require "api.logging" require "common.class" require "api.filesys" require "api.graphics" local Image = require "api.image" ---@class AnimationParam: Animation ---@field animPath string ---@field animFPS number ---@class Animation ---@field frames Image[] ---@field frameCount integer ---@field frameTime number ---@field loop boolean ---@field loopPoint integer ---@field width number? ---@field height number? ---@field x number? ---@field y number? ---@field scaleX number? ---@field scaleY number? ---@field centered boolean? ---@field blendOp integer? ---@field color number[]? ---@field alpha number? ---@field stroke StrokeParams? local Animation = { } ---@class AnimationState ---@field animation Animation # The animation data this state is playing through ---@field frameIndex integer # Current frame in the animation ---@field timer number # Timer used to determine when to change to the next frame ---@field running boolean # Is the animation currently running and accepting updates? ---@field callback function? # Called when the animation completes local AnimationState = { } ---Load Animation Frames from path ---@param animPath string ---@return Image[] ---@return integer local function loadSequentialAnimationFrames(animPath) local frames = { } ---@type Image[] local count = 0 local anim_files = filesys.scandir(filesys.fromTexturePath(animPath)) for index, frame_path in ipairs(anim_files) do frame_path = filesys.join(filesys.normpath(animPath), frame_path) DetailedLog("Frame "..index..": '"..frame_path.."'", game.LOGGER_DEBUG) local frame = Image.new(frame_path, true) if not frame then DetailedLog("Could not load frame image '"..frame_path.."'", game.LOGGER_ERROR) break end frames[index] = frame count = count + 1 end return frames, count end ---Animation constructor ---@param params AnimationParam ---@return Animation function Animation.new(params) local self = CreateInstance(Animation, params) self.frames, self.frameCount = loadSequentialAnimationFrames(params.animPath) self.frameTime = 1 / (params.animFPS or 30) self.loop = params.loop or false self.loopPoint = params.loopPoint or 1 self.width = params.width self.height = params.height self.x = params.x self.y = params.y self.scaleX = params.scaleX self.scaleY = params.scaleY self.centered = params.centered self.blendOp = params.blendOp self.color = params.color self.alpha = params.alpha self.stroke = params.stroke return self end ---Create an AnimationState to play this animation. ---The AnimationState is not started. ---@param callback function? ---@return AnimationState function Animation:createState(callback) ---@type AnimationState local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false } return CreateInstance(AnimationState, state) end ---Create an AnimationState to play this animation and start it. ---@param callback function? ---@return AnimationState function Animation:start(callback) local state = self:createState(callback) state:start() return state end ---Start this AnimationState. ---Does nothing if it's already running. function AnimationState:start() self.running = true end ---Restart this AnimationState. ---The frame index is reset to 1. function AnimationState:restart() self.running = true self.frameIndex = 1 self.timer = 0 end ---Stop this AnimationState. function AnimationState:stop() self.running = false end ---Updates this AnimationState and then renders it, passing on the given ImageParams to each frame. ---@param deltaTime number ---@param params? ImageParams function AnimationState:render(deltaTime, params) if (not self.running) then return end self.timer = self.timer + deltaTime while (self.timer > self.animation.frameTime) do self.timer = self.timer - self.animation.frameTime self.frameIndex = self.frameIndex + 1 if (self.frameIndex > self.animation.frameCount) then if (self.animation.loop) then self.frameIndex = self.animation.loopPoint else self.running = false if (self.callback) then self.callback() end return end end end if (params) then if (params.width == nil) then params.width = self.animation.width end if (params.height == nil) then params.height = self.animation.height end if (params.x == nil) then params.x = self.animation.x end if (params.y == nil) then params.y = self.animation.y end if (params.scaleX == nil) then params.scaleX = self.animation.scaleX end if (params.scaleY == nil) then params.scaleY = self.animation.scaleY end if (params.centered == nil) then params.centered = self.animation.centered end if (params.blendOp == nil) then params.blendOp = self.animation.blendOp end if (params.alpha == nil) then params.alpha = self.animation.alpha end if (params.stroke == nil) then params.stroke = self.animation.stroke end end local frame = self.animation.frames[self.frameIndex] if (not frame) then -- TODO(local): what do else frame:render(params) end end return Animation