ExperimentalGear/scripts/graphics/frameanimation.lua

185 lines
5.3 KiB
Lua
Raw Permalink Normal View History

2024-02-02 02:27:05 +01:00
require "api.logging"
2022-04-27 00:12:14 +02:00
require "common.class"
2024-02-02 02:27:05 +01:00
require "api.filesys"
2022-04-27 00:12:14 +02:00
require "api.graphics"
2023-08-19 04:48:46 +02:00
local Image = require "scripts.graphics.image"
2022-04-27 00:12:14 +02:00
2024-02-02 02:27:05 +01:00
---@class AnimationParam: Animation
---@field animPath string
---@field animFPS number
2022-04-27 00:12:14 +02:00
---@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?
2024-02-02 02:27:05 +01:00
local Animation = { }
2022-04-27 00:12:14 +02:00
---@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
2024-02-02 02:27:05 +01:00
local AnimationState = { }
2022-04-27 00:12:14 +02:00
2024-02-02 02:27:05 +01:00
---Load Animation Frames from path
---@param animPath string
---@return Image[]
---@return integer
2022-04-27 00:12:14 +02:00
local function loadSequentialAnimationFrames(animPath)
2024-02-02 02:27:05 +01:00
local frames = { } ---@type Image[]
local count = 0
local anim_files = filesys.scandir(filesys.fromTexturePath(animPath))
2022-04-27 00:12:14 +02:00
2024-02-02 02:27:05 +01:00
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
2022-04-27 00:12:14 +02:00
end
2024-02-02 02:27:05 +01:00
frames[index] = frame
count = count + 1
2022-04-27 00:12:14 +02:00
end
2024-02-02 02:27:05 +01:00
return frames, count
2022-04-27 00:12:14 +02:00
end
---Animation constructor
2024-02-02 02:27:05 +01:00
---@param params AnimationParam
2022-04-27 00:12:14 +02:00
---@return Animation
2024-02-02 02:27:05 +01:00
function Animation.new(params)
local self = CreateInstance(Animation, params)
2024-02-02 02:27:05 +01:00
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
2022-04-27 00:12:14 +02:00
end
---Create an AnimationState to play this animation.
---The AnimationState is not started.
---@param callback function?
---@return AnimationState
function Animation:createState(callback)
---@type AnimationState
2024-02-02 02:27:05 +01:00
local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false }
return CreateInstance(AnimationState, state)
2022-04-27 00:12:14 +02:00
end
---Create an AnimationState to play this animation and start it.
---@param callback function?
---@return AnimationState
function Animation:start(callback)
2024-02-02 02:27:05 +01:00
local state = self:createState(callback)
state:start()
2022-04-27 00:12:14 +02:00
2024-02-02 02:27:05 +01:00
return state
2022-04-27 00:12:14 +02:00
end
---Start this AnimationState.
---Does nothing if it's already running.
function AnimationState:start()
2024-02-02 02:27:05 +01:00
self.running = true
2022-04-27 00:12:14 +02:00
end
---Restart this AnimationState.
---The frame index is reset to 1.
function AnimationState:restart()
2024-02-02 02:27:05 +01:00
self.running = true
self.frameIndex = 1
self.timer = 0
2022-04-27 00:12:14 +02:00
end
---Stop this AnimationState.
function AnimationState:stop()
2024-02-02 02:27:05 +01:00
self.running = false
2022-04-27 00:12:14 +02:00
end
---Updates this AnimationState and then renders it, passing on the given ImageParams to each frame.
2022-04-27 00:12:14 +02:00
---@param deltaTime number
---@param params? ImageParams
function AnimationState:render(deltaTime, params)
2024-02-02 02:27:05 +01:00
if (not self.running) then return end
2022-04-27 00:12:14 +02:00
2024-02-02 02:27:05 +01:00
self.timer = self.timer + deltaTime
2022-04-27 00:12:14 +02:00
while (self.timer > self.animation.frameTime) do
2024-02-02 02:27:05 +01:00
self.timer = self.timer - self.animation.frameTime
self.frameIndex = self.frameIndex + 1
2022-04-27 00:12:14 +02:00
if (self.frameIndex > self.animation.frameCount) then
if (self.animation.loop) then
2024-02-02 02:27:05 +01:00
self.frameIndex = self.animation.loopPoint
2022-04-27 00:12:14 +02:00
else
2024-02-02 02:27:05 +01:00
self.running = false
2022-04-27 00:12:14 +02:00
if (self.callback) then
2024-02-02 02:27:05 +01:00
self.callback()
2022-04-27 00:12:14 +02:00
end
2024-02-02 02:27:05 +01:00
return
2022-04-27 00:12:14 +02:00
end
end
end
if (params) then
2024-02-02 02:27:05 +01:00
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
2022-04-27 00:12:14 +02:00
end
2024-02-02 02:27:05 +01:00
local frame = self.animation.frames[self.frameIndex]
2022-04-27 00:12:14 +02:00
if (not frame) then
-- TODO(local): what do
else
2024-02-02 02:27:05 +01:00
frame:render(params)
2022-04-27 00:12:14 +02:00
end
end
2024-02-02 02:27:05 +01:00
return Animation