From bf1171a775b8256bd9755e0bfd026dafaa465d0c Mon Sep 17 00:00:00 2001 From: Daanish Date: Fri, 3 Apr 2026 14:46:35 +0100 Subject: [PATCH] Refactor input handling: replace hardcoded key inputs with configurable key bindings for character movement, inventory navigation, and actions; enhance settings scene with key rebind functionality and add credits scene. --- .gitignore | 3 +- credits/scene.rb | 44 +++++++++++++++++++++++++ game/character.rb | 8 ++--- game/hud/health.rb | 6 ++-- game/hud/inventory.rb | 10 +++--- game/props/base.rb | 2 +- game/props/handler.rb | 2 +- game/scene.rb | 6 ++-- game/weapons/handler.rb | 2 +- menu/scene.rb | 1 + settings/configuration.rb | 49 ++++++++++++++++++++------- settings/scene.rb | 69 ++++++++++++++++++++++++++++++++++++--- window.rb | 14 ++++---- 13 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 credits/scene.rb diff --git a/.gitignore b/.gitignore index 0ab1592..b068f51 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.old.* \ No newline at end of file +*.old.* +config.json \ No newline at end of file diff --git a/credits/scene.rb b/credits/scene.rb new file mode 100644 index 0000000..02b31f2 --- /dev/null +++ b/credits/scene.rb @@ -0,0 +1,44 @@ +class Credits < Scene + def initialize + super + @font = Gosu::Font.new(32, name: "assets/fonts/tn.ttf") + @small_font = Gosu::Font.new(20, name: "assets/fonts/tn.ttf") + end + + def update(_dt) + # nothing needed + end + + def draw + Gosu.draw_rect(0, 0, *SCREEN_SIZE, Gosu::Color.new(0xFF131313)) + + # title (centered) + title = "Credits" + tx = (720 - @font.text_width(title)) / 2 + @font.draw_text(title, tx, 80, 1, 1, 1, Gosu::Color::YELLOW) + + # main content + # this is kinda useless and i could just hardcode the positions, or even the image itself but yeah + lines = [ + "Developer: Syed Daanish", + "Artist: Syed Daanish" + ] + + lines.each_with_index do |line, i| + x = (720 - @small_font.text_width(line)) / 2 + y = 180 + i * 40 + @small_font.draw_text(line, x, y, 1, 1, 1, Gosu::Color::WHITE) + end + + # hint at bottom + hint = "Press ESC to return" + hx = (720 - @small_font.text_width(hint)) / 2 + @small_font.draw_text(hint, hx, 420, 1, 1, 1, Gosu::Color::GRAY) + end + + def button_down(id, _pos) + if id == Gosu::KB_ESCAPE || id == Gosu::KB_RETURN + $bus.emit(:change_scene, Menu) + end + end +end \ No newline at end of file diff --git a/game/character.rb b/game/character.rb index c31be6a..b5557f8 100644 --- a/game/character.rb +++ b/game/character.rb @@ -76,10 +76,10 @@ class Character dx = 0.0 dy = 0.0 - dx -= 1 if Gosu.button_down?(Gosu::KB_A) - dx += 1 if Gosu.button_down?(Gosu::KB_D) - dy -= 1 if Gosu.button_down?(Gosu::KB_W) - dy += 1 if Gosu.button_down?(Gosu::KB_S) + dx -= 1 if Gosu.button_down?($bus.get(:settings, :leftward)) + dx += 1 if Gosu.button_down?($bus.get(:settings, :rightward)) + dy -= 1 if Gosu.button_down?($bus.get(:settings, :forward)) + dy += 1 if Gosu.button_down?($bus.get(:settings, :backward)) moving = dx != 0 || dy != 0 diff --git a/game/hud/health.rb b/game/hud/health.rb index a1e81de..ca7d138 100644 --- a/game/hud/health.rb +++ b/game/hud/health.rb @@ -35,12 +35,12 @@ class HealthBar def draw # Draw the background of the health bar (red) - Gosu.draw_rect(SCREEN_SIZE[0] - 100 - 10, 180, 100, 10, Gosu::Color::RED) + Gosu.draw_rect(SCREEN_SIZE[0] - 100 - 10, 180, 100, 10, Gosu::Color::RED, Float::INFINITY) # Draw the foreground of the health bar (green) based on current health health_width = (health.to_f / max_health) * 100 - Gosu.draw_rect(SCREEN_SIZE[0] - 100 - 10, 180, health_width, 10, Gosu::Color::GREEN) + Gosu.draw_rect(SCREEN_SIZE[0] - 100 - 10, 180, health_width, 10, Gosu::Color::GREEN, Float::INFINITY) - @heart_image.draw(SCREEN_SIZE[0] - 100 - 20, 173, 1, 1.5, 1.5) + @heart_image.draw(SCREEN_SIZE[0] - 100 - 20, 173, Float::INFINITY, 1.5, 1.5) end end \ No newline at end of file diff --git a/game/hud/inventory.rb b/game/hud/inventory.rb index fc1ae1d..b90f29e 100644 --- a/game/hud/inventory.rb +++ b/game/hud/inventory.rb @@ -94,19 +94,19 @@ class Inventory when Gosu::KB_1 then @inventory_selected = :occams_razor when Gosu::KB_2 then @inventory_selected = :quantum_bow when Gosu::KB_3 then @inventory_selected = :blade_of_recursion - when Gosu::KB_DOWN + when $bus.get(:settings, :"inventory down") row, col = find_slot(@inventory_selected) @inventory_selected = @grid[(row + 1) % @grid.size][col][0] - when Gosu::KB_UP + when $bus.get(:settings, :"inventory up") row, col = find_slot(@inventory_selected) @inventory_selected = @grid[(row - 1) % @grid.size][col][0] - when Gosu::KB_LEFT + when $bus.get(:settings, :"inventory left") row, col = find_slot(@inventory_selected) @inventory_selected = @grid[row][(col - 1) % @grid[row].size][0] - when Gosu::KB_RIGHT + when $bus.get(:settings, :"inventory right") row, col = find_slot(@inventory_selected) @inventory_selected = @grid[row][(col + 1) % @grid[row].size][0] - when Gosu::KB_RETURN + when $bus.get(:settings, :craft) craft(@inventory_selected, ctrl: Gosu.button_down?(Gosu::KB_RIGHT_CONTROL) || Gosu.button_down?(Gosu::KB_LEFT_CONTROL)) else return false diff --git a/game/props/base.rb b/game/props/base.rb index e5719de..9f2c071 100644 --- a/game/props/base.rb +++ b/game/props/base.rb @@ -49,7 +49,7 @@ class Prop end def update(dt) - return unless Gosu.button_down?(Gosu::MS_LEFT) || Gosu.button_down?(Gosu::KB_X) + return unless Gosu.button_down?(Gosu::MS_LEFT) || Gosu.button_down?($bus.get(:settings, :break)) player_x, player_y = $bus.get(:player_position) || [0, 0] diff --git a/game/props/handler.rb b/game/props/handler.rb index e7e4cb8..a294aea 100644 --- a/game/props/handler.rb +++ b/game/props/handler.rb @@ -122,7 +122,7 @@ class PropsHandler @grid.delete([prop.x, prop.y]) end end - if Gosu.button_down?(Gosu::KB_P) # place selected if it is a placeable item + if Gosu.button_down?($bus.get(:settings, :place)) selected_item = $bus.get(:selected_item) if selected_item == :torch player_x, player_y = $bus.get(:player_position) || [0, 0] diff --git a/game/scene.rb b/game/scene.rb index e92ecb0..a5ed086 100644 --- a/game/scene.rb +++ b/game/scene.rb @@ -27,7 +27,7 @@ class Game < Scene @camera = [0, 0] - $bus.emit(:log, "Welcome to the Mist! Use WASD to move, SPACE or left-click to attack with your equipped weapon, and ESC to return to the menu.") + $bus.emit(:log, "Welcome to the Mist!") $bus.on(:player_move) do |pos| @camera = pos @@ -55,7 +55,7 @@ class Game < Scene @enemies.draw @props.draw @traps.draw - $bus.emit(:shade) if $bus.get(:settings, :fog) + $bus.emit(:shade) @hud.draw @weapons.draw draw_debug! if $bus.get(:settings, :debug) @@ -98,7 +98,7 @@ class Game < Scene 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) if id == Gosu::KB_ESCAPE return if @hud.button_down(id, pos) return if @props.button_down(id, pos) diff --git a/game/weapons/handler.rb b/game/weapons/handler.rb index f09437e..b42e80a 100644 --- a/game/weapons/handler.rb +++ b/game/weapons/handler.rb @@ -27,7 +27,7 @@ class WeaponHandler weapon = @weapons[selected_item] if $bus.get(:count, selected_item) > 0 - if id == Gosu::KB_SPACE + if id == $bus.get(:settings, :attack) player_x, player_y = $bus.get(:player_position) cam_x, cam_y = $bus.get(:camera_pos) || [0, 0] mouse_x, mouse_y = $bus.get(:mouse_pos) || [0, 0] diff --git a/menu/scene.rb b/menu/scene.rb index 50d980e..678cd61 100644 --- a/menu/scene.rb +++ b/menu/scene.rb @@ -2,6 +2,7 @@ class Menu < Scene OPTIONS = { "Start" => -> { $bus.emit(:change_scene, Game) }, "Settings" => -> { $bus.emit(:change_scene, Settings) }, + "Credits" => -> { $bus.emit(:change_scene, Credits) }, "Quit" => -> { $bus.emit(:quit_game) } }.freeze diff --git a/settings/configuration.rb b/settings/configuration.rb index da2f736..087082c 100644 --- a/settings/configuration.rb +++ b/settings/configuration.rb @@ -5,28 +5,55 @@ N, S, W, E = 1, 2, 4, 8 # Config module to hold global settings that can be toggled in the settings scene, and accessed anywhere else in the code via the event bus module Config - @config = { - debug: ARGV.include?("--debug"), - fog: true, - torches_lightup: true, + @debug = false + + @keybinds = { + forward: Gosu::KB_W, + backward: Gosu::KB_S, + leftward: Gosu::KB_A, + rightward: Gosu::KB_D, + break: Gosu::KB_X, + attack: Gosu::KB_SPACE, + place: Gosu::KB_P, + "inventory up": Gosu::KB_UP, + "inventory down": Gosu::KB_DOWN, + "inventory right": Gosu::KB_RIGHT, + "inventory left": Gosu::KB_LEFT, + craft: Gosu::KB_RETURN, } - + if File.exist?("config.json") data = JSON.parse(File.read("config.json")) - @config.merge!(data.transform_keys(&:to_sym)) + @debug = data["debug"] || false + @keybinds.merge!(data["keybinds"]&.transform_keys(&:to_sym) || {}) end - $bus.on(:settings, :master) { |k| @config[k] } + $bus.on(:settings, :master) { |k| k == :debug ? @debug : @keybinds[k] } module_function - def toggle(key) - @config[key] = !@config[key] + def bindings + @keybinds + end + + def debug + @debug + end + + def debug=(value) + @debug = value + end + + def set_key(action, key) + @keybinds[action] = key end def persist! File.open("config.json", "w") do |f| - f.write(JSON.pretty_generate(@config)) + f.write(JSON.pretty_generate({ + keybinds: @keybinds, + debug: @debug + })) end end -end +end \ No newline at end of file diff --git a/settings/scene.rb b/settings/scene.rb index 107a23b..597d274 100644 --- a/settings/scene.rb +++ b/settings/scene.rb @@ -1,15 +1,76 @@ class Settings < Scene def initialize super - @font = Gosu::Font.new(24) + @font_title = Gosu::Font.new(28, name: "assets/fonts/tn.ttf") + @font = Gosu::Font.new(20, name: "assets/fonts/tn.ttf") + @selected = 0 + @waiting_for_key = nil end def draw - Gosu.draw_rect(0, 0, SCREEN_SIZE[0], SCREEN_SIZE[1], Gosu::Color::BLACK) - @font.draw_text("Settings Scene - Press ESC to return to Menu", 50, 50, 1, 1, 1, Gosu::Color::WHITE) + Gosu.draw_rect(0, 0, *SCREEN_SIZE, Gosu::Color.new(0xFF131313)) + + title = "Settings" + @font_title.draw_text(title, center_x(title), 40, 1, 1, 1, Gosu::Color::YELLOW) + + debug_option = "Debug Mode".ljust(15) + " : " + "#{Config.debug ? 'ON' : 'OFF'}".rjust(12) + debug_option = "> #{debug_option} <" if @selected == 0 + @font.draw_text(debug_option, center_x(debug_option), 90, 1, 1, 1, @selected == 0 ? Gosu::Color::YELLOW : Gosu::Color::WHITE) + + Config.bindings.each_with_index do |opt, i| + y = 120 + i * 25 + selected = (i == @selected - 1) + + text = "#{opt[0].to_s.ljust(15)} : #{Gosu.button_name(opt[1]).rjust(12)}" + text = "> #{text} <" if selected + + color = selected ? Gosu::Color::YELLOW : Gosu::Color::WHITE + @font.draw_text(text, center_x(text), y, 1, 1, 1, color) + end + + if @waiting_for_key + msg = "Press a key... (ESC to cancel)" + @font.draw_text(msg, center_x(msg), 430, 1, 1, 1, Gosu::Color::GRAY) + else + hint = "Enter: Select | ESC: Back" + @font.draw_text(hint, center_x(hint), 430, 1, 1, 1, Gosu::Color::GRAY) + end end def button_down(id, _pos) - $bus.emit(:change_scene, Menu) if id == Gosu::KB_ESCAPE + # waiting for keybind input + if @waiting_for_key + if [Gosu::KB_ESCAPE].include?(id) + @waiting_for_key = nil + return + end + Config.set_key(@waiting_for_key, id) + Config.persist! + @waiting_for_key = nil + return + end + + case id + when Gosu::KB_UP + @selected = (@selected - 1) % (Config.bindings.size + 1) + when Gosu::KB_DOWN + @selected = (@selected + 1) % (Config.bindings.size + 1) + when Gosu::KB_RETURN + if @selected == 0 + Config.debug = !Config.debug + Config.persist! + else + @waiting_for_key = Config.bindings.keys[@selected] + end + when Gosu::KB_ESCAPE + Config.persist! + $bus.emit(:change_scene, Menu) + end + end + + private + + def center_x(text) + (SCREEN_SIZE[0] - @font.text_width(text)) / 2 end end \ No newline at end of file diff --git a/window.rb b/window.rb index 8540cd4..746d426 100644 --- a/window.rb +++ b/window.rb @@ -1,11 +1,13 @@ require 'opengl' require 'glu' require 'gosu' +require 'json' require_relative 'settings/configuration' require_relative 'utils/utils' require_relative 'utils/scene' require_relative 'settings/scene' +require_relative 'credits/scene' require_relative 'menu/scene' require_relative 'game_over/scene' require_relative 'game/scene' @@ -68,13 +70,11 @@ class Window < Gosu::Window cam_x, cam_y = $bus.get(:camera_pos) || [0, 0] torch_lights = [] - # Only calculate torch lights if the setting is enabled, to save performance - if $bus.get(:settings, :torches_lightup) - torches = $bus.get(:nearby_torches, - [cam_x - SCREEN_SIZE[0], cam_y - SCREEN_SIZE[1], SCREEN_SIZE[0] * 4, SCREEN_SIZE[1] * 4] - ) || [] - torch_lights = torches.map { |t| [t[0] - cam_x, SCREEN_SIZE[1] - (t[1] - cam_y)] }.flatten - end + + torches = $bus.get(:nearby_torches, + [cam_x - SCREEN_SIZE[0], cam_y - SCREEN_SIZE[1], SCREEN_SIZE[0] * 4, SCREEN_SIZE[1] * 4] + ) || [] + torch_lights = torches.map { |t| [t[0] - cam_x, SCREEN_SIZE[1] - (t[1] - cam_y)] }.flatten lorentz_field = $bus.get(:lorentz_field) || 0.0