Compare commits

..

2 Commits

Author SHA1 Message Date
Sage Vaillancourt 590121f7a6 Get most of the way toward basic new-round behavior
Committing while in a mostly-working state so I can get some refactoring done.
2025-03-06 19:15:31 -05:00
Sage Vaillancourt 2e87bc8836 Almost-working upgrade menu.
Basic input system.
More advanced text-drawing system.
2025-03-06 01:06:46 -05:00
23 changed files with 676 additions and 122 deletions

View File

@ -782,10 +782,11 @@ function tiny.update(world, dt, filter)
for i = 1, #systems do
local system = systems[i]
if system.active and ((not filter) or filter(world, system)) then
-- TODO: Track how long each system runs for during any given frame.
-- Update Systems that have an update method (most Systems)
local update = system.update
if update then
--local currentMs = playdate.getCurrentTimeMilliseconds()
local interval = system.interval
if interval then
local bufferedTime = (system.bufferedTime or 0) + dt
@ -797,11 +798,14 @@ function tiny.update(world, dt, filter)
else
update(system, dt)
end
--local endTimeMs = playdate.getCurrentTimeMilliseconds()
--print(tostring(endTimeMs - currentMs) .. "ms taken to update " .. system.name)
end
system.modified = false
end
end
--print("")
-- Iterate through Systems IN ORDER AGAIN
for i = 1, #systems do

View File

@ -28,6 +28,8 @@ TomatoSprite = playdate.graphics.image.new("assets/images/TomatoSprite.png")
-- !!(dirLookup('assets/sounds', 'wav', 'playdate.sound.sampleplayer.new', 'pd_sampleplayer'))
-- !!(dirLookup('assets/music', 'wav', 'playdate.sound.sampleplayer.new', 'pd_sampleplayer'))
-- !!(dirLookup('assets/fonts', 'fnt', 'playdate.graphics.font.new', 'pd_font', nil, nil, function(varName, value)
-- return varName:gsub("[- ]", "") .. " = " .. value:gsub("fnt", "pft")
-- end))
-- luacheck: ignore
---@type pd_font
AshevilleSans14Bold = playdate.graphics.font.new("assets/fonts/Asheville-Sans-14-Bold.pft")

View File

@ -28,6 +28,6 @@ end)!!(generatedFileWarning())
!!(dirLookup('assets/images', 'png', 'playdate.graphics.image.new', 'pd_image'))
-- !!(dirLookup('assets/sounds', 'wav', 'playdate.sound.sampleplayer.new', 'pd_sampleplayer'))
-- !!(dirLookup('assets/music', 'wav', 'playdate.sound.sampleplayer.new', 'pd_sampleplayer'))
-- !!(dirLookup('assets/fonts', 'fnt', 'playdate.graphics.font.new', 'pd_font', nil, nil, function(varName, value)
-- return varName:gsub("[- ]", "") .. " = " .. value:gsub("fnt", "pft")
-- end))
!!(dirLookup('assets/fonts', 'fnt', 'playdate.graphics.font.new', 'pd_font', nil, nil, function(varName, value)
return varName:gsub("[- ]", "") .. " = " .. value:gsub("fnt", "pft")
end))

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,295 @@
tracking=1
space 3
! 2
" 5
# 9
$ 8
% 12
& 11
' 3
( 5
) 5
* 8
+ 8
, 3
- 6
. 2
/ 6
0 9
1 4
2 9
3 9
4 9
5 9
6 9
7 9
8 10
9 9
: 2
; 2
< 7
= 7
> 7
? 9
@ 11
A 10
B 9
C 9
D 9
E 8
F 8
G 9
H 9
I 2
J 8
K 10
L 9
M 12
N 9
O 9
P 9
Q 9
R 9
S 9
T 10
U 9
V 10
W 14
X 8
Y 8
Z 8
[ 3
\ 6
] 3
^ 6
_ 8
` 3
a 8
b 8
c 8
d 8
e 8
f 6
g 8
h 8
i 2
j 4
k 8
l 2
m 12
n 8
o 8
p 8
q 8
r 6
s 8
t 6
u 8
v 8
w 12
x 9
y 8
z 8
{ 6
| 2
} 6
~ 10
… 8
¥ 8
‼ 5
™ 8
© 11
® 11
。 16
、 16
ぁ 16
あ 16
ぃ 16
い 16
ぅ 16
う 16
ぇ 16
え 16
ぉ 16
お 16
か 16
が 16
き 16
ぎ 16
く 16
ぐ 16
け 16
げ 16
こ 16
ご 16
さ 16
ざ 16
し 16
じ 16
す 16
ず 16
せ 16
ぜ 16
そ 16
ぞ 16
た 16
だ 16
ち 16
ぢ 16
っ 16
つ 16
づ 16
て 16
で 16
と 16
ど 16
な 16
に 16
ぬ 16
ね 16
の 16
は 16
ば 16
ぱ 16
ひ 16
び 16
ぴ 16
ふ 16
ぶ 16
ぷ 16
へ 16
べ 16
ぺ 16
ほ 16
ぼ 16
ぽ 16
ま 16
み 16
む 16
め 16
も 16
ゃ 16
や 16
ゅ 16
ゆ 16
ょ 16
よ 16
ら 16
り 16
る 16
れ 16
ろ 16
ゎ 16
わ 16
ゐ 16
ゑ 16
を 16
ん 16
ゔ 16
ゕ 16
ゖ 16
゛ 1
゜ 0
ゝ 16
ゞ 16
ゟ 16
16
ァ 16
ア 16
ィ 16
イ 16
ゥ 16
ウ 16
ェ 16
エ 16
ォ 16
オ 16
カ 16
ガ 16
キ 16
ギ 16
ク 16
グ 16
ケ 16
ゲ 16
コ 16
ゴ 16
サ 16
ザ 16
シ 16
ジ 16
ス 16
ズ 16
セ 16
ゼ 16
ソ 16
ゾ 16
タ 16
ダ 16
チ 16
ヂ 16
ッ 16
ツ 16
ヅ 16
テ 16
デ 16
ト 16
ド 16
ナ 16
ニ 16
ヌ 16
ネ 16
16
ハ 16
バ 16
パ 16
ヒ 16
ビ 16
ピ 16
フ 16
ブ 16
プ 16
ヘ 16
ベ 16
ペ 16
ホ 16
ボ 16
ポ 16
マ 16
ミ 16
ム 16
メ 16
モ 16
ャ 16
ヤ 16
ュ 16
ユ 16
ョ 16
ヨ 16
ラ 16
リ 16
ル 16
レ 16
ロ 16
ヮ 16
ワ 16
ヰ 16
ヱ 16
ヲ 16
ン 16
ヴ 16
ヵ 16
ヶ 16
ヷ 16
ヸ 16
ヹ 16
ヺ 16
・ 16
ー 16
ヽ 16
ヾ 16
ヿ 16
「 16
」 16
円 16
<EFBFBD> 13

View File

@ -3,13 +3,16 @@ Cart = {}
local sizeX, sizeY = CartSprite:getSize()
local size = { x = sizeX, y = sizeY * 0.75 }
cartSystem = filteredSystem("cart", { isCart = T.marker })
function Cart.reset(o)
o.isCart = T.marker
o.position = {
x = 20,
y = 50,
}
o.velocity = {
x = 200 + (100 * math.random()),
x = 300 + (100 * math.random()),
y = 175 * (math.random() - 1),
}
o.size = size
@ -33,9 +36,11 @@ function Cart.reset(o)
y = self.position.y,
},
})
-- Focus on the center, where the cart stopped
world:addEntity({
position = { x = self.position.x, y = self.position.y },
focusPriority = 1,
removeAtRoundStart = true,
})
end

View File

@ -1,4 +1,4 @@
effectSystem = filteredSystem({ canReceive })
effectSystem = filteredSystem("effects", { canReceive })
Effects = {}

View File

@ -4,7 +4,7 @@ Ingredients = {}
local ingredientCache = {}
local _ingredientCacheIndex = 1
local maxCache = 100
local maxCache = 80
for i = 1, maxCache do
ingredientCache[i] = {}
end
@ -19,6 +19,16 @@ function Ingredients.cacheSize()
return maxCache
end
function Ingredients.clearCache(world)
for _, o in ipairs(ingredientCache) do
for key in pairs(o) do
o[key] = nil
end
world:addEntity(o)
end
world:addEntity(Ingredients.getFirst())
end
function Ingredients.nextInCache()
local index = _ingredientCacheIndex
_ingredientCacheIndex = _ingredientCacheIndex + 1

View File

@ -15,15 +15,19 @@ world = tiny.world()
import("tiny-tools.lua")
import("assets.lua")
import("systems/filter-types.lua")
import("systems/camera-pan.lua")
import("systems/collision-detection.lua")
import("systems/collision-resolution.lua")
import("systems/draw.lua")
import("systems/gravity.lua")
import("systems/move-toward.lua")
import("systems/rounds.lua")
import("systems/spawner.lua")
import("systems/velocity.lua")
import("systems/spawner.lua")
import("systems/rounds.lua")
import("systems/camera-pan.lua")
import("systems/collision-resolution.lua")
import("systems/collision-detection.lua")
import("systems/draw.lua")
import("systems/input.lua")
import("systems/menu.lua")
import("ingredients/ingredients.lua")
import("cart.lua")
import("utils.lua")
@ -70,22 +74,23 @@ world:addEntity(Ingredients.getFirst())
-- TODO: Re-enable when cart stops
playdate.setAutoLockDisabled(true)
local startMsOffset = -playdate.getCurrentTimeMilliseconds()
function playdate.update()
local deltaSeconds = playdate.getElapsedTime()
playdate.resetElapsedTime()
playdate.timer.updateTimers()
gfx.clear(gfx.kColorWhite)
playdate.drawFPS(5, 5)
local fps = playdate.getFPS()
if fps > 0 and fps < 20 then
local currentTime = playdate.getCurrentTimeMilliseconds() + startMsOffset
print("At " .. (currentTime / 1000) .. "s, FPS below 20: " .. fps)
end
floor.position.x = Camera.pan.x - 600
world:update(deltaSeconds)
world:update(math.min(1 / 40, deltaSeconds))
gfx.setDrawOffset(0, 0)
Score:draw()
if playdate.buttonJustPressed(playdate.kButtonA) then
Cart.reset(cart)
init()
end
end

View File

@ -9,9 +9,9 @@ Camera = {
},
}
expireBelowScreenSystem = filteredSystem({ position = T.XyPair, expireBelowScreenBy = T.number })
expireBelowScreenSystem = filteredSystem("expireBelowScreen", { position = T.XyPair, expireBelowScreenBy = T.number })
cameraPanSystem = filteredSystem({ focusPriority = T.number, position = T.XyPair }, function(e, dt)
cameraPanSystem = filteredSystem("cameraPan", { focusPriority = T.number, position = T.XyPair }, function(e, dt)
if e.focusPriority >= focusPriority.priority then
focusPriority.position = e.position
end

View File

@ -1,16 +1,17 @@
collidingEntities = filteredSystem({
collidingEntities = filteredSystem("collidingEntitites", {
velocity = T.XyPair,
position = T.XyPair,
size = T.XyPair,
canCollideWith = T.bitMask,
isSolid = Maybe(T.bool),
})
collisionDetection = filteredSystem(
collisionDetection = filteredSystem("collisionDetection",
{ 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*
function(e, _, system)
for _, collider in pairs(collidingEntities.entities) do
if (e ~= collider) and collider.canCollideWith 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 colliderBottom = collider.position.y + collider.size.y
local entityTop = e.position.y
@ -33,3 +34,8 @@ collisionDetection = filteredSystem(
end
end
)
function collisionDetection:preProcess()
-- print("collidingEntities count: " .. #collidingEntities.entities)
-- print("collidedEntities count: " .. #self.entities)
end

View File

@ -1,4 +1,4 @@
collisionResolution = filteredSystem({ 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 colliderTop = collidedInto.position.y
@ -22,6 +22,7 @@ collisionResolution = filteredSystem({ collisionBetween = T.Collision }, functio
if collider.focusOnCollide then
system.world:addEntity({
removeAtRoundStart = true,
focusPriority = collider.focusOnCollide,
position = {
x = collider.position.x,

View File

@ -1,13 +1,49 @@
local gfx <const> = playdate.graphics
drawRectanglesSystem = filteredSystem({ 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)
end)
drawTextSystem = filteredSystem({ position = T.XyPair, drawAsText = { text = T.str } }, function(e, dt)
gfx.drawTextAligned(e.drawAsText.text, e.position.x, e.position.y, gfx.kAlignCenter)
end)
drawSpriteSystem = filteredSystem({ position = T.XyPair, drawAsSprite = T.PdImage }, function(e, dt, system)
drawSpriteSystem = filteredSystem("drawSprites", { position = T.XyPair, drawAsSprite = T.PdImage }, function(e, dt, system)
e.drawAsSprite:draw(e.position.x, e.position.y)
end)
local textHeight = AshevilleSans14Bold:getHeight()
local xMargin = 4
drawTextSystem = filteredSystem("drawText",
{ position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str) } },
function(e, dt)
local textWidth = AshevilleSans14Bold:getTextWidth(e.drawAsText.text)
if e.drawAsText.style == TextStyle.Inverted then
gfx.fillRect(
e.position.x - xMargin - textWidth / 2,
e.position.y - 2,
textWidth + (xMargin * 2),
textHeight + 2
)
gfx.setImageDrawMode(gfx.kDrawModeInverted)
end
if e.drawAsText.style == TextStyle.Bordered then
gfx.setColor(gfx.kColorWhite)
gfx.fillRect(
e.position.x - xMargin - textWidth / 2,
e.position.y - 2,
textWidth + (xMargin * 2),
textHeight + 2
)
gfx.setColor(gfx.kColorBlack)
gfx.setImageDrawMode(gfx.kDrawModeCopy)
gfx.drawRect(
e.position.x - xMargin - textWidth / 2,
e.position.y - 2,
textWidth + (xMargin * 2),
textHeight + 2
)
end
AshevilleSans14Bold:drawTextAligned(e.drawAsText.text, e.position.x, e.position.y, kTextAlignment.center)
if e.drawAsText.style == TextStyle.Inverted then
gfx.setImageDrawMode(gfx.kDrawModeCopy)
end
end
)

View File

@ -18,6 +18,8 @@ local XyPair = { x = 1, y = 1 }
---@alias CanSpawn { entity: Entity }
---@alias InRelations Entity[]
T = {
XyPair = XyPair,
bool = true,
@ -49,6 +51,12 @@ T = {
RoundStateAction = "start",
---@type CanSpawn
CanSpawn = {},
---@type InRelations
InRelations = {},
---@type InputState
InputState = {},
---@type Entity
Entity = {},
}
---@generic T
@ -57,3 +65,9 @@ T = {
function Maybe(t)
return { maybe = t }
end
TextStyle = {
Inverted = "INVERTED",
Bordered = "BORDERED",
None = "None",
}

View File

@ -1,4 +1,4 @@
local G = -300
fallSystem = filteredSystem({ velocity = T.XyPair, mass = T.number }, function(e, dt)
fallSystem = filteredSystem("fall", { velocity = T.XyPair, mass = T.number }, function(e, dt)
e.velocity.y = e.velocity.y - (G * dt * e.mass) - (0.5 * dt * dt)
end)

18
src/systems/input.lua Normal file
View File

@ -0,0 +1,18 @@
---@alias InputState { upJustPressed: boolean, downJustPressed: boolean, rightJustPressed: boolean, leftJustPressed: boolean, aJustPressed: boolean, bJustPressed: boolean }
local buttonJustPressed = playdate.buttonJustPressed
local inputState = {}
inputSystem = filteredSystem("input", { canReceiveInput = T.marker }, function(e, _, system)
e.inputState = inputState
system.world:addEntity(e)
end)
function inputSystem:preProcess()
inputState.upJustPressed = buttonJustPressed(playdate.kButtonUp)
inputState.downJustPressed = buttonJustPressed(playdate.kButtonDown)
inputState.rightJustPressed = buttonJustPressed(playdate.kButtonRight)
inputState.leftJustPressed = buttonJustPressed(playdate.kButtonLeft)
inputState.aJustPressed = buttonJustPressed(playdate.kButtonA)
inputState.bJustPressed = buttonJustPressed(playdate.kButtonB)
end

31
src/systems/menu.lua Normal file
View File

@ -0,0 +1,31 @@
---@alias MenuItem { onSelect: fun(), highlighted: boolean, navigateDown: MenuItem | nil, navigateUp: MenuItem | nil }
---@type MenuItem[]
local MenuItems = {}
menuController = filteredSystem("menuController", { menuItems = MenuItems, inputState = T.InputState }, function(e, _, system)
for _, menuItem in pairs(e.menuItems) do
if menuItem.highlighted then
if e.inputState.aJustPressed then
menuItem.onSelect(system.world)
end
if e.inputState.downJustPressed and menuItem.navigateDown then
menuItem.highlighted = false
menuItem.navigateDown.highlighted = true
return
end
if e.inputState.upJustPressed and menuItem.navigateUp then
menuItem.highlighted = false
menuItem.navigateUp.highlighted = true
return
end
end
end
for _, menuItem in pairs(e.menuItems) do
if menuItem.highlighted then
menuItem.drawAsText.style = TextStyle.Inverted
else
menuItem.drawAsText.style = TextStyle.Bordered
end
end
end)

View File

@ -15,27 +15,24 @@ local function normalizeVector(xy1, xy2)
return x / distance, y / distance, distance
end
moveTowardSystem = filteredSystem(
{ moveToward = MoveToward, position = T.XyPair },
function(e, dt, system)
local xNorm, yNorm, distance = normalizeVector(e.position, e.moveToward.target)
if distance > e.moveToward.range then
return
end
-- TODO May be incorrect when signs are mismatched between vel and diff
local xVel = xNorm * e.moveToward.speed * dt
if abs(e.position.x - e.moveToward.target.x) < abs(xVel) then
e.position.x = e.moveToward.target.x
else
e.position.x = e.position.x + xVel
end
local yVel = yNorm * e.moveToward.speed * dt
if abs(e.position.y - e.moveToward.target.y) < abs(yVel) then
e.position.y = e.moveToward.target.y
else
e.position.y = e.position.y + yVel
end
moveTowardSystem = filteredSystem("moveToward", { moveToward = MoveToward, position = T.XyPair }, function(e, dt, system)
local xNorm, yNorm, distance = normalizeVector(e.position, e.moveToward.target)
if distance > e.moveToward.range then
return
end
)
-- TODO May be incorrect when signs are mismatched between vel and diff
local xVel = xNorm * e.moveToward.speed * dt
if abs(e.position.x - e.moveToward.target.x) < abs(xVel) then
e.position.x = e.moveToward.target.x
else
e.position.x = e.position.x + xVel
end
local yVel = yNorm * e.moveToward.speed * dt
if abs(e.position.y - e.moveToward.target.y) < abs(yVel) then
e.position.y = e.moveToward.target.y
else
e.position.y = e.position.y + yVel
end
end)

View File

@ -1,10 +1,66 @@
collectedEntities = filteredSystem({ collected = T.PdImage })
collectedEntities = filteredSystem("collectedEntities", { collected = T.PdImage })
local onCollidingRemove = { "mass", "velocity", "canCollideWith" }
roundSystem = filteredSystem({ roundAction = T.RoundStateAction, position = Maybe(T.XyPair) }, function(e, _, system)
if e.roundAction == "end" then
playdate.setAutoLockDisabled(false)
local Drop = { i = T.number, delay = T.number, startAt = T.XyPair }
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 })
filteredSystem("afterDelayAdd", { afterDelayAdd = { entity = T.Entity, delay = T.number } }, function(e, dt, system)
e.afterDelayAdd.delay = e.afterDelayAdd.delay - dt
if e.afterDelayAdd.delay > 0 then
return
end
system.world:addEntity(e.afterDelayAdd.entity)
system.world:removeEntity(e)
end)
roundSystem = filteredSystem("round", { roundAction = T.RoundStateAction, position = Maybe(T.XyPair) }, function(e, _, system)
if e.roundAction == "start" then
for _, cart in pairs(cartSystem.entities) do
Cart.reset(cart)
system.world:addEntity(cart)
end
for _, remove in pairs(removeAtRoundStart.entities) do
system.world:removeEntity(remove)
end
system.world:addSystem(spawnerSystem)
Ingredients.clearCache(system.world)
elseif e.roundAction == "end" then
system.world:removeSystem(spawnerSystem)
for _, toExpire in pairs(disableCollisionWhenRoundEnds.entities) do
toExpire.canCollideWith = nil
toExpire.canBeCollidedBy = nil
--system.world:addEntity(toExpire)
system.world:removeEntity(toExpire)
end
-- playdate.setAutoLockDisabled(false)
local y = e.position.y - 240
local rectWidth = 150
@ -20,59 +76,84 @@ roundSystem = filteredSystem({ roundAction = T.RoundStateAction, position = Mayb
canBeCollidedBy = 2,
isSolid = true,
stopMovingOnCollision = true,
removeAtRoundStart = true,
})
local delayPerDrop = 150
-- TODO: Big ol' numbers displaying how many ingredients were collected?
-- TODO: Could layer ingredients in rows of three? Maybe just when it's higher?
local delayPerDrop = 0.100
local delay = 0
for i, collectable in ipairs(collectedEntities.entities) do
local collX, collY = collectable.collected:getSize()
local _, collY = collectable.collected:getSize()
y = y - collY - 15
playdate.timer.new(delay, function(ee, ccollX, ccollY, yy, ii, ssystem, ccollectable)
ssystem.world:addEntity({
drawAsSprite = ccollectable.collected,
size = { x = ccollX, y = ccollY / 2 },
mass = 0.5,
velocity = { x = 0, y = 0 },
position = { x = ee.position.x - (ccollX / 2), y = yy },
canCollideWith = 2,
canBeCollidedBy = 2,
isSolid = true,
stopMovingOnCollision = true,
onCollidingRemove = onCollidingRemove,
focusOnCollide = ii,
expireBelowScreenBy = 5,
})
end, e, collX, collY, y, i, system, collectable)
system.world:addEntity({
drop = {
sprite = collectable.collected,
i = i,
delay = delay,
startAt = {
x = e.position.x,
y = y
}
},
})
delay = delay + delayPerDrop
system.world:removeEntity(collectable)
end
local availableUpgrades = Utils.getNDifferentValues(getAvailableSpawnerUpgrades(), 3)
-- Sorting from shortest to longest sort of makes them look like a bun?
table.sort(availableUpgrades, function(a, b)
return #a.name > #b.name
end)
y = y - 50
local menuEntity = {
menuItems = {},
canReceiveInput = T.marker,
}
local upgradeBelow
local i = #collectedEntities.entities
for _, upgrade in ipairs(availableUpgrades) do
printTable(upgrade)
local collX, collY = 75, 30
y = y - collY - 15
playdate.timer.new(delay, function(ee, ccollX, ccollY, yy, ii, ssystem, ccollectable)
ssystem.world:addEntity({
drawAsText = {
text = upgrade.name,
},
size = { x = ccollX, y = ccollY / 2 },
mass = 0.5,
velocity = { x = 0, y = 0 },
position = { x = ee.position.x - (ccollX / 2), y = yy },
canCollideWith = 2,
canBeCollidedBy = 2,
isSolid = true,
stopMovingOnCollision = true,
onCollidingRemove = onCollidingRemove,
focusOnCollide = ii,
})
end, e, collX, collY, y, i, system, collectable)
i = i + 1
local collX, collY = 75, 21
y = y - collY - 15 - 15
local upgradeEntity = {
onSelect = upgrade.apply,
drawAsText = {
text = upgrade.name,
style = TextStyle.Inverted,
},
size = { x = collX, y = collY },
mass = 0.5,
velocity = { x = 0, y = 0 },
position = { x = e.position.x, y = y },
canCollideWith = 2,
canBeCollidedBy = 2,
isSolid = true,
stopMovingOnCollision = true,
onCollidingRemove = onCollidingRemove,
focusOnCollide = i,
navigateDown = upgradeBelow,
highlighted = true,
removeAtRoundStart = true,
}
if upgradeBelow then
upgradeBelow.navigateUp = upgradeEntity
upgradeBelow.highlighted = false
upgradeBelow.drawAsText.style = TextStyle.Bordered
end
upgradeBelow = upgradeEntity
menuEntity.menuItems[#menuEntity.menuItems + 1] = upgradeEntity
delay = delay + delayPerDrop
system.world:addEntity({
afterDelayAdd = {
delay = delay,
entity = upgradeEntity
},
})
system.world:addEntity(menuEntity)
end
system.world:removeEntity(e)
end
system.world:removeEntity(e)
end)

View File

@ -3,7 +3,7 @@ local odds = 0
---@type { canSpawn: CanSpawn }
local selectedSpawner
spawnerSystem = filteredSystem({ canSpawn = T.CanSpawn, odds = T.number }, function(spawner, _, _)
spawnerSystem = filteredSystem("spawner", { canSpawn = T.CanSpawn, odds = T.number }, function(spawner, _, _)
if odds <= 0 then
return
end
@ -35,7 +35,7 @@ function spawnerSystem:preProcess()
return tiny.SKIP_PROCESS
end
local spawnEveryX = 26
local spawnEveryX = 30
-- Currently spawns AT MOST one new ingredient per frame, which is probably not enough at high speeds!
function spawnerSystem:postProcess()
@ -55,8 +55,6 @@ function spawnerSystem:postProcess()
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)
@ -78,6 +76,7 @@ function addAllSpawners(world)
drawAsSprite = sprite,
collectable = sprite,
expireWhenOffScreenBy = expireWhenOffScreenBy,
disableCollisionWhenRoundEnds = T.marker,
canBounce = canBounce,
},
},
@ -85,7 +84,7 @@ function addAllSpawners(world)
end
addCollectableSpawner("Lettuce", 0.7, 1, LettuceSprite, {
flat = { x = 22, y = 190 },
flat = { x = 12, y = 220 },
mult = { x = 1, y = -0.5 },
})
@ -109,24 +108,30 @@ function getAvailableSpawnerUpgrades()
local upgrades = {}
for _, spawner in pairs(spawnerSystem.entities) do
if spawner.hasUpgradeSpeed then
upgrades[#upgrades + 1] = { hasUpgradeSpeed = spawner.hasUpgradeSpeed }
-- upgrades[#upgrades + 1] = { hasUpgradeSpeed = spawner.hasUpgradeSpeed }
end
if spawner.canSpawn.entity.score then
local name = "Double " .. spawner.name .. " value"
upgrades[#upgrades + 1] = {
name = "Double " .. spawner.name .. " value",
apply = function()
name = name,
apply = function(world)
print("Applying " .. name)
spawner.canSpawn.entity.score = spawner.canSpawn.entity.score * 2
end
world:addEntity({ roundAction = "start" })
end,
}
end
assert(spawner.odds, "Expected all spawners to have an `odds` field!")
local name = "Double " .. spawner.name .. " frequency"
upgrades[#upgrades + 1] = {
name = "Double " .. spawner.name .. " frequency",
apply = function()
name = name,
apply = function(world)
print("Applying " .. name)
spawner.odds = spawner.odds * 2
end
world:addEntity({ roundAction = "start" })
end,
}
-- if not spawner.canSpawn.entity.velocity then

View File

@ -1,10 +1,7 @@
local sqrt = math.sqrt
velocitySystem = filteredSystem({ position = T.XyPair, velocity = T.XyPair }, function(e, dt, system)
if not e.velocity then
return
end
if sqrt((e.velocity.x * e.velocity.x) + (e.velocity.y * e.velocity.y)) < 1.5 then
velocitySystem = filteredSystem("velocity", { position = T.XyPair, velocity = T.XyPair }, function(e, dt, system)
if sqrt((e.velocity.x * e.velocity.x) + (e.velocity.y * e.velocity.y)) < 2 then
e.velocity = nil
if e.spawnEntitiesWhenStopped then
e:spawnEntitiesWhenStopped(system.world)

View File

@ -13,8 +13,9 @@ local isSimulator = playdate.isSimulator
---@param shape T
---@param process fun(entity: T, dt: number, system: System)
---@return System | { entities: T[] }
function filteredSystem(shape, process)
function filteredSystem(name, shape, process)
local system = tiny.processingSystem()
system.name = name
local keys = {}
for key, value in pairs(shape) do
if type(value) ~= "table" or value.maybe == nil then

View File

@ -4,7 +4,7 @@ Utils = {}
---@generic T
---@param fromArr T[]
---@param n number
---@generic T[]
---@return T[]
function Utils.getNDifferentValues(fromArr, n)
assert(n >= 0, "n must be a non-negative integer")
if n > #fromArr then
@ -25,4 +25,50 @@ function Utils.getNDifferentValues(fromArr, n)
randoms[#randoms + 1] = fromArr[i]
end
return randoms
end
end
--- Track the number of instances of a given element, instead of needing multiple copies.
---@class CountSet
---@field private data table<table, number>
---@field private elementCount number
CountSet = {}
function CountSet.new()
return setmetatable({ data = {}, elementCount = 0 }, { __index = CountSet })
end
function CountSet:add(element)
local existing = self.data[element]
if existing then
self.data[element] = existing + 1
else
self.data[element] = 1
end
self.elementCount = self.elementCount + 1
end
function CountSet:balancedRandomPop()
if self.elementCount == 0 then
return
end
local toPop = math.random(self.elementCount)
for element, count in pairs(self.data) do
toPop = toPop - count
if toPop <= 0 then
local newCount = count - 1
if newCount == 0 then
self.data[element] = nil
else
self.data[element] = newCount
end
self.elementCount = self.elementCount - 1
return element
end
end
end
function CountSet:iterRandom()
return function()
return self:balancedRandomPop()
end
end