Better ECS-ization
Added Tomatoes. Separated collision detection and resolution.
This commit is contained in:
parent
af5b494cdc
commit
22ff80cea7
|
@ -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
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
Booster = {}
|
||||
|
||||
local gfx <const> = 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
|
||||
|
|
@ -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
|
63
src/main.lua
63
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 <const> = 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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
|
@ -1,3 +1,9 @@
|
|||
drawSystem = tiny.filteredSystem({ draw = T.SelfFunction }, function(e, dt)
|
||||
local gfx <const> = 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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue