From cb7c3ac7799ed1e6d2d7e01e5f69d9aae51d7ed7 Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Sat, 22 Mar 2025 01:06:56 -0400 Subject: [PATCH] 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. --- assets/images/DiceFive.png | Bin 0 -> 687 bytes assets/images/DiceFour.png | Bin 0 -> 631 bytes assets/images/DiceOne.png | Bin 0 -> 497 bytes assets/images/DiceSix.png | Bin 0 -> 650 bytes assets/images/DiceThree.png | Bin 0 -> 621 bytes assets/images/DiceTwo.png | Bin 0 -> 567 bytes components.lua | 33 ++++++++++++++ generated/all-systems.lua | 4 ++ generated/assets.lua | 26 ++++++++++- generated/assets.lua2p | 10 +++-- generated/filter-types.lua | 4 ++ generated/filter-types.lua2p | 1 + main.lua | 56 ++++++++++++++---------- scenarios.lua | 81 +++++++++++++++++++++++++++++++++++ systems/animation.lua | 41 ++++++++++++++++++ systems/diceRoll.lua | 27 ++++++++++++ systems/draw.lua | 16 ++++--- systems/scoreDisplay.lua | 40 +++++++++++++++++ systems/upgradeScorer.lua | 42 ++++++++++++++++++ 19 files changed, 349 insertions(+), 32 deletions(-) create mode 100644 assets/images/DiceFive.png create mode 100644 assets/images/DiceFour.png create mode 100644 assets/images/DiceOne.png create mode 100644 assets/images/DiceSix.png create mode 100644 assets/images/DiceThree.png create mode 100644 assets/images/DiceTwo.png create mode 100644 components.lua create mode 100644 scenarios.lua create mode 100644 systems/animation.lua create mode 100644 systems/diceRoll.lua create mode 100644 systems/scoreDisplay.lua create mode 100644 systems/upgradeScorer.lua diff --git a/assets/images/DiceFive.png b/assets/images/DiceFive.png new file mode 100644 index 0000000000000000000000000000000000000000..e15f3170ba66961fff2138fe22a468cc96ca43f6 GIT binary patch literal 687 zcmV;g0#N;lP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;wH)0002_L%V+f000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2k8SA1S$tPOJgno00Hw!L_t(|+U?v+a>7syL{UE%``}>s0HP89%@>JJBsk&kb(~3jzZ!PKnM^(2#_JV z?L7GX{$0SWwVdsYviB#1rytr7&ZmMa49H(VbOVzSTp>W-fl(PsW^kREFe2Vx0Y>ES zP-PVhuCqdPS96K`kOA(9Iu0OH91@*oq%9698C-x&aY*QaeFWWdV{C-CbP5A%IX@AXgk$r(crtMqI4zSdXaus{rb}kNp5eX%Iftkf+98x;K0GZ$r`xd3F7U?SD zcz{fBh<%Gv)>hfMNbx}ITa>a|q^;DQi}o!RbjYLuwsAWbr*sTQ?p&PEQ@3-m2^>2Y zUDOE>0t65Oq?;b~n@Xz)4-P5n5H2agf

Bgi8u=c=k{Rm>feKubz?t^C1uqeE}F_ V;hk;aX1V|X002ovPDHLkV1lkJ48Q;Y literal 0 HcmV?d00001 diff --git a/assets/images/DiceFour.png b/assets/images/DiceFour.png new file mode 100644 index 0000000000000000000000000000000000000000..509a398b1ede259274849fb187e6f4274f19fb03 GIT binary patch literal 631 zcmeAS@N?(olHy`uVBq!ia0vp^DImeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?d}4kf#9d}Zjfo51s;*b3=De8Ak0{?)V>TT$X?><>&pI;S&T(XWbap{4?q{}_H=O! zskrs_&gr1T1_CaDr;q;szbSI*AvK#I<<+Khb}!|4V6B^?{j#v;d}#vP_yyndnRI=ib~NAj+c`re6kKXze`1>5XhM_Vb#e^F`gc83g)h^XM8zh!h~}D ztXVxR4~vo_N|M93J&_f@zjMd$nZLHFxBYK?d+ti&ZEvvS8U&miI209H1O$MNWmIfI z@R^*Te1qQ1x!u3S?l1oGm?QSL*n61`siO7P9XCG-hur-o7S4a;lH1NBUTSl@c~e5X zBI6R4?eKA0#NIQ3kMrX8jFY#LcCMZWWxFgW+C5$UT-G@yGywq9g!X*^ literal 0 HcmV?d00001 diff --git a/assets/images/DiceOne.png b/assets/images/DiceOne.png new file mode 100644 index 0000000000000000000000000000000000000000..ecfbdef36ef7eef010a2953535c595f19653b6eb GIT binary patch literal 497 zcmeAS@N?(olHy`uVBq!ia0vp^DImeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?d}4kf#9d}Zjfo51s;*b3=De8Ak0{?)V>TT$X?><>&pI;S&T(fW(M<5MxYB!JzX3_ zDsH{KbGol7K*S}G@8kb^Np~0ZA543epSX3|k3S*O#dYn=($hcM)!DA}{O)|mkflhkulm&|9qx_> zptBeml@uIUSO~H^*WRD-#rCk8oYeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4i|NrU9E-$kuuI6hC`?jr=IO4j@_S&x6wV&N&qxS!07I01w z6NpR@lZXu9o9~+$P;rhU*LX-K^Wb=AagUNAyn#||_#7p&f=^vlc=%T?H=M!n&yEpXyyOt?SDkciB zJ7+Nl?LF(07ws9Ra%7sTuEQyLNuGJPChzg+(Cxm}5aMe4P%*hnobzVx>8fwi6TfiS z9XxHAsq)b-gx{7RHdjd_5YS1|Kb&a~>Hk~ZyMH=Dz`&!;?OVvDewJLB5 z_vc5)49;{fikTtqYWZA%iACVoYTB6%G#-SW%zs_?q{M>fp`zkK&WV!*#Ds+A2xxLX s`0&Eg$iTR9NA33n`5EGkJ6_9{tzg+wwWx6#FtQmuUHx3vIVCg!0H4(Tl>h($ literal 0 HcmV?d00001 diff --git a/assets/images/DiceThree.png b/assets/images/DiceThree.png new file mode 100644 index 0000000000000000000000000000000000000000..89c285ca3962d94b088a2ca0f332d34034232248 GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0vp^DImeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?d}4kf#9d}Zjfo51s;*b3=De8Ak0{?)V>TT$X?><>&pI;S&T)~^w!42T%Zd!db&7< zRNQ)d>vU0zf&gp4%uoO8Z?0K3$@Pr$u8jwaw`|ZA{pL@r(Sr_*u-~285@QeGy zC(VCYna(VzJ|WsO?{R|)pO(n#HKmS=*5&D56i}GNBV>Da#x@g{4wWXC_nY3{G6k}l zU3`s~Y4b*kFZurJT3<_BjBJR+{hpK0dLQ0WNVgE+YUl_R6JTU=X|OT-{pSCHEbrHv z)AwrU+bw6RJoJ3k`l*Iheaw?ARv0ggNM+pl{MEdrs!~iY%UCNPJzs^wV5__Z5=r@U z)%LRTk4&jgQ_hF(HDsC8{&?;#pjM!lmiUx82V|S3PYuo4&-_HmMXaaVgTd{5p@R^+ zDHCf)>ib!5JFNHxcm)-jm#yyj%y?0N(I{iOY@wZkl#&CRU-aJRjEfXsZ&O*6>d?ey znse&*o&~RN6;>xlFdVloH7{jUa%kX~@ba7E`_Q=yS3F#(=qkNP)m6u2NrCg0B@Ksf pI!hXS7fJg4R#S3@qrm$b2Cw7&OQ&62eg+ty44$rjF6*2UngGCZ@JRpw literal 0 HcmV?d00001 diff --git a/assets/images/DiceTwo.png b/assets/images/DiceTwo.png new file mode 100644 index 0000000000000000000000000000000000000000..ab9d0c0a92d6886a081766d38985c20b18920977 GIT binary patch literal 567 zcmeAS@N?(olHy`uVBq!ia0vp^DImeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?d}4kf#9d}Zjfo51s;*b3=De8Ak0{?)V>TT$X?><>&pI;S&T(f^V|koC7=tcJzX3_ zDsH{K4O{3{Q5Le|BZ~x4cUY*`cVv)>=RiZO9?d?ezH>NbnbOvd~{=X zn)RY3~K}VdNxy)U>xh&q)YA`>W<+SC$ohq|2 b=c9@CQXiAo&J{5I$N&VMu6{1-oD!M @@ -31,6 +32,9 @@ T = { ---@type Collision Collision = SOME_TABLE, + ---@type Drawable + Drawable = SOME_TABLE, + ---@type Entity Entity = SOME_TABLE, diff --git a/generated/filter-types.lua2p b/generated/filter-types.lua2p index 078b475..7718c74 100644 --- a/generated/filter-types.lua2p +++ b/generated/filter-types.lua2p @@ -60,6 +60,7 @@ local SOME_TABLE = {} Collision = "{ collidedInto: Entity, collider: Entity }", BitMask = "number", FontData = "love.FontData", + Drawable = "love.Drawable", KeyState = "table", })) T = { diff --git a/main.lua b/main.lua index d7ed858..5724e62 100644 --- a/main.lua +++ b/main.lua @@ -4,42 +4,38 @@ require("generated/filter-types") require("generated/assets") require("generated/all-systems") +UiZIndex = math.huge + +local smallFont = EtBt7001Z0xa(12) + +local scenarios = require("scenarios") + local world = require("world") -local scenarios = { - 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 currentScenario = scenarios.default local freeze = false local delta +local playerBus + function love.load() - currentScenario() - love.graphics.setBackgroundColor(1, 1, 1) + playerBus = currentScenario(world) + love.graphics.setBackgroundColor(0, 0, 0) love.graphics.setFont(EtBt7001Z0xa(32)) end +local nth = 1 + +world:addEntity({ + triggerDiceRoll = "", +}) + function love.update(dt) delta = dt if love.keyboard.isDown("r") then world:clearEntities() - currentScenario() + playerBus = currentScenario(world) freeze = false end @@ -51,6 +47,18 @@ function love.update(dt) return end + -- if nth == 3 then + -- world:addEntity({ + -- scoringTrigger = { + -- mask = 1, + -- target = playerBus, + -- }, + -- }) + -- nth = 1 + -- else + -- nth = nth + 1 + -- end + world:update(delta, function(_, system) if system.deferToEnd then return false @@ -60,6 +68,10 @@ function love.update(dt) end 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) if system.deferToEnd then return false diff --git a/scenarios.lua b/scenarios.lua new file mode 100644 index 0000000..9d88b5f --- /dev/null +++ b/scenarios.lua @@ -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 diff --git a/systems/animation.lua b/systems/animation.lua new file mode 100644 index 0000000..7c1bb20 --- /dev/null +++ b/systems/animation.lua @@ -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 diff --git a/systems/diceRoll.lua b/systems/diceRoll.lua new file mode 100644 index 0000000..415d76c --- /dev/null +++ b/systems/diceRoll.lua @@ -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) diff --git a/systems/draw.lua b/systems/draw.lua index 01b8f15..0feff85 100644 --- a/systems/draw.lua +++ b/systems/draw.lua @@ -40,7 +40,13 @@ drawSystem( "drawText", { position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.FontData) } }, 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 textWidth = font:getWidth(e.drawAsText.text) @@ -49,14 +55,14 @@ drawSystem( local bgWidth, bgHeight = textWidth + (margin * 2), textHeight + 2 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.setColor(1, 1, 1) + gfx.setColor(0, 0, 0) 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.setColor(0, 0, 0) + gfx.setColor(1, 1, 1) gfx.rectangle("line", bgLeftEdge, bgTopEdge, bgWidth, bgHeight) end diff --git a/systems/scoreDisplay.lua b/systems/scoreDisplay.lua new file mode 100644 index 0000000..0f3f356 --- /dev/null +++ b/systems/scoreDisplay.lua @@ -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) diff --git a/systems/upgradeScorer.lua b/systems/upgradeScorer.lua new file mode 100644 index 0000000..7072c5e --- /dev/null +++ b/systems/upgradeScorer.lua @@ -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)