diff --git a/src/burger.lua b/src/burger.lua index 398730f..ddfa692 100644 --- a/src/burger.lua +++ b/src/burger.lua @@ -8,13 +8,17 @@ function Burger.reset(o) y = 50, } o.velocity = { - x = 100 * math.random(), + x = 50 + (150 * math.random()), y = 150 * (math.random() - 1), } o.size = { x = 10, y = 10, } + o.canBeBounced = { + flat = { x = 0, y = 0 }, + mult = { x = 1, y = 1 }, + } o.mass = T.marker return o end diff --git a/src/booster.lua b/src/ingredients/booster.lua similarity index 60% rename from src/booster.lua rename to src/ingredients/booster.lua index 4752dfc..89048d5 100644 --- a/src/booster.lua +++ b/src/ingredients/booster.lua @@ -1,26 +1,23 @@ Booster = {} -local gfx = playdate.graphics - local function newBooster(x, y) - return setmetatable({ + local size = { x = 40, y = 10 } + return { + score = 1, + expireAfterCollision = true, + size = size, position = { x = x, y = y }, - size = { x = 40, y = 10 }, - }, { __index = Booster }) + canBounce = { + flat = { x = 122, y = 190 }, + mult = { x = 1, y = -0.5 }, + }, + drawAsRectangle = { size = size }, + } end -function Booster:elasticity(velocity) - velocity.y = (math.abs(velocity.y) + 190) * -0.5 - velocity.x = velocity.x + 50 -end - -function Booster:draw() - gfx.fillRect(self.position.x, self.position.y, self.size.x, self.size.y) -end - -local boosterIndex = 1 -local boosterCache = {} -for i = 1, 200 do +boosterIndex = 1 +boosterCache = {} +for i = 1, 100 do boosterCache[i] = newBooster(-999, 999) end diff --git a/src/ingredients/tomato.lua b/src/ingredients/tomato.lua new file mode 100644 index 0000000..8bd8608 --- /dev/null +++ b/src/ingredients/tomato.lua @@ -0,0 +1,36 @@ +Tomato = {} + +local function newTomato(x, y) + local size = { x = 20, y = 20 } + return { + score = 15, + expireAfterCollision = true, + position = { x = x, y = y }, + size = size, + drawAsRectangle = { size = size }, + } +end + +local tomatoIndex = 1 +local tomatoCache = {} +for i = 1, 100 do + tomatoCache[i] = newTomato(-999, 999) +end + +function Tomato.cacheSize() + return #tomatoCache +end + +-- Tomatos should be "initialized" almost always increasing in X value +function Tomato.get(x, y) + local tomato = tomatoCache[tomatoIndex] + tomato.position.x = x + tomato.position.y = y + + tomatoIndex = tomatoIndex + 1 + if tomatoIndex > #tomatoCache then + tomatoIndex = 1 + end + + return tomato +end diff --git a/src/main.lua b/src/main.lua index e886a67..d7dcc46 100644 --- a/src/main.lua +++ b/src/main.lua @@ -13,10 +13,12 @@ import("../lib/tiny.lua") import("tiny-tools.lua") import("systems/filter-types.lua") import("systems/collision-detection.lua") +import("systems/collision-resolution.lua") import("systems/draw.lua") import("systems/gravity.lua") import("systems/velocity.lua") -import("booster.lua") +import("ingredients/booster.lua") +import("ingredients/tomato.lua") import("burger.lua") local tiny = tiny @@ -25,18 +27,17 @@ playdate.display.setRefreshRate(50) gfx.setBackgroundColor(gfx.kColorWhite) burger = Burger.new() +local floorSize = { x = 10000, y = 10 } floor = { - position = { x = 0, y = 230 }, - size = { x = 10000, y = 10 }, + position = { x = 0, y = 235 }, + size = floorSize, + canBounce = { + flat = { x = 0, y = 0 }, + mult = { x = 0.9, y = -0.5 }, + }, + drawAsRectangle = { size = floorSize }, isSolid = true, } -function floor:elasticity(velocity) - velocity.x = velocity.x * 0.9 - velocity.y = velocity.y * -0.7 -end -function floor:draw() - gfx.fillRect(floor.position.x, floor.position.y, floor.size.x, floor.size.y) -end Camera = { pan = { @@ -45,7 +46,32 @@ Camera = { }, } -world = tiny.world(fallSystem, velocitySystem, collisionDetection, drawSystem, burger, floor) +Score = { + points = 0, +} + +-- TODO: Shops with random upgrades instead of fixed ones. + +function Score:add(add) + self.points = self.points + add +end + +function Score:draw() + gfx.drawText("Z|" .. self.points, 360, 5) +end + +world = tiny.world( + fallSystem, + velocitySystem, + collisionResolution, + collisionDetection, + drawSystem, + drawRectanglesSystem, + + burger, + floor +) + for i = 1, Booster.cacheSize() do world:addEntity(Booster.get(i * 60 + (math.random(0, 50)), math.random(50, 200))) end @@ -57,19 +83,26 @@ function playdate.update() playdate.drawFPS(5, 5) Camera.pan.x = math.min(0, -burger.position.x + 200) - Camera.pan.y = burger.position.y + 120 + Camera.pan.y = math.max(0, -burger.position.y + 120) local newestX = Booster.newestBooster().position.x local panX = -Camera.pan.x - printTable({ panX = panX, newestX = newestX }) local offset = 600 - if newestX < panX then - Booster.get(panX + offset + (math.random(0, 50)), math.random(50, 200)) + if newestX + 300 < panX then + if math.random() > 0.5 then + world:addEntity(Tomato.get(panX + offset + (math.random(0, 50)), math.random(-500, 150))) + else + -- Implicitly updates cached boosters + Booster.get(panX + offset + (math.random(0, 50)), math.random(-500, 150)) + end end floor.position.x = -Camera.pan.x - offset gfx.setDrawOffset(Camera.pan.x, Camera.pan.y) world:update(deltaSeconds) + gfx.setDrawOffset(0, 0) + Score:draw() + if playdate.buttonJustPressed(playdate.kButtonA) then Burger.reset(burger) for i = 1, Booster.cacheSize() do diff --git a/src/systems/collision-detection.lua b/src/systems/collision-detection.lua index 545ae64..744311a 100644 --- a/src/systems/collision-detection.lua +++ b/src/systems/collision-detection.lua @@ -1,28 +1,28 @@ -collisionDetection = tiny.filteredSystem( - { position = T.XyPair, size = T.XyPair, elasticity = T.Elasticity, isSolid = Maybe(T.bool) }, +collisionDetection = filteredSystem( + { position = T.XyPair, size = T.XyPair, isSolid = Maybe(T.bool) }, -- Here, the entity, e, refers to some entity that the burger global(!) may be colliding with. - function(e) - if not burger.velocity then + function(e, _, system) + local collider = burger + if not collider.velocity then return end - local burgerTop = burger.position.y - local burgerBottom = burger.position.y + burger.size.y + local colliderTop = collider.position.y + local colliderBottom = collider.position.y + collider.size.y local entityTop = e.position.y local entityBottom = entityTop + e.size.y - local withinY = (entityTop > burgerTop and entityTop < burgerBottom) - or (entityBottom > burgerTop and entityBottom < burgerBottom) + local withinY = (entityTop > colliderTop and entityTop < colliderBottom) + or (entityBottom > colliderTop and entityBottom < colliderBottom) if not withinY then return end - if burger.position.x < e.position.x + e.size.x and burger.position.x + burger.size.x > e.position.x then - if e.isSolid then - -- Assumes impact from the top - burger.position.y = entityTop - burger.size.y + if collider.position.x < e.position.x + e.size.x and collider.position.x + collider.size.x > e.position.x then + system.world:addEntity({ collisionBetween = { e, collider } }) + if e.expireAfterCollision then + system.world:removeEntity(e) end - e:elasticity(burger.velocity) end end ) diff --git a/src/systems/collision-resolution.lua b/src/systems/collision-resolution.lua new file mode 100644 index 0000000..86379ee --- /dev/null +++ b/src/systems/collision-resolution.lua @@ -0,0 +1,38 @@ +collisionResolution = filteredSystem({ collisionBetween = T.Collision }, function(e, _, system) + local collider, probablyBurger = e.collisionBetween[1], e.collisionBetween[2] + local colliderTop = collider.position.y + + if collider.isSolid then + -- Assumes impact from the top + probablyBurger.position.y = colliderTop - probablyBurger.size.y + end + + if collider.canBounce and probablyBurger.canBeBounced then + probablyBurger.velocity.x = probablyBurger.velocity.x + + collider.canBounce.flat.x + + probablyBurger.canBeBounced.flat.x + + probablyBurger.velocity.x = probablyBurger.velocity.x + * collider.canBounce.mult.x + * probablyBurger.canBeBounced.mult.x + + -- abs() makes sure we always push upward + probablyBurger.velocity.y = math.abs(probablyBurger.velocity.y) + + collider.canBounce.flat.y + + probablyBurger.canBeBounced.flat.y + + probablyBurger.velocity.y = probablyBurger.velocity.y + * collider.canBounce.mult.y + * probablyBurger.canBeBounced.mult.y + end + + if collider.score then + Score:add(collider.score) + end + + if collider.expireAfterCollision then + system.world:removeEntity(collider) + end + + system.world:removeEntity(e) +end) diff --git a/src/systems/draw.lua b/src/systems/draw.lua index a1104fd..fd293a8 100644 --- a/src/systems/draw.lua +++ b/src/systems/draw.lua @@ -1,3 +1,9 @@ -drawSystem = tiny.filteredSystem({ draw = T.SelfFunction }, function(e, dt) +local gfx = playdate.graphics + +drawSystem = filteredSystem({ draw = T.SelfFunction }, function(e, dt) e:draw() end) + +drawRectanglesSystem = filteredSystem({ 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) diff --git a/src/systems/filter-types.lua b/src/systems/filter-types.lua index 4e497c8..14c0439 100644 --- a/src/systems/filter-types.lua +++ b/src/systems/filter-types.lua @@ -1,6 +1,11 @@ ---@alias XyPair { x: number, y: number } ----@alias Elasticity fun(self, velocity: XyPair) +---@alias Entity table + +---@alias Collision { collisionBetween: Entity[] } + +---@type Entity +local Entity = {} T = { ---@type XyPair @@ -12,11 +17,19 @@ T = { marker = {}, ---@type fun(self) SelfFunction = function(self) end, - ---@type Elasticity - Elasticity = { - isRigid = true, - apply = function() end, + --- Actor + CanBounce = { + isSolid = true, + flat = { x = 1, y = 1 }, + mult = { x = 1, y = 1 }, }, + --- Receiver + CanBeBounced = { + flat = { x = 1, y = 1 }, + mult = { x = 1, y = 1 }, + }, + ---@type Collision + Collision = { Entity, Entity }, } ---@generic T diff --git a/src/systems/gravity.lua b/src/systems/gravity.lua index 2344cc2..362ea30 100644 --- a/src/systems/gravity.lua +++ b/src/systems/gravity.lua @@ -1,4 +1,4 @@ local G = -300 -fallSystem = tiny.filteredSystem({ velocity = T.XyPair, mass = T.marker }, function(e, dt) +fallSystem = filteredSystem({ velocity = T.XyPair, mass = T.marker }, function(e, dt) e.velocity.y = e.velocity.y - (G * dt) - (0.5 * dt * dt) end) diff --git a/src/systems/velocity.lua b/src/systems/velocity.lua index 6576f0d..4236caa 100644 --- a/src/systems/velocity.lua +++ b/src/systems/velocity.lua @@ -1,6 +1,6 @@ local sqrt = math.sqrt -velocitySystem = tiny.filteredSystem({ position = T.XyPair, velocity = T.XyPair }, function(e, dt) +velocitySystem = filteredSystem({ position = T.XyPair, velocity = T.XyPair }, function(e, dt) if sqrt((e.velocity.x * e.velocity.x) + (e.velocity.y * e.velocity.y)) < 0.1 then e.velocity = nil world:addEntity(e) diff --git a/src/tiny-tools.lua b/src/tiny-tools.lua index 48ff029..df08bf9 100644 --- a/src/tiny-tools.lua +++ b/src/tiny-tools.lua @@ -13,7 +13,7 @@ local isSimulator = playdate.isSimulator ---@param shape T ---@param process fun(entity: T, dt: number, system: System) ---@return -function tiny.filteredSystem(shape, process) +function filteredSystem(shape, process) local system = tiny.processingSystem() local keys = {} for key, value in pairs(shape) do