Replace collectableDrop and afterDelayAdd systems with collision-chaining.

Waits for one entity to collide to enable collision on the next entity in the chain.
This commit is contained in:
Sage Vaillancourt 2025-03-08 12:54:25 -05:00
parent 23f5ba9f03
commit 9f71874272
14 changed files with 286 additions and 226 deletions

View File

@ -3,7 +3,7 @@ if not playdate.isSimulator then
end end
getCurrentTimeMilliseconds = playdate.getCurrentTimeMilliseconds getCurrentTimeMilliseconds = playdate.getCurrentTimeMilliseconds
tinyTrackEntityAges = true tinyTrackEntityAges = false
ENTITY_INIT_MS = { "ENTITY_INIT_MS" } ENTITY_INIT_MS = { "ENTITY_INIT_MS" }
if tinyTrackEntityAges then if tinyTrackEntityAges then
@ -12,7 +12,7 @@ if tinyTrackEntityAges then
end end
end end
tinyLogSystemUpdateTime = false tinyLogSystemUpdateTime = true
tinyLogSystemChanges = false tinyLogSystemChanges = false
tinyWarnWhenNonDataOnEntities = false tinyWarnWhenNonDataOnEntities = false

View File

@ -49,10 +49,13 @@ function Cart.reset(o)
end end
function Cart.new() function Cart.new()
return setmetatable(Cart.reset({ return setmetatable(
Cart.reset({
baseVelocity = { baseVelocity = {
x = 300, x = 300,
y = -170, y = -170,
} },
}), { __index = Cart }) }),
{ __index = Cart }
)
end end

View File

@ -67,7 +67,27 @@ function Score:draw()
end end
world:addEntity(floor) world:addEntity(floor)
local scenarios = {
default = function()
world:addEntity(Cart.new()) world:addEntity(Cart.new())
end ,
manyCollectables = function()
local cart = Cart.new()
cart.velocity.x = 0
cart.velocity.y = 1
cart.position.x = 200
cart.position.y = 200
world:addEntity(cart)
local collectables = { LettuceSprite, TomatoSprite, MushroomSprite, CheeseSprite }
for _ = 1, 200 do
world:addEntity({ collected = collectables[math.random(#collectables)] })
end
end,
}
scenarios.manyCollectables()
addAllSpawners(world) addAllSpawners(world)
world:addEntity(Ingredients.getFirst()) world:addEntity(Ingredients.getFirst())
@ -77,6 +97,29 @@ playdate.setAutoLockDisabled(true)
local startMsOffset = -playdate.getCurrentTimeMilliseconds() local startMsOffset = -playdate.getCurrentTimeMilliseconds()
-- local maxBatcherLength = 10
-- local root = {}
-- local physicsGroup = root
--
-- function append(element)
-- if #physicsGroup < maxBatcherLength then
-- physicsGroup[#physicsGroup + 1] = element
-- else
-- physicsGroup = { element }
-- root[#root + 1] = physicsGroup
-- end
-- end
--
-- for i = 1, 25 do
-- append(i)
-- end
--
-- for i = 1, 10 do
--
-- end
--
-- printTable(root)
function playdate.update() function playdate.update()
local deltaSeconds = playdate.getElapsedTime() local deltaSeconds = playdate.getElapsedTime()
playdate.resetElapsedTime() playdate.resetElapsedTime()

View File

@ -29,7 +29,6 @@ function cameraPanSystem:postProcess()
for _, entity in pairs(expireBelowScreenSystem.entities) do for _, entity in pairs(expireBelowScreenSystem.entities) do
if entity.position.y - (Camera.pan.y + 240) > entity.expireBelowScreenBy then if entity.position.y - (Camera.pan.y + 240) > entity.expireBelowScreenBy then
print("Entity expired - was too far below screen!")
self.world:removeEntity(entity) self.world:removeEntity(entity)
end end
end end

View File

@ -6,12 +6,18 @@ collidingEntities = filteredSystem("collidingEntitites", {
isSolid = Maybe(T.bool), isSolid = Maybe(T.bool),
}) })
collisionDetection = filteredSystem("collisionDetection", collisionDetection = filteredSystem(
"collisionDetection",
{ position = T.XyPair, size = T.XyPair, canBeCollidedBy = T.BitMask, isSolid = Maybe(T.bool) }, { position = T.XyPair, size = T.XyPair, canBeCollidedBy = T.BitMask, isSolid = Maybe(T.bool) },
-- Here, the entity, e, refers to some entity that a moving object may be colliding *into* -- Here, the entity, e, refers to some entity that a moving object may be colliding *into*
function(e, _, system) function(e, _, system)
for _, collider in pairs(collidingEntities.entities) do for _, collider in pairs(collidingEntities.entities) do
if (e ~= collider) and collider.canCollideWith and e.canBeCollidedBy and ((collider.canCollideWith & e.canBeCollidedBy) ~= 0) then if
(e ~= collider)
and collider.canCollideWith
and e.canBeCollidedBy
and ((collider.canCollideWith & e.canBeCollidedBy) ~= 0)
then
local colliderTop = collider.position.y local colliderTop = collider.position.y
local colliderBottom = collider.position.y + collider.size.y local colliderBottom = collider.position.y + collider.size.y
local entityTop = e.position.y local entityTop = e.position.y

View File

@ -1,3 +1,5 @@
---@alias CollisionChain { entityToGiveCollision: Entity, canCollideWith: BitMask, canBeCollidedBy: BitMask }
collisionResolution = filteredSystem("collisionResolution", { collisionBetween = T.Collision }, function(e, dt, system) collisionResolution = filteredSystem("collisionResolution", { collisionBetween = T.Collision }, function(e, dt, system)
local collidedInto, collider = e.collisionBetween[1], e.collisionBetween[2] local collidedInto, collider = e.collisionBetween[1], e.collisionBetween[2]
local colliderTop = collidedInto.position.y local colliderTop = collidedInto.position.y
@ -20,6 +22,14 @@ collisionResolution = filteredSystem("collisionResolution", { collisionBetween =
collider.velocity.y = collider.velocity.y * collidedInto.canBounce.mult.y * collider.canBeBounced.mult.y collider.velocity.y = collider.velocity.y * collidedInto.canBounce.mult.y * collider.canBeBounced.mult.y
end end
if collider.collisionChain then
---@type CollisionChain
local chain = collider.collisionChain
chain.entityToGiveCollision.canCollideWith = chain.canCollideWith
chain.entityToGiveCollision.canBeCollidedBy = chain.canBeCollidedBy
system.world:addEntity(chain.entityToGiveCollision)
end
if collider.focusOnCollide then if collider.focusOnCollide then
system.world:addEntity({ system.world:addEntity({
removeAtRoundStart = true, removeAtRoundStart = true,

View File

@ -1,16 +1,28 @@
local gfx <const> = playdate.graphics local gfx <const> = playdate.graphics
drawRectanglesSystem = filteredSystem("drawRectangles", { position = T.XyPair, drawAsRectangle = { size = T.XyPair } }, function(e, dt) drawRectanglesSystem = filteredSystem(
"drawRectangles",
{ position = T.XyPair, drawAsRectangle = { size = T.XyPair } },
function(e, dt)
gfx.fillRect(e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y) gfx.fillRect(e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y)
end) end
)
drawSpriteSystem = filteredSystem("drawSprites", { position = T.XyPair, drawAsSprite = T.pd_image }, function(e, dt, system) drawSpriteSystem = filteredSystem(
"drawSprites",
{ position = T.XyPair, drawAsSprite = T.pd_image },
function(e)
if e.position.y < Camera.pan.y - 240 or e.position.y > Camera.pan.y + 480 then
return
end
e.drawAsSprite:draw(e.position.x, e.position.y) e.drawAsSprite:draw(e.position.x, e.position.y)
end) end
)
local xMargin = 4 local xMargin = 4
drawTextSystem = filteredSystem("drawText", drawTextSystem = filteredSystem(
"drawText",
{ position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.pd_font) } }, { position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.pd_font) } },
function(e) function(e)
local font = e.drawAsText.font or AshevilleSans14Bold local font = e.drawAsText.font or AshevilleSans14Bold

View File

@ -8,8 +8,10 @@ filteredSystem("changeGravity", { changeGravityTo = T.number }, function(e, _, _
end end
end) end)
local min = math.min
fallSystem = filteredSystem("fall", { velocity = T.XyPair, mass = T.number }, function(e, dt) fallSystem = filteredSystem("fall", { velocity = T.XyPair, mass = T.number }, function(e, dt)
for _, ge in pairs(gravities.entities) do for _, ge in pairs(gravities.entities) do
e.velocity.y = e.velocity.y - (ge.gravity * dt * e.mass) - (0.5 * dt * dt) e.velocity.y = min(400, e.velocity.y - (ge.gravity * dt * e.mass) - (0.5 * dt * dt))
end end
end) end)

View File

@ -16,8 +16,7 @@ function inputSystem:preProcess()
inputState.aJustPressed = buttonJustPressed(playdate.kButtonA) inputState.aJustPressed = buttonJustPressed(playdate.kButtonA)
inputState.bJustPressed = buttonJustPressed(playdate.kButtonB) inputState.bJustPressed = buttonJustPressed(playdate.kButtonB)
inputState.receivedInputThisFrame = inputState.receivedInputThisFrame = inputState.upJustPressed
inputState.upJustPressed
or inputState.downJustPressed or inputState.downJustPressed
or inputState.rightJustPressed or inputState.rightJustPressed
or inputState.leftJustPressed or inputState.leftJustPressed

View File

@ -7,7 +7,10 @@ local function applyReplacementRelation(relation, world)
world:addEntity(relation.entityToModify) world:addEntity(relation.entityToModify)
end end
menuController = filteredSystem("menuController", { menuItems = Arr(T.Selectable), inputState = T.InputState }, function(e, _, system) menuController = filteredSystem(
"menuController",
{ menuItems = Arr(T.Selectable), inputState = T.InputState },
function(e, _, system)
for _, menuItem in pairs(e.menuItems) do for _, menuItem in pairs(e.menuItems) do
if menuItem.highlighted then if menuItem.highlighted then
if e.inputState.aJustPressed then if e.inputState.aJustPressed then
@ -53,4 +56,5 @@ menuController = filteredSystem("menuController", { menuItems = Arr(T.Selectable
menuItem.drawAsText.style = TextStyle.Bordered menuItem.drawAsText.style = TextStyle.Bordered
end end
end end
end) end
)

View File

@ -15,7 +15,10 @@ local function normalizeVector(xy1, xy2)
return x / distance, y / distance, distance return x / distance, y / distance, distance
end end
moveTowardSystem = filteredSystem("moveToward", { moveToward = MoveToward, position = T.XyPair }, function(e, dt, system) moveTowardSystem = filteredSystem(
"moveToward",
{ moveToward = MoveToward, position = T.XyPair },
function(e, dt, system)
local xNorm, yNorm, distance = normalizeVector(e.position, e.moveToward.target) local xNorm, yNorm, distance = normalizeVector(e.position, e.moveToward.target)
if distance > e.moveToward.range then if distance > e.moveToward.range then
return return
@ -35,4 +38,5 @@ moveTowardSystem = filteredSystem("moveToward", { moveToward = MoveToward, posit
else else
e.position.y = e.position.y + yVel e.position.y = e.position.y + yVel
end end
end) end
)

View File

@ -2,46 +2,15 @@ collectedEntities = filteredSystem("collectedEntities", { collected = T.pd_image
local onCollidingRemove = { "mass", "velocity", "canCollideWith" } local onCollidingRemove = { "mass", "velocity", "canCollideWith" }
local Drop = { i = T.number, delay = T.number, startAt = T.XyPair } disableCollisionWhenRoundEnds =
filteredSystem("disableCollisionWhenRoundEnds", { disableCollisionWhenRoundEnds = T.marker })
disableCollisionWhenRoundEnds = filteredSystem("disableCollisionWhenRoundEnds", { disableCollisionWhenRoundEnds = T.marker })
collectableDropSystem = filteredSystem("collectableDrop", { drop = Drop }, function(e, dt, system)
e.drop.delay = e.drop.delay - dt
if e.drop.delay > 0 then
return
end
local collX, collY = e.drop.sprite:getSize()
system.world:addEntity({
drawAsSprite = e.drop.sprite,
size = { x = collX, y = collY / 2 },
mass = 0.5,
velocity = { x = 0, y = 0 },
position = { x = e.drop.startAt.x - (collX / 2), y = e.drop.startAt.y },
canCollideWith = 2,
canBeCollidedBy = 2,
isSolid = true,
stopMovingOnCollision = true,
onCollidingRemove = onCollidingRemove,
focusOnCollide = e.drop.i,
expireBelowScreenBy = 5,
removeAtRoundStart = true,
})
system.world:removeEntity(e)
end)
removeAtRoundStart = filteredSystem("removeAtRoundStart", { removeAtRoundStart = T.bool }) removeAtRoundStart = filteredSystem("removeAtRoundStart", { removeAtRoundStart = T.bool })
filteredSystem("afterDelayAdd", { afterDelayAdd = { entityToAdd = T.Entity, delay = T.number } }, function(e, dt, system) roundSystem = filteredSystem(
e.afterDelayAdd.delay = e.afterDelayAdd.delay - dt "round",
if e.afterDelayAdd.delay > 0 then { roundAction = T.RoundStateAction, position = Maybe(T.XyPair) },
return function(e, _, system)
end
system.world:addEntity(e.afterDelayAdd.entityToAdd)
system.world:removeEntity(e)
end)
roundSystem = filteredSystem("round", { roundAction = T.RoundStateAction, position = Maybe(T.XyPair) }, function(e, _, system)
system.world:removeEntity(e) system.world:removeEntity(e)
if e.roundAction == "start" then if e.roundAction == "start" then
for _, cart in pairs(cartSystem.entities) do for _, cart in pairs(cartSystem.entities) do
@ -58,7 +27,6 @@ roundSystem = filteredSystem("round", { roundAction = T.RoundStateAction, positi
for _, toExpire in pairs(disableCollisionWhenRoundEnds.entities) do for _, toExpire in pairs(disableCollisionWhenRoundEnds.entities) do
toExpire.canCollideWith = nil toExpire.canCollideWith = nil
toExpire.canBeCollidedBy = nil toExpire.canBeCollidedBy = nil
--system.world:addEntity(toExpire)
system.world:removeEntity(toExpire) system.world:removeEntity(toExpire)
end end
-- playdate.setAutoLockDisabled(false) -- playdate.setAutoLockDisabled(false)
@ -83,22 +51,38 @@ roundSystem = filteredSystem("round", { roundAction = T.RoundStateAction, positi
-- TODO: Big ol' numbers displaying how many ingredients were collected? -- TODO: Big ol' numbers displaying how many ingredients were collected?
-- TODO: Could layer ingredients in rows of three? Maybe just when it's higher? -- TODO: Could layer ingredients in rows of three? Maybe just when it's higher?
local delayPerDrop = 0.100 local delayPerDrop = 0.100
local delay = 0 local previousEntity
for i, collectable in ipairs(collectedEntities.entities) do local collectables = collectedEntities.entities
local _, collY = collectable.collected:getSize() for i, collectable in ipairs(collectables) do
local collX, collY = collectable.collected:getSize()
y = y - collY - 15 y = y - collY - 15
system.world:addEntity({ local currentEntity = {
drop = { drawAsSprite = collectable.collected,
sprite = collectable.collected, size = { x = collX, y = collY / 2 },
i = i, mass = 0.5,
delay = delay, velocity = { x = 0, y = 0 },
startAt = { position = { x = e.position.x - (collX / 2), y = y },
x = e.position.x, isSolid = true,
y = y stopMovingOnCollision = true,
onCollidingRemove = onCollidingRemove,
focusOnCollide = i,
expireBelowScreenBy = 5,
removeAtRoundStart = true,
} }
}, if previousEntity then
}) -- Don't enable collision on this drop until the previous entity collides.
delay = delay + delayPerDrop previousEntity.collisionChain = {
entityToGiveCollision = currentEntity,
canCollideWith = 2,
canBeCollidedBy = 2,
}
else
-- Enable collision on the first entity
currentEntity.canBeCollidedBy = 2
currentEntity.canCollideWith = 2
end
previousEntity = currentEntity
system.world:addEntity(currentEntity)
system.world:removeEntity(collectable) system.world:removeEntity(collectable)
end end
@ -148,14 +132,9 @@ roundSystem = filteredSystem("round", { roundAction = T.RoundStateAction, positi
end end
upgradeBelow = upgradeEntity upgradeBelow = upgradeEntity
menuEntity.menuItems[#menuEntity.menuItems + 1] = upgradeEntity menuEntity.menuItems[#menuEntity.menuItems + 1] = upgradeEntity
delay = delay + delayPerDrop system.world:addEntity(upgradeEntity)
system.world:addEntity({
afterDelayAdd = {
delay = delay,
entityToAdd = upgradeEntity
},
})
system.world:addEntity(menuEntity) system.world:addEntity(menuEntity)
end end
end end
end) end
)

View File

@ -100,4 +100,3 @@ function addAllSpawners(world)
mult = { x = 0.7, y = -0.5 }, mult = { x = 0.7, y = -0.5 },
}) })
end end

View File

@ -28,7 +28,7 @@ function getAvailableSpawnerUpgrades(upgrades)
entityToModify = spawner, entityToModify = spawner,
replacement = { replacement = {
odds = spawner.odds, odds = spawner.odds,
} },
}, },
} }
end end
@ -43,8 +43,8 @@ function getAvailableCartUpgrades(upgrades)
replace = { replace = {
entityToModify = cart, entityToModify = cart,
replacement = { replacement = {
mass = cart.mass * 0.9 mass = cart.mass * 0.9,
} },
}, },
} }
upgrades[#upgrades + 1] = { upgrades[#upgrades + 1] = {
@ -52,8 +52,8 @@ function getAvailableCartUpgrades(upgrades)
replace = { replace = {
entityToModify = cart.baseVelocity, entityToModify = cart.baseVelocity,
replacement = { replacement = {
x = cart.baseVelocity.x * 1.2 x = cart.baseVelocity.x * 1.2,
} },
}, },
} }
upgrades[#upgrades + 1] = { upgrades[#upgrades + 1] = {
@ -61,8 +61,8 @@ function getAvailableCartUpgrades(upgrades)
replace = { replace = {
entityToModify = cart.baseVelocity, entityToModify = cart.baseVelocity,
replacement = { replacement = {
y = cart.baseVelocity.y * 1.2 y = cart.baseVelocity.y * 1.2,
} },
}, },
} }
end end