From feb7541944581fd16d76bf62a341724cb9ed2987 Mon Sep 17 00:00:00 2001 From: annabunches Date: Wed, 4 Oct 2023 01:19:05 -0400 Subject: [PATCH] Fix defend state, add animation tables (not used quite yet), and add onExit functions to the state machine. --- src/entity.lua | 30 +++++++++++++++-- src/kani.lua | 79 ++++++++++++++++++++++++++++---------------- src/main.lua | 8 ----- src/statemachine.lua | 29 +++++++++++----- 4 files changed, 99 insertions(+), 47 deletions(-) diff --git a/src/entity.lua b/src/entity.lua index 3d30366..648dec8 100644 --- a/src/entity.lua +++ b/src/entity.lua @@ -16,6 +16,10 @@ function Entity:init(img, health, armor) self.armor = armor or 0 self.introAnimator = nil + -- For imagetable-based animation + self.animationIndex = nil + self.animationTimer = nil + -- movement direction, every update() the entity will move along this vector and return -- collision data to the subclass self.vector = geom.vector2D.new(0, 0) @@ -32,15 +36,15 @@ function Entity:init(img, health, armor) local states = { ["INIT"] = { main=self.runInit, - transition=self.onInit + incoming=self.onInit }, ["INTRO"] = { main=self.runIntro, - transition=self.onIntro, + incoming=self.onIntro, }, ["READY"] = { main=self.runReady, - transition=self.onReady, + incoming=self.onReady, }, } self.fsm = StateMachine.new({self}, states) @@ -68,6 +72,26 @@ end function Entity:update() -- update state machine self.fsm:execute() + + if self.animationTimer then + local targetIndex = math.floor(self.animationTimer.value) + if self.animationIndex ~= targetIndex then + self:setImage(self.animationTable:getImage(targetIndex)) + end + end +end + +-- animate the entity based on the passed in imageTable. If reverse is true, play the +-- animation backwards. duration is how long the animation should be in total +function Entity:animate(imgTable, duration, reverse) + self.animationTable = imgTable + local startValue = 1 + local endValue = #imgTable + if reverse then + startValue = #imgTable + endValue = 1 + end + self.animationTimer = playdate.timer.new(duration, startValue, endValue) end -- State machine-controlled functions diff --git a/src/kani.lua b/src/kani.lua index bf0cfd2..9de5c6a 100644 --- a/src/kani.lua +++ b/src/kani.lua @@ -69,25 +69,7 @@ function Kani:init(ui) -- the UI gets passed in to Kani so that we can update pieces of it easily. self.ui = ui - self.inputHandlers = { - upButtonDown = function() self.vector.dy = -3 end, - downButtonDown = function() self.vector.dy = 3 end, - leftButtonDown = function() self.vector.dx = -3 end, - rightButtonDown = function() self.vector.dx = 3 end, - upButtonUp = function() self.vector.dy = 0 end, - downButtonUp = function() self.vector.dy = 0 end, - leftButtonUp = function() self.vector.dx = 0 end, - rightButtonUp = function() self.vector.dx = 0 end, - cranked = function(change, accelChange) self:chargeReserve(change) end, - AButtonDown = function() - self.firingTimer = playdate.timer.keyRepeatTimerWithDelay(self.weaponChargeSpeed / 2, - self.weaponChargeSpeed, - self.chargeShot, self) - end, - AButtonUp = function() self:fire() end, - } - - self.fsm:changeState("READY") + self.fsm:addState("DEFEND", self.runDefend, self.onDefend, self.onDefendExit) end function Kani:chargeReserve(change) @@ -138,24 +120,65 @@ end function Kani:damage(amount) Kani.super.damage(self, amount) self.ui.healthMeter:setValue(self.health) - + print("Kani health now at " .. self.health) -- debug if self.health == 0 then -- TODO: end game here end end -function Kani:addInputHandlers() - playdate.inputHandlers.push(self.inputHandlers) -end - -function Kani:removeInputHandlers() - playdate.inputHandlers.pop() -end - -- move that crab! function Kani:runReady() + -- input handling + if playdate.buttonIsPressed(playdate.kButtonDown) then + self.vector.dy = 3 + elseif playdate.buttonIsPressed(playdate.kButtonUp) then + self.vector.dy = -3 + else + self.vector.dy = 0 + end + + if playdate.buttonIsPressed(playdate.kButtonLeft) then + self.vector.dx = -3 + elseif playdate.buttonIsPressed(playdate.kButtonRight) then + self.vector.dx = 3 + else + self.vector.dx = 0 + end + + local change = select(1, playdate.getCrankChange()) + self:chargeReserve(change) + + if playdate.buttonJustPressed(playdate.kButtonA) then + self.firingTimer = playdate.timer.keyRepeatTimerWithDelay(self.weaponChargeSpeed / 2, + self.weaponChargeSpeed, + self.chargeShot, self) + end + if playdate.buttonJustReleased(playdate.kButtonA) then + self:fire() + end + + if playdate.buttonIsPressed(playdate.kButtonB) then + print("Triggering entrance to DEFEND mode") + self.fsm:changeState("DEFEND") + end + + -- collision handling local collisions = Kani.super.runReady(self) for i=0, #collisions, 1 do -- TODO: handle player-triggered collisions end end + +function Kani:onDefend() + self.armor = 5 +end + +function Kani:onDefendExit() + self.armor = 0 +end + +function Kani:runDefend() + if not playdate.buttonIsPressed(playdate.kButtonB) then + self.fsm:changeState("READY") + end +end diff --git a/src/main.lua b/src/main.lua index 0b478ce..6a5e5aa 100644 --- a/src/main.lua +++ b/src/main.lua @@ -23,7 +23,6 @@ function setup() ui = UI() player = Kani(ui) player:moveTo(16, 120) - player:addInputHandlers() player:add() ui:add() currentWave = newWave() @@ -31,13 +30,6 @@ function setup() makeWalls() drawBackground() - - -- debug, TODO remove this code - playdate.inputHandlers.push({ - BButtonUp = function() - print(gfx.sprite.spriteCount()) - end - }) end function makeWalls() diff --git a/src/statemachine.lua b/src/statemachine.lua index 91a743e..b12a553 100644 --- a/src/statemachine.lua +++ b/src/statemachine.lua @@ -10,24 +10,26 @@ end -- params is a table of parameters to always pass to the functions -- stateTable is a table of initial states. Entries should be of the form: --- name = {main=mainFunc, transition=transFunc} +-- name = {main=mainFunc, incoming=inFunc, outgoing=outFunc} -- where mainFunc and transFunc are functions that should be executed in relation to the state. -- the State Machine will call mainFunc on execute() --- the State Machine will call transFunc when the state is entered +-- the State Machine will call inFunc when the state is entered +-- the State Machine will call outFunc when the state in exited function StateMachine:init(params, stateTable) self.currentState = nil self.parameters = params or {} self.states = stateTable or {} end -function StateMachine:addState(name, mainFunc, transFunc) - self.states[name] = {main=mainFunc, transition=transFunc} +function StateMachine:addState(name, mainFunc, inFunc, outFunc) + self.states[name] = {main=mainFunc, incoming=inFunc, outgoing=outFunc} end -- execute the main function for the current state. -- returns true if the function was executed, false otherwise function StateMachine:execute() - if self.currentState == nil or not self.states[self.currentState] then + if self.currentState == nil or not self.states[self.currentState] + or not self.states[self.currentState].main then return false end @@ -37,11 +39,22 @@ end -- change to the specified state. returns true if the state change is successful function StateMachine:changeState(state) - if not self.states[state] then return false end + if not self.states[state] then + print("WARN (StateMachine): tried to enter nonexistent state " .. state) + return false + end + -- run the exit function for the previous state + if self.currentState and self.states[self.currentState].outgoing then + self.states[self.currentState].outgoing(table.unpack(self.parameters)) + end + + -- change state self.currentState = state - if self.states[state].transition then - self.states[state].transition(table.unpack(self.parameters)) + + -- run the entrance function for the new state + if self.states[state].incoming then + self.states[state].incoming(table.unpack(self.parameters)) end return true end