From 8162c7f723f6a7fe6e46a89b50ec46de0e33c5a0 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Wed, 5 Mar 2025 00:10:41 -0500 Subject: [PATCH] Turn spawners into entities driven by the spawnerSystem. --- src/effects.lua | 18 ++++++ src/ingredients/cheese.lua | 24 ------- src/ingredients/ingredients.lua | 28 +++------ src/ingredients/lettuce.lua | 24 ------- src/ingredients/mushroom.lua | 24 ------- src/ingredients/tomato.lua | 18 ------ src/main.lua | 31 ++------- src/systems/camera-pan.lua | 13 +++- src/systems/filter-types.lua | 6 +- src/systems/spawner.lua | 108 ++++++++++++++++++++++++++++++++ 10 files changed, 156 insertions(+), 138 deletions(-) delete mode 100644 src/ingredients/cheese.lua delete mode 100644 src/ingredients/lettuce.lua delete mode 100644 src/ingredients/mushroom.lua delete mode 100644 src/ingredients/tomato.lua create mode 100644 src/systems/spawner.lua diff --git a/src/effects.lua b/src/effects.lua index 70fd887..74204be 100644 --- a/src/effects.lua +++ b/src/effects.lua @@ -1,10 +1,28 @@ +effectSystem = filteredSystem({ canReceive }) + Effects = {} ---@param entity { canBeBounced: CanBeBounced } function Effects.makeBouncier(entity) entity.canBeBounced.mult = entity.canBeBounced.mult * 1.5 end + ---@param entity { mass: number } function Effects.makeFloatier(entity) entity.mass = entity.mass * 0.75 end + +--- Cause the given ingredient to spawn more frequently +function Effects.moreOfIngredient(ingredient, spawner) + +end + +--- Each of the given ingredient will have a *mult score from now on. +function Effects.multiplyIngredientValue(ingredient, mult, spawner) + +end + +--- Each ingredient *multiplies* score in some way, in addition to adding. +function Effects.multiplicativeIngredientValue(ingredient, addedMult, spawner) + +end diff --git a/src/ingredients/cheese.lua b/src/ingredients/cheese.lua deleted file mode 100644 index 370ba49..0000000 --- a/src/ingredients/cheese.lua +++ /dev/null @@ -1,24 +0,0 @@ -Cheese = {} - -local sizeX, sizeY = CheeseSprite:getSize() -local size = { x = sizeX, y = sizeY } -local expireWhenOffScreenBy = { x = 2000, y = 480 } - -local canBounce = { - flat = { x = 50, y = 390 }, - mult = { x = 0.7, y = -0.5 }, -} - -function Cheese.initialize(o, x, y) - o.score = 5 - o.canBeCollidedBy = 1 - o.expireAfterCollision = true - o.size = size - o.position = { x = x, y = y } - o.drawAsSprite = CheeseSprite - o.collectable = o.drawAsSprite - o.expireWhenOffScreenBy = expireWhenOffScreenBy - o.canBounce = canBounce - newestBooster = o - return newestBooster -end diff --git a/src/ingredients/ingredients.lua b/src/ingredients/ingredients.lua index 542f598..c0f6476 100644 --- a/src/ingredients/ingredients.lua +++ b/src/ingredients/ingredients.lua @@ -1,8 +1,3 @@ -import("ingredients/cheese.lua") -import("ingredients/lettuce.lua") -import("ingredients/mushroom.lua") -import("ingredients/tomato.lua") - ---@class Ingredients = {} @@ -16,7 +11,7 @@ end local newestIngredient = {} function Ingredients.newestIngredient() - assert(newestIngredient.position ~= nil, "All ingredients are implicitly required to have a position component.") + -- assert(newestIngredient.position ~= nil, "All ingredients are implicitly required to have a position component.") return newestIngredient end @@ -24,7 +19,7 @@ function Ingredients.cacheSize() return maxCache end -function Ingredients.getNext(x, y) +function Ingredients.nextInCache() local index = _ingredientCacheIndex _ingredientCacheIndex = _ingredientCacheIndex + 1 if _ingredientCacheIndex >= maxCache then @@ -35,16 +30,13 @@ function Ingredients.getNext(x, y) for key in pairs(o) do o[key] = nil end - newestIngredient = o - local odds = math.random() - if odds > 0.3 then - return Lettuce.initialize(o, x, y) - elseif odds > 0.2 then - return Tomato.initialize(o, x, y) - elseif odds > 0.05 then - return Mushroom.initialize(o, x, 218 + math.random(1, 5)) - else - return Cheese.initialize(o, x, y) - end + newestIngredient = o + return o +end + +function Ingredients.getFirst() + local ret = Ingredients.nextInCache() + ret.position = { x = 0, y = 0 } + return ret end diff --git a/src/ingredients/lettuce.lua b/src/ingredients/lettuce.lua deleted file mode 100644 index 521d2bf..0000000 --- a/src/ingredients/lettuce.lua +++ /dev/null @@ -1,24 +0,0 @@ -Lettuce = {} - -local sizeX, sizeY = LettuceSprite:getSize() -local size = { x = sizeX, y = sizeY / 2 } -local expireWhenOffScreenBy = { x = 2000, y = 480 } - -local canBounce = { - flat = { x = 22, y = 190 }, - mult = { x = 1, y = -0.5 }, -} - -function Lettuce.initialize(o, x, y) - o.score = 1 - o.canBeCollidedBy = 1 - o.expireAfterCollision = true - o.size = size - o.position = { x = x, y = y } - o.drawAsSprite = LettuceSprite - o.collectable = o.drawAsSprite - o.expireWhenOffScreenBy = expireWhenOffScreenBy - o.canBounce = canBounce - newestBooster = o - return newestBooster -end diff --git a/src/ingredients/mushroom.lua b/src/ingredients/mushroom.lua deleted file mode 100644 index 08ed596..0000000 --- a/src/ingredients/mushroom.lua +++ /dev/null @@ -1,24 +0,0 @@ -Mushroom = {} - -local sizeX, sizeY = MushroomSprite:getSize() -local size = { x = sizeX, y = sizeY } -local expireWhenOffScreenBy = { x = 2000, y = 480 } - -local canBounce = { - flat = { x = 0, y = 290 }, - mult = { x = 1, y = -1 }, -} - -function Mushroom.initialize(o, x, y) - o.score = 5 - o.canBeCollidedBy = 1 - o.expireAfterCollision = true - o.size = size - o.position = { x = x, y = y } - o.drawAsSprite = MushroomSprite - o.collectable = o.drawAsSprite - o.expireWhenOffScreenBy = expireWhenOffScreenBy - o.canBounce = canBounce - newestBooster = o - return newestBooster -end diff --git a/src/ingredients/tomato.lua b/src/ingredients/tomato.lua deleted file mode 100644 index 209d31f..0000000 --- a/src/ingredients/tomato.lua +++ /dev/null @@ -1,18 +0,0 @@ -Tomato = {} - -local sizeX, sizeY = TomatoSprite:getSize() -local size = { x = sizeX, y = sizeY } -local expireWhenOffScreenBy = { x = 2000, y = 480 } - -function Tomato.initialize(o, x, y) - o.score = 15 - o.canBeCollidedBy = 1 - o.expireAfterCollision = true - o.size = size - o.position = { x = x, y = y } - o.drawAsSprite = TomatoSprite - o.collectable = o.drawAsSprite - o.expireWhenOffScreenBy = expireWhenOffScreenBy - newestBooster = o - return newestBooster -end diff --git a/src/main.lua b/src/main.lua index bd5beb4..ee8a8a4 100644 --- a/src/main.lua +++ b/src/main.lua @@ -17,6 +17,7 @@ import("systems/collision-resolution.lua") import("systems/draw.lua") import("systems/gravity.lua") import("systems/rounds.lua") +import("systems/spawner.lua") import("systems/velocity.lua") import("ingredients/ingredients.lua") import("cart.lua") @@ -41,14 +42,6 @@ floor = { sparkOnCollision = true, } -Camera = { - --- The upper-left corner of the Camera is represented by this `pan` value - pan = { - x = 0, - y = 0, - }, -} - Score = { points = 0, } @@ -67,6 +60,7 @@ world = tiny.world( fallSystem, velocitySystem, roundSystem, + spawnerSystem, collectedEntities, collidingEntities, collisionResolution, @@ -77,16 +71,9 @@ world = tiny.world( floor, cart ) +addAllSpawners(world) -local ingredientsEveryX = 60 - -local function init() - for i = 1, Ingredients.cacheSize() do - world:addEntity(Ingredients.getNext(i * ingredientsEveryX + (math.random(0, 20)), math.random(50, 200))) - end -end - -init() +world:addEntity(Ingredients.getFirst()) -- TODO: Re-enable when cart stops playdate.setAutoLockDisabled(true) @@ -97,15 +84,7 @@ function playdate.update() gfx.clear(gfx.kColorWhite) playdate.drawFPS(5, 5) - local newestX = Ingredients.newestIngredient().position.x - local panX = Camera.pan.x - local rightEdge = panX + 400 - local offset = 600 - while newestX < rightEdge + 100 do - newestX = newestX + ingredientsEveryX - world:addEntity(Ingredients.getNext(newestX, math.random(-500, 150))) - end - floor.position.x = Camera.pan.x - offset + floor.position.x = Camera.pan.x - 600 world:update(deltaSeconds) diff --git a/src/systems/camera-pan.lua b/src/systems/camera-pan.lua index 4bfbb02..1bf2ec3 100644 --- a/src/systems/camera-pan.lua +++ b/src/systems/camera-pan.lua @@ -2,6 +2,13 @@ local gfx = playdate.graphics local focusPriority = {} +Camera = { + pan = { + x = 0, + y = 0, + }, +} + cameraPanSystem = filteredSystem({ focusPriority = T.number, position = T.XyPair }, function(e, dt) if e.focusPriority >= focusPriority.priority then focusPriority.position = e.position @@ -14,7 +21,7 @@ function cameraPanSystem:preProcess() end function cameraPanSystem:postProcess() - local panX = math.max(0, focusPriority.position.x - 200) - local panY = math.min(0, focusPriority.position.y - 120) - gfx.setDrawOffset(-panX, -panY) + Camera.pan.x = math.max(0, focusPriority.position.x - 200) + Camera.pan.y = math.min(0, focusPriority.position.y - 120) + gfx.setDrawOffset(-Camera.pan.x, -Camera.pan.y) end diff --git a/src/systems/filter-types.lua b/src/systems/filter-types.lua index b04f011..5a088ad 100644 --- a/src/systems/filter-types.lua +++ b/src/systems/filter-types.lua @@ -16,6 +16,8 @@ local XyPair = { x = 1, y = 1 } ---@alias RoundStateAction "end" | "start" +---@alias CanSpawn { entity: Entity } + T = { XyPair = XyPair, bool = true, @@ -44,7 +46,9 @@ T = { ---@type pd_image PdImage = {}, ---@type RoundStateAction - RoundStateAction = "start" + RoundStateAction = "start", + ---@type CanSpawn + CanSpawn = {} } ---@generic T diff --git a/src/systems/spawner.lua b/src/systems/spawner.lua new file mode 100644 index 0000000..3fd94fa --- /dev/null +++ b/src/systems/spawner.lua @@ -0,0 +1,108 @@ +local odds = 0 + +---@type { canSpawn: CanSpawn } +local selectedSpawner + +spawnerSystem = filteredSystem({ canSpawn = T.CanSpawn, odds = T.number }, function(spawner, _, system) + if odds <= 0 then + return + end + odds = odds - spawner.odds + if odds <= 0 then + selectedSpawner = spawner + else + selectedSpawner = selectedSpawner or spawner + end +end) + +--- process() and postProcess() are skipped when preProcess() returns true +function spawnerSystem:preProcess() + local newestX = Ingredients.newestIngredient().position.x + local panX = Camera.pan.x + local rightEdge = panX + 400 + + if newestX < rightEdge + 100 then + odds = math.random() + selectedSpawner = nil + return false -- A new ingredient needs spawning! + end + + -- We do not need a new ingredient at this time + return true +end + +-- Currently spawns AT MOST one new ingredient per frame, which is probably not enough at high speeds! +function spawnerSystem:postProcess() + local newestX = Ingredients.newestIngredient().position.x + local newlySpawned = Ingredients.nextInCache() + + -- TODO: If performance becomes an issue, maybe just swap out __index + for k, v in pairs(selectedSpawner.canSpawn.entity) do + newlySpawned[k] = v + end + + -- TODO: May not need to include lower spawners when we reach higher altitudes. + local panY = math.floor(Camera.pan.y or 0) + local yRange = selectedSpawner.canSpawn.yRange or { top = panY - 240, bottom = panY + 220 } + newlySpawned.position = { x = newestX + 60, y = math.random(yRange.top, yRange.bottom) } + + self.world:addEntity(newlySpawned) +end + +local expireWhenOffScreenBy = { x = 2000, y = 480 } + +---@param world World +function addAllSpawners(world) + function addCollectableSpawner(name, spawnerOdds, score, sprite, canBounce, yRange) + local sizeX, sizeY = sprite:getSize() + local size = { x = sizeX, y = sizeY / 2 } + + world:addEntity({ + name = name, + odds = spawnerOdds, + canSpawn = { + yRange = yRange, + entity = { + score = score, + canBeCollidedBy = 1, + expireAfterCollision = true, + size = size, + drawAsSprite = sprite, + collectable = sprite, + expireWhenOffScreenBy = expireWhenOffScreenBy, + canBounce = canBounce, + }, + }, + }) + end + + addCollectableSpawner("Lettuce", 0.7, 1, LettuceSprite, { + flat = { x = 22, y = 190 }, + mult = { x = 1, y = -0.5 }, + }) + + addCollectableSpawner("Tomato", 0.1, 15, TomatoSprite) + + addCollectableSpawner("Mushroom", 0.7, 5, MushroomSprite, { + flat = { x = 0, y = 290 }, + mult = { x = 1, y = -1 }, + }, { top = 219, bottom = 223 }) + + addCollectableSpawner("Cheese", 0.05, 1, CheeseSprite, { + flat = { x = 50, y = 390 }, + mult = { x = 0.7, y = -0.5 }, + }) +end + +function getAvailableSpawnerUpgrades() + local upgrades = {} + for _, spawner in pairs(spawnSystem.entities) do + if spawner.hasUpgradeSpeed then + upgrades[#upgrades + 1] = { hasUpgradeSpeed = spawner.hasUpgradeSpeed } + end + if spawner.hasUpgradeValue then + upgrades[#upgrades + 1] = { hasUpgradeValue = spawner.hasUpgradeValue } + end + end + return upgrades +end \ No newline at end of file