From cffb946fc71df4a04a0c3b49c8b3da7c26c8f50a Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Tue, 18 Mar 2025 16:32:24 -0400 Subject: [PATCH] Finally in a halfway-decent state with animation --- assets/images/ConveyerDownRight.png | Bin 0 -> 413 bytes assets/images/Ramp.png | Bin 0 -> 480 bytes assets/images/Teleporter.png | Bin 0 -> 531 bytes assets/images/TeleporterInactive.png | Bin 0 -> 518 bytes generated/all-systems.lua | 2 +- generated/assets.lua | 8 ++ main.lua | 167 ++++++++++++++++++--------- systems/ballEffects.lua | 159 +++++++++++++++++++++++++ systems/draw.lua | 41 ++++++- systems/input.lua | 2 +- systems/rounds.lua | 107 ----------------- 11 files changed, 319 insertions(+), 167 deletions(-) create mode 100644 assets/images/ConveyerDownRight.png create mode 100644 assets/images/Ramp.png create mode 100644 assets/images/Teleporter.png create mode 100644 assets/images/TeleporterInactive.png create mode 100644 systems/ballEffects.lua delete mode 100644 systems/rounds.lua diff --git a/assets/images/ConveyerDownRight.png b/assets/images/ConveyerDownRight.png new file mode 100644 index 0000000000000000000000000000000000000000..a0cb785943205adaa93622ce7fd3b8253a6f2aea GIT binary patch literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^J|N7&1|*M957Y)yjKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4+>~P&* zdDGl#BcS*usc-MomgM>al}B8gwq~E`{ydNxoz;m~x8J?Kzdx~oaoXJtFK&#@9HRXqQ<@>i)PHH%t74hra6$5`E177cY^LRe1Fkar^2UOj_xGeFcZ?Z%} rLuN$LWeGNk1gU9vK#q!7xtn1|a7R6tr|?gpXBj+Q{an^LB{Ts5{Kb%F literal 0 HcmV?d00001 diff --git a/assets/images/Ramp.png b/assets/images/Ramp.png new file mode 100644 index 0000000000000000000000000000000000000000..f382536740ce08546af53533c4c1f80d811eecd1 GIT binary patch literal 480 zcmeAS@N?(olHy`uVBq!ia0vp^J|N7&1|*M957Y)yjKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4#c)oZ>&Y!V4F z-dU?0Lp>@9TV=&!SIh3)wI5`S#hOjRsyqeySb@SEB_oF;Oep{!1H+D(ug!4{*z#wGs MboFyt=akR{0Aq5#hX4Qo literal 0 HcmV?d00001 diff --git a/assets/images/Teleporter.png b/assets/images/Teleporter.png new file mode 100644 index 0000000000000000000000000000000000000000..3bbae164a72b369aca472b4755c2f788059e0c29 GIT binary patch literal 531 zcmeAS@N?(olHy`uVBq!ia0vp^J|N7&1|*M957Y)yjKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4Z)Gut6o2&V`A3%|05+1 zZW0hYyL$V*`VZCh|8gsx-nZMk>0WSe1*N~~M*mE_uzSpR8-YCVZOvv;?<&J<0( z^tSHyl?IK=J~KRwa`f81?i16I3^&rbeR@&P8pE?KohpZ)CEfV`NjT}oHCwII9z4RH zZN6*0*Ot3VZYY0$YTbI9|C2J+d{iXO%nrK+_j(3COP!f>$YoOE@uY}e#Xz?$|LR`P zOFtxWY(w(*4Qt=GyHPF#F<@%y{dzCerL+_BZUJvmBVKDDT~5M)}JUR&?A z#hdo0gN$-s`BU!tweLTwzaRMYdHvJN{|qLk%#i!`X>0WR+Fw_{t_5pYA}97X^`hLi z@1Lj4n=aznHn%(RX7<^=Pxp!{oiyN)o>_QLUTqSOveF{YFZ^3IYbTX^aP9$yI)kUH KpUXO@geCw;gxGZe literal 0 HcmV?d00001 diff --git a/assets/images/TeleporterInactive.png b/assets/images/TeleporterInactive.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7b51fb77d1554deef751e7c453ab77b74d5ff6 GIT binary patch literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^J|N7&1|*M957Y)yjKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4Z?($jDy5$FOh zPZ!6Kid%2*929Ia5NSy)dDAiNMJKmz;N*M7Cyz{@z_M?zmRH%gzncRSB0D;sU0wZE zT;}-4y+w|zMF9x zPvV)~ow)XUljh@~HrMLNxAW8z-6kbEzr4KL{pmgVx2antH4=G}Z`|PVt`1+P-g|BL zqUY1s*}qK{N>hNPA={N@i(L!=4_D@{c8hqleE{>^@VHy zO4q-Zo3U@-``y_wy()*lY}o%jZ|(Za{YIgOdQ=WSdzH9!@0u;`U-!+w^mh`_(dUyQ xO4i?uo)?{{BH5OxY<%wXVW6^(jtrr{3@@FkPF2M}u?Ge;gQu&X%Q~loCICMl)b;=X literal 0 HcmV?d00001 diff --git a/generated/all-systems.lua b/generated/all-systems.lua index 2e2451f..7fb93ac 100644 --- a/generated/all-systems.lua +++ b/generated/all-systems.lua @@ -1,3 +1,4 @@ +require("../systems/ballEffects") require("../systems/camera-pan") require("../systems/collision-detection") require("../systems/collision-resolution") @@ -5,5 +6,4 @@ require("../systems/decay") require("../systems/draw") require("../systems/gravity") require("../systems/input") -require("../systems/rounds") require("../systems/velocity") diff --git a/generated/assets.lua b/generated/assets.lua index 2be4bac..c6051b0 100644 --- a/generated/assets.lua +++ b/generated/assets.lua @@ -5,6 +5,10 @@ ---@type love.Texture Ball = love.graphics.newImage("assets/images/Ball.png") +-- luacheck: ignore +---@type love.Texture +ConveyerDownRight = love.graphics.newImage("assets/images/ConveyerDownRight.png") + -- luacheck: ignore ---@type love.Texture Flag = love.graphics.newImage("assets/images/Flag.png") @@ -33,6 +37,10 @@ GolferUp = love.graphics.newImage("assets/images/GolferUp.png") ---@type love.Texture Grass = love.graphics.newImage("assets/images/Grass.png") +-- luacheck: ignore +---@type love.Texture +Ramp = love.graphics.newImage("assets/images/Ramp.png") + -- luacheck: ignore ---@type love.Texture SandTrap = love.graphics.newImage("assets/images/SandTrap.png") diff --git a/main.lua b/main.lua index 154dc39..dbc859b 100644 --- a/main.lua +++ b/main.lua @@ -133,58 +133,7 @@ Scenarios = { previous = current end end - return yxGrid - end, - firstLevel = function() - local yxGrid = Scenarios.emptyGrid() - replaceAt(yxGrid, 4, 7, { - drawAsSprite = Flag, - z = 1, - effectsToApply = { - endOfRound = T.marker, - movement = { x = 0, y = 0 }, - }, - }) - replaceAt(yxGrid, 2, 2, { - drawAsSprite = SmallSandTrap, - z = 1, - spriteAfterEffect = SmallSandTrapActivated, - effectsToApply = { - slowsBy = { x = 1, y = 1 }, - movement = { x = 0, y = 0 }, - }, - }) - World:addEntity({ - canBeCollidedBy = CursorMask, - pickedUpOnClick = T.marker, - size = squareSize, - drawAsSprite = GolferRight, - z = 1, - spriteAfterEffect = GolferRightActivated, - effectsToApply = { - movement = { x = 99, y = 0 }, - }, - position = { x = 20, y = height - 80 }, - }) - World:addEntity({ - canBeCollidedBy = CursorMask, - pickedUpOnClick = T.marker, - size = squareSize, - drawAsSprite = GolferDown, - z = 1, - spriteAfterEffect = GolferRightActivated, - effectsToApply = { - movement = { x = 0, y = 99 }, - }, - position = { x = 20 + squareSide, y = height - 80 }, - }) - World:addEntity({ - ballEffects = {}, - drawAsSprite = Ball, - gridPosition = { x = 1, y = 1 }, - z = 2, - }) local startButtonX, startButtonY = StartButton:getDimensions() World:addEntity({ canBeCollidedBy = CursorMask, @@ -199,6 +148,101 @@ Scenarios = { y = height - 50, }, }) + return yxGrid + end, + + firstLevel = function() + local yxGrid = Scenarios.emptyGrid() + local clickables = 0 + ---@param entity table | { effectsToApply: BallEffects } + local function addClickable(entity) + entity.canBeCollidedBy = bit.bor(CursorMask, entity.canBeCollidedBy or 0) + entity.pickedUpOnClick = T.marker + entity.size = squareSize + entity.z = 1 + entity.position = { x = 20 + (clickables * squareSide), y = height - 80 } + + clickables = clickables + 1 + + return World:addEntity(entity) + end + replaceAt(yxGrid, 4, 7, { + drawAsSprite = Flag, + z = 1, + effectsToApply = { + endOfRound = T.marker, + movement = { x = 0, y = 0 }, + }, + }) + replaceAt(yxGrid, 2, 5, { + drawAsSprite = SmallSandTrap, + z = 1, + spriteAfterEffect = SmallSandTrapActivated, + effectsToApply = { + slowsTo = { x = 1, y = 1 }, + --movement = { x = 0, y = 0 }, + }, + }) + replaceAt(yxGrid, 3, 1, { + drawAsSprite = SmallSandTrap, + z = 1, + spriteAfterEffect = SmallSandTrapActivated, + effectsToApply = { + slowsTo = { x = 1, y = 1 }, + --movement = { x = 0, y = 0 }, + }, + }) + addClickable({ + drawAsSprite = GolferRight, + spriteAfterEffect = GolferRightActivated, + effectsToApply = { + movement = { x = 8, y = 0 }, + } + }) + addClickable({ + drawAsSprite = ConveyerDownRight, + spriteAfterEffect = ConveyerDownRight, + effectsToApply = { + movement = { x = 1, y = 1 }, + } + }) + addClickable({ + drawAsSprite = Ramp, + spriteAfterEffect = Ramp, + effectsToApply = { + fliesFor = 1, + slowsBy = { x = 3, y = 3 }, + } + }) + addClickable({ + drawAsSprite = GolferRight, + spriteAfterEffect = GolferRightActivated, + effectsToApply = { + movement = { x = 8, y = 0 }, + }, + }) + addClickable({ + drawAsSprite = GolferDown, + spriteAfterEffect = GolferRightActivated, + effectsToApply = { + movement = { x = 0, y = 6 }, + }, + }) + addClickable({ + drawAsSprite = SmallSandTrap, + spriteAfterEffect = SmallSandTrapActivated, + effectsToApply = { + slowsTo = { x = 1, y = 1 }, + --movement = { x = 0, y = 0 }, + }, + }) + World:addEntity({ + ballEffects = {}, + drawAsSprite = Ball, + gridPosition = { x = 1, y = 1 }, + endAnimating = T.marker, + z = 2, + }) end, } @@ -210,16 +254,29 @@ function love.load() love.graphics.setFont(EtBt7001Z0xa(32)) end +local bail = false + +World:setSystemIndex(LiveForNFrames, 1) + function love.draw() local dt = love.timer.getDelta() + if love.keyboard.isDown("r") then World:clearEntities() currentLevel() + bail = false end - World:setSystemIndex(LiveForNFrames, 1) - World:update(dt, function(_, system) - return not system.isDrawSystem - end) + + if love.keyboard.isDown("f") then + bail = not bail + end + + if not bail then + World:update(dt, function(_, system) + return not system.isDrawSystem + end) + end + World:update(dt, function(_, system) return system.isDrawSystem end) diff --git a/systems/ballEffects.lua b/systems/ballEffects.lua new file mode 100644 index 0000000..20587d7 --- /dev/null +++ b/systems/ballEffects.lua @@ -0,0 +1,159 @@ +local gridElements = filteredSystem("gridElements", { gridPosition = T.XyPair, effectsToApply = T.AnyComponent }) + +filteredSystem("timers", { timerSec = T.number, callback = T.SelfFunction }, function(e, dt, system) + e.timerSec = e.timerSec - dt + if e.timerSec < 0 then + e:callback() + system.world:removeEntity(e) + end +end) + +function math.sign(n) + return n > 0 and 1 or n < 0 and -1 or 0 +end + +RoundSystem = filteredSystem("rounds", { roundState = T.str }, function(e) + -- print("roundState: " .. e.roundState) +end) + +---@alias RoundState "active" | "animating" | "end" + +---@param state RoundState +function RoundSystem:remove(state) + for _, round in pairs(self.entities) do + if round.roundState == state then + self.world:removeEntity(round) + end + end +end + +---@param state RoundState +function RoundSystem:hasState(state) + for _, round in pairs(self.entities) do + if round.roundState == state then + return true + end + end + return false +end + +---@param n number +---@param nToSlowBy number +---@return number slowedValue +local function slowBy(n, nToSlowBy) + if math.abs(n) < math.abs(nToSlowBy) then + return 0 + end + return n - (math.sign(n) * nToSlowBy) +end +--- +---@param n number +---@param nToSlowTo number +---@return number slowedValue +local function slowTo(n, nToSlowTo) + if math.abs(n) < math.abs(nToSlowTo) then + return 0 + end + return math.sign(n) * nToSlowTo +end + +---@class BallEffects +---@field slowsTo nil | XyPair +---@field slowsBy nil | XyPair +---@field movement nil | XyPair +---@field fliesFor nil | number + +---@type BallEffects +local BallEffects = {} + +local activeBallEffects = filteredSystem( + "activeBallEffects", + { ballEffects = BallEffects, gridPosition = T.XyPair }, + function(e, _, system) + local gridPosition, effects = e.gridPosition, e.ballEffects + + if effects.fliesFor == nil then + -- Search for new effects from the current tile + for _, gridElement in pairs(gridElements.entities) do + if gridPosition.x == gridElement.gridPosition.x and gridPosition.y == gridElement.gridPosition.y then + -- More direct-mutation-y than I'd like, + -- but offers a simple way to overwrite existing effects. + -- We're "setting InRelations" :D + for key, value in pairs(gridElement.effectsToApply) do + effects[key] = value + end + if gridElement.spriteAfterEffect then + gridElement.drawAsSprite = gridElement.spriteAfterEffect + gridElement.spriteAfterEffect = nil + end + gridElement.effectsToApply = nil + system.world:addEntity(gridElement) + end + end + else + effects.fliesFor = effects.fliesFor - 1 + if effects.fliesFor == 0 then + effects.fliesFor = nil + end + end + + -------------------------------------------------------- + -- Apply any effects currently connected to this ball -- + -------------------------------------------------------- + + if effects.movement ~= nil then + if effects.slowsBy ~= nil then + effects.movement.x = slowBy(effects.movement.x, effects.slowsBy.x) + effects.movement.y = slowBy(effects.movement.y, effects.slowsBy.y) + effects.slowsBy = nil + end + if effects.slowsTo ~= nil then + effects.movement.x = slowTo(effects.movement.x, effects.slowsTo.x) + effects.movement.y = slowTo(effects.movement.y, effects.slowsTo.y) + effects.slowsTo = nil + end + + if effects.movement.x == 0 and effects.movement.y == 0 then + effects.movement = nil + else + gridPosition.x = gridPosition.x + math.sign(effects.movement.x) + gridPosition.y = gridPosition.y + math.sign(effects.movement.y) + + effects.movement.x = effects.movement.x - math.sign(effects.movement.x) + effects.movement.y = effects.movement.y - math.sign(effects.movement.y) + end + end + + if effects.endOfRound ~= nil then + effects.endOfRound = nil + system.world:addEntity({ roundState = "end" }) + system.world:removeEntity(e) + -- print("End of round") + return + end + + for _, _ in pairs(effects) do + -- Return if there are any effects left + -- print("Setting roundState to animating...") + system.world:addEntity({ roundState = "animating" }) + -- system.world:removeEntity(e) + return + end + + -- print("End of round") + system.world:addEntity({ roundState = "end" }) + system.world:removeEntity(e) + end +) + +function activeBallEffects:preProcess(dt) + if RoundSystem:hasState("animating") then + return tiny.SKIP_PROCESS + end + + if RoundSystem:hasState("active") then + return + end + + return tiny.SKIP_PROCESS +end diff --git a/systems/draw.lua b/systems/draw.lua index d621b48..4861251 100644 --- a/systems/draw.lua +++ b/systems/draw.lua @@ -19,8 +19,43 @@ local function drawSystem(name, shape, process) return system end -filteredSystem("mapGridPositionToRealPosition", { gridPosition = T.XyPair }, function(e, _, system) - e.position = PositionAtGridXy(e.gridPosition.x, e.gridPosition.y) +local mapGridPositions = filteredSystem("mapGridPositionToRealPosition", { gridPosition = T.XyPair }, function(e, dt, system) + local target = PositionAtGridXy(e.gridPosition.x, e.gridPosition.y) + if e.position == nil then + e.position = target + return + end + local speed = 100 + if e.ballEffects and e.ballEffects.movement then + local movement = e.ballEffects.movement + local longTarget = PositionAtGridXy(e.gridPosition.x + movement.x, e.gridPosition.y + movement.y) + local xDiff = e.position.x - longTarget.x + local yDiff = e.position.y - longTarget.y + speed = 30 + math.sqrt((xDiff * xDiff) + (yDiff * yDiff)) + end + local stops = 0 + + local xDiff = target.x - e.position.x + local xChange = dt * speed * math.sign(xDiff) + if math.abs(xDiff) <= math.abs(xChange) then + e.position.x = target.x + stops = stops + 1 + else + e.position.x = e.position.x + xChange + end + + local yDiff = target.y - e.position.y + local yChange = dt * speed * math.sign(yDiff) + if math.abs(yDiff) <= math.abs(yChange) then + e.position.y = target.y + stops = stops + 1 + else + e.position.y = e.position.y + yChange + end + + if e.endAnimating and stops == 2 then + RoundSystem:remove("animating") + end system.world:addEntity(e) end) @@ -71,6 +106,6 @@ drawSystem( drawSystem("drawRectangles", { position = T.XyPair, drawAsRectangle = { size = T.XyPair } }, function(e, _, _) gfx.setColor(1, 1, 1, 0.5) - local mode = e.drawAsRectangle.mode or (e.highlighted and "fill" or "line") + local mode = e.drawAsRectangle.mode or "line" -- (e.highlighted and "fill" or "line") gfx.rectangle(mode, e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y) end) diff --git a/systems/input.lua b/systems/input.lua index cde440d..93a18c1 100644 --- a/systems/input.lua +++ b/systems/input.lua @@ -108,7 +108,7 @@ local menuSystem = filteredSystem( pressed = tryShiftMenu(e.below, { "down", "s" }) or pressed pressed = tryShiftMenu(e.above, { "up", "w" }) or pressed - if isDown("return") then + if isDown("return") or isDown("e") then pressed = true system.world:addEntity({ roundState = "active", diff --git a/systems/rounds.lua b/systems/rounds.lua deleted file mode 100644 index b0373c8..0000000 --- a/systems/rounds.lua +++ /dev/null @@ -1,107 +0,0 @@ -local gridElements = filteredSystem("gridElements", { gridPosition = T.XyPair, effectsToApply = T.AnyComponent }) - -filteredSystem("timers", { timerSec = T.number, callback = T.SelfFunction }, function(e, dt, system) - e.timerSec = e.timerSec - dt - if e.timerSec < 0 then - e:callback() - system.world:removeEntity(e) - end -end) - -local function sign(n) - return n > 0 and 1 or n < 0 and -1 or 0 -end - -local roundSystem = filteredSystem("rounds", { roundState = T.str }, function(e) - print("roundState: " .. e.roundState) -end) - -function roundSystem:preProcess() - if #self.entities ~= 0 then - print("#states: " .. #self.entities) - end -end - -local activeBallEffects = filteredSystem( - "activeBallEffects", - { ballEffects = T.AnyComponent, gridPosition = T.XyPair }, - function(e, dt, system) - local gridPosition, effects = e.gridPosition, e.ballEffects - - -- Search for new effects from the current tile - for _, gridElement in pairs(gridElements.entities) do - if gridPosition.x == gridElement.gridPosition.x and gridPosition.y == gridElement.gridPosition.y then - -- More direct-mutation-y than I'd like, - -- but offers a simple way to overwrite existing effects. - -- We're "setting InRelations" :D - for key, value in pairs(gridElement.effectsToApply) do - effects[key] = value - end - if gridElement.spriteAfterEffect then - gridElement.drawAsSprite = gridElement.spriteAfterEffect - gridElement.spriteAfterEffect = nil - end - gridElement.effectsToApply = nil - system.world:addEntity(gridElement) - end - end - - -- Apply any effects currently connected to this ball - if effects.movement ~= nil then - gridPosition.x = gridPosition.x + sign(effects.movement.x) - gridPosition.y = gridPosition.y + sign(effects.movement.y) - - effects.movement.x = effects.movement.x - sign(effects.movement.x) - effects.movement.y = effects.movement.y - sign(effects.movement.y) - - if effects.movement.x == 0 and effects.movement.y == 0 then - effects.movement = nil - end - end - - if effects.endOfRound ~= nil then - effects.endOfRound = nil - system.world:addEntity({ roundState = "end" }) - system.world:removeEntity(e) - print("End of round") - return - end - - for _, _ in pairs(effects) do - -- Return if there are any effects left - print("Setting roundState to animating...") - --system.world:addEntity({ roundState = "animating" }) - --system.world:removeEntity(e) - return - end - - print("End of round") - system.world:addEntity({ roundState = "end" }) - end -) - -local stepTimeSec = 0.1 -local stepTimer = stepTimeSec - -function activeBallEffects:preProcess(dt) - stepTimer = stepTimer - dt - if stepTimer <= 0 then - stepTimer = stepTimeSec - else - return tiny.SKIP_PROCESS - end - - -- for _, round in pairs(roundSystem.entities) do - -- if round.roundState == "animating" then - -- return tiny.SKIP_PROCESS - -- end - -- end - - for _, round in pairs(roundSystem.entities) do - if round.roundState == "active" then - return - end - end - - return tiny.SKIP_PROCESS -end