diff --git a/src/cart.lua b/src/cart.lua index 6dbc90a..c62be6f 100644 --- a/src/cart.lua +++ b/src/cart.lua @@ -1,3 +1,5 @@ +import("plate.lua") + Cart = {} local sizeX, sizeY = CartSprite:getSize() @@ -17,8 +19,54 @@ function Cart.reset(o) flat = { x = 0, y = 0 }, mult = { x = 1, y = 1 }, } - o.mass = T.marker + o.canCollideWith = 1 + o.mass = 1 o.drawAsSprite = CartSprite + o.focusPriority = 0 + + ---@type pd_image[] + o.collectables = {} + + ---@param world World + function o:spawnEntitiesWhenStopped(world) + local y = self.position.y - 240 + local rectWidth = 150 + local plateSize = { x = rectWidth, y = 10 } + self.velocity = { x = 300, y = 0 } + world:addEntity(self) + world:addEntity({ + position = { x = self.position.x, y = self.position.y }, + focusPriority = 1, + }) + world:addEntity({ + drawAsRectangle = { size = plateSize }, + size = plateSize, + mass = 0.5, + velocity = { x = 0, y = 0 }, + position = { x = self.position.x - (rectWidth / 2), y = y }, + canCollideWith = 2, + canBeCollidedBy = 2, + isSolid = true, + stopMovingOnCollision = true, + }) + + for _, collectable in ipairs(self.collectables) do + local collX, collY = collectable:getSize() + y = y - collY - 5 + world:addEntity({ + drawAsSprite = collectable, + size = { x = collX, y = collY / 2 }, + mass = 0.5, + velocity = { x = 0, y = 0 }, + position = { x = self.position.x - (collX / 2), y = y }, + canCollideWith = 2, + canBeCollidedBy = 2, + isSolid = true, + stopMovingOnCollision = true, + }) + end + end + return o end diff --git a/src/ingredients/cheese.lua b/src/ingredients/cheese.lua index 17ed97d..370ba49 100644 --- a/src/ingredients/cheese.lua +++ b/src/ingredients/cheese.lua @@ -11,10 +11,12 @@ local canBounce = { 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 diff --git a/src/ingredients/ingredients.lua b/src/ingredients/ingredients.lua index aad9298..542f598 100644 --- a/src/ingredients/ingredients.lua +++ b/src/ingredients/ingredients.lua @@ -42,7 +42,7 @@ function Ingredients.getNext(x, y) return Lettuce.initialize(o, x, y) elseif odds > 0.2 then return Tomato.initialize(o, x, y) - elseif odds > 0.1 then + elseif odds > 0.05 then return Mushroom.initialize(o, x, 218 + math.random(1, 5)) else return Cheese.initialize(o, x, y) diff --git a/src/ingredients/lettuce.lua b/src/ingredients/lettuce.lua index f28f284..521d2bf 100644 --- a/src/ingredients/lettuce.lua +++ b/src/ingredients/lettuce.lua @@ -11,10 +11,12 @@ local canBounce = { 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 diff --git a/src/ingredients/mushroom.lua b/src/ingredients/mushroom.lua index c251e1f..e2b24cf 100644 --- a/src/ingredients/mushroom.lua +++ b/src/ingredients/mushroom.lua @@ -11,10 +11,12 @@ local canBounce = { 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 diff --git a/src/ingredients/tomato.lua b/src/ingredients/tomato.lua index f4b65c9..209d31f 100644 --- a/src/ingredients/tomato.lua +++ b/src/ingredients/tomato.lua @@ -6,10 +6,12 @@ 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 diff --git a/src/main.lua b/src/main.lua index 32ff6a8..4db775e 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,18 +1,17 @@ --- stylua: ignore start -import 'CoreLibs/animation.lua' -import 'CoreLibs/animator.lua' -import 'CoreLibs/easing.lua' -import 'CoreLibs/graphics.lua' -import 'CoreLibs/object.lua' -import 'CoreLibs/timer.lua' -import 'CoreLibs/ui.lua' -import 'CoreLibs/utilities/where.lua' --- stylua: ignore end +import("CoreLibs/animation.lua") +import("CoreLibs/animator.lua") +import("CoreLibs/easing.lua") +import("CoreLibs/graphics.lua") +import("CoreLibs/object.lua") +import("CoreLibs/timer.lua") +import("CoreLibs/ui.lua") +import("CoreLibs/utilities/where.lua") import("../lib/tiny.lua") 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") @@ -31,6 +30,7 @@ local floorSize = { x = 10000, y = 10 } floor = { position = { x = 0, y = 235 }, size = floorSize, + canBeCollidedBy = 1 | 2, canBounce = { flat = { x = 0, y = 0 }, mult = { x = 0.9, y = -0.5 }, @@ -64,17 +64,17 @@ end world = tiny.world( fallSystem, velocitySystem, + collidingEntities, collisionResolution, collisionDetection, - + cameraPanSystem, drawRectanglesSystem, drawSpriteSystem, - - cart, - floor + floor, + cart ) -local ingredientsEveryX = 50 +local ingredientsEveryX = 90 local function init() for i = 1, Ingredients.cacheSize() do @@ -84,6 +84,9 @@ end init() +-- TODO: Re-enable when cart stops +playdate.setAutoLockDisabled(true) + function playdate.update() local deltaSeconds = playdate.getElapsedTime() playdate.resetElapsedTime() @@ -97,12 +100,11 @@ function playdate.update() local panX = Camera.pan.x local rightEdge = panX + 400 local offset = 600 - while newestX < (rightEdge + 100) do + 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 - gfx.setDrawOffset(-Camera.pan.x, -Camera.pan.y) world:update(deltaSeconds) diff --git a/src/systems/camera-pan.lua b/src/systems/camera-pan.lua new file mode 100644 index 0000000..4bfbb02 --- /dev/null +++ b/src/systems/camera-pan.lua @@ -0,0 +1,20 @@ +local gfx = playdate.graphics + +local focusPriority = {} + +cameraPanSystem = filteredSystem({ focusPriority = T.number, position = T.XyPair }, function(e, dt) + if e.focusPriority >= focusPriority.priority then + focusPriority.position = e.position + end +end) + +function cameraPanSystem:preProcess() + focusPriority.priority = 0 + focusPriority.position = { x = 0, y = 0 } +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) +end diff --git a/src/systems/collision-detection.lua b/src/systems/collision-detection.lua index 8e853da..acf1b42 100644 --- a/src/systems/collision-detection.lua +++ b/src/systems/collision-detection.lua @@ -1,27 +1,34 @@ +collidingEntities = filteredSystem({ + position = T.XyPair, + size = T.XyPair, + canCollideWith = T.bitMask, + 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 cart global(!) may be colliding with. + { 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) - local collider = cart - if not collider.velocity then - return - end - local colliderTop = collider.position.y - local colliderBottom = collider.position.y + collider.size.y - local entityTop = e.position.y - local entityBottom = entityTop + e.size.y + for _, collider in pairs(collidingEntities.entities) do + if (e ~= collider) and collider.canCollideWith 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 + local entityBottom = entityTop + e.size.y - local withinY = (entityTop > colliderTop and entityTop < colliderBottom) - or (entityBottom > colliderTop and entityBottom < colliderBottom) + local withinY = (entityTop > colliderTop and entityTop < colliderBottom) + or (entityBottom > colliderTop and entityBottom < colliderBottom) - if not withinY then - return - end - - 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) + if + withinY + and 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 + end end end end diff --git a/src/systems/collision-resolution.lua b/src/systems/collision-resolution.lua index e41a550..2536654 100644 --- a/src/systems/collision-resolution.lua +++ b/src/systems/collision-resolution.lua @@ -1,37 +1,42 @@ collisionResolution = filteredSystem({ collisionBetween = T.Collision }, function(e, _, system) - local collider, probablyCart = e.collisionBetween[1], e.collisionBetween[2] - local colliderTop = collider.position.y + local collidedInto, collider = e.collisionBetween[1], e.collisionBetween[2] + local colliderTop = collidedInto.position.y - if collider.isSolid then + if collidedInto.isSolid then -- Assumes impact from the top - probablyCart.position.y = colliderTop - probablyCart.size.y + collider.position.y = colliderTop - collider.size.y end - if collider.canBounce and probablyCart.canBeBounced then - probablyCart.velocity.x = probablyCart.velocity.x - + collider.canBounce.flat.x - + probablyCart.canBeBounced.flat.x + if collidedInto.canBounce and collider.canBeBounced then + collider.velocity.x = collider.velocity.x + collidedInto.canBounce.flat.x + collider.canBeBounced.flat.x - probablyCart.velocity.x = probablyCart.velocity.x - * collider.canBounce.mult.x - * probablyCart.canBeBounced.mult.x + collider.velocity.x = collider.velocity.x * collidedInto.canBounce.mult.x * collider.canBeBounced.mult.x -- abs() makes sure we always push upward - probablyCart.velocity.y = math.abs(probablyCart.velocity.y) - + collider.canBounce.flat.y - + probablyCart.canBeBounced.flat.y + collider.velocity.y = math.abs(collider.velocity.y) + + collidedInto.canBounce.flat.y + + collider.canBeBounced.flat.y - probablyCart.velocity.y = probablyCart.velocity.y - * collider.canBounce.mult.y - * probablyCart.canBeBounced.mult.y + collider.velocity.y = collider.velocity.y * collidedInto.canBounce.mult.y * collider.canBeBounced.mult.y end - if collider.score then - Score:add(collider.score) + if collider.stopMovingOnCollision then + print("stopMovingOnCollision!") + collider.velocity = nil + collider.canCollideWith = nil + system.world:addEntity(collider) end - if collider.expireAfterCollision then - system.world:removeEntity(collider) + if collidedInto.score then + Score:add(collidedInto.score) + end + + if collidedInto.expireAfterCollision then + system.world:removeEntity(collidedInto) + end + + if collidedInto.collectable and collider.collectables then + collider.collectables[#collider.collectables + 1] = collidedInto.collectable end system.world:removeEntity(e) diff --git a/src/systems/draw.lua b/src/systems/draw.lua index 229dc2d..30d8b03 100644 --- a/src/systems/draw.lua +++ b/src/systems/draw.lua @@ -6,4 +6,4 @@ end) drawSpriteSystem = filteredSystem({ position = T.XyPair, drawAsSprite = T.PdImage }, function(e, dt, system) e.drawAsSprite:draw(e.position.x, e.position.y) -end) \ No newline at end of file +end) diff --git a/src/systems/filter-types.lua b/src/systems/filter-types.lua index f8c3297..14004ad 100644 --- a/src/systems/filter-types.lua +++ b/src/systems/filter-types.lua @@ -10,10 +10,14 @@ local Entity = {} ---@type XyPair local XyPair = { x = 1, y = 1 } +---@alias BitMask number + T = { XyPair = XyPair, bool = true, number = 0, + ---@type BitMask + bitMask = 0, numberArray = { 1, 2, 3 }, str = "", marker = {}, diff --git a/src/systems/gravity.lua b/src/systems/gravity.lua index 362ea30..fbde43f 100644 --- a/src/systems/gravity.lua +++ b/src/systems/gravity.lua @@ -1,4 +1,4 @@ local G = -300 -fallSystem = filteredSystem({ velocity = T.XyPair, mass = T.marker }, function(e, dt) - e.velocity.y = e.velocity.y - (G * dt) - (0.5 * dt * dt) +fallSystem = filteredSystem({ velocity = T.XyPair, mass = T.number }, function(e, dt) + e.velocity.y = e.velocity.y - (G * dt * e.mass) - (0.5 * dt * dt) end) diff --git a/src/systems/velocity.lua b/src/systems/velocity.lua index 4236caa..c55e9f4 100644 --- a/src/systems/velocity.lua +++ b/src/systems/velocity.lua @@ -1,9 +1,16 @@ local sqrt = math.sqrt -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 +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 then e.velocity = nil - world:addEntity(e) + if e.spawnEntitiesWhenStopped then + e:spawnEntitiesWhenStopped(system.world) + e.spawnEntitiesWhenStopped = nil + end + system.world:addEntity(e) else e.position.x = e.position.x + (e.velocity.x * dt) e.position.y = e.position.y + (e.velocity.y * dt) diff --git a/src/tiny-tools.lua b/src/tiny-tools.lua index df08bf9..ebcd3c4 100644 --- a/src/tiny-tools.lua +++ b/src/tiny-tools.lua @@ -12,7 +12,7 @@ local isSimulator = playdate.isSimulator ---@generic T ---@param shape T ---@param process fun(entity: T, dt: number, system: System) ----@return +---@return System | { entities: T[] } function filteredSystem(shape, process) local system = tiny.processingSystem() local keys = {} @@ -23,6 +23,9 @@ function filteredSystem(shape, process) end end system.filter = tiny.requireAll(table.unpack(keys)) + if not process then + return system + end if isSimulator then -- local acceptableKeys = "" -- for _, key in ipairs(keys) do