Something went wrong. Try again.
A Love2D remaster of my bug-catching-cult-classic Bait and Switch
Something went wrong. Try again.
9.2 kB · 262 lines
Lua
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263local speedyBug = {}
local config = require("src.config")local assets = require("src.assets")local player = require("src.player")local particle = require("src.particle")local gameState = require("src.gameState")local utils = require("src.utils")
speedyBug.config = { minWave = 5, spawnChance = 0.1, spawnDelayMin = 1.0, spawnDelayMax = 3.0,
speed = 115, spinSpeed = 6,
sineAmplitude = 18, sineFrequency = 6,
minSegments = 3, maxSegments = 6, segmentSpacing = 6, segmentRotationStagger = 0.4,
margin = 20,
particleDelay = 0.04, particle = { minScale = 0.2, maxScale = 0.5, minScaleDecay = 0.6, maxScaleDecay = 1.2, maxRotationSpeed = 4, maxVelocity = 30, },}
speedyBug.active = {}speedyBug.pending = {}
function speedyBug.load() speedyBug.sprite = assets.enemySprite speedyBug.shadowColor = assets.speedyBugShadowColor speedyBug.color = assets.speedyBugColor
speedyBug.screenWidth = config.screen.width speedyBug.screenHeight = config.screen.height speedyBug.shadowOffset = config.visual.shadowOffset speedyBug.angleSnapFactor = config.visual.angleSnapFactor
speedyBug.spriteOX = speedyBug.sprite:getWidth() / 2 speedyBug.spriteOY = speedyBug.sprite:getHeight() / 2end
function speedyBug.scheduleSpawn() local delay = math.random() * (speedyBug.config.spawnDelayMax - speedyBug.config.spawnDelayMin) + speedyBug.config.spawnDelayMin table.insert(speedyBug.pending, delay)end
function speedyBug.createBug() local b = {}
local playerOnLeft = player.x < speedyBug.screenWidth / 2 if playerOnLeft then b.x = speedyBug.screenWidth + speedyBug.config.margin else b.x = -speedyBug.config.margin end b.y = math.random(speedyBug.config.margin, speedyBug.screenHeight - speedyBug.config.margin)
local dx = player.x - b.x local dy = player.y - b.y local len = math.sqrt(dx * dx + dy * dy) if len > 0 then dx, dy = dx / len, dy / len end
-- forward direction (toward player) b.dirX = dx b.dirY = dy -- perpendicular direction (for sine wave) b.perpX = -dy b.perpY = dx
b.baseX = b.x b.baseY = b.y b.traveled = 0 b.angle = 0 b.dead = false b.particleTimer = 0 b.segments = math.random(speedyBug.config.minSegments, speedyBug.config.maxSegments)
b.sound = assets.audio.speedyBugPass:clone() b.sound:setLooping(true) b.sound:setVolume(0) b.sound:play()
table.insert(speedyBug.active, b)end
function speedyBug.update(dt) if gameState.isPaused() then return end
if not gameState.tutorialMode and gameState.stats.currentWave >= speedyBug.config.minWave then local n = #speedyBug.pending local i = 1 while i <= n do speedyBug.pending[i] = speedyBug.pending[i] - dt if speedyBug.pending[i] <= 0 then speedyBug.createBug() speedyBug.pending[i] = speedyBug.pending[n] speedyBug.pending[n] = nil n = n - 1 else i = i + 1 end end end
local sqrt = math.sqrt local sin = math.sin local random = math.random local screenW = speedyBug.screenWidth local screenH = speedyBug.screenHeight local margin = speedyBug.config.margin local playerX, playerY = player.x, player.y local playerRadius = player.getCollisionRadius() local color = speedyBug.color local speed = speedyBug.config.speed local sineAmp = speedyBug.config.sineAmplitude local sineFreq = speedyBug.config.sineFrequency
local n = #speedyBug.active local i = 1 while i <= n do local b = speedyBug.active[i]
b.traveled = b.traveled + speed * dt b.angle = b.angle + speedyBug.config.spinSpeed * dt
-- position along sine wave path local sineOffset = sin(b.traveled / speed * sineFreq) * sineAmp b.x = b.baseX + b.dirX * b.traveled + b.perpX * sineOffset b.y = b.baseY + b.dirY * b.traveled + b.perpY * sineOffset
-- update sound volume: fade across full travel range including off-screen margins local extentMin = -margin * 3 local extentMax = screenW + margin * 3 local center = screenW / 2 local halfRange = center - extentMin local closeness = 1 - math.abs(b.x - center) / halfRange if closeness < 0 then closeness = 0 end b.sound:setVolume(closeness * closeness)
-- player collision local dx = playerX - b.x local dy = playerY - b.y local dist = sqrt(dx * dx + dy * dy) if dist < (speedyBug.spriteOX + playerRadius) and not player.isInvincible then player.takeDamage() end
-- trail particles (spawn behind the last tail segment) b.particleTimer = b.particleTimer + dt if b.particleTimer >= speedyBug.config.particleDelay then local tailTraveled = b.traveled - b.segments * speedyBug.config.segmentSpacing local tailSine = sin(tailTraveled / speed * sineFreq) * sineAmp local tailX = b.baseX + b.dirX * tailTraveled + b.perpX * tailSine local tailY = b.baseY + b.dirY * tailTraveled + b.perpY * tailSine
local scale = random() * (speedyBug.config.particle.maxScale - speedyBug.config.particle.minScale) + speedyBug.config.particle.minScale local scaleDecay = random() * (speedyBug.config.particle.maxScaleDecay - speedyBug.config.particle.minScaleDecay) + speedyBug.config.particle.minScaleDecay local rotation = (random() * 2 - 1) * speedyBug.config.particle.maxRotationSpeed local vx = -b.dirX * speedyBug.config.particle.maxVelocity * random() + (random() * 2 - 1) * speedyBug.config.particle.maxVelocity * 0.5 local vy = -b.dirY * speedyBug.config.particle.maxVelocity * random() + (random() * 2 - 1) * speedyBug.config.particle.maxVelocity * 0.5
particle.create(tailX, tailY, speedyBug.sprite, { scale = scale, scaleDecay = scaleDecay, rotation = rotation, vx = vx, vy = vy, shadowOffset = speedyBug.shadowOffset, shadowColor = speedyBug.shadowColor, color = color, }) b.particleTimer = 0 end
-- remove if off screen if b.x < -margin * 3 or b.x > screenW + margin * 3 or b.y < -margin * 3 or b.y > screenH + margin * 3 then b.sound:stop() speedyBug.active[i] = speedyBug.active[n] speedyBug.active[n] = nil n = n - 1 else i = i + 1 end endend
function speedyBug.draw() local floor = math.floor local sin = math.sin local sprite = speedyBug.sprite local ox = speedyBug.spriteOX local oy = speedyBug.spriteOY local snapFactor = speedyBug.angleSnapFactor local shadowOffset = speedyBug.shadowOffset local shadowColor = speedyBug.shadowColor local tint = speedyBug.color local speed = speedyBug.config.speed local sineAmp = speedyBug.config.sineAmplitude local sineFreq = speedyBug.config.sineFrequency local spacing = speedyBug.config.segmentSpacing local stagger = speedyBug.config.segmentRotationStagger
-- shadow pass love.graphics.setColor(shadowColor) for _, b in ipairs(speedyBug.active) do for s = b.segments, 1, -1 do local segTraveled = b.traveled - s * spacing local segSine = sin(segTraveled / speed * sineFreq) * sineAmp local sx = b.baseX + b.dirX * segTraveled + b.perpX * segSine local sy = b.baseY + b.dirY * segTraveled + b.perpY * segSine local segAngle = floor((b.angle + s * stagger) * snapFactor) / snapFactor love.graphics.draw(sprite, floor(sx) + shadowOffset, floor(sy) + shadowOffset, segAngle, 1, 1, ox, oy) end local headAngle = floor(b.angle * snapFactor) / snapFactor love.graphics.draw(sprite, floor(b.x) + shadowOffset, floor(b.y) + shadowOffset, headAngle, 1, 1, ox, oy) end
-- sprite pass love.graphics.setColor(tint) for _, b in ipairs(speedyBug.active) do for s = b.segments, 1, -1 do local segTraveled = b.traveled - s * spacing local segSine = sin(segTraveled / speed * sineFreq) * sineAmp local sx = b.baseX + b.dirX * segTraveled + b.perpX * segSine local sy = b.baseY + b.dirY * segTraveled + b.perpY * segSine local segAngle = floor((b.angle + s * stagger) * snapFactor) / snapFactor love.graphics.draw(sprite, floor(sx), floor(sy), segAngle, 1, 1, ox, oy) end local headAngle = floor(b.angle * snapFactor) / snapFactor love.graphics.draw(sprite, floor(b.x), floor(b.y), headAngle, 1, 1, ox, oy) endend
return speedyBug