This commit is contained in:
2026-02-23 20:52:54 +01:00
parent fae2abf94e
commit 9eb426021e
32 changed files with 268 additions and 78 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
source/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View File

@@ -24,14 +24,16 @@ ZIndex = {
ui = 10, ui = 10,
alert = 12, alert = 12,
ground = 100, ground = 100,
flash = 101 flash = 101,
foreground = 102
} }
CollideGroups = { CollideGroups = {
player = 1, player = 1,
enemy = 2, enemy = 2,
props = 3, props = 3,
items = 4, items = 4,
wall = 5 wall = 5,
granade = 6
} }
Maps = { Maps = {
@@ -111,7 +113,8 @@ import "scripts/MapCard"
import "scripts/bomber/movableCrosshair" import "scripts/bomber/movableCrosshair"
import "scripts/bomber/granade" import "scripts/bomber/granade"
import "scripts/bomber/explosionMark" import "scripts/bomber/explosionMark"
import "scripts/bomber/enemy"
import "scripts/bomber/noiseAnimation"
import "scenes/BaseScene" import "scenes/BaseScene"
import 'scenes/Assemble' import 'scenes/Assemble'
import 'scenes/DroneCardSelector' import 'scenes/DroneCardSelector'
@@ -159,4 +162,6 @@ playdate.display.setRefreshRate(50)
Noble.showFPS = false Noble.showFPS = false
Noble.new(BomberScene)
--Noble.new(BomberScene)
Noble.new(Menu)

View File

@@ -2,7 +2,7 @@ name=FPV Game
author=ut3usw author=ut3usw
description=This is a FPV Game description=This is a FPV Game
bundleID=guru.dead.fpv bundleID=guru.dead.fpv
version=0.2.0 version=0.2.6
buildNumber=10 buildNumber=13
imagePath=assets/launcher/ imagePath=assets/launcher/
launchSoundPath=assets/launcher/sound.wav launchSoundPath=assets/launcher/sound.wav

View File

@@ -159,11 +159,6 @@ This operation is crucial. Execute with precision. Command out.]]
-- self.dialogue:setPadding(4) -- self.dialogue:setPadding(4)
end end
function round(number)
local formatted = string.format("%.2f", number)
return formatted
end
local elapsedTime = 0 local elapsedTime = 0
function scene:update() function scene:update()
scene.super.update(self) scene.super.update(self)

View File

@@ -10,7 +10,7 @@ scene.inputHandler = {
return return
end end
scene.menuConfirmSound:play(1) scene.menuConfirmSound:play(1)
mode = Drones[scene.menuIndex].mode local mode = Drones[scene.menuIndex].mode
local soundTable = playdate.sound.playingSources() local soundTable = playdate.sound.playingSources()
for i=1, #soundTable do for i=1, #soundTable do
soundTable[i]:stop() soundTable[i]:stop()

View File

@@ -4,19 +4,6 @@ local scene = Game
local font = Graphics.font.new('assets/fonts/Mini Sans 2X') local font = Graphics.font.new('assets/fonts/Mini Sans 2X')
local function screenShake(shakeTime, shakeMagnitude)
local shakeTimer = playdate.timer.new(shakeTime, shakeMagnitude, 0)
shakeTimer.updateCallback = function(timer)
local magnitude = math.floor(timer.value)
local shakeX = math.random(-magnitude, magnitude)
local shakeY = math.random(-magnitude, magnitude)
playdate.display.setOffset(shakeX, shakeY)
end
shakeTimer.timerEndedCallback = function()
playdate.display.setOffset(0, 0)
end
end
function scene:drawBackground() function scene:drawBackground()
local speed = 0.1 local speed = 0.1
if scene.ground ~= nil then if scene.ground ~= nil then
@@ -66,8 +53,6 @@ end
function scene:start() function scene:start()
scene.super.start(self) scene.super.start(self)
playdate.ui.crankIndicator:draw() -- not sure why this is not working
self.optionsMenu:addMenuItem("Main Menu", function() Noble.transition(Menu) end) self.optionsMenu:addMenuItem("Main Menu", function() Noble.transition(Menu) end)
Noble.showFPS = false Noble.showFPS = false
end end
@@ -103,14 +88,13 @@ function scene:enter()
end end
end end
function round(number)
local formatted = string.format("%.2f", number)
return formatted
end
function scene:update() function scene:update()
scene.super.update(self) scene.super.update(self)
if playdate.isCrankDocked() then
playdate.ui.crankIndicator:draw()
end
if scene.player == nil then if scene.player == nil then
return return
end end
@@ -169,6 +153,7 @@ function scene:exit()
if scene.tank ~= nil then if scene.tank ~= nil then
scene.tank:remove() scene.tank:remove()
end end
scene.helloAudio:stop()
scene.telemLostSound:stop() scene.telemLostSound:stop()
scene.levelAudio:stop() scene.levelAudio:stop()
scene.balebaSpawner:remove() scene.balebaSpawner:remove()

View File

@@ -42,6 +42,7 @@ end
function scene:update() function scene:update()
scene.super.update(self) scene.super.update(self)
if not scene.cards then return end
elapsedTime = elapsedTime + 1 / playdate.display.getRefreshRate() elapsedTime = elapsedTime + 1 / playdate.display.getRefreshRate()
local dy = 2 * math.sin(20 * elapsedTime) local dy = 2 * math.sin(20 * elapsedTime)

View File

@@ -110,7 +110,7 @@ end
function scene:exit() function scene:exit()
scene.super.exit(self) scene.super.exit(self)
-- scene.levelAudio:stop() scene.levelAudio:stop()
self.sequence = Sequence.new():from(self.menuY):to(self.menuYTo, 0.5, Ease.inSine) self.sequence = Sequence.new():from(self.menuY):to(self.menuYTo, 0.5, Ease.inSine)
self.sequence:start() self.sequence:start()
end end

View File

@@ -11,6 +11,11 @@ function scene:init()
self.bgY = 0 self.bgY = 0
self.scrollSpeed = 0.6 self.scrollSpeed = 0.6
scene.dropSound = playdate.sound.fileplayer.new("assets/audio/drop1")
scene.themeSound = playdate.sound.fileplayer.new("assets/audio/bomberTheme")
scene.themeSound:setVolume(0.5)
scene.themeSound:play()
scene.progressBar = ProgressBar(50, 210, 50, 5) scene.progressBar = ProgressBar(50, 210, 50, 5)
scene.progressBar:set(0) scene.progressBar:set(0)
scene.progressBar:setVisible(false) scene.progressBar:setVisible(false)
@@ -20,8 +25,21 @@ function scene:init()
scene.grenadeCooldownDuration = 100 scene.grenadeCooldownDuration = 100
scene.progressBarMax = 100 scene.progressBarMax = 100
scene.autoReload = false
scene.reloadProgress = 0
scene.crankSensitivity = 0.2
scene.availableGrenades = 8 scene.availableGrenades = 8
scene.enemies = {}
scene.enemySpawnTimer = nil
scene.enemySpawnInterval = 1000
scene.maxEnemies = 5
scene.nextEnemyIndex = 1
scene.minSpawnDelay = 500
scene.maxSpawnDelay = 3500
BomberScene.instance = self BomberScene.instance = self
end end
@@ -64,6 +82,9 @@ scene.inputHandler = {
scene.progressBar:setVisible(true) scene.progressBar:setVisible(true)
scene.availableGrenades = scene.availableGrenades - 1 scene.availableGrenades = scene.availableGrenades - 1
scene.dropSound:play()
if scene.autoReload then
scene.grenadeCooldownTimer = playdate.timer.new(scene.grenadeCooldownDuration, function() scene.grenadeCooldownTimer = playdate.timer.new(scene.grenadeCooldownDuration, function()
scene.grenadeCooldown = false scene.grenadeCooldown = false
scene.progressBar:setVisible(false) scene.progressBar:setVisible(false)
@@ -73,6 +94,9 @@ scene.inputHandler = {
local percentage = (scene.grenadeCooldownDuration - timer.timeLeft) / scene.grenadeCooldownDuration * scene.progressBarMax local percentage = (scene.grenadeCooldownDuration - timer.timeLeft) / scene.grenadeCooldownDuration * scene.progressBarMax
scene.progressBar:set(percentage) scene.progressBar:set(percentage)
end end
else
scene.reloadProgress = 0
end
end end
end end
} }
@@ -81,6 +105,9 @@ function scene:enter()
scene.super.enter(self) scene.super.enter(self)
Noble.Input.setHandler(scene.inputHandler) Noble.Input.setHandler(scene.inputHandler)
scene.crosshair = MovableCrosshair(100, 100) scene.crosshair = MovableCrosshair(100, 100)
scene:scheduleNextEnemySpawn()
NoiseAnimation(200, 120)
end end
function scene:start() function scene:start()
@@ -91,23 +118,81 @@ end
function scene:update() function scene:update()
scene.super.update(self) scene.super.update(self)
if scene.grenadeCooldown and not scene.autoReload and not playdate.isCrankDocked() then
local change = playdate.getCrankChange()
if change > 0 or change < 0 then
scene.reloadProgress = scene.reloadProgress + (change * scene.crankSensitivity)
if scene.reloadProgress > scene.progressBarMax then
scene.reloadProgress = scene.progressBarMax
scene.grenadeCooldown = false
scene.progressBar:setVisible(false)
end
scene.progressBar:set(scene.reloadProgress)
end
end
Noble.Text.draw(scene.availableGrenades .. "x", 10, 210, Noble.Text.ALIGN_LEFT, false, font) Noble.Text.draw(scene.availableGrenades .. "x", 10, 210, Noble.Text.ALIGN_LEFT, false, font)
if scene.availableGrenades <= 0 then if scene.availableGrenades <= 0 then
Noble.Text.draw("No grenades left", 200, 110, Noble.Text.ALIGN_CENTER, false, font) Noble.Text.draw("No grenades left", 200, 110, Noble.Text.ALIGN_CENTER, false, font)
scene.crosshair:setVisible(false) scene.crosshair:setVisible(false)
end scene.progressBar:setVisible(false)
elseif playdate.isCrankDocked() then
if playdate.isCrankDocked() then
Noble.Text.draw("Crank it to reload!", 200, 110, Noble.Text.ALIGN_CENTER, false, font) Noble.Text.draw("Crank it to reload!", 200, 110, Noble.Text.ALIGN_CENTER, false, font)
playdate.ui.crankIndicator:draw() playdate.ui.crankIndicator:draw()
end end
end end
-- TODO: to reset grenades spin crank function scene:spawnEnemies()
-- TODO: random spawn of enemies local activeEnemies = 0
for i = 1, #scene.enemies do
if scene.enemies[i] and not scene.enemies[i].removed then
activeEnemies = activeEnemies + 1
end
end
if activeEnemies < self.maxEnemies then
scene.enemies[scene.nextEnemyIndex] = Enemy(math.random(30, 370), -20)
scene.nextEnemyIndex = scene.nextEnemyIndex + 1
end
scene:scheduleNextEnemySpawn()
end
function scene:scheduleNextEnemySpawn()
local delay = math.random(scene.minSpawnDelay, scene.maxSpawnDelay)
scene.enemySpawnTimer = playdate.timer.new(delay, function()
scene:spawnEnemies()
end)
end
function scene:finish()
scene.themeSound:stop()
scene.enemySpawnTimer:remove()
for i = 1, #scene.enemies do
if scene.enemies[i] then
scene.enemies[i]:remove()
end
end
scene.enemies = {}
if scene.progressBar then
scene.progressBar:remove()
end
scene.progressBar = nil
if scene.grenadeCooldownTimer then
scene.grenadeCooldownTimer:remove()
end
scene.grenadeCooldownTimer = nil
scene.crosshair:remove()
scene.crosshair = nil
BomberScene.instance = nil
end
-- TODO: random spawn some decorations -- TODO: random spawn some decorations
-- TODO: add some music
-- TODO: add some sound effects
-- TODO: add clouds or smoke -- TODO: add clouds or smoke
-- TODO: random disactivate granades -- TODO: random disactivate granades

View File

@@ -0,0 +1,94 @@
Enemy = {}
class('Enemy').extends(NobleSprite)
function Enemy:init(x,y)
Enemy.super.init(self)
self:moveTo(x, y)
self:setZIndex(4)
self:add(x,y)
self.markImage = Graphics.image.new("assets/sprites/enemy"..math.random(1,2)) -- TODO: make it random
self.deadImage = Graphics.image.new("assets/sprites/enemy1_3")
self.hitSound = playdate.sound.fileplayer.new("assets/audio/hit1")
self:setImage(self.markImage)
self.removed = false
self:setGroups(CollideGroups.enemy)
self:setCollidesWithGroups({
CollideGroups.granade,
CollideGroups.enemy
})
self:setCollideRect(-6, -6, 46, 46)
self:setSize(32, 32)
self.vx = 0
self.vy = 0
self.isDying = false
self.friction = 0.95
end
function Enemy:update()
if not BomberScene.instance then return end
local speed = 0
if self.isDying then
self.vx = self.vx * self.friction
self.vy = self.vy * self.friction
self:moveBy(self.vx, self.vy + BomberScene.instance.scrollSpeed)
if math.abs(self.vx) < 0.1 and math.abs(self.vy) < 0.1 then
self.isDying = false
self.removed = true
end
elseif not self.removed then
speed = math.random(0, 7)/10
self:moveBy(0, BomberScene.instance.scrollSpeed + speed)
else
self:moveBy(0, BomberScene.instance.scrollSpeed)
end
local actualX, actualY, collisions, numberOfCollisions = self:checkCollisions(self.x, self.y)
if numberOfCollisions > 0 then
for i, collision in ipairs(collisions) do
if collision.other:getTag() == 154 and collision.other.currentRadius <= 0.05 and not self.isDying then
print("Collision with granade")
self:setImage(self.deadImage)
self.hitSound:play()
self:applyExplosionForce(collision.other.x, collision.other.y)
end
end
end
if self.y > 240 + 10 then
if not self.removed then
print("Removing enemy")
self:remove()
self:superRemove()
self.removed = true
end
end
end
function Enemy:applyExplosionForce(explosionX, explosionY)
local dx = self.x - explosionX
local dy = self.y - explosionY
local dist = math.sqrt(dx*dx + dy*dy)
if dist == 0 then dist = 0.001 end
dx = dx / dist
dy = dy / dist
local maxForce = 5
local maxRadius = 100
local force = maxForce * (1 - math.min(dist, maxRadius) / maxRadius)
force = math.max(force, 1)
self.vx = dx * force
self.vy = dy * force * 0.5
self.isDying = true
self:setRotation(math.random() * 360)
end

View File

@@ -4,7 +4,7 @@ class('ExplosionMark').extends(NobleSprite)
function ExplosionMark:init(x, y) function ExplosionMark:init(x, y)
ExplosionMark.super.init(self) ExplosionMark.super.init(self)
self.id = math.random(1, 2) self.id = math.random(1, 2)
self.markImage = Graphics.image.new("assets/sprites/boomSplash" .. self.id) -- TODO: make it random self.markImage = Graphics.image.new("assets/sprites/boomSplash" .. self.id)
self:setImage(self.markImage) self:setImage(self.markImage)
self:moveTo(x, y) self:moveTo(x, y)
self:setZIndex(5) self:setZIndex(5)
@@ -12,6 +12,7 @@ function ExplosionMark:init(x, y)
end end
function ExplosionMark:update() function ExplosionMark:update()
if not BomberScene.instance then return end
self:moveBy(0, BomberScene.instance.scrollSpeed) self:moveBy(0, BomberScene.instance.scrollSpeed)
if self.y > 240 + 32 then if self.y > 240 + 32 then

View File

@@ -1,19 +1,6 @@
Granade = {} Granade = {}
class('Granade').extends(NobleSprite) class('Granade').extends(NobleSprite)
local function screenShake(shakeTime, shakeMagnitude)
local shakeTimer = playdate.timer.new(shakeTime, shakeMagnitude, 0)
shakeTimer.updateCallback = function(timer)
local magnitude = math.floor(timer.value)
local shakeX = math.random(-magnitude, magnitude)
local shakeY = math.random(-magnitude, magnitude)
playdate.display.setOffset(shakeX, shakeY)
end
shakeTimer.timerEndedCallback = function()
playdate.display.setOffset(0, 0)
end
end
function Granade:init(x, y) function Granade:init(x, y)
Granade.super.init(self) Granade.super.init(self)
@@ -21,12 +8,12 @@ function Granade:init(x, y)
self.currentRadius = self.initialRadius self.currentRadius = self.initialRadius
self.shrinkRate = 0.2 self.shrinkRate = 0.2
random = math.random(1, 4) local random = math.random(1, 4)
self.boomSound = playdate.sound.fileplayer.new("assets/audio/boom" .. random) self.boomSound = playdate.sound.fileplayer.new("assets/audio/boom" .. random)
self.boomSound:setVolume(0.5) self.boomSound:setVolume(0.5)
self.isActive = true self.isActive = true
-- Variables for random movement
self.randomMovementTimer = 0 self.randomMovementTimer = 0
self.randomXVelocity = 0 self.randomXVelocity = 0
self.randomYVelocity = 0 self.randomYVelocity = 0
@@ -35,7 +22,14 @@ function Granade:init(x, y)
self.spriteSize = size self.spriteSize = size
self:setSize(size, size) self:setSize(size, size)
self:moveTo(x, y) self:moveTo(x, y)
self:setZIndex(10)
self:setTag(154)
self:setCenter(0.5, 0.5) self:setCenter(0.5, 0.5)
self:setGroups(CollideGroups.granade)
self:setCollidesWithGroups({
CollideGroups.enemy
})
self:setCollideRect(0, 0, self:getSize())
print("Granade init") print("Granade init")
print(self.x, self.y) print(self.x, self.y)

View File

@@ -4,28 +4,25 @@ class('MovableCrosshair').extends(playdate.graphics.sprite)
function MovableCrosshair:init() function MovableCrosshair:init()
MovableCrosshair.super.init(self) MovableCrosshair.super.init(self)
-- Parameters for crosshair
self.lineLength = 10 self.lineLength = 10
self.gapSize = 3 self.gapSize = 3
-- Parameters for movement
self.baseX = 200 self.baseX = 200
self.baseY = 150 self.baseY = 150
self.moveRadius = 2 self.moveRadius = 2
self.moveSpeed = 2 self.moveSpeed = 2.3
self.time = 0 self.time = 0
-- Calculate size based on crosshair dimensions
local totalSize = (self.lineLength + self.gapSize) * 2 + 10 local totalSize = (self.lineLength + self.gapSize) * 2 + 10
self:setSize(totalSize, totalSize) self:setSize(totalSize, totalSize)
-- Set the drawing offset to middle of sprite
self.drawOffsetX = totalSize / 2 self.drawOffsetX = totalSize / 2
self.drawOffsetY = totalSize / 2 self.drawOffsetY = totalSize / 2
self:add(self.baseX, self.baseY) self:add(self.baseX, self.baseY)
self:setCenter(0.5, 0.5) self:setCenter(0.5, 0.5)
self:markDirty() self:markDirty()
self:setZIndex(11)
end end
function MovableCrosshair:update() function MovableCrosshair:update()
@@ -66,24 +63,24 @@ end
function MovableCrosshair:moveUp() function MovableCrosshair:moveUp()
if self.baseY > 5 then if self.baseY > 5 then
self.baseY = self.baseY - 1 self.baseY = self.baseY - self.moveSpeed
end end
end end
function MovableCrosshair:moveDown() function MovableCrosshair:moveDown()
if self.baseY < 235 then if self.baseY < 235 then
self.baseY = self.baseY + 1 self.baseY = self.baseY + self.moveSpeed
end end
end end
function MovableCrosshair:moveLeft() function MovableCrosshair:moveLeft()
if self.baseX > 5 then if self.baseX > 5 then
self.baseX = self.baseX - 1 self.baseX = self.baseX - self.moveSpeed
end end
end end
function MovableCrosshair:moveRight() function MovableCrosshair:moveRight()
if self.baseX < 395 then if self.baseX < 395 then
self.baseX = self.baseX + 1 self.baseX = self.baseX + self.moveSpeed
end end
end end

View File

@@ -0,0 +1,36 @@
NoiseAnimation = {}
class('NoiseAnimation').extends(NobleSprite)
function NoiseAnimation:init(x, y)
NoiseAnimation.super.init(self, "assets/sprites/noise", true)
self.animation:addState("run", 2, 11)
self.animation:addState("idle", 1, 1)
self.animation.run.frameDuration = 2.5
self.animation:setState("idle")
self:setZIndex(ZIndex.foreground)
self:setSize(400, 240)
self:add()
self:moveTo(x, y)
self.state = "idle"
self.idleFrames = 0
end
function NoiseAnimation:update()
if self.state == "idle" then
self.idleFrames -= 1
if self.idleFrames <= 0 then
self.state = "run"
self.animation:setState("run")
end
else
local r = math.random(0)
if r < 0.01 then
self.state = "idle"
self.idleFrames = math.random(30, 100)
self.animation:setState("idle")
else
self.animation:setState("run")
end
end
end

View File

@@ -2,7 +2,7 @@ PageSprite = {}
class('PageSprite').extends(NobleSprite) class('PageSprite').extends(NobleSprite)
function PageSprite:init(x, y) function PageSprite:init(x, y)
Baleba.super.init(self, "assets/sprites/pages", true) PageSprite.super.init(self, "assets/sprites/pages", true)
self.animation:addState("1", 1, 1) self.animation:addState("1", 1, 1)
self.animation:addState("2", 2, 2) self.animation:addState("2", 2, 2)
self.animation:addState("3", 3, 3) self.animation:addState("3", 3, 3)

View File

@@ -192,9 +192,6 @@ function Player:handleMovementAndCollisions()
self:boom() self:boom()
return return
elseif collisionTag == 154 then -- Baleba elseif collisionTag == 154 then -- Baleba
-- if self.debug then TODO: why debug always true?
-- return
-- end
self:boom(collisionObject) self:boom(collisionObject)
return return
elseif collisionTag == 2 then -- Tank elseif collisionTag == 2 then -- Tank