Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Redefined animations and support multiple simultaneous animations #104

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
209 changes: 146 additions & 63 deletions engine/client/sprite.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,57 @@
--
--==========================================================================--

require( "engine.client.spriteanim" )

class( "sprite" )

accessor( sprite, "spriteSheet" )
accessor( sprite, "spriteSheetName" )
accessor( sprite, "width" )
accessor( sprite, "height" )
accessor( sprite, "frameTime" )
accessor( sprite, "animations" )
accessor( sprite, "events" )

sprite._commands = {
setFrameTime = 1,
setFrameIndex = 2
}

function sprite:sprite( spriteSheet )
local data = require( spriteSheet )
local image = love.graphics.newImage( data[ "image" ] )
self.spriteSheet = image
self.width = data[ "width" ]
self.height = data[ "height" ]
self.frametime = data[ "frametime" ]
self.animations = data[ "animations" ] or {}
self.events = data[ "events" ] or {}
-- Make sure this exists before loading a sprite sheet
self.animations = {}

if (spriteSheet) then
self:setSpriteSheet(spriteSheet)
else
self.spriteSheetName = ""
self.width = 0
self.height = 0
self.frameTime = 0
self.events = {}
end

self.curtime = 0
self.frame = 1
self.animInstances = {}
self.curAnim = nil
end

function sprite:draw()
local image = self:getSpriteSheet()
if (not image) then return end

love.graphics.draw( image, self:getQuad() )
end

accessor( sprite, "animation" )
accessor( sprite, "animationName" )
accessor( sprite, "animations" )
accessor( sprite, "events" )
accessor( sprite, "frametime" )

function sprite:setFilter( ... )
local image = self:getSpriteSheet()
if (not image) then return end

image:setFilter( ... )
end

function sprite:getQuad()
if ( self.quad == nil ) then
if ( self.quad == nil and self.spriteSheet) then
local image = self:getSpriteSheet()
self.quad = love.graphics.newQuad(
0,
Expand All @@ -52,79 +69,145 @@ function sprite:getQuad()
return self.quad
end

accessor( sprite, "spriteSheet" )
accessor( sprite, "width" )
accessor( sprite, "height" )

function sprite:onAnimationEnd( animation )
end

function sprite:onAnimationEvent( event )
function sprite:onAnimationEvent( instance, event )
end

function sprite:setAnimation( animation )
local animations = self:getAnimations()
local name = animation
animation = animations[ name ]
if ( animation == nil ) then
return
end

if ( animation == self:getAnimation() ) then
return
if (typeof(animation, "spriteanim")) then
self.curAnim = animation
elseif (type(animation) == "string") then
if (not self.curAnim or (self.curAnim and self.curAnim:getAnimationName() ~= animation)) then
local instance = self:createAnimInstance(animation);
instance:remove()
instance:setSprite(self)
instance.sprIndex = 0
self.animInstances[0] = instance
self.curAnim = instance
end
elseif (not animation) then
self.curAnim = nil
else
error(string.format("Invalid animation type %q", type(animation)))
end

self.animation = animation
self.animationName = name
self.frame = animation.from
self:updateQuad()
end

self:updateFrame()
function sprite:getAnimation()
return self.curAnim
end

function sprite:update( dt )
local animation = self:getAnimation()
if ( animation == nil ) then
return
end

self.curtime = self.curtime + dt

if ( self.curtime >= self.frametime ) then
self.curtime = 0
self.frame = self.frame + 1

if ( self.frame > animation.to ) then
local name = self:getAnimationName()
self.frame = animation.from
self:onAnimationEnd( name )
for index = 0, table.getn(self.animInstances) do
local instance = self.animInstances[index]
if (instance and not instance.paused) then
instance:update(dt)
end

self:updateFrame()
end
end

function sprite:updateFrame()
function sprite:updateQuad()
if (not self.curAnim) then return end

local quad = self:getQuad()
local frame = self.frame - 1
local frame = self.curAnim.frameIndex - 1
local width = self:getWidth()
local height = self:getHeight()
local image = self:getSpriteSheet()
local imageWidth = image:getWidth()
local x = frame * width % imageWidth
local y = math.floor( frame * width / imageWidth ) * height
quad:setViewport( x, y, width, height )
end

local function processAnimFrame(spr, frame)
if (type(frame) == "number") then
return { { command = sprite._commands.setFrameIndex, value = frame } }
elseif (type(frame) == "function") then
local ret = {}

while (true) do
local i = frame()

if (not i) then break end

table.insert(ret, { command = sprite._commands.setFrameIndex, value = i })
end

return ret
elseif (type(frame) == "table") then
if (type(frame.frameTime) == "number" and frame.frames) then
local ret = processAnimFrame(spr, frame.frames)
table.insert(ret, 1, { command = sprite._commands.setFrameTime, value = frame.frameTime })
table.insert(ret, { command = sprite._commands.setFrameTime, value = spr:getFrameTime() })
return ret
elseif (type(frame.from) == "number" and type(frame.to) == "number") then
local ret = {}

for frameIndex = frame.from, frame.to, (frame.from < frame.to and 1 or -1) do
table.insert(ret, { command = sprite._commands.setFrameIndex, value = frameIndex })
end

return ret
else
local ret = {}

for i, v in ipairs(frame) do
table.append(ret, processAnimFrame(spr, v))
end

return ret
end
else
assert(false, "Frame table must contain frame indices, a range, or a frame sub-table")
end
end

function sprite:loadAnimations(animations)
if (not animations) then return end
assert(type(animations) == "table", "Animations must be a table")

local events = self:getEvents()
local event = events[ frame ]
if ( event ) then
self:onAnimationEvent( event )
for animName, frameTbl in pairs(animations) do
local sequence = processAnimFrame(self, frameTbl)
table.insert(sequence, 1, { command = sprite._commands.setFrameTime, value = self:getFrameTime() })
self.animations[animName] = sequence
end
end

function sprite:createAnimInstance(animName)
local animations = self:getAnimations()
local frames = animations[ animName ]

assert(frames, string.format("Sprite Sheet %q does not contain animation %q", self:getSpriteSheetName(), animName))

local instance = spriteanim()
instance:setSprite(self)
instance:setAnimationName(animName)
instance:setSequence(frames)

table.insert(self.animInstances, instance)
instance.sprIndex = table.getn(self.animInstances)

return instance
end


function sprite:__tostring()
local t = getmetatable( self )
setmetatable( self, {} )
local s = string.gsub( tostring( self ), "table", "sprite" )
setmetatable( self, t )
return s
return string.format("sprite: %q", self.spriteSheetName)
end

function sprite:setSpriteSheet(spriteSheet)
local data = require( spriteSheet )
self.spriteSheet = love.graphics.newImage( data[ "image" ] )
self.spriteSheetName = spriteSheet

self:setEvents(data[ "events" ] or {})
self:setFrameTime(data[ "frametime" ])
self:loadAnimations(data[ "animations" ]) -- load animations after the frametime is set

self.width = data[ "width" ]
self.height = data[ "height" ]
end
Loading