Fix defend state, add animation tables (not used quite yet), and add onExit functions to the state machine.

This commit is contained in:
Anna Rose 2023-10-04 01:19:05 -04:00
parent 9a26eb4b3a
commit feb7541944
4 changed files with 99 additions and 47 deletions

View File

@ -16,6 +16,10 @@ function Entity:init(img, health, armor)
self.armor = armor or 0 self.armor = armor or 0
self.introAnimator = nil 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 -- movement direction, every update() the entity will move along this vector and return
-- collision data to the subclass -- collision data to the subclass
self.vector = geom.vector2D.new(0, 0) self.vector = geom.vector2D.new(0, 0)
@ -32,15 +36,15 @@ function Entity:init(img, health, armor)
local states = { local states = {
["INIT"] = { ["INIT"] = {
main=self.runInit, main=self.runInit,
transition=self.onInit incoming=self.onInit
}, },
["INTRO"] = { ["INTRO"] = {
main=self.runIntro, main=self.runIntro,
transition=self.onIntro, incoming=self.onIntro,
}, },
["READY"] = { ["READY"] = {
main=self.runReady, main=self.runReady,
transition=self.onReady, incoming=self.onReady,
}, },
} }
self.fsm = StateMachine.new({self}, states) self.fsm = StateMachine.new({self}, states)
@ -68,6 +72,26 @@ end
function Entity:update() function Entity:update()
-- update state machine -- update state machine
self.fsm:execute() 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 end
-- State machine-controlled functions -- State machine-controlled functions

View File

@ -69,25 +69,7 @@ function Kani:init(ui)
-- the UI gets passed in to Kani so that we can update pieces of it easily. -- the UI gets passed in to Kani so that we can update pieces of it easily.
self.ui = ui self.ui = ui
self.inputHandlers = { self.fsm:addState("DEFEND", self.runDefend, self.onDefend, self.onDefendExit)
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")
end end
function Kani:chargeReserve(change) function Kani:chargeReserve(change)
@ -138,24 +120,65 @@ end
function Kani:damage(amount) function Kani:damage(amount)
Kani.super.damage(self, amount) Kani.super.damage(self, amount)
self.ui.healthMeter:setValue(self.health) self.ui.healthMeter:setValue(self.health)
print("Kani health now at " .. self.health) -- debug
if self.health == 0 then if self.health == 0 then
-- TODO: end game here -- TODO: end game here
end end
end end
function Kani:addInputHandlers()
playdate.inputHandlers.push(self.inputHandlers)
end
function Kani:removeInputHandlers()
playdate.inputHandlers.pop()
end
-- move that crab! -- move that crab!
function Kani:runReady() 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) local collisions = Kani.super.runReady(self)
for i=0, #collisions, 1 do for i=0, #collisions, 1 do
-- TODO: handle player-triggered collisions -- TODO: handle player-triggered collisions
end end
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

View File

@ -23,7 +23,6 @@ function setup()
ui = UI() ui = UI()
player = Kani(ui) player = Kani(ui)
player:moveTo(16, 120) player:moveTo(16, 120)
player:addInputHandlers()
player:add() player:add()
ui:add() ui:add()
currentWave = newWave() currentWave = newWave()
@ -31,13 +30,6 @@ function setup()
makeWalls() makeWalls()
drawBackground() drawBackground()
-- debug, TODO remove this code
playdate.inputHandlers.push({
BButtonUp = function()
print(gfx.sprite.spriteCount())
end
})
end end
function makeWalls() function makeWalls()

View File

@ -10,24 +10,26 @@ end
-- params is a table of parameters to always pass to the functions -- 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: -- 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. -- 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 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) function StateMachine:init(params, stateTable)
self.currentState = nil self.currentState = nil
self.parameters = params or {} self.parameters = params or {}
self.states = stateTable or {} self.states = stateTable or {}
end end
function StateMachine:addState(name, mainFunc, transFunc) function StateMachine:addState(name, mainFunc, inFunc, outFunc)
self.states[name] = {main=mainFunc, transition=transFunc} self.states[name] = {main=mainFunc, incoming=inFunc, outgoing=outFunc}
end end
-- execute the main function for the current state. -- execute the main function for the current state.
-- returns true if the function was executed, false otherwise -- returns true if the function was executed, false otherwise
function StateMachine:execute() 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 return false
end end
@ -37,11 +39,22 @@ end
-- change to the specified state. returns true if the state change is successful -- change to the specified state. returns true if the state change is successful
function StateMachine:changeState(state) 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 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 end
return true return true
end end