SNKRX/engine/graphics/animation.lua

142 lines
6.3 KiB
Lua

-- The class responsible for holding an animation's graphics.
-- It takes in an already loaded image, the width and height of each frames, and a list of frames in terms of indexed position on the image.
-- player_sheet = Image('player_sheet')
-- player_idle_frames = AnimationFrames(player_sheet, 32, 32, {{1, 1}, {2, 1}})
-- player_run_frames = AnimationFrames(player_sheet, 32, 32, {{1, 2}, {2, 2}, {3, 2}})
-- player_attack_frames = AnimationFrames(player_sheet, 32, 32, {{1, 3}, {2, 3}, {3, 3}, {4, 3}})
--
-- In the example above we first load an image, and then load 3 player animations.
-- Each animation comes from different rows in the same spritesheet, and that's reflected by the last argument in each call.
-- If your animation comes from a single spritesheet that doesn't have multiple animations, then you can omit the last argument and it will automatically go through it.
AnimationFrames = Object:extend()
function AnimationFrames:init(image, frame_w, frame_h, frames_list)
self.source = image
self.frame_w, self.frame_h = frame_w, frame_h
self.frames_list = frames_list
if type(self.frames_list) == 'number' then -- the source is a single row spritesheet and number of frames are specified
local frames_list = {}
for i = 1, self.frames_list do table.insert(frames_list, {i, 1}) end
self.frames_list = frames_list
elseif not self.frames_list then
local frames_list = {}
for i = 1, math.floor(self.source.w/self.frame_w) do table.insert(frames_list, {i, 1}) end
self.frames_list = frames_list
end
self.frames = {}
for i, frame in ipairs(self.frames_list) do
self.frames[i] = {quad = love.graphics.newQuad((frame[1]-1)*self.frame_w, (frame[2]-1)*self.frame_h, self.frame_w, self.frame_h, self.source.w, self.source.h), w = self.frame_w, h = self.frame_h}
end
self.size = #self.frames
end
function AnimationFrames:draw(frame, x, y, r, sx, sy, ox, oy, color)
local _r, g, b, a
if color then
_r, g, b, a = love.graphics.getColor()
graphics.set_color(color)
end
love.graphics.draw(self.source.image, self.frames[frame].quad, x, y, r or 0, sx or 1, sy or sx or 1, self.frames[frame].w/2 + (ox or 0), self.frames[frame].h/2 + (oy or 0))
if color then love.graphics.setColor(_r, g, b, a) end
end
-- The class that logically updates an animation.
-- This being separated from the visual part of an animation is useful whenever you need animation-like behavior unrelated to graphics, like making your own animations with code only. For instance:
--[[
self.animation = AnimationLogic(0.04, 6, 'loop', {
[1] = function()
if self.parent:is(Player) then
for i = 1, random:int(1, 3) do DustParticle(game.current_state.floor, self.parent.x, self.parent.y)) end
end
self.z = 9
end,
[2] = function() self.parent.timer:tween(0.025, self, {z = 6}, math.linear, nil, 'move_2') end,
[3] = function() self.parent.timer:tween(0.025, self, {z = 3}, math.linear, nil, 'move_3') end,
[4] = function()
self.parent.timer:tween(0.025, self, {z = 0}, math.linear, nil, 'move_4')
self.sx = 0.1
self.parent.timer:tween(0.05, self, {sx = 0}, math.linear, nil, 'move_5')
end,
})
]]--
--
-- That was an example of a code-only movement animation for a game I'm making.
-- The arguments that this takes are the delay between each frame, how many frames there are, the loop mode ('loop', 'once' or 'bounce') and a table of actions.
-- The delay argument can either be a number or a table, if it's a table then the delay for each frame can be set individually:
-- animation = AnimationLogic({0.02, 0.04, 0.06, 0.04}, ...)
-- In the example above, it would take 0.02s to go from frame 1 to 2, 0.04s from 2 to 3, 0.06s from 3 to 4 and 0.04s from 4 to 5 (or 1 if there are only 4 frames).
-- Loop can be either: 'loop', the animation will start once from frame 1 once it reaches the end; 'once', it will stop once it reaches the end; 'bounce', it will reverse once it reaches the end or start
-- Finally, the actions table can contain a list of functions, as shown in the code-only animation example above, and each function will be performed when that frame is reached.
-- In that example, once the second frame is reached, this function would be executed:
-- function() self.parent.timer:tween(0.025, self, {z = 6}, math.linear, nil, 'move_2') end
-- The index 0 can be used to perform an action once the animation reaches its end:
-- self.animation = AnimationLogic(0.04, self.player_dead_frames.size, 'once', {[0] = function() self.dead = true end})
AnimationLogic = Object:extend()
function AnimationLogic:init(delay, frames, loop_mode, actions)
self.delay = delay
self.frames = frames
self.loop_mode = loop_mode or "once"
self.actions = actions
self.timer = 0
self.frame = 1
self.direction = 1
end
function AnimationLogic:update(dt)
if self.dead then return end
self.timer = self.timer + dt
local delay = self.delay
if type(self.delay) == "table" then delay = self.delay[self.frame] end
if self.timer > delay then
self.timer = 0
self.frame = self.frame + self.direction
if self.frame > self.frames or self.frame < 1 then
if self.loop_mode == "once" then
self.frame = self.frames
self.dead = true
elseif self.loop_mode == "loop" then
self.frame = 1
elseif self.loop_mode == "bounce" then
self.direction = -self.direction
self.frame = self.frame + 2*self.direction
end
if self.actions and self.actions[0] then self.actions[0]() end
end
if self.actions and self.actions[self.frame] then self.actions[self.frame]() end
end
end
-- The Animation class, a mix of AnimationFrames and AnimationLogic.
-- Takes in a delay, an AnimationFrames object, the loop mode and a table of actions.
-- Read more about the AnimationFrames and AnimationLogic classes as everything there applies here.
Animation = Object:extend()
function Animation:init(delay, animation_frames, loop_mode, actions)
self.delay = delay
self.animation_frames = animation_frames
self.size = self.animation_frames.size
self.loop_mode = loop_mode
self.actions = actions
self.animation_logic = AnimationLogic(self.delay, self.animation_frames.size, self.loop_mode, self.actions)
end
function Animation:update(dt)
self.animation_logic:update(dt)
end
function Animation:draw(x, y, r, sx, sy, ox, oy, color)
self.animation_frames:draw(self.animation_logic.frame, x, y, r, sx, sy, ox, oy, color)
end