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) $bus.emit(:trap_stepped_on, rect, :character)
end 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) 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 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
end end
+7 -2
View File
@@ -5,6 +5,8 @@ class HealthBar
@max_health = max_health @max_health = max_health
@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| $bus.on(:blast) do |x, y, radius, damage, safety|
next if safety == :safe next if safety == :safe
@@ -21,6 +23,7 @@ class HealthBar
@health -= amount @health -= amount
if @health <= 0 if @health <= 0
@health = 0 @health = 0
$is_dead = true
$bus.emit(:change_scene, GameOver) $bus.emit(:change_scene, GameOver)
end end
end end
@@ -32,10 +35,12 @@ class HealthBar
def draw def draw
# Draw the background of the health bar (red) # 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 # Draw the foreground of the health bar (green) based on current health
health_width = (health.to_f / max_health) * 100 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
end end
+17
View File
@@ -1,6 +1,7 @@
require_relative "inventory" require_relative "inventory"
require_relative "health" require_relative "health"
require_relative "logs" require_relative "logs"
require_relative "minimap"
class HUDLayer class HUDLayer
def initialize def initialize
@@ -9,6 +10,9 @@ class HUDLayer
@inventory = Inventory.new @inventory = Inventory.new
@health = HealthBar.new(100) @health = HealthBar.new(100)
@logs = GameLogger.new @logs = GameLogger.new
@minimap = Minimap.new
@direction_sprite = Gosu::Image.new("assets/images/direction.png", retro: true)
$bus.on(:enemy_attack) do |damage| $bus.on(:enemy_attack) do |damage|
@health.take_damage(damage) @health.take_damage(damage)
@@ -26,6 +30,19 @@ class HUDLayer
@health.draw @health.draw
@logs.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) @hud_fg.draw(0, 0, Float::INFINITY)
end 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| $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] } nearby_props(rect).select { |p| p.is_a?(Torch) }.map(&:rect).map { |t| [t[0] + t[2] / 2, t[1] + t[3] / 2] }
end 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 end
def spawn_chests! def spawn_chests!
@@ -49,7 +77,7 @@ class PropsHandler
end end
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] start_room_x, start_room_y = $bus.get(:start_room_coords) || [0, 0]
x, y = [ x, y = [
[start_room_x, start_room_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 @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 if $bus.get_all(:collides?, torch.collision_rect).include?(:character) # can't place if colliding with player
return unless $bus.get(:consume, :torch, 1) return unless $bus.get(:consume, :torch, 1)
$bus.emit(:torch_placed, torch.x, torch.y)
add(torch) add(torch)
elsif selected_item == :radar elsif selected_item == :radar
player_x, player_y = $bus.get(:player_position) || [0, 0] player_x, player_y = $bus.get(:player_position) || [0, 0]
+9
View File
@@ -6,4 +6,13 @@ class Torch < Prop
def resources def resources
{ wood: rand(0..8) } { wood: rand(0..8) }
end 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 end
+4
View File
@@ -9,6 +9,8 @@ require_relative 'character'
class Game < Scene class Game < Scene
def initialize def initialize
super super
$is_dead = false
@font = Gosu::Font.new(24) @font = Gosu::Font.new(24)
@floor_image = Gosu::Image.new("assets/images/floor.png", retro: true) @floor_image = Gosu::Image.new("assets/images/floor.png", retro: true)
@@ -94,6 +96,8 @@ class Game < Scene
end end
def button_down(id, pos) 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 $bus.emit(:change_scene, Menu.new) if id == Gosu::KB_ESCAPE
return if @hud.button_down(id, pos) return if @hud.button_down(id, pos)
+6 -3
View File
@@ -11,6 +11,8 @@ class BladeOfRecursion
# the knife is pointed top-right # the knife is pointed top-right
@sprite = Gosu::Image.new('assets/images/blade_of_recursion.png', retro: true) @sprite = Gosu::Image.new('assets/images/blade_of_recursion.png', retro: true)
@memory_sprite = Gosu::Image.new('assets/images/memory.png', retro: true)
end end
def attack(direction) 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 # this should be in the HUD layer but due to time constraints im putting it here, sorry
bar_width = 100 bar_width = 100
bar_height = 10 bar_height = 10
padding = 10
bar_x = SCREEN_SIZE[0] - bar_width - padding bar_x = SCREEN_SIZE[0] - bar_width - 10
bar_y = padding bar_y = 205
# full red background # full red background
Gosu.draw_rect(bar_x, bar_y, bar_width, bar_height, Gosu::Color::RED, Float::INFINITY) 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_ratio = Math.log2(@damage) / 9.0
damage_width = bar_width * [damage_ratio, 1.0].min 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) 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
end end
+1 -1
View File
@@ -8,7 +8,7 @@ class GameOver < Scene
super super
@font = Gosu::Font.new(32, name: "assets/fonts/tn.ttf") @font = Gosu::Font.new(32, name: "assets/fonts/tn.ttf")
@selected_index = 0 @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 @last_mouse_pos = nil
end end
+1 -1
View File
@@ -10,6 +10,6 @@ class Settings < Scene
end end
def button_down(id, _pos) 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
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 next @start_room
end end
$bus.on(:exit_room_coords) do
next @exit_room
end
$bus.on(:room?) do |gx, gy| $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) && gx.between?(rx, rx + BOSS_ROOM_SIZE) &&
gy.between?(ry, ry + BOSS_ROOM_SIZE) gy.between?(ry, ry + BOSS_ROOM_SIZE)
end end
@@ -30,6 +34,18 @@ class MazeData
next @boss_rooms next @boss_rooms
end 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) return unless $bus.get(:settings, :debug)
print_debug print_debug
@@ -113,7 +129,7 @@ class MazeData
def wall_at?(gx, gy) def wall_at?(gx, gy)
return true if gx == 0 || gy == 0 || gx == width - 1 || gy == height - 1 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_x1 = rx * 2 + 1
display_y1 = ry * 2 + 1 display_y1 = ry * 2 + 1
display_x2 = display_x1 + (BOSS_ROOM_SIZE - 1) * 2 display_x2 = display_x1 + (BOSS_ROOM_SIZE - 1) * 2
@@ -184,7 +200,7 @@ class MazeData
end end
def fix_room_entrances! 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 dir = [N, S, E, W].sample
cx = x + BOSS_ROOM_SIZE / 2 cx = x + BOSS_ROOM_SIZE / 2
cy = y + BOSS_ROOM_SIZE / 2 cy = y + BOSS_ROOM_SIZE / 2
@@ -239,8 +255,26 @@ class MazeData
rooms << [x, y] rooms << [x, y]
end end
@start_room = rooms.shift # assign the start and exit room to be the two furthest apart rooms
@boss_rooms = 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 end
def carve_passages_from(cx, cy) def carve_passages_from(cx, cy)