Turn spawners into entities driven by the spawnerSystem.

This commit is contained in:
Sage Vaillancourt 2025-03-05 00:10:41 -05:00
parent 5d16c75cf6
commit 8162c7f723
10 changed files with 156 additions and 138 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -2,6 +2,13 @@ local gfx <const> = 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

View File

@ -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

108
src/systems/spawner.lua Normal file
View File

@ -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