Enhance game over scene: implement GameOver class with options for retry and main menu, add background image, and handle mouse and keyboard interactions.

This commit is contained in:
2026-04-02 15:33:50 +01:00
parent 32de437f86
commit db7c2aa7a2
12 changed files with 100 additions and 28 deletions
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+1 -1
View File
@@ -18,7 +18,7 @@ class EnemyHandler
def spawn! def spawn!
# For now, just spawn a single enemy at a fixed location # For now, just spawn a single enemy at a fixed location
start_room_coords = $bus.get(:start_room_coords) start_room_coords = $bus.get(:start_room_coords)
if @enemies.empty? if @enemies.empty? && start_room_coords
@enemies << Force.new((start_room_coords[0] * 2 + 1 + 4) * 60 + 30, (start_room_coords[1] * 2 + 1 + 4) * 60 + 30) @enemies << Force.new((start_room_coords[0] * 2 + 1 + 4) * 60 + 30, (start_room_coords[1] * 2 + 1 + 4) * 60 + 30)
end end
end end
+7 -4
View File
@@ -8,7 +8,10 @@ class HealthBar
def take_damage(amount) def take_damage(amount)
@health -= amount @health -= amount
@health = 0 if @health < 0 if @health <= 0
@health = 0
$bus.emit(:change_scene, GameOver)
end
end end
def heal(amount) def heal(amount)
@@ -16,12 +19,12 @@ class HealthBar
@health = @max_health if @health > @max_health @health = @max_health if @health > @max_health
end end
def draw(x, y) def draw
# Draw the background of the health bar (red) # Draw the background of the health bar (red)
Gosu.draw_rect(x, y, 100, 10, Gosu::Color::RED) Gosu.draw_rect(30, 10, 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(x, y, health_width, 10, Gosu::Color::GREEN) Gosu.draw_rect(30, 10, health_width, 10, Gosu::Color::GREEN)
end end
end end
+1 -1
View File
@@ -17,7 +17,7 @@ class HUDLayer
@hud_bg.draw(0, 0, Float::INFINITY) @hud_bg.draw(0, 0, Float::INFINITY)
@inventory.draw @inventory.draw
@health.draw(10, 10) @health.draw
@hud_fg.draw(0, 0, Float::INFINITY) @hud_fg.draw(0, 0, Float::INFINITY)
end end
+7 -6
View File
@@ -1,16 +1,17 @@
class LorentzField class LorentzField
BASE_TIME = 25 # Base time for the field to be active in seconds BASE_TIME = 25 # Base time for the field to be active in seconds
def initialize def initialize(bus)
@bus = bus
@time = 0 @time = 0
@active = false @active = false
@font = Gosu::Font.new(24, name: "assets/fonts/tn.ttf") @font = Gosu::Font.new(24, name: "assets/fonts/tn.ttf")
$bus.on(:lorentz_field?) do @bus.on(:lorentz_field?) do
next @active next @active
end end
$bus.on(:lorentz_field) do @bus.on(:lorentz_field) do
if @active if @active
t = @time t = @time
remaining = BASE_TIME - t remaining = BASE_TIME - t
@@ -33,10 +34,10 @@ class LorentzField
def attack(_direction) def attack(_direction)
return if @active return if @active
$bus.emit(:consume, :lorentz_field, 1) @bus.emit(:consume, :lorentz_field, 1)
@active = true @active = true
@time = 0 @time = 0
$bus.emit(:lorentz_field!, true) @bus.emit(:lorentz_field!, true)
end end
def update(dt) def update(dt)
@@ -46,7 +47,7 @@ class LorentzField
if @time >= BASE_TIME if @time >= BASE_TIME
@time = 0 @time = 0
@active = false @active = false
$bus.emit(:lorentz_field!, false) @bus.emit(:lorentz_field!, false)
end end
end end
+66
View File
@@ -0,0 +1,66 @@
class GameOver < Scene
OPTIONS = {
"Retry" => -> { $bus.emit(:change_scene, Game) },
"Main Menu" => -> { $bus.emit(:change_scene, Menu) }
}.freeze
def initialize
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)
@last_mouse_pos = nil
end
def update(_dt)
pos = $bus.get(:mouse_pos)
return unless pos
if @last_mouse_pos.nil? || ( (pos[0] - @last_mouse_pos[0]).abs > 2 || (pos[1] - @last_mouse_pos[1]).abs > 2 )
hovered_option = item_at(pos)
@selected_index = hovered_option[0] if hovered_option
@last_mouse_pos = pos.dup
end
end
def draw
@bg_image.draw(0, 0, 1)
OPTIONS.each_with_index do |(text, _), index|
y = 260 + index * 50
if index == @selected_index
@font.draw_text("[ #{text.ljust(10)} ]", 50, y, 1, 1, 1, Gosu::Color::YELLOW)
else
@font.draw_text(" #{text.ljust(10)} ", 50, y, 1, 1, 1, Gosu::Color::WHITE)
end
end
end
def button_down(id, pos)
case id
when Gosu::KB_UP
@selected_index = (@selected_index - 1) % OPTIONS.size
when Gosu::KB_DOWN
@selected_index = (@selected_index + 1) % OPTIONS.size
when Gosu::KB_RETURN
OPTIONS.values[@selected_index].call
end
return unless id == Gosu::MS_LEFT && pos
selected_option = item_at(pos)
selected_option[1].call if selected_option
end
private
def item_at(pos)
OPTIONS.each_with_index do |(_, action), index|
y = 260 + index * 50
if pos[1].between?(y, y + 32) && pos[0].between?(50, 50 + @font.text_width(" " * 14))
return [index, action]
end
end
nil
end
end
+2 -2
View File
@@ -1,7 +1,7 @@
class Menu < Scene class Menu < Scene
OPTIONS = { OPTIONS = {
"Start" => -> { $bus.emit(:change_scene, Game.new) }, "Start" => -> { $bus.emit(:change_scene, Game) },
"Settings" => -> { $bus.emit(:change_scene, Settings.new) }, "Settings" => -> { $bus.emit(:change_scene, Settings) },
"Quit" => -> { $bus.emit(:quit_game) } "Quit" => -> { $bus.emit(:quit_game) }
}.freeze }.freeze
+1 -1
View File
@@ -16,7 +16,7 @@ module Config
@config.merge!(data.transform_keys(&:to_sym)) @config.merge!(data.transform_keys(&:to_sym))
end end
$bus.on(:settings) { |k| @config[k] } $bus.on(:settings, :master) { |k| @config[k] }
module_function module_function
+8 -3
View File
@@ -12,7 +12,7 @@ class EventBus
end end
# Subscribe to an event # Subscribe to an event
def on(event, owner: nil, &callback) def on(event, owner = nil, &callback)
owner ||= callback.binding.eval("self") owner ||= callback.binding.eval("self")
@events[event] << { callback: callback, owner: owner } @events[event] << { callback: callback, owner: owner }
end end
@@ -51,9 +51,14 @@ class EventBus
results results
end end
# Unsubscribe all events for a given owner (e.g. when an object is destroyed) def reset
@events.each do |_, handlers|
handlers.reject! { |h| h[:owner] != :master }
end
end
def remove_owner(owner) def remove_owner(owner)
@events.each do |event, handlers| @events.each do |_, handlers|
handlers.reject! { |h| h[:owner] == owner } handlers.reject! { |h| h[:owner] == owner }
end end
end end
-4
View File
@@ -12,8 +12,4 @@ class Scene
def button_down(id, pos) def button_down(id, pos)
end end
def close
$bus.remove_owner(self)
end
end end
+7 -6
View File
@@ -7,6 +7,7 @@ require_relative 'utils/utils'
require_relative 'utils/scene' require_relative 'utils/scene'
require_relative 'settings/scene' require_relative 'settings/scene'
require_relative 'menu/scene' require_relative 'menu/scene'
require_relative 'game_over/scene'
require_relative 'game/scene' require_relative 'game/scene'
class Window < Gosu::Window class Window < Gosu::Window
@@ -23,18 +24,18 @@ class Window < Gosu::Window
@scene = Menu.new @scene = Menu.new
@last_time = Gosu.milliseconds @last_time = Gosu.milliseconds
$bus.on(:quit_game) { close! } $bus.on(:quit_game, :master) { close! }
$bus.on(:mouse_pos) do $bus.on(:mouse_pos, :master) do
next mouse_relative(mouse_x, mouse_y) next mouse_relative(mouse_x, mouse_y)
end end
$bus.on(:change_scene) do |new_scene| $bus.on(:change_scene, :master) do |new_scene|
@scene.close $bus.reset
@scene = new_scene @scene = new_scene.new
end end
$bus.on(:shade) do $bus.on(:shade, :master) do
shade! shade!
end end
end end