diff --git a/assets/images/game_over_bg.aseprite b/assets/images/game_over_bg.aseprite new file mode 100644 index 0000000..ee6070f Binary files /dev/null and b/assets/images/game_over_bg.aseprite differ diff --git a/assets/images/game_over_bg.png b/assets/images/game_over_bg.png new file mode 100644 index 0000000..adf3cf0 Binary files /dev/null and b/assets/images/game_over_bg.png differ 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