From db7c2aa7a224a5dc064fbe61bb18a8ae1dae3c1d Mon Sep 17 00:00:00 2001 From: Daanish Date: Thu, 2 Apr 2026 15:33:50 +0100 Subject: [PATCH] Enhance game over scene: implement GameOver class with options for retry and main menu, add background image, and handle mouse and keyboard interactions. --- assets/images/game_over_bg.aseprite | Bin 0 -> 5976 bytes assets/images/game_over_bg.png | Bin 0 -> 2908 bytes game/enemy/handler.rb | 2 +- game/hud/health.rb | 11 +++-- game/hud/layer.rb | 2 +- game/weapons/lorentz_field.rb | 13 +++--- game_over/scene.rb | 66 ++++++++++++++++++++++++++++ menu/scene.rb | 4 +- settings/configuration.rb | 2 +- utils/bus.rb | 11 +++-- utils/scene.rb | 4 -- window.rb | 13 +++--- 12 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 assets/images/game_over_bg.aseprite create mode 100644 assets/images/game_over_bg.png create mode 100644 game_over/scene.rb diff --git a/assets/images/game_over_bg.aseprite b/assets/images/game_over_bg.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..ee6070fd48149e661bc8fc8b79fcf170dacd2eb5 GIT binary patch literal 5976 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 literal 0 HcmV?d00001 diff --git a/game/enemy/handler.rb b/game/enemy/handler.rb index a7e5cf4..76357f9 100644 --- a/game/enemy/handler.rb +++ b/game/enemy/handler.rb @@ -18,7 +18,7 @@ class EnemyHandler def spawn! # For now, just spawn a single enemy at a fixed location 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) end end diff --git a/game/hud/health.rb b/game/hud/health.rb index 7114369..6cd060e 100644 --- a/game/hud/health.rb +++ b/game/hud/health.rb @@ -8,7 +8,10 @@ class HealthBar def take_damage(amount) @health -= amount - @health = 0 if @health < 0 + if @health <= 0 + @health = 0 + $bus.emit(:change_scene, GameOver) + end end def heal(amount) @@ -16,12 +19,12 @@ class HealthBar @health = @max_health if @health > @max_health end - def draw(x, y) + def draw # 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 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 \ No newline at end of file diff --git a/game/hud/layer.rb b/game/hud/layer.rb index 85b6228..a2f0984 100644 --- a/game/hud/layer.rb +++ b/game/hud/layer.rb @@ -17,7 +17,7 @@ class HUDLayer @hud_bg.draw(0, 0, Float::INFINITY) @inventory.draw - @health.draw(10, 10) + @health.draw @hud_fg.draw(0, 0, Float::INFINITY) end diff --git a/game/weapons/lorentz_field.rb b/game/weapons/lorentz_field.rb index 21e687d..21662e8 100644 --- a/game/weapons/lorentz_field.rb +++ b/game/weapons/lorentz_field.rb @@ -1,16 +1,17 @@ class LorentzField BASE_TIME = 25 # Base time for the field to be active in seconds - def initialize + def initialize(bus) + @bus = bus @time = 0 @active = false @font = Gosu::Font.new(24, name: "assets/fonts/tn.ttf") - $bus.on(:lorentz_field?) do + @bus.on(:lorentz_field?) do next @active end - $bus.on(:lorentz_field) do + @bus.on(:lorentz_field) do if @active t = @time remaining = BASE_TIME - t @@ -33,10 +34,10 @@ class LorentzField def attack(_direction) return if @active - $bus.emit(:consume, :lorentz_field, 1) + @bus.emit(:consume, :lorentz_field, 1) @active = true @time = 0 - $bus.emit(:lorentz_field!, true) + @bus.emit(:lorentz_field!, true) end def update(dt) @@ -46,7 +47,7 @@ class LorentzField if @time >= BASE_TIME @time = 0 @active = false - $bus.emit(:lorentz_field!, false) + @bus.emit(:lorentz_field!, false) end end diff --git a/game_over/scene.rb b/game_over/scene.rb new file mode 100644 index 0000000..edc1fab --- /dev/null +++ b/game_over/scene.rb @@ -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 \ No newline at end of file diff --git a/menu/scene.rb b/menu/scene.rb index e60ffa3..50d980e 100644 --- a/menu/scene.rb +++ b/menu/scene.rb @@ -1,7 +1,7 @@ class Menu < Scene OPTIONS = { - "Start" => -> { $bus.emit(:change_scene, Game.new) }, - "Settings" => -> { $bus.emit(:change_scene, Settings.new) }, + "Start" => -> { $bus.emit(:change_scene, Game) }, + "Settings" => -> { $bus.emit(:change_scene, Settings) }, "Quit" => -> { $bus.emit(:quit_game) } }.freeze diff --git a/settings/configuration.rb b/settings/configuration.rb index b1d8839..da2f736 100644 --- a/settings/configuration.rb +++ b/settings/configuration.rb @@ -16,7 +16,7 @@ module Config @config.merge!(data.transform_keys(&:to_sym)) end - $bus.on(:settings) { |k| @config[k] } + $bus.on(:settings, :master) { |k| @config[k] } module_function diff --git a/utils/bus.rb b/utils/bus.rb index 026bb62..25dd8c6 100644 --- a/utils/bus.rb +++ b/utils/bus.rb @@ -12,7 +12,7 @@ class EventBus end # Subscribe to an event - def on(event, owner: nil, &callback) + def on(event, owner = nil, &callback) owner ||= callback.binding.eval("self") @events[event] << { callback: callback, owner: owner } end @@ -51,9 +51,14 @@ class EventBus results 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) - @events.each do |event, handlers| + @events.each do |_, handlers| handlers.reject! { |h| h[:owner] == owner } end end diff --git a/utils/scene.rb b/utils/scene.rb index 7b1cb63..058ba94 100644 --- a/utils/scene.rb +++ b/utils/scene.rb @@ -12,8 +12,4 @@ class Scene def button_down(id, pos) end - - def close - $bus.remove_owner(self) - end end \ No newline at end of file diff --git a/window.rb b/window.rb index 4fc1e40..8540cd4 100644 --- a/window.rb +++ b/window.rb @@ -7,6 +7,7 @@ require_relative 'utils/utils' require_relative 'utils/scene' require_relative 'settings/scene' require_relative 'menu/scene' +require_relative 'game_over/scene' require_relative 'game/scene' class Window < Gosu::Window @@ -23,18 +24,18 @@ class Window < Gosu::Window @scene = Menu.new @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) end - $bus.on(:change_scene) do |new_scene| - @scene.close - @scene = new_scene + $bus.on(:change_scene, :master) do |new_scene| + $bus.reset + @scene = new_scene.new end - $bus.on(:shade) do + $bus.on(:shade, :master) do shade! end end