From 6f66db4ffe08c085d5c69b9475fb12571b339f40 Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Sun, 20 Sep 2020 05:08:32 +1000 Subject: [PATCH 1/9] Added spriteAnimation class and redefined animations --- engine/client/sprite.lua | 72 +++++++++--------------- engine/client/spriteAnimator.lua | 92 +++++++++++++++++++++++++++++++ engine/shared/entities/entity.lua | 9 +-- images/player.lua | 80 ++++++--------------------- 4 files changed, 139 insertions(+), 114 deletions(-) create mode 100644 engine/client/spriteAnimator.lua diff --git a/engine/client/sprite.lua b/engine/client/sprite.lua index 6ab70b9a..c8b89007 100644 --- a/engine/client/sprite.lua +++ b/engine/client/sprite.lua @@ -4,6 +4,8 @@ -- --==========================================================================-- +require("engine.client.spriteAnimator") + class( "sprite" ) function sprite:sprite( spriteSheet ) @@ -25,11 +27,7 @@ function sprite:draw() love.graphics.draw( image, self:getQuad() ) end -accessor( sprite, "animation" ) -accessor( sprite, "animationName" ) -accessor( sprite, "animations" ) -accessor( sprite, "events" ) -accessor( sprite, "frametime" ) +accessor( sprite, "animator" ) function sprite:setFilter( ... ) local image = self:getSpriteSheet() @@ -63,49 +61,24 @@ function sprite:onAnimationEvent( 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 - end - - self.animation = animation - self.animationName = name - self.frame = animation.from - - self:updateFrame() + if (not self.animator) then return end + self.animator:setAnimation(animation) end function sprite:update( dt ) - local animation = self:getAnimation() - if ( animation == nil ) then - return + if (self.animator) then + self.animator:update(dt) end +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 ) - end +function sprite:updateQuad() + if (not self.animator) then return end - self:updateFrame() - end -end + local animation = self.animator:getAnimation() + if (#animation == 0) then return end -function sprite:updateFrame() local quad = self:getQuad() - local frame = self.frame - 1 + local frame = animation[self.animator.frameIndex] - 1 local width = self:getWidth() local height = self:getHeight() local image = self:getSpriteSheet() @@ -113,12 +86,6 @@ function sprite:updateFrame() local x = frame * width % imageWidth local y = math.floor( frame * width / imageWidth ) * height quad:setViewport( x, y, width, height ) - - local events = self:getEvents() - local event = events[ frame ] - if ( event ) then - self:onAnimationEvent( event ) - end end function sprite:__tostring() @@ -128,3 +95,16 @@ function sprite:__tostring() setmetatable( self, t ) return s end + +function sprite:setSpriteSheet(spriteSheet) + local data = require( spriteSheet ) + self.spriteSheet = love.graphics.newImage( data[ "image" ] ) + + self.animator = spriteAnimator(self) + self.animator:setAnimations(data[ "animations" ] or {}) + self.animator:setEvents(data[ "events" ] or {}) + self.animator:setFrametime(data[ "frametime" ]) + + self.width = data[ "width" ] + self.height = data[ "height" ] +end diff --git a/engine/client/spriteAnimator.lua b/engine/client/spriteAnimator.lua new file mode 100644 index 00000000..c7616b4c --- /dev/null +++ b/engine/client/spriteAnimator.lua @@ -0,0 +1,92 @@ +class ("spriteAnimator") + +accessor(spriteAnimator, "frametime") +accessor(spriteAnimator, "animation") +accessor(spriteAnimator, "animationName") +accessor(spriteAnimator, "animations") +accessor(spriteAnimator, "events") + +function spriteAnimator:spriteAnimator(sprite) + self.sprite = sprite + self.animations = {} + self.events = {} + + self.curtime = 0 + self.frametime = 0 + self.frameIndex = 1 + self.animation = nil + self.animationName = "" +end + +--[[ + animTbl = { + animName = { + frame1, frame2, { from = frame3, to = frame7 }, ... + } + } +]] +function spriteAnimator:setAnimations(animTbl) + assert(type(animTbl) == "table", "animTbl must be a table") + + for animName, frameTbl in pairs(animTbl) do + local expanded = {} + + for index, frame in ipairs(frameTbl) do + if (type(frame) == "number") then + table.insert(expanded, frame) + elseif (type(frame) == "table") then + assert(type(frame["from"]) == "number", "frameTbl range table \"from\" must be a frame index") + assert(type(frame["to"]) == "number", "frameTbl range table \"to\" must be a frame index") + for frameIndex = frame.from, frame.to, (frame.to < frame.from and -1 or 1) do + table.insert(expanded, frameIndex) + end + else + assert(false, "frameTbl must contain frame indices, or a range table") + end + end + + self.animations[animName] = expanded + end +end + +function spriteAnimator:setAnimation( name ) + local animations = self:getAnimations() + local animation = animations[ name ] + + if ( animation == nil ) then return end + if ( animation == self:getAnimation() ) then return end + + self.animation = animation + self.animationName = name + self.frameIndex = 1 + + self.sprite:updateQuad() +end + +function spriteAnimator: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.frameIndex = self.frameIndex + 1 + + if ( self.frameIndex > #self.animation ) then + self.frameIndex = 1 + local name = self:getAnimationName() + self.sprite:onAnimationEnd( name ) + end + + self.sprite:updateQuad() + end +end + +function spriteAnimator:checkEvents() + local event = self.events[self.animation[self.frameIndex]] + if (not event) then return end + + self.sprite:onAnimationEvent( event ) +end diff --git a/engine/shared/entities/entity.lua b/engine/shared/entities/entity.lua index 162c6923..c428d40b 100644 --- a/engine/shared/entities/entity.lua +++ b/engine/shared/entities/entity.lua @@ -408,11 +408,12 @@ end if ( _CLIENT ) then function entity:getAnimation() local sprite = self:getSprite() - if ( type( sprite ) ~= "sprite" ) then - return - end + if ( type( sprite ) ~= "sprite" ) then return end + + local animator = sprite:getAnimator() + if (not animator) then return end - return sprite:getAnimationName() + return sprite:getAnimator():getAnimationName() end end diff --git a/images/player.lua b/images/player.lua index 9b134385..2376ffa9 100644 --- a/images/player.lua +++ b/images/player.lua @@ -4,74 +4,26 @@ return { height = 32, frametime = 0.25, animations = { - idlenorth = { - from = 1, - to = 1 - }, - idleeast = { - from = 2, - to = 2 - }, - idlesouth = { - from = 3, - to = 3 - }, - idlewest = { - from = 4, - to = 4 - }, + idlenorth = { 1 }, + idleeast = { 2 }, + idlesouth = { 3 }, + idlewest = { 4 }, -- idlenorth - idlenortheast = { - from = 1, - to = 1, - }, + idlenortheast = { 5 }, -- idlesouth - idlesoutheast = { - from = 3, - to = 3, - }, + idlesoutheast = { 3 }, -- idlesouth - idlesouthwest = { - from = 3, - to = 3, - }, + idlesouthwest = { 3 }, -- idlenorth - idlenorthwest = { - from = 1, - to = 1, - }, - walknorth = { - from = 33, - to = 36 - }, - walkeast = { - from = 65, - to = 68, - }, - walksouth = { - from = 97, - to = 100, - }, - walkwest = { - from = 129, - to = 132, - }, - walknortheast = { - from = 161, - to = 164, - }, - walksoutheast = { - from = 193, - to = 196, - }, - walksouthwest = { - from = 225, - to = 228, - }, - walknorthwest = { - from = 257, - to = 260, - } + idlenorthwest = { 1 }, + walknorth = { { from = 33, to = 36 } }, + walkeast = { { from = 65, to = 68 } }, + walksouth = { { from = 97, to = 100 } }, + walkwest = { { from = 129, to = 132 } }, + walknortheast = { { from = 161, to = 164 } }, + walksoutheast = { { from = 193, to = 196 } }, + walksouthwest = { { from = 225, to = 228 } }, + walknorthwest = { { from = 257, to = 260 } } }, events = { -- walknorth From cc76e1877a54fa6b13e0cc8444ffc9757fb75682 Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Sun, 20 Sep 2020 05:15:44 +1000 Subject: [PATCH 2/9] Fixes bad merge, assert on bad animation name --- engine/client/sprite.lua | 23 ++++++++++++++--------- engine/client/spriteAnimator.lua | 3 ++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/engine/client/sprite.lua b/engine/client/sprite.lua index c8b89007..c28f6503 100644 --- a/engine/client/sprite.lua +++ b/engine/client/sprite.lua @@ -9,14 +9,15 @@ require("engine.client.spriteAnimator") class( "sprite" ) 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 {} + if (spriteSheet) then + self:setSpriteSheet(spriteSheet) + else + self.width = 0 + self.height = 0 + self.frametime = 0 + self.animations = {} + self.events = {} + end self.curtime = 0 self.frame = 1 @@ -24,6 +25,8 @@ end function sprite:draw() local image = self:getSpriteSheet() + if (not image) then return end + love.graphics.draw( image, self:getQuad() ) end @@ -31,11 +34,13 @@ accessor( sprite, "animator" ) 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, diff --git a/engine/client/spriteAnimator.lua b/engine/client/spriteAnimator.lua index c7616b4c..cf4d656e 100644 --- a/engine/client/spriteAnimator.lua +++ b/engine/client/spriteAnimator.lua @@ -53,8 +53,9 @@ function spriteAnimator:setAnimation( name ) local animations = self:getAnimations() local animation = animations[ name ] - if ( animation == nil ) then return end + assert(animation, string.format("Sprite Sheet %q does not contain animation %q", self.sprite:getSpriteSheetName(), name)) if ( animation == self:getAnimation() ) then return end + self.animation = animation self.animationName = name From 4b07cf4ceadbaacb27810d6aec1f67cc8c821ee9 Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Sat, 24 Oct 2020 10:40:46 +1000 Subject: [PATCH 3/9] Fixed bad player idlenorthwest anim frame --- images/player.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/player.lua b/images/player.lua index 2376ffa9..1746ff9a 100644 --- a/images/player.lua +++ b/images/player.lua @@ -9,7 +9,7 @@ return { idlesouth = { 3 }, idlewest = { 4 }, -- idlenorth - idlenortheast = { 5 }, + idlenortheast = { 1 }, -- idlesouth idlesoutheast = { 3 }, -- idlesouth From 17b1c262965a2b5eb4f96de6a3c2fc577e3f9522 Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Tue, 27 Oct 2020 18:09:19 +1000 Subject: [PATCH 4/9] Added sprites can now have multiple simultaneous animations --- engine/client/sprite.lua | 131 +++++++++++++++++++++++------- engine/client/spriteAnimator.lua | 93 --------------------- engine/client/spriteanim.lua | 77 ++++++++++++++++++ engine/shared/entities/entity.lua | 5 +- 4 files changed, 180 insertions(+), 126 deletions(-) delete mode 100644 engine/client/spriteAnimator.lua create mode 100644 engine/client/spriteanim.lua diff --git a/engine/client/sprite.lua b/engine/client/sprite.lua index c28f6503..d39e18e8 100644 --- a/engine/client/sprite.lua +++ b/engine/client/sprite.lua @@ -4,23 +4,34 @@ -- --==========================================================================-- -require("engine.client.spriteAnimator") +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" ) + function sprite:sprite( spriteSheet ) + -- Make sure this exists before loading a sprite sheet + self.animations = {} + if (spriteSheet) then self:setSpriteSheet(spriteSheet) else - self.width = 0 - self.height = 0 - self.frametime = 0 - self.animations = {} - self.events = {} + 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() @@ -30,8 +41,6 @@ function sprite:draw() love.graphics.draw( image, self:getQuad() ) end -accessor( sprite, "animator" ) - function sprite:setFilter( ... ) local image = self:getSpriteSheet() if (not image) then return end @@ -55,10 +64,6 @@ function sprite:getQuad() return self.quad end -accessor( sprite, "spriteSheet" ) -accessor( sprite, "width" ) -accessor( sprite, "height" ) - function sprite:onAnimationEnd( animation ) end @@ -66,24 +71,50 @@ function sprite:onAnimationEvent( event ) end function sprite:setAnimation( animation ) - if (not self.animator) then return end - self.animator:setAnimation(animation) + 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:updateQuad() +end + +function sprite:getAnimation() + return self.curAnim end function sprite:update( dt ) - if (self.animator) then - self.animator:update(dt) + for index = 0, table.getn(self.animInstances) do + local instance = self.animInstances[index] + if (instance and not instance.paused) then + instance:update(dt) + + local event = self.events[instance.frameIndex] + if (event) then + local status, ret = pcall(self.onAnimationEnd, self, event) + if (not status) then print(ret) end + end + end end end function sprite:updateQuad() - if (not self.animator) then return end - - local animation = self.animator:getAnimation() - if (#animation == 0) then return end + if (not self.curAnim) then return end local quad = self:getQuad() - local frame = animation[self.animator.frameIndex] - 1 + local frame = self.animations[self.curAnim:getAnimationName()][self.curAnim.frameIndex] - 1 local width = self:getWidth() local height = self:getHeight() local image = self:getSpriteSheet() @@ -93,6 +124,48 @@ function sprite:updateQuad() quad:setViewport( x, y, width, height ) end +function sprite:loadAnimations(animations) + assert(type(animations) == "table", "animTbl must be a table") + + for animName, frameTbl in pairs(animations) do + local expanded = {} + + for index, frame in ipairs(frameTbl) do + if (type(frame) == "number") then + table.insert(expanded, frame) + elseif (type(frame) == "table") then + assert(type(frame["from"]) == "number", "frameTbl range table \"from\" must be a frame index") + assert(type(frame["to"]) == "number", "frameTbl range table \"to\" must be a frame index") + for frameIndex = frame.from, frame.to, (frame.to < frame.from and -1 or 1) do + table.insert(expanded, frameIndex) + end + else + assert(false, "frameTbl must contain frame indices, or a range table") + end + end + + self.animations[animName] = expanded + 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:setFrameTime(self:getFrameTime()) + instance:setAnimationName(animName) + instance:setFrames(frames) + + table.insert(self.animInstances, instance) + instance.sprIndex = table.getn(self.animInstances) + + return instance +end + function sprite:__tostring() local t = getmetatable( self ) setmetatable( self, {} ) @@ -102,13 +175,13 @@ function sprite:__tostring() end function sprite:setSpriteSheet(spriteSheet) - local data = require( spriteSheet ) - self.spriteSheet = love.graphics.newImage( data[ "image" ] ) + local data = require( spriteSheet ) + self.spriteSheet = love.graphics.newImage( data[ "image" ] ) + self.spriteSheetName = spriteSheet - self.animator = spriteAnimator(self) - self.animator:setAnimations(data[ "animations" ] or {}) - self.animator:setEvents(data[ "events" ] or {}) - self.animator:setFrametime(data[ "frametime" ]) + self:loadAnimations(data[ "animations" ] or {}) + self:setEvents(data[ "events" ] or {}) + self:setFrameTime(data[ "frametime" ]) self.width = data[ "width" ] self.height = data[ "height" ] diff --git a/engine/client/spriteAnimator.lua b/engine/client/spriteAnimator.lua deleted file mode 100644 index cf4d656e..00000000 --- a/engine/client/spriteAnimator.lua +++ /dev/null @@ -1,93 +0,0 @@ -class ("spriteAnimator") - -accessor(spriteAnimator, "frametime") -accessor(spriteAnimator, "animation") -accessor(spriteAnimator, "animationName") -accessor(spriteAnimator, "animations") -accessor(spriteAnimator, "events") - -function spriteAnimator:spriteAnimator(sprite) - self.sprite = sprite - self.animations = {} - self.events = {} - - self.curtime = 0 - self.frametime = 0 - self.frameIndex = 1 - self.animation = nil - self.animationName = "" -end - ---[[ - animTbl = { - animName = { - frame1, frame2, { from = frame3, to = frame7 }, ... - } - } -]] -function spriteAnimator:setAnimations(animTbl) - assert(type(animTbl) == "table", "animTbl must be a table") - - for animName, frameTbl in pairs(animTbl) do - local expanded = {} - - for index, frame in ipairs(frameTbl) do - if (type(frame) == "number") then - table.insert(expanded, frame) - elseif (type(frame) == "table") then - assert(type(frame["from"]) == "number", "frameTbl range table \"from\" must be a frame index") - assert(type(frame["to"]) == "number", "frameTbl range table \"to\" must be a frame index") - for frameIndex = frame.from, frame.to, (frame.to < frame.from and -1 or 1) do - table.insert(expanded, frameIndex) - end - else - assert(false, "frameTbl must contain frame indices, or a range table") - end - end - - self.animations[animName] = expanded - end -end - -function spriteAnimator:setAnimation( name ) - local animations = self:getAnimations() - local animation = animations[ name ] - - assert(animation, string.format("Sprite Sheet %q does not contain animation %q", self.sprite:getSpriteSheetName(), name)) - if ( animation == self:getAnimation() ) then return end - - - self.animation = animation - self.animationName = name - self.frameIndex = 1 - - self.sprite:updateQuad() -end - -function spriteAnimator: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.frameIndex = self.frameIndex + 1 - - if ( self.frameIndex > #self.animation ) then - self.frameIndex = 1 - local name = self:getAnimationName() - self.sprite:onAnimationEnd( name ) - end - - self.sprite:updateQuad() - end -end - -function spriteAnimator:checkEvents() - local event = self.events[self.animation[self.frameIndex]] - if (not event) then return end - - self.sprite:onAnimationEvent( event ) -end diff --git a/engine/client/spriteanim.lua b/engine/client/spriteanim.lua new file mode 100644 index 00000000..60098b4b --- /dev/null +++ b/engine/client/spriteanim.lua @@ -0,0 +1,77 @@ +class "spriteanim" + +accessor( spriteanim, "sprite" ) +accessor( spriteanim, "frameTime" ) +accessor( spriteanim, "animationName" ) +accessor( spriteanim, "frames" ) + +function spriteanim:spriteanim() + self.sprIndex = 0 -- This is the index in the owning sprite's animInstance table. (result of table.insert) + self.curTime = 0 + self.paused = false + self.frames = {} + self.frameIndex = 1 + self.singleFrameFinished = false + self.loop = true + self.animEnded = false +end + +function spriteanim:pause() + self.paused = true +end + +function spriteanim:resume() + self.paused = false +end + +function spriteanim:loop(bShouldLoop) + self.loop = toboolean(bShouldLoop) + self:play() +end + +function spriteanim:play() + self.frameIndex = 1 + self.curTime = 0 +end + +function spriteanim:update(dt) + local spr = self:getSprite() + local frames = self:getFrames() + + if ( not spr or #frames == 0 or (self.singleFrameFinished and #frames == 1) or self.paused ) then return end + + self.curTime = self.curTime + dt + + if ( self.curTime >= spr:getFrameTime() ) then + self.curTime = 0 + self.frameIndex = self.frameIndex + 1 + + -- This should always pass when the animation is a single frame + if ( self.frameIndex > #frames ) then + if (self.loop) then + self.frameIndex = 1 + else + self.frameIndex = #frames + end + + local name = self:getAnimationName() + local status, ret = pcall(spr.onAnimationEnd, spr, name ) + if (not status) then print(ret) end + end + + spr:updateQuad() + self.singleFrameFinished = true + end +end + +function spriteanim:remove() + local spr = self:getSprite() + if (not spr) then return end + + local instances = spr.animInstances + if (instances[self.sprIndex] == self) then -- prevent accidentally removing another anim at the same index.. just in case + table.remove(instances, self.sprIndex) + end + + self:setSprite(nil) +end diff --git a/engine/shared/entities/entity.lua b/engine/shared/entities/entity.lua index c428d40b..18cb2fc9 100644 --- a/engine/shared/entities/entity.lua +++ b/engine/shared/entities/entity.lua @@ -410,10 +410,7 @@ if ( _CLIENT ) then local sprite = self:getSprite() if ( type( sprite ) ~= "sprite" ) then return end - local animator = sprite:getAnimator() - if (not animator) then return end - - return sprite:getAnimator():getAnimationName() + return sprite:getAnimation() end end From dd0897ec3ec17ee1aa805c792c3ca7d351e352be Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Tue, 27 Oct 2020 18:19:37 +1000 Subject: [PATCH 5/9] Update moveindicator to new format --- images/moveindicator.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/images/moveindicator.lua b/images/moveindicator.lua index 16cde4d6..d5e62a5a 100644 --- a/images/moveindicator.lua +++ b/images/moveindicator.lua @@ -4,9 +4,6 @@ return { height = 16, frametime = 0.04, animations = { - click = { - from = 1, - to = 8 - } + click = { { from = 1, to = 8 } } } } From a95342599f5365c36813160f79c8694012883735 Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Tue, 10 Nov 2020 00:55:54 +1000 Subject: [PATCH 6/9] Sprite animations now support multiple frame times --- engine/client/sprite.lua | 83 ++++++++++++++++++++++++----------- engine/client/spriteanim.lua | 85 ++++++++++++++++++++++++++---------- images/moveindicator.lua | 2 +- images/player.lua | 32 +++++++------- 4 files changed, 137 insertions(+), 65 deletions(-) diff --git a/engine/client/sprite.lua b/engine/client/sprite.lua index d39e18e8..b67804ca 100644 --- a/engine/client/sprite.lua +++ b/engine/client/sprite.lua @@ -16,6 +16,11 @@ accessor( sprite, "frameTime" ) accessor( sprite, "animations" ) accessor( sprite, "events" ) +sprite._commands = { + setFrameTime = 1, + setFrameIndex = 2 +} + function sprite:sprite( spriteSheet ) -- Make sure this exists before loading a sprite sheet self.animations = {} @@ -114,7 +119,7 @@ function sprite:updateQuad() if (not self.curAnim) then return end local quad = self:getQuad() - local frame = self.animations[self.curAnim:getAnimationName()][self.curAnim.frameIndex] - 1 + local frame = self.curAnim.frameIndex - 1 local width = self:getWidth() local height = self:getHeight() local image = self:getSpriteSheet() @@ -124,27 +129,57 @@ function sprite:updateQuad() quad:setViewport( x, y, width, height ) end -function sprite:loadAnimations(animations) - assert(type(animations) == "table", "animTbl must be a table") +local function processAnimFrame(spr, frame) + if (type(frame) == "number") then + return { { command = sprite._commands.setFrameIndex, value = frame } } + elseif (type(frame) == "function") then + local ret = {} - for animName, frameTbl in pairs(animations) do - local expanded = {} - - for index, frame in ipairs(frameTbl) do - if (type(frame) == "number") then - table.insert(expanded, frame) - elseif (type(frame) == "table") then - assert(type(frame["from"]) == "number", "frameTbl range table \"from\" must be a frame index") - assert(type(frame["to"]) == "number", "frameTbl range table \"to\" must be a frame index") - for frameIndex = frame.from, frame.to, (frame.to < frame.from and -1 or 1) do - table.insert(expanded, frameIndex) - end - else - assert(false, "frameTbl must contain frame indices, or a range table") + 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 - self.animations[animName] = expanded +function sprite:loadAnimations(animations) + if (not animations) then return end + assert(type(animations) == "table", "Animations must be a table") + + 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 @@ -156,9 +191,8 @@ function sprite:createAnimInstance(animName) local instance = spriteanim() instance:setSprite(self) - instance:setFrameTime(self:getFrameTime()) instance:setAnimationName(animName) - instance:setFrames(frames) + instance:setSequence(frames) table.insert(self.animInstances, instance) instance.sprIndex = table.getn(self.animInstances) @@ -166,12 +200,9 @@ function sprite:createAnimInstance(animName) 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) @@ -179,9 +210,9 @@ function sprite:setSpriteSheet(spriteSheet) self.spriteSheet = love.graphics.newImage( data[ "image" ] ) self.spriteSheetName = spriteSheet - self:loadAnimations(data[ "animations" ] or {}) 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" ] diff --git a/engine/client/spriteanim.lua b/engine/client/spriteanim.lua index 60098b4b..333611c3 100644 --- a/engine/client/spriteanim.lua +++ b/engine/client/spriteanim.lua @@ -1,21 +1,31 @@ class "spriteanim" accessor( spriteanim, "sprite" ) -accessor( spriteanim, "frameTime" ) accessor( spriteanim, "animationName" ) -accessor( spriteanim, "frames" ) +accessor( spriteanim, "sequence" ) function spriteanim:spriteanim() self.sprIndex = 0 -- This is the index in the owning sprite's animInstance table. (result of table.insert) self.curTime = 0 + self.targetFrameTime = 0 self.paused = false - self.frames = {} + self.sequence = {} + self.sequenceIndex = 1 self.frameIndex = 1 self.singleFrameFinished = false self.loop = true self.animEnded = false end +function spriteanim:__tostring() + return string.format("sprite animation: %q [frame: %i]", self.animationName, self.frameIndex) +end + +function spriteanim:setSequence(sequence) + self.sequence = sequence + self:play() +end + function spriteanim:pause() self.paused = true end @@ -26,38 +36,69 @@ end function spriteanim:loop(bShouldLoop) self.loop = toboolean(bShouldLoop) - self:play() + + if (bShouldLoop) then + self:play() + end end function spriteanim:play() - self.frameIndex = 1 + self.sequenceIndex = 1 self.curTime = 0 + self:pollCommands() +end + +function spriteanim:pollCommands() + if (self.paused) then return end + + local sequence = self.sequence + if (self.sequenceIndex > #sequence) then return end + + local command = sequence[self.sequenceIndex] + + if (command.command == sprite._commands.setFrameTime) then + self.targetFrameTime = command.value + self.curTime = 0 + + -- increment sequence index and repoll to prevent frameIndex flickering + self.sequenceIndex = self.sequenceIndex + 1 + self:pollCommands() + return + elseif (command.command == sprite._commands.setFrameIndex) then + self.frameIndex = command.value + else + error(string.format("Invalid sprite command %q", tostring(command.command))) + end + + self.sequenceIndex = self.sequenceIndex + 1 -- Increment after so we dont have to bounds check it because lazy + + if ( self.sequenceIndex > #sequence ) then + if (self.loop) then + self.sequenceIndex = 1 + end + + local name = self:getAnimationName() + local spr = self:getSprite() + local status, ret = pcall(spr.onAnimationEnd, spr, name ) + if (not status) then print(ret) end + end end function spriteanim:update(dt) + if (self.paused) then return end + local spr = self:getSprite() - local frames = self:getFrames() + if (not spr) then return end - if ( not spr or #frames == 0 or (self.singleFrameFinished and #frames == 1) or self.paused ) then return end + local sequence = self:getSequence() + if ( #sequence == 0 or (self.singleFrameFinished and #sequence == 1) ) then return end self.curTime = self.curTime + dt - if ( self.curTime >= spr:getFrameTime() ) then + if ( self.curTime >= self.targetFrameTime ) then self.curTime = 0 - self.frameIndex = self.frameIndex + 1 - - -- This should always pass when the animation is a single frame - if ( self.frameIndex > #frames ) then - if (self.loop) then - self.frameIndex = 1 - else - self.frameIndex = #frames - end - - local name = self:getAnimationName() - local status, ret = pcall(spr.onAnimationEnd, spr, name ) - if (not status) then print(ret) end - end + + self:pollCommands() spr:updateQuad() self.singleFrameFinished = true diff --git a/images/moveindicator.lua b/images/moveindicator.lua index d5e62a5a..8c0b90f0 100644 --- a/images/moveindicator.lua +++ b/images/moveindicator.lua @@ -4,6 +4,6 @@ return { height = 16, frametime = 0.04, animations = { - click = { { from = 1, to = 8 } } + click = { from = 1, to = 8 } } } diff --git a/images/player.lua b/images/player.lua index 1746ff9a..42c053fd 100644 --- a/images/player.lua +++ b/images/player.lua @@ -4,26 +4,26 @@ return { height = 32, frametime = 0.25, animations = { - idlenorth = { 1 }, - idleeast = { 2 }, - idlesouth = { 3 }, - idlewest = { 4 }, + idlenorth = 1, + idleeast = 2, + idlesouth = 3, + idlewest = 4, -- idlenorth - idlenortheast = { 1 }, + idlenortheast = 1, -- idlesouth - idlesoutheast = { 3 }, + idlesoutheast = 3, -- idlesouth - idlesouthwest = { 3 }, + idlesouthwest = 3, -- idlenorth - idlenorthwest = { 1 }, - walknorth = { { from = 33, to = 36 } }, - walkeast = { { from = 65, to = 68 } }, - walksouth = { { from = 97, to = 100 } }, - walkwest = { { from = 129, to = 132 } }, - walknortheast = { { from = 161, to = 164 } }, - walksoutheast = { { from = 193, to = 196 } }, - walksouthwest = { { from = 225, to = 228 } }, - walknorthwest = { { from = 257, to = 260 } } + idlenorthwest = 1, + walknorth = { from = 33, to = 36 }, + walkeast = { from = 65, to = 68 }, + walksouth = { from = 97, to = 100 }, + walkwest = { from = 129, to = 132 }, + walknortheast = { from = 161, to = 164 }, + walksoutheast = { from = 193, to = 196 }, + walksouthwest = { from = 225, to = 228 }, + walknorthwest = { from = 257, to = 260 } }, events = { -- walknorth From 8a59d5df3fc3c1d05a338550b7b38269bba99c91 Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Thu, 12 Nov 2020 01:37:56 +1000 Subject: [PATCH 7/9] Fixed animation events calling the wrong function in the wrong place --- engine/client/sprite.lua | 6 ------ engine/client/spriteanim.lua | 14 +++++++++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/engine/client/sprite.lua b/engine/client/sprite.lua index b67804ca..1da6b99a 100644 --- a/engine/client/sprite.lua +++ b/engine/client/sprite.lua @@ -105,12 +105,6 @@ function sprite:update( dt ) local instance = self.animInstances[index] if (instance and not instance.paused) then instance:update(dt) - - local event = self.events[instance.frameIndex] - if (event) then - local status, ret = pcall(self.onAnimationEnd, self, event) - if (not status) then print(ret) end - end end end end diff --git a/engine/client/spriteanim.lua b/engine/client/spriteanim.lua index 333611c3..a360b765 100644 --- a/engine/client/spriteanim.lua +++ b/engine/client/spriteanim.lua @@ -55,6 +55,7 @@ function spriteanim:pollCommands() if (self.sequenceIndex > #sequence) then return end local command = sequence[self.sequenceIndex] + local spr = self:getSprite() if (command.command == sprite._commands.setFrameTime) then self.targetFrameTime = command.value @@ -66,6 +67,18 @@ function spriteanim:pollCommands() return elseif (command.command == sprite._commands.setFrameIndex) then self.frameIndex = command.value + + local event = spr.events[self.frameIndex] + if (event) then + if (type(event) ~= "table") then + event = { event } + end + + for i, v in ipairs(event) do + local status, ret = pcall(spr.onAnimationEvent, spr, v) + if (not status) then print(ret) end + end + end else error(string.format("Invalid sprite command %q", tostring(command.command))) end @@ -78,7 +91,6 @@ function spriteanim:pollCommands() end local name = self:getAnimationName() - local spr = self:getSprite() local status, ret = pcall(spr.onAnimationEnd, spr, name ) if (not status) then print(ret) end end From d978df1358b90518d5cca2a4b7815a84fe572efc Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Thu, 12 Nov 2020 01:50:20 +1000 Subject: [PATCH 8/9] Fixed single frame animations continuously polling commands --- engine/client/spriteanim.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/engine/client/spriteanim.lua b/engine/client/spriteanim.lua index a360b765..7c23da73 100644 --- a/engine/client/spriteanim.lua +++ b/engine/client/spriteanim.lua @@ -23,6 +23,13 @@ end function spriteanim:setSequence(sequence) self.sequence = sequence + + -- `#sequence == 2` should be true only for single frame anims. The first sequence should be the frameTime command, second should be frameIndex. + -- This prevents single frame animations from constantly calling `spri:onAnimationEnd`, also single frame animations dont need to loop + if (#self.sequence == 2) then + self.loop = false + end + self:play() end @@ -45,6 +52,8 @@ end function spriteanim:play() self.sequenceIndex = 1 self.curTime = 0 + self.singleFrameFinished = false + self:resume() self:pollCommands() end @@ -52,7 +61,10 @@ function spriteanim:pollCommands() if (self.paused) then return end local sequence = self.sequence - if (self.sequenceIndex > #sequence) then return end + if (self.sequenceIndex > #sequence) then + self:pause() + return + end local command = sequence[self.sequenceIndex] local spr = self:getSprite() @@ -102,9 +114,6 @@ function spriteanim:update(dt) local spr = self:getSprite() if (not spr) then return end - local sequence = self:getSequence() - if ( #sequence == 0 or (self.singleFrameFinished and #sequence == 1) ) then return end - self.curTime = self.curTime + dt if ( self.curTime >= self.targetFrameTime ) then From 06fd269080d02ce38eb93804721efe5f857285bb Mon Sep 17 00:00:00 2001 From: Tyler Duffus Date: Thu, 12 Nov 2020 02:06:02 +1000 Subject: [PATCH 9/9] Added instance parameter to `sprite:onAnimationEvent` --- engine/client/sprite.lua | 2 +- engine/client/spriteanim.lua | 2 +- engine/shared/entities/entity.lua | 6 +++--- engine/shared/entities/player.lua | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/engine/client/sprite.lua b/engine/client/sprite.lua index 1da6b99a..f39782be 100644 --- a/engine/client/sprite.lua +++ b/engine/client/sprite.lua @@ -72,7 +72,7 @@ end function sprite:onAnimationEnd( animation ) end -function sprite:onAnimationEvent( event ) +function sprite:onAnimationEvent( instance, event ) end function sprite:setAnimation( animation ) diff --git a/engine/client/spriteanim.lua b/engine/client/spriteanim.lua index 7c23da73..bd7835cf 100644 --- a/engine/client/spriteanim.lua +++ b/engine/client/spriteanim.lua @@ -87,7 +87,7 @@ function spriteanim:pollCommands() end for i, v in ipairs(event) do - local status, ret = pcall(spr.onAnimationEvent, spr, v) + local status, ret = pcall(spr.onAnimationEvent, spr, self, v) if (not status) then print(ret) end end end diff --git a/engine/shared/entities/entity.lua b/engine/shared/entities/entity.lua index 18cb2fc9..7e39e231 100644 --- a/engine/shared/entities/entity.lua +++ b/engine/shared/entities/entity.lua @@ -729,7 +729,7 @@ if ( _CLIENT ) then function entity:onAnimationEnd( animation ) end - function entity:onAnimationEvent( event ) + function entity:onAnimationEvent( instance, event ) end end @@ -893,8 +893,8 @@ if ( _CLIENT ) then self:onAnimationEnd( animation ) end - sprite.onAnimationEvent = function( _, event ) - self:onAnimationEvent( event ) + sprite.onAnimationEvent = function( _, instance, event ) + self:onAnimationEvent( instance, event ) end end diff --git a/engine/shared/entities/player.lua b/engine/shared/entities/player.lua index 079119db..2b673ccd 100644 --- a/engine/shared/entities/player.lua +++ b/engine/shared/entities/player.lua @@ -363,7 +363,7 @@ if ( _CLIENT ) then "Plays footstep sounds for players", nil, { "archive" } ) - function player:onAnimationEvent( event ) + function player:onAnimationEvent( instance, event ) if ( cl_footsteps:getBoolean() ) then updateStepSound( self, event ) end