142 lines
6.3 KiB
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
|