Skip to content
New issue

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

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

Already on GitHub? Sign in 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