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:
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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user