diff --git a/Makefile b/Makefile index e825dcf..d09cc46 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ all: pdc --skip-unknown src Luncher.pdx assets: - # lua lib/preprocess-cl.lua src/assets.lua2p + lua lib/preprocess-cl.lua src/assets.lua2p check: assets stylua -c --indent-type Spaces src/ diff --git a/README.md b/README.md index 23438e5..a7f765e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # Luncher -Build the BIGGEST burger by **lunching** it through *delicious* ingredients. +Load up the BIGGEST cart by **lunching** it through *delicious* ingredients. diff --git a/src/assets.lua b/src/assets.lua new file mode 100644 index 0000000..fd14996 --- /dev/null +++ b/src/assets.lua @@ -0,0 +1,29 @@ +-- GENERATED FILE - DO NOT EDIT +-- Instead, edit the source file directly: assets.lua2p. + +-- luacheck: ignore +---@type pd_image +CartSprite = playdate.graphics.image.new("assets/images/CartSprite.png") + +-- luacheck: ignore +---@type pd_image +CheeseSprite = playdate.graphics.image.new("assets/images/CheeseSprite.png") + +-- luacheck: ignore +---@type pd_image +LettuceSprite = playdate.graphics.image.new("assets/images/LettuceSprite.png") + +-- luacheck: ignore +---@type pd_image +MushroomSprite = playdate.graphics.image.new("assets/images/MushroomSprite.png") + +-- luacheck: ignore +---@type pd_image +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)) diff --git a/src/assets.lua2p b/src/assets.lua2p new file mode 100644 index 0000000..99a0f6c --- /dev/null +++ b/src/assets.lua2p @@ -0,0 +1,33 @@ +!(function dirLookup(dir, extension, newFunc, type, sep, indent, handle) + indent = indent or "" + sep = sep or "\n\n" + handle = handle ~= nil and handle or function(varName, value) + return varName .. ' = ' .. value + end + + local p = io.popen('find src/' .. dir .. ' -maxdepth 1 -type f | sort -h') + + local assetCode = "" + --Loop through all files + for file in p:lines() do + if file:find(extension) then + local varName = file:gsub(".*/(.*)." .. extension, "%1") + file = file:gsub("src/", "") + assetCode = assetCode .. indent .. '-- luacheck: ignore\n' + assetCode = assetCode .. indent .. '---@type ' .. type ..'\n' + assetCode = assetCode .. indent .. handle(varName, newFunc .. '("' .. file .. '")') .. sep + end + end + return assetCode +end +function generatedFileWarning() + -- Only in a function to make clear that THIS .lua2p is not the generated file! + return "-- GENERATED FILE - DO NOT EDIT\n-- Instead, edit the source file directly: assets.lua2p." +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)) diff --git a/src/assets/images/CartSprite.png b/src/assets/images/CartSprite.png new file mode 100644 index 0000000..d45eebc Binary files /dev/null and b/src/assets/images/CartSprite.png differ diff --git a/src/assets/images/CheeseSprite.png b/src/assets/images/CheeseSprite.png new file mode 100644 index 0000000..5766c77 Binary files /dev/null and b/src/assets/images/CheeseSprite.png differ diff --git a/src/assets/images/LettuceSprite.png b/src/assets/images/LettuceSprite.png new file mode 100644 index 0000000..793cd72 Binary files /dev/null and b/src/assets/images/LettuceSprite.png differ diff --git a/src/assets/images/MushroomSprite.png b/src/assets/images/MushroomSprite.png new file mode 100644 index 0000000..bee4471 Binary files /dev/null and b/src/assets/images/MushroomSprite.png differ diff --git a/src/assets/images/TomatoSprite.png b/src/assets/images/TomatoSprite.png new file mode 100644 index 0000000..e42477f Binary files /dev/null and b/src/assets/images/TomatoSprite.png differ diff --git a/src/burger.lua b/src/burger.lua deleted file mode 100644 index ddfa692..0000000 --- a/src/burger.lua +++ /dev/null @@ -1,32 +0,0 @@ -Burger = {} - -local gfx = playdate.graphics - -function Burger.reset(o) - o.position = { - x = 20, - y = 50, - } - o.velocity = { - 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 - -function Burger.new() - return setmetatable(Burger.reset({}), { __index = Burger }) -end - -function Burger:draw() - gfx.fillCircleAtPoint(burger.position.x, burger.position.y, 10) -end diff --git a/src/cart.lua b/src/cart.lua new file mode 100644 index 0000000..6dbc90a --- /dev/null +++ b/src/cart.lua @@ -0,0 +1,27 @@ +Cart = {} + +local sizeX, sizeY = CartSprite:getSize() +local size = { x = sizeX, y = sizeY * 0.75 } + +function Cart.reset(o) + o.position = { + x = 20, + y = 50, + } + o.velocity = { + x = 50 + (150 * math.random()), + y = 150 * (math.random() - 1), + } + o.size = size + o.canBeBounced = { + flat = { x = 0, y = 0 }, + mult = { x = 1, y = 1 }, + } + o.mass = T.marker + o.drawAsSprite = CartSprite + return o +end + +function Cart.new() + return setmetatable(Cart.reset({}), { __index = Cart }) +end diff --git a/src/ingredients/booster.lua b/src/ingredients/booster.lua deleted file mode 100644 index 89048d5..0000000 --- a/src/ingredients/booster.lua +++ /dev/null @@ -1,45 +0,0 @@ -Booster = {} - -local function newBooster(x, y) - local size = { x = 40, y = 10 } - return { - score = 1, - expireAfterCollision = true, - size = size, - position = { x = x, y = y }, - canBounce = { - flat = { x = 122, y = 190 }, - mult = { x = 1, y = -0.5 }, - }, - drawAsRectangle = { size = size }, - } -end - -boosterIndex = 1 -boosterCache = {} -for i = 1, 100 do - boosterCache[i] = newBooster(-999, 999) -end - -function Booster.cacheSize() - return #boosterCache -end - --- Boosters should be "initialized" almost always increasing in X value -function Booster.get(x, y) - local booster = boosterCache[boosterIndex] - booster.position.x = x - booster.position.y = y - - boosterIndex = boosterIndex + 1 - if boosterIndex > #boosterCache then - boosterIndex = 1 - end - - return booster -end - --- Assumes that at least one Booster has already been created. -function Booster.newestBooster() - return boosterCache[boosterIndex] -end diff --git a/src/ingredients/cheese.lua b/src/ingredients/cheese.lua new file mode 100644 index 0000000..17ed97d --- /dev/null +++ b/src/ingredients/cheese.lua @@ -0,0 +1,22 @@ +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.expireAfterCollision = true + o.size = size + o.position = { x = x, y = y } + o.drawAsSprite = CheeseSprite + o.expireWhenOffScreenBy = expireWhenOffScreenBy + o.canBounce = canBounce + newestBooster = o + return newestBooster +end diff --git a/src/ingredients/ingredients.lua b/src/ingredients/ingredients.lua new file mode 100644 index 0000000..aad9298 --- /dev/null +++ b/src/ingredients/ingredients.lua @@ -0,0 +1,50 @@ +import("ingredients/cheese.lua") +import("ingredients/lettuce.lua") +import("ingredients/mushroom.lua") +import("ingredients/tomato.lua") + +---@class +Ingredients = {} + +local ingredientCache = {} + +local _ingredientCacheIndex = 1 +local maxCache = 100 +for i = 1, maxCache do + ingredientCache[i] = {} +end + +local newestIngredient = {} +function Ingredients.newestIngredient() + assert(newestIngredient.position ~= nil, "All ingredients are implicitly required to have a position component.") + return newestIngredient +end + +function Ingredients.cacheSize() + return maxCache +end + +function Ingredients.getNext(x, y) + local index = _ingredientCacheIndex + _ingredientCacheIndex = _ingredientCacheIndex + 1 + if _ingredientCacheIndex >= maxCache then + _ingredientCacheIndex = 1 + end + + local o = ingredientCache[index] + 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.1 then + return Mushroom.initialize(o, x, 218 + math.random(1, 5)) + else + return Cheese.initialize(o, x, y) + end +end diff --git a/src/ingredients/lettuce.lua b/src/ingredients/lettuce.lua new file mode 100644 index 0000000..f28f284 --- /dev/null +++ b/src/ingredients/lettuce.lua @@ -0,0 +1,22 @@ +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.expireAfterCollision = true + o.size = size + o.position = { x = x, y = y } + o.drawAsSprite = LettuceSprite + o.expireWhenOffScreenBy = expireWhenOffScreenBy + o.canBounce = canBounce + newestBooster = o + return newestBooster +end diff --git a/src/ingredients/mushroom.lua b/src/ingredients/mushroom.lua new file mode 100644 index 0000000..c251e1f --- /dev/null +++ b/src/ingredients/mushroom.lua @@ -0,0 +1,22 @@ +Mushroom = {} + +local sizeX, sizeY = MushroomSprite:getSize() +local size = { x = sizeX, y = sizeY } +local expireWhenOffScreenBy = { x = 2000, y = 480 } + +local canBounce = { + flat = { x = 0, y = 190 }, + mult = { x = 1, y = -2 }, +} + +function Mushroom.initialize(o, x, y) + o.score = 5 + o.expireAfterCollision = true + o.size = size + o.position = { x = x, y = y } + o.drawAsSprite = MushroomSprite + o.expireWhenOffScreenBy = expireWhenOffScreenBy + o.canBounce = canBounce + newestBooster = o + return newestBooster +end diff --git a/src/ingredients/tomato.lua b/src/ingredients/tomato.lua index 8bd8608..f4b65c9 100644 --- a/src/ingredients/tomato.lua +++ b/src/ingredients/tomato.lua @@ -1,36 +1,16 @@ 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 +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.expireAfterCollision = true + o.size = size + o.position = { x = x, y = y } + o.drawAsSprite = TomatoSprite + o.expireWhenOffScreenBy = expireWhenOffScreenBy + newestBooster = o + return newestBooster end diff --git a/src/main.lua b/src/main.lua index d7dcc46..32ff6a8 100644 --- a/src/main.lua +++ b/src/main.lua @@ -11,22 +11,22 @@ import 'CoreLibs/utilities/where.lua' import("../lib/tiny.lua") import("tiny-tools.lua") +import("assets.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("ingredients/booster.lua") -import("ingredients/tomato.lua") -import("burger.lua") +import("ingredients/ingredients.lua") +import("cart.lua") local tiny = tiny local gfx = playdate.graphics playdate.display.setRefreshRate(50) gfx.setBackgroundColor(gfx.kColorWhite) -burger = Burger.new() +cart = Cart.new() local floorSize = { x = 10000, y = 10 } floor = { position = { x = 0, y = 235 }, @@ -40,6 +40,7 @@ floor = { } Camera = { + --- The upper-left corner of the Camera is represented by this `pan` value pan = { x = 0, y = 0, @@ -65,38 +66,43 @@ world = tiny.world( velocitySystem, collisionResolution, collisionDetection, - drawSystem, - drawRectanglesSystem, - burger, + drawRectanglesSystem, + drawSpriteSystem, + + cart, floor ) -for i = 1, Booster.cacheSize() do - world:addEntity(Booster.get(i * 60 + (math.random(0, 50)), math.random(50, 200))) +local ingredientsEveryX = 50 + +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() + function playdate.update() local deltaSeconds = playdate.getElapsedTime() playdate.resetElapsedTime() gfx.clear(gfx.kColorWhite) playdate.drawFPS(5, 5) - Camera.pan.x = math.min(0, -burger.position.x + 200) - Camera.pan.y = math.max(0, -burger.position.y + 120) - local newestX = Booster.newestBooster().position.x - local panX = -Camera.pan.x + Camera.pan.x = math.max(0, cart.position.x - 200) + Camera.pan.y = math.min(0, cart.position.y - 120) + + local newestX = Ingredients.newestIngredient().position.x + local panX = Camera.pan.x + local rightEdge = panX + 400 local offset = 600 - 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 + 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) + floor.position.x = Camera.pan.x - offset + gfx.setDrawOffset(-Camera.pan.x, -Camera.pan.y) world:update(deltaSeconds) @@ -104,9 +110,7 @@ function playdate.update() Score:draw() if playdate.buttonJustPressed(playdate.kButtonA) then - Burger.reset(burger) - for i = 1, Booster.cacheSize() do - Booster.get(i * 60 + (math.random(0, 50)), math.random(50, 200)) - end + Cart.reset(cart) + init() end end diff --git a/src/systems/collision-detection.lua b/src/systems/collision-detection.lua index 744311a..8e853da 100644 --- a/src/systems/collision-detection.lua +++ b/src/systems/collision-detection.lua @@ -1,8 +1,8 @@ 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. + -- Here, the entity, e, refers to some entity that the cart global(!) may be colliding with. function(e, _, system) - local collider = burger + local collider = cart if not collider.velocity then return end diff --git a/src/systems/collision-resolution.lua b/src/systems/collision-resolution.lua index 86379ee..e41a550 100644 --- a/src/systems/collision-resolution.lua +++ b/src/systems/collision-resolution.lua @@ -1,29 +1,29 @@ collisionResolution = filteredSystem({ collisionBetween = T.Collision }, function(e, _, system) - local collider, probablyBurger = e.collisionBetween[1], e.collisionBetween[2] + local collider, probablyCart = 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 + probablyCart.position.y = colliderTop - probablyCart.size.y end - if collider.canBounce and probablyBurger.canBeBounced then - probablyBurger.velocity.x = probablyBurger.velocity.x + if collider.canBounce and probablyCart.canBeBounced then + probablyCart.velocity.x = probablyCart.velocity.x + collider.canBounce.flat.x - + probablyBurger.canBeBounced.flat.x + + probablyCart.canBeBounced.flat.x - probablyBurger.velocity.x = probablyBurger.velocity.x + probablyCart.velocity.x = probablyCart.velocity.x * collider.canBounce.mult.x - * probablyBurger.canBeBounced.mult.x + * probablyCart.canBeBounced.mult.x -- abs() makes sure we always push upward - probablyBurger.velocity.y = math.abs(probablyBurger.velocity.y) + probablyCart.velocity.y = math.abs(probablyCart.velocity.y) + collider.canBounce.flat.y - + probablyBurger.canBeBounced.flat.y + + probablyCart.canBeBounced.flat.y - probablyBurger.velocity.y = probablyBurger.velocity.y + probablyCart.velocity.y = probablyCart.velocity.y * collider.canBounce.mult.y - * probablyBurger.canBeBounced.mult.y + * probablyCart.canBeBounced.mult.y end if collider.score then diff --git a/src/systems/draw.lua b/src/systems/draw.lua index fd293a8..229dc2d 100644 --- a/src/systems/draw.lua +++ b/src/systems/draw.lua @@ -1,9 +1,9 @@ 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) + +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 diff --git a/src/systems/filter-types.lua b/src/systems/filter-types.lua index 14c0439..f8c3297 100644 --- a/src/systems/filter-types.lua +++ b/src/systems/filter-types.lua @@ -7,9 +7,11 @@ ---@type Entity local Entity = {} +---@type XyPair +local XyPair = { x = 1, y = 1 } + T = { - ---@type XyPair - XyPair = { x = 1, y = 1 }, + XyPair = XyPair, bool = true, number = 0, numberArray = { 1, 2, 3 }, @@ -20,16 +22,18 @@ T = { --- Actor CanBounce = { isSolid = true, - flat = { x = 1, y = 1 }, - mult = { x = 1, y = 1 }, + flat = XyPair, + mult = XyPair, }, --- Receiver CanBeBounced = { - flat = { x = 1, y = 1 }, - mult = { x = 1, y = 1 }, + flat = XyPair, + mult = XyPair, }, ---@type Collision Collision = { Entity, Entity }, + ---@type pd_image + PdImage = {}, } ---@generic T diff --git a/src/systems/offscreen.lua b/src/systems/offscreen.lua new file mode 100644 index 0000000..67513b9 --- /dev/null +++ b/src/systems/offscreen.lua @@ -0,0 +1,20 @@ +local screenWidth, screenHeight = 400, 240 + +offscreenSystem = filteredSystem({ position = T.XyPair, expireWhenOffScreenBy = T.XyPair }, function(e, dt, system) + -- if Camera.pan.x - e.expireWhenOffScreenBy.x > e.position.x then + -- print("Fell behind") + -- system.world:removeEntity(e) + -- end + -- if Camera.pan.x + screenWidth + e.expireWhenOffScreenBy.x < e.position.x then + -- print("Too far ahead") + -- system.world:removeEntity(e) + -- end + -- if Camera.pan.y - e.expireWhenOffScreenBy.y > e.position.y then + -- print("Too high") + -- system.world:removeEntity(e) + -- end + -- if Camera.pan.y + screenHeight + e.expireWhenOffScreenBy.y < e.position.y then + -- print("Too low") + -- system.world:removeEntity(e) + -- end +end)