120 lines
4.7 KiB
Lua
120 lines
4.7 KiB
Lua
local World = require("game.world")
|
||
local Palette = require("rendering.palette")
|
||
|
||
local Radar = {}
|
||
|
||
-- Authentic-arcade circular radar: rotating sweep line, fading blips where the
|
||
-- sweep last crossed an enemy, forward-cone diagonals, 4 compass ticks.
|
||
|
||
local RADAR_RANGE = 1000 -- world units shown inside the circle
|
||
local SWEEP_SPEED = 4 -- rad/s
|
||
-- Blip fade: alpha drops ~0.51 per second after the sweep has passed an enemy
|
||
local BLIP_FADE_PER_SEC = 0.51
|
||
local FORWARD_CONE_HALF = math.rad(30)
|
||
|
||
local sweepAngle = 0
|
||
local blips = {} -- [id] = { rx, ry, alpha } in radar-local coords
|
||
|
||
function Radar.init()
|
||
sweepAngle = 0
|
||
blips = {}
|
||
end
|
||
|
||
function Radar.update(dt, player, enemy)
|
||
-- Sweep rotates counter-clockwise (angle decreases each tick)
|
||
sweepAngle = (sweepAngle - SWEEP_SPEED * dt) % (math.pi * 2)
|
||
|
||
-- Fade existing blips
|
||
for id, b in pairs(blips) do
|
||
b.alpha = b.alpha - BLIP_FADE_PER_SEC * dt
|
||
if b.alpha <= 0 then blips[id] = nil end
|
||
end
|
||
|
||
if not (enemy and enemy.alive and player) then return end
|
||
|
||
-- Enemy position in radar-local (player-facing) coords — forward = +y on the scope
|
||
local dx, dz, dist = World.wrappedDist(player.x, player.z, enemy.x, enemy.z)
|
||
if dist > RADAR_RANGE then return end
|
||
local cosA = math.cos(player.angle)
|
||
local sinA = math.sin(player.angle)
|
||
local rx = dx * cosA - dz * sinA -- right on radar
|
||
local ry = dx * sinA + dz * cosA -- forward on radar (drawn as -y on screen)
|
||
|
||
-- Register a blip if the sweep line passed over the enemy's bearing this frame.
|
||
--
|
||
-- The sweep rotates CCW, so across one frame the sweep angle moves from
|
||
-- `lastSweep` → `sweepAngle` (a decrease mod 2π). The enemy is "inside" that
|
||
-- arc when its bearing lies in [sweepAngle, lastSweep]. The arc wraps past 0
|
||
-- when sweepAngle > lastSweep (decrementing wrapped around), so there are
|
||
-- two cases:
|
||
-- • non-wrap: sweepAngle < lastSweep → enemyBearing in [sweepAngle, lastSweep]
|
||
-- • wrapped: sweepAngle > lastSweep → enemyBearing in [sweepAngle, 2π) ∪ [0, lastSweep]
|
||
local enemyBearing = math.atan2(rx, ry) % (math.pi * 2)
|
||
local lastSweep = (sweepAngle + SWEEP_SPEED * dt) % (math.pi * 2)
|
||
local swept
|
||
if sweepAngle <= lastSweep then
|
||
swept = enemyBearing >= sweepAngle and enemyBearing <= lastSweep
|
||
else
|
||
swept = enemyBearing >= sweepAngle or enemyBearing <= lastSweep
|
||
end
|
||
if swept then
|
||
blips.current = { rx = rx / RADAR_RANGE, ry = ry / RADAR_RANGE, alpha = 1.0 }
|
||
end
|
||
end
|
||
|
||
function Radar.draw(player, enemy, _obstacles)
|
||
local p = Palette.get()
|
||
local sw = World.screenW
|
||
local rh = World.radarH
|
||
local sc = sw / 1024
|
||
|
||
-- Update drift, but in draw so caller doesn't need to plumb a dedicated hook yet.
|
||
Radar.update(love.timer.getDelta(), player, enemy)
|
||
|
||
-- Radar strip background
|
||
love.graphics.setColor(p.radar_bg[1], p.radar_bg[2], p.radar_bg[3], 0.4)
|
||
love.graphics.rectangle("fill", 0, 0, sw, rh)
|
||
|
||
-- Radar circle position — top-centre of the HUD strip
|
||
local radius = math.min(rh * 0.42, sw * 0.08)
|
||
local cx = sw / 2
|
||
local cy = rh / 2
|
||
|
||
-- Outer ring
|
||
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.65)
|
||
love.graphics.setLineWidth(1.5 * sc)
|
||
love.graphics.circle("line", cx, cy, radius, 48)
|
||
|
||
-- Forward cone (two diagonals upward from centre, spanning ±30°)
|
||
local coneX = math.sin(FORWARD_CONE_HALF) * radius
|
||
local coneY = math.cos(FORWARD_CONE_HALF) * radius
|
||
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.35)
|
||
love.graphics.setLineWidth(1 * sc)
|
||
love.graphics.line(cx, cy, cx - coneX, cy - coneY)
|
||
love.graphics.line(cx, cy, cx + coneX, cy - coneY)
|
||
|
||
-- Compass ticks (N/E/S/W) on the rim
|
||
local tick = radius * 0.12
|
||
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.5)
|
||
love.graphics.line(cx, cy - radius, cx, cy - radius + tick)
|
||
love.graphics.line(cx, cy + radius, cx, cy + radius - tick)
|
||
love.graphics.line(cx - radius, cy, cx - radius + tick, cy)
|
||
love.graphics.line(cx + radius, cy, cx + radius - tick, cy)
|
||
|
||
-- Sweep line (bearing 0 = straight up)
|
||
local sx = cx + math.sin(sweepAngle) * radius
|
||
local sy = cy - math.cos(sweepAngle) * radius
|
||
love.graphics.setColor(p.bright[1], p.bright[2], p.bright[3], 0.8)
|
||
love.graphics.setLineWidth(1.5 * sc)
|
||
love.graphics.line(cx, cy, sx, sy)
|
||
|
||
-- Fading blips
|
||
for _, b in pairs(blips) do
|
||
local bx = cx + b.rx * radius
|
||
local by = cy - b.ry * radius
|
||
love.graphics.setColor(p.enemy[1], p.enemy[2], p.enemy[3], math.min(1, b.alpha))
|
||
love.graphics.circle("fill", bx, by, 3 * sc)
|
||
end
|
||
end
|
||
|
||
return Radar
|