Start sketching out how scoring might work.

Import some fixes from tiny-ecs-love-template.
Add some simple sprites for dice.
Sketch out some basic bus/slot logic.
Implement simple sprite animation system.
This commit is contained in:
Sage Vaillancourt 2025-03-22 01:06:56 -04:00
parent ab655c1b8b
commit cb7c3ac779
19 changed files with 349 additions and 32 deletions

BIN
assets/images/DiceFive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 B

BIN
assets/images/DiceFour.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

BIN
assets/images/DiceOne.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

BIN
assets/images/DiceSix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

BIN
assets/images/DiceThree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

BIN
assets/images/DiceTwo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

33
components.lua Normal file
View File

@ -0,0 +1,33 @@
local components = {}
---@param entity Entity
function components.allowedInEngine(entity)
entity.allowedInEngine = T.marker
end
---@param entity Entity
function components.allowedInWheels(entity)
entity.allowedInWheels = T.marker
end
---@param entity Entity
function components.allowedInPropulsion(entity)
entity.allowedInPropulsion = T.marker
end
---@param entity Entity
function components.allowedInSeating(entity)
entity.allowedInSeating = T.marker
end
---@param entity Entity
function components.allowedInStyling(entity)
entity.allowedInStyling = T.marker
end
---@param entity Entity
function components.allowedInBrakes(entity)
entity.allowedInBrakes = T.marker
end
return components

View File

@ -1,8 +1,12 @@
require("../systems/animation")
require("../systems/camera-pan") require("../systems/camera-pan")
require("../systems/collision-detection") require("../systems/collision-detection")
require("../systems/collision-resolution") require("../systems/collision-resolution")
require("../systems/decay") require("../systems/decay")
require("../systems/diceRoll")
require("../systems/draw") require("../systems/draw")
require("../systems/gravity") require("../systems/gravity")
require("../systems/input") require("../systems/input")
require("../systems/scoreDisplay")
require("../systems/upgradeScorer")
require("../systems/velocity") require("../systems/velocity")

View File

@ -1,11 +1,35 @@
-- GENERATED FILE - DO NOT EDIT -- GENERATED FILE - DO NOT EDIT
-- Instead, edit the source file directly: assets.lua2p. -- Instead, edit the source file directly: assets.lua2p.
-- luacheck: ignore
---@type love.Texture
DiceFive = love.graphics.newImage("assets/images/DiceFive.png")
-- luacheck: ignore
---@type love.Texture
DiceFour = love.graphics.newImage("assets/images/DiceFour.png")
-- luacheck: ignore
---@type love.Texture
DiceOne = love.graphics.newImage("assets/images/DiceOne.png")
-- luacheck: ignore
---@type love.Texture
DiceSix = love.graphics.newImage("assets/images/DiceSix.png")
-- luacheck: ignore
---@type love.Texture
DiceThree = love.graphics.newImage("assets/images/DiceThree.png")
-- luacheck: ignore
---@type love.Texture
DiceTwo = love.graphics.newImage("assets/images/DiceTwo.png")
-- luacheck: ignore -- luacheck: ignore
---@type FontData ---@type fun(fontSize: number | nil): love.Font
EtBt7001Z0xa = function(fontSize) EtBt7001Z0xa = function(fontSize)
return love.graphics.newFont("assets/fonts/EtBt7001Z0xa.ttf", fontSize) return love.graphics.newFont("assets/fonts/EtBt7001Z0xa.ttf", fontSize)
end end

View File

@ -25,9 +25,11 @@ function generatedFileWarning()
return "-- GENERATED FILE - DO NOT EDIT\n-- Instead, edit the source file directly: assets.lua2p." return "-- GENERATED FILE - DO NOT EDIT\n-- Instead, edit the source file directly: assets.lua2p."
end)!!(generatedFileWarning()) end)!!(generatedFileWarning())
!!(dirLookup('assets/images', 'png', 'love.graphics.newImage', 'Image')) !!(dirLookup('assets/images', 'png', 'love.graphics.newImage', 'love.Texture'))
!!(dirLookup('assets/sounds', 'wav', 'love.sound.newSoundData', 'SoundData')) !!(dirLookup('assets/sounds', 'ogg', 'love.audio.newSource', 'love.Source', function(varName, newFunc, file)
!!(dirLookup('assets/music', 'wav', 'love.sound.newSoundData', 'SoundData')) return varName .. ' = ' .. newFunc .. '("' .. file .. '", "static")'
!!(dirLookup('assets/fonts', 'ttf', 'love.graphics.newFont', 'FontData', function(varName, newFunc, file) end))
!!(dirLookup('assets/music', 'wav', 'love.sound.newSoundData', 'love.SoundData'))
!!(dirLookup('assets/fonts', 'ttf', 'love.graphics.newFont', 'fun(fontSize: number | nil): love.Font', function(varName, newFunc, file)
return varName .. ' = function(fontSize)\n return ' .. newFunc .. '("' .. file .. '", fontSize)\nend' return varName .. ' = function(fontSize)\n return ' .. newFunc .. '("' .. file .. '", fontSize)\nend'
end)) end))

View File

@ -8,6 +8,7 @@ local SOME_TABLE = {}
---@alias AnyComponent any ---@alias AnyComponent any
---@alias BitMask number ---@alias BitMask number
---@alias Collision { collidedInto: Entity, collider: Entity } ---@alias Collision { collidedInto: Entity, collider: Entity }
---@alias Drawable love.Drawable
---@alias Entity table ---@alias Entity table
---@alias FontData love.FontData ---@alias FontData love.FontData
---@alias KeyState table<string, boolean> ---@alias KeyState table<string, boolean>
@ -31,6 +32,9 @@ T = {
---@type Collision ---@type Collision
Collision = SOME_TABLE, Collision = SOME_TABLE,
---@type Drawable
Drawable = SOME_TABLE,
---@type Entity ---@type Entity
Entity = SOME_TABLE, Entity = SOME_TABLE,

View File

@ -60,6 +60,7 @@ local SOME_TABLE = {}
Collision = "{ collidedInto: Entity, collider: Entity }", Collision = "{ collidedInto: Entity, collider: Entity }",
BitMask = "number", BitMask = "number",
FontData = "love.FontData", FontData = "love.FontData",
Drawable = "love.Drawable",
KeyState = "table<string, boolean>", KeyState = "table<string, boolean>",
})) }))
T = { T = {

View File

@ -4,42 +4,38 @@ require("generated/filter-types")
require("generated/assets") require("generated/assets")
require("generated/all-systems") require("generated/all-systems")
UiZIndex = math.huge
local smallFont = EtBt7001Z0xa(12)
local scenarios = require("scenarios")
local world = require("world") local world = require("world")
local scenarios = { local currentScenario = scenarios.default
default = function()
-- TODO: Add default entities
end,
textTestScenario = function()
world:addEntity({
position = { x = 0, y = 600 },
drawAsText = {
text = "Hello, world!",
style = TextStyle.Inverted,
},
velocity = { x = 240, y = -500 },
mass = 1,
decayAfterSeconds = 10,
})
end,
}
local currentScenario = scenarios.textTestScenario
local freeze = false local freeze = false
local delta local delta
local playerBus
function love.load() function love.load()
currentScenario() playerBus = currentScenario(world)
love.graphics.setBackgroundColor(1, 1, 1) love.graphics.setBackgroundColor(0, 0, 0)
love.graphics.setFont(EtBt7001Z0xa(32)) love.graphics.setFont(EtBt7001Z0xa(32))
end end
local nth = 1
world:addEntity({
triggerDiceRoll = "",
})
function love.update(dt) function love.update(dt)
delta = dt delta = dt
if love.keyboard.isDown("r") then if love.keyboard.isDown("r") then
world:clearEntities() world:clearEntities()
currentScenario() playerBus = currentScenario(world)
freeze = false freeze = false
end end
@ -51,6 +47,18 @@ function love.update(dt)
return return
end end
-- if nth == 3 then
-- world:addEntity({
-- scoringTrigger = {
-- mask = 1,
-- target = playerBus,
-- },
-- })
-- nth = 1
-- else
-- nth = nth + 1
-- end
world:update(delta, function(_, system) world:update(delta, function(_, system)
if system.deferToEnd then if system.deferToEnd then
return false return false
@ -60,6 +68,10 @@ function love.update(dt)
end end
function love.draw() function love.draw()
love.graphics.setColor(1, 1, 1)
love.graphics.setFont(smallFont)
love.graphics.print("FPS: "..tostring(love.timer.getFPS( )), 10, 10)
world:update(delta, function(_, system) world:update(delta, function(_, system)
if system.deferToEnd then if system.deferToEnd then
return false return false

81
scenarios.lua Normal file
View File

@ -0,0 +1,81 @@
local scenarios = {}
local width, height = love.graphics.getWidth(), love.graphics.getHeight()
local bigFont = EtBt7001Z0xa(96)
local bigHeight = bigFont:getHeight()
local midFont = EtBt7001Z0xa(32)
local midHeight = midFont:getHeight()
function BitAt(n)
return math.floor(2 ^ (n - 1))
end
---@param world World
function scenarios.default(world)
-- TODO: Add default entities
local busText = "BUS"
local textWidth = bigFont:getWidth(busText)
local playerScoreDisplay = world:addEntity({
position = { x = width - 100, y = 20 },
z = UiZIndex,
drawAsText = {
text = "0",
style = TextStyle.Inverted,
font = midFont,
},
})
local bus = world:addEntity({
position = { x = 400, y = (height - bigHeight) / 2 },
drawAsText = {
font = bigFont,
text = busText,
style = TextStyle.Inverted,
},
score = 0,
scoreDisplay = playerScoreDisplay,
slots = {},
})
local slotNum = 0
local function buildSlot(text, otherEntityData)
otherEntityData.position = { x = 20, y = 40 + (slotNum * midHeight * 1.2) }
otherEntityData.drawAsText = {
font = midFont,
text = text,
style = TextStyle.Inverted,
}
otherEntityData.upgrades = {}
bus.slots[#bus.slots + 1] = world:addEntity(otherEntityData)
slotNum = slotNum + 1
return otherEntityData
end
local wheels = buildSlot("Wheels", {
scoredOn = BitAt(1),
})
wheels.upgrades[#wheels.upgrades + 1] = {
flatScore = 1,
}
buildSlot("Propulsion", {
scoredOn = BitAt(2),
})
buildSlot("Passengers", {
scoredOn = BitAt(3),
})
buildSlot("Style", {
scoredOn = BitAt(4),
})
buildSlot("Brakes", {
scoredOn = BitAt(5),
})
buildSlot("Engine", {
scoredOn = BitAt(6),
})
return bus
end
return scenarios

41
systems/animation.lua Normal file
View File

@ -0,0 +1,41 @@
local world = require("world")
local function getCurrentTimeMilliseconds()
return love.timer.getTime() * 1000
end
local Animation = {
animation = {
initialMs = T.number,
msPerFrame = T.number,
frames = Arr({ drawable = T.Drawable }),
},
position = T.XyPair,
drawAsSprite = T.Drawable
}
---@return { initialMs: number, frames: Entity[], msPerFrame: number }
function BuildAnimation(frames, msPerFrame)
return {
initialMs = getCurrentTimeMilliseconds(),
frames = frames,
msPerFrame = msPerFrame,
}
end
local currentMs = getCurrentTimeMilliseconds()
local animation = world:filteredSystem("animation", Animation, function(e, _, system)
local nextFrameIndex = 1 + math.floor((currentMs - e.animation.initialMs) / e.animation.msPerFrame)
if nextFrameIndex > #e.animation.frames then
e.animation = nil
system.world:addEntity(e)
else
e.drawAsSprite = e.animation.frames[nextFrameIndex]
end
end)
function animation:preProcess()
currentMs = getCurrentTimeMilliseconds()
end

27
systems/diceRoll.lua Normal file
View File

@ -0,0 +1,27 @@
local world = require("world")
local width, height = love.graphics.getWidth(), love.graphics.getHeight()
local dice = {
DiceOne,
DiceTwo,
DiceThree,
DiceFour,
DiceFive,
DiceSix,
}
world:filteredSystem("dice", { triggerDiceRoll = T.marker }, function(e, dt, system)
system.world:removeEntity(e)
local frames = {}
for _ = 1, 20 do
frames[#frames + 1] = dice[math.random(#dice)]
end
system.world:addEntity({
position = {
x = 20,
y = height - DiceOne:getHeight() - 20,
},
drawAsSprite = dice[math.random(#dice)],
animation = BuildAnimation(frames, 100)
})
end)

View File

@ -40,7 +40,13 @@ drawSystem(
"drawText", "drawText",
{ position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.FontData) } }, { position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.FontData) } },
function(e) function(e)
local font = e.drawAsText.font or gfx.getFont() -- e.drawAsText.font or AshevilleSans14Bold local font
if e.drawAsText.font ~= nil then
font = e.drawAsText.font
gfx.setFont(font)
else
font = gfx.getFont()
end
local textHeight = font:getHeight() local textHeight = font:getHeight()
local textWidth = font:getWidth(e.drawAsText.text) local textWidth = font:getWidth(e.drawAsText.text)
@ -49,14 +55,14 @@ drawSystem(
local bgWidth, bgHeight = textWidth + (margin * 2), textHeight + 2 local bgWidth, bgHeight = textWidth + (margin * 2), textHeight + 2
if e.drawAsText.style == TextStyle.Inverted then if e.drawAsText.style == TextStyle.Inverted then
gfx.setColor(0, 0, 0) gfx.setColor(1, 1, 1)
gfx.rectangle("fill", bgLeftEdge, bgTopEdge, textWidth + margin, textHeight + 2) gfx.rectangle("fill", bgLeftEdge, bgTopEdge, textWidth + margin, textHeight + 2)
gfx.setColor(1, 1, 1) gfx.setColor(0, 0, 0)
elseif e.drawAsText.style == TextStyle.Bordered then elseif e.drawAsText.style == TextStyle.Bordered then
gfx.setColor(1, 1, 1) gfx.setColor(0, 0, 0)
gfx.rectangle("fill", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) gfx.rectangle("fill", bgLeftEdge, bgTopEdge, bgWidth, bgHeight)
gfx.setColor(0, 0, 0) gfx.setColor(1, 1, 1)
gfx.rectangle("line", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) gfx.rectangle("line", bgLeftEdge, bgTopEdge, bgWidth, bgHeight)
end end

40
systems/scoreDisplay.lua Normal file
View File

@ -0,0 +1,40 @@
local world = require("world")
local width, height = love.graphics.getWidth(), love.graphics.getHeight()
local midFont = EtBt7001Z0xa(32)
local AddScore = {
addingScoreTo = {
score = T.number,
scoreDisplay = Maybe({ drawAsText = { text = T.str } }),
},
scoreToAdd = T.number,
}
local z = 1
world:filteredSystem("score", { addScore = AddScore }, function(e, _, system)
system.world:removeEntity(e)
local target, scoreToAdd = e.addScore.addingScoreTo, e.addScore.scoreToAdd
target.score = target.score + scoreToAdd
if target.scoreDisplay ~= nil then
target.scoreDisplay.drawAsText.text = "" .. target.score
system.world:addEntity({
z = z,
drawAsText = {
text = "+" .. scoreToAdd,
style = TextStyle.Inverted,
font = midFont,
},
position = {
x = target.scoreDisplay.position.x,
y = target.scoreDisplay.position.y,
},
velocity = { x = 100 * (math.random() - 0.5), y = -100 },
mass = 1,
decayAfterSeconds = 3,
})
-- Prevents z-fighting of falling score updates
z = (z + 1) % 1000
end
end)

42
systems/upgradeScorer.lua Normal file
View File

@ -0,0 +1,42 @@
local world = require("world")
local width, height = love.graphics.getWidth(), love.graphics.getHeight()
---@class ScoringTarget
local ScoringTarget = {
slots = Arr({
upgrades = Arr(T.Entity),
}),
score = T.number,
}
local ScoringTrigger = {
mask = T.number,
target = ScoringTarget,
}
--- Probably need a way to access the total so far.
---@param w World
---@param target ScoringTarget
local function addSlotScores(w, target, slotIndex)
local upgrades = target.slots[slotIndex].upgrades
for i, upgrade in ipairs(upgrades) do
if upgrade.flatScore ~= nil then
w:addEntity({
addScore = {
addingScoreTo = target,
scoreToAdd = upgrade.flatScore,
},
})
end
end
end
world:filteredSystem("upgradeScorer", { scoringTrigger = ScoringTrigger }, function(e, _, system)
system.world:removeEntity(e)
local target, mask = e.scoringTrigger.target, e.scoringTrigger.mask
for i, slot in ipairs(target.slots) do
if bit.band(BitAt(i), mask) ~= 0 then
addSlotScores(system.world, target, i)
end
end
end)