Add new assets and implement minimap functionality

- Added new image assets for game over screens: `game_over_dead_bg.png`, `game_over_victory_bg.png`, and their respective Aseprite files.
- Introduced a new `Minimap` class to manage torch placement and rendering on the minimap.
- Implemented methods for adding and removing torches, updating the grid area, and drawing the minimap based on the current state of the game.
- Enhanced the minimap display with color coding for walls, empty spaces, and torches.
This commit is contained in:
2026-04-03 02:51:11 +01:00
parent b325b35c88
commit 222b28d196
24 changed files with 209 additions and 32 deletions
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

+3 -3
View File
@@ -126,11 +126,11 @@ class Character
$bus.emit(:trap_stepped_on, rect, :character)
end
# teleport to boss room for debug purposes
# teleport to exit room for debug purposes
if $bus.get(:settings, :debug) && Gosu.button_down?(Gosu::KB_T)
room_coords = $bus.get(:boss_room_coords)
room_coords = $bus.get(:exit_room_coords)
if room_coords
@world_x, @world_y = [room_coords[0] * 60 + 5, room_coords[1] * 60 + 5]
@world_x, @world_y = [room_coords[0] * 120 + 90, room_coords[1] * 120 + 90]
end
end
+7 -2
View File
@@ -5,6 +5,8 @@ class HealthBar
@max_health = max_health
@health = max_health
@heart_image = Gosu::Image.new("assets/images/heart.png", retro: true)
$bus.on(:blast) do |x, y, radius, damage, safety|
next if safety == :safe
@@ -21,6 +23,7 @@ class HealthBar
@health -= amount
if @health <= 0
@health = 0
$is_dead = true
$bus.emit(:change_scene, GameOver)
end
end
@@ -32,10 +35,12 @@ class HealthBar
def draw
# Draw the background of the health bar (red)
Gosu.draw_rect(30, 10, 100, 10, Gosu::Color::RED)
Gosu.draw_rect(SCREEN_SIZE[0] - 100 - 10, 180, 100, 10, Gosu::Color::RED)
# Draw the foreground of the health bar (green) based on current health
health_width = (health.to_f / max_health) * 100
Gosu.draw_rect(30, 10, health_width, 10, Gosu::Color::GREEN)
Gosu.draw_rect(SCREEN_SIZE[0] - 100 - 10, 180, health_width, 10, Gosu::Color::GREEN)
@heart_image.draw(SCREEN_SIZE[0] - 100 - 20, 173, 1, 1.5, 1.5)
end
end
+17
View File
@@ -1,6 +1,7 @@
require_relative "inventory"
require_relative "health"
require_relative "logs"
require_relative "minimap"
class HUDLayer
def initialize
@@ -9,6 +10,9 @@ class HUDLayer
@inventory = Inventory.new
@health = HealthBar.new(100)
@logs = GameLogger.new
@minimap = Minimap.new
@direction_sprite = Gosu::Image.new("assets/images/direction.png", retro: true)
$bus.on(:enemy_attack) do |damage|
@health.take_damage(damage)
@@ -26,6 +30,19 @@ class HUDLayer
@health.draw
@logs.draw
player_x, player_y = $bus.get(:player_position) || [0, 0]
tile_x = ((player_x - 90) / 120).round
tile_y = ((player_y - 90) / 120).round
center_x = tile_x * 2 + 1
center_y = tile_y * 2 + 1
@minimap.draw(center_x, center_y)
if $bus.get(:radar_triangulation?, player_x, player_y)
exit_x, exit_y = $bus.get(:exit_room_coords) || [0, 0]
angle_to_exit = Math.atan2(exit_y - tile_x, exit_x - tile_y) * 180 / Math::PI
@direction_sprite.draw_rot(SCREEN_SIZE[0] / 2, SCREEN_SIZE[1] / 2, Float::INFINITY, angle_to_exit, 0.5, 0.5, 5, 5)
end
@hud_fg.draw(0, 0, Float::INFINITY)
end
+92
View File
@@ -0,0 +1,92 @@
class Minimap
CELL_SIZE = 3 # pixels per minimap cell
RADIUS = 10 # cells around torch to update
# Please ignore my overuse of that stupid ruby conditional assignment operator
def initialize
@torches = []
@grid_cache = {}
$bus.on(:torch_placed) do |gx, gy|
add_torch(gx, gy)
end
$bus.on(:torch_removed) do |gx, gy|
remove_torch(gx, gy)
end
end
def add_torch(x, y)
x, y = x * 2 + 1, y * 2 + 1 # convert from room coords to grid coords
@torches << [x, y]
update_grid_area(x, y)
end
def remove_torch(x, y)
x, y = x * 2 + 1, y * 2 + 1
@torches.delete([x, y])
update_grid_area(x, y)
end
# Update only cells in 10-cell box around given gx,gy
def update_grid_area(cx, cy)
(cx-RADIUS..cx+RADIUS).each do |gx|
(cy-RADIUS..cy+RADIUS).each do |gy|
if @torches.any? { |tx, ty| (gx - tx).abs <= RADIUS && (gy - ty).abs <= RADIUS }
@grid_cache[[gx, gy]] = if @torches.include?([gx, gy])
:torch
elsif $bus.get(:maze_wall?, gx, gy)
:wall
else
:empty
end
else
@grid_cache.delete([gx, gy])
end
end
end
end
def draw(center_x, center_y)
half_size = 25 # 40 cells → 20 each side
map_max = $bus.get(:maze_size).map { |s| s * 2 - 1 } || 0
offset_x = SCREEN_SIZE[0] - CELL_SIZE * (half_size * 2 + 1) - 10
offset_y = 10
size_in_cells = 2 * half_size + 1
width = size_in_cells * CELL_SIZE
height = size_in_cells * CELL_SIZE
# border
Gosu.draw_rect(offset_x - 3, offset_y - 3, width + 6, height + 6, Gosu::Color.new(0xFFa9b2a2), Float::INFINITY)
(-half_size..half_size).each do |dx|
(-half_size..half_size).each do |dy|
gx = center_x + dx
gy = center_y + dy
type = if gx < 0 || gy < 0 || gx > map_max[0] || gy > map_max[1]
:wall
else
@grid_cache[[gx, gy]] || :unrevealed
end
color = case type
when :wall then Gosu::Color.new(0xFF28353e)
when :empty then Gosu::Color.new(0xFFd6dad3)
when :torch then Gosu::Color.new(0xFFfbb954)
else Gosu::Color.new(0xFF576069)
end
color = Gosu::Color::RED if gx == center_x && gy == center_y
x = (dx + half_size) * CELL_SIZE + offset_x
y = (dy + half_size) * CELL_SIZE + offset_y
Gosu.draw_rect(x, y, CELL_SIZE, CELL_SIZE, color, Float::INFINITY)
end
end
end
end
+30 -1
View File
@@ -22,6 +22,34 @@ class PropsHandler
$bus.on(:nearby_torches) do |rect|
nearby_props(rect).select { |p| p.is_a?(Torch) }.map(&:rect).map { |t| [t[0] + t[2] / 2, t[1] + t[3] / 2] }
end
$bus.on(:radar_triangulation?) do |player_x, player_y|
radar_triangulation?(player_x, player_y)
end
end
def radar_triangulation?(player_x, player_y)
# small rect around player to find the first radar
small_rect = [player_x - 60, player_y - 60, 120, 120] # 1 block radius
first_radar = nearby_props(small_rect).find { |p| p.is_a?(Radar) }
return false unless first_radar
# larger rect to find other radars (max 15 blocks away, 15*120 pixels)
large_rect = [first_radar.x * 120 - 15 * 120, first_radar.y * 120 - 15 * 120, 30 * 120, 30 * 120]
nearby_radars = nearby_props(large_rect).select { |p| p.is_a?(Radar) }
# need at least 3 radars total
return false if nearby_radars.size < 3
nearby_radars.combination(3).any? do |r1, r2, r3|
next unless r1 == first_radar || r2 == first_radar || r3 == first_radar
d1 = Math.hypot((r1.x - r2.x), (r1.y - r2.y))
d2 = Math.hypot((r1.x - r3.x), (r1.y - r3.y))
d3 = Math.hypot((r2.x - r3.x), (r2.y - r3.y))
[d1, d2, d3].all? { |d| d >= 3 && d <= 15 }
end
end
def spawn_chests!
@@ -49,7 +77,7 @@ class PropsHandler
end
end
# Add one in the start room for players to have something to interact with right away
# Add one in the start room for players to have something right away
start_room_x, start_room_y = $bus.get(:start_room_coords) || [0, 0]
x, y = [
[start_room_x, start_room_y],
@@ -103,6 +131,7 @@ class PropsHandler
return if @grid[[tile[0], tile[1]]] # can't place if something is already there
return if $bus.get_all(:collides?, torch.collision_rect).include?(:character) # can't place if colliding with player
return unless $bus.get(:consume, :torch, 1)
$bus.emit(:torch_placed, torch.x, torch.y)
add(torch)
elsif selected_item == :radar
player_x, player_y = $bus.get(:player_position) || [0, 0]
+9
View File
@@ -6,4 +6,13 @@ class Torch < Prop
def resources
{ wood: rand(0..8) }
end
def update(dt)
dead = super(dt)
if dead
$bus.emit(:torch_removed, @x, @y)
pp "Emitted torch_removed for torch at #{@x}, #{@y}"
end
dead
end
end
+4
View File
@@ -9,6 +9,8 @@ require_relative 'character'
class Game < Scene
def initialize
super
$is_dead = false
@font = Gosu::Font.new(24)
@floor_image = Gosu::Image.new("assets/images/floor.png", retro: true)
@@ -94,6 +96,8 @@ class Game < Scene
end
def button_down(id, pos)
# Pressing ESC returns to the menu, this is bad but I don't have time to make a proper pause menu,
# as the game has no persistent state outside of the current scene, just returning to the menu resets everything
return $bus.emit(:change_scene, Menu.new) if id == Gosu::KB_ESCAPE
return if @hud.button_down(id, pos)
+6 -3
View File
@@ -11,6 +11,8 @@ class BladeOfRecursion
# the knife is pointed top-right
@sprite = Gosu::Image.new('assets/images/blade_of_recursion.png', retro: true)
@memory_sprite = Gosu::Image.new('assets/images/memory.png', retro: true)
end
def attack(direction)
@@ -72,10 +74,9 @@ class BladeOfRecursion
# this should be in the HUD layer but due to time constraints im putting it here, sorry
bar_width = 100
bar_height = 10
padding = 10
bar_x = SCREEN_SIZE[0] - bar_width - padding
bar_y = padding
bar_x = SCREEN_SIZE[0] - bar_width - 10
bar_y = 205
# full red background
Gosu.draw_rect(bar_x, bar_y, bar_width, bar_height, Gosu::Color::RED, Float::INFINITY)
@@ -84,5 +85,7 @@ class BladeOfRecursion
damage_ratio = Math.log2(@damage) / 9.0
damage_width = bar_width * [damage_ratio, 1.0].min
Gosu.draw_rect(bar_x, bar_y, damage_width, bar_height, Gosu::Color::GREEN, Float::INFINITY)
@memory_sprite.draw(bar_x - 10, bar_y - 7, Float::INFINITY, 1.5, 1.5)
end
end
+1 -1
View File
@@ -8,7 +8,7 @@ class GameOver < Scene
super
@font = Gosu::Font.new(32, name: "assets/fonts/tn.ttf")
@selected_index = 0
@bg_image = Gosu::Image.new("assets/images/game_over_bg.png", retro: true)
@bg_image = Gosu::Image.new("assets/images/game_over_#{$is_dead ? 'dead' : 'victory'}_bg.png", retro: true)
@last_mouse_pos = nil
end
+1 -1
View File
@@ -10,6 +10,6 @@ class Settings < Scene
end
def button_down(id, _pos)
$bus.emit(:change_scene, Menu.new) if id == Gosu::KB_ESCAPE
$bus.emit(:change_scene, Menu) if id == Gosu::KB_ESCAPE
end
end
-16
View File
@@ -1,16 +0,0 @@
So,
#landmines and event horizon should be easy - done
#make enemy spawn in the boss rooms with randomness + enemy count - done
#make the lizard kind of enemy with very high health + dps but slow - done
#make blade work as planned - done
#make bow quantum - done
make minimap for the torches revealing teh maze
click on minimap makes game pause to allow pausing for safety
make radars find exit if even one placed, and make it expensive
then start writing every mistake and time terribility
+39 -5
View File
@@ -19,8 +19,12 @@ class MazeData
next @start_room
end
$bus.on(:exit_room_coords) do
next @exit_room
end
$bus.on(:room?) do |gx, gy|
([@start_room] + @boss_rooms).any? do |rx, ry|
([@start_room, @exit_room] + @boss_rooms).any? do |rx, ry|
gx.between?(rx, rx + BOSS_ROOM_SIZE) &&
gy.between?(ry, ry + BOSS_ROOM_SIZE)
end
@@ -30,6 +34,18 @@ class MazeData
next @boss_rooms
end
$bus.on(:maze_wall?) do |gx, gy|
next wall_at?(gx, gy)
end
$bus.on(:torch_placed) do |gx, gy|
# check for end condition, if torch is placed in exit room, change scene to victory
if (gx.between?(@exit_room[0], @exit_room[0] + BOSS_ROOM_SIZE - 1) &&
gy.between?(@exit_room[1], @exit_room[1] + BOSS_ROOM_SIZE - 1))
$bus.emit(:change_scene, GameOver)
end
end
return unless $bus.get(:settings, :debug)
print_debug
@@ -113,7 +129,7 @@ class MazeData
def wall_at?(gx, gy)
return true if gx == 0 || gy == 0 || gx == width - 1 || gy == height - 1
@boss_rooms.each do |rx, ry|
(@boss_rooms + [@start_room, @exit_room]).each do |rx, ry|
display_x1 = rx * 2 + 1
display_y1 = ry * 2 + 1
display_x2 = display_x1 + (BOSS_ROOM_SIZE - 1) * 2
@@ -184,7 +200,7 @@ class MazeData
end
def fix_room_entrances!
(@boss_rooms + [@start_room]).each do |x, y|
(@boss_rooms + [@start_room, @exit_room]).each do |x, y|
dir = [N, S, E, W].sample
cx = x + BOSS_ROOM_SIZE / 2
cy = y + BOSS_ROOM_SIZE / 2
@@ -239,8 +255,26 @@ class MazeData
rooms << [x, y]
end
@start_room = rooms.shift
@boss_rooms = rooms
# assign the start and exit room to be the two furthest apart rooms
@start_room, @exit_room = furthest_pair(rooms)
@boss_rooms = rooms - [@start_room, @exit_room]
end
def furthest_pair(rects)
max_dist = -Float::INFINITY
pair = nil
rects.combination(2) do |r1, r2|
x1, y1 = r1
x2, y2 = r2
dist = Math.hypot(x2 - x1, y2 - y1)
if dist > max_dist
max_dist = dist
pair = [r1, r2]
end
end
pair
end
def carve_passages_from(cx, cy)