Refactor event bus to use non-retrievable events;

update collision handling and add new inventory initialization;
add torch lighting in fog shader;
This commit is contained in:
2026-03-28 11:38:20 +00:00
parent 15c457e7a6
commit bf93aecad1
14 changed files with 150 additions and 60 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

+7 -3
View File
@@ -37,9 +37,13 @@ class Character
@current_animation = :idle_front @current_animation = :idle_front
@facing = :front @facing = :front
$bus.on_retrievable(:player_position) do $bus.on(:player_position) do
next [@world_x, @world_y] next [@world_x, @world_y]
end end
$bus.on(:collides?) do |rect|
next collides?(rect) ? :character : nil
end
end end
def rect def rect
@@ -95,13 +99,13 @@ class Character
steps.times do steps.times do
# X axis # X axis
@world_x += step_dx @world_x += step_dx
if ($bus.get(:collides?, rect) & [:wall, :object])&.any? if ($bus.get_all(:collides?, rect) & [:wall, :object])&.any?
@world_x -= step_dx @world_x -= step_dx
end end
# Y axis # Y axis
@world_y += step_dy @world_y += step_dy
if ($bus.get(:collides?, rect) & [:wall, :object])&.any? if ($bus.get_all(:collides?, rect) & [:wall, :object])&.any?
@world_y -= step_dy @world_y -= step_dy
end end
end end
+2 -2
View File
@@ -19,7 +19,7 @@ class EnemyAI
player_tile = [(player_x - 90) / 120, (player_y - 90) / 120].map(&:round) player_tile = [(player_x - 90) / 120, (player_y - 90) / 120].map(&:round)
enemy_tile = [(@enemy.x - 90) / 120, (@enemy.y - 90) / 120].map(&:round) enemy_tile = [(@enemy.x - 90) / 120, (@enemy.y - 90) / 120].map(&:round)
return if $bus.get(:collides?, @enemy.rect)&.include?(:character) return if $bus.get_all(:collides?, @enemy.rect)&.include?(:character)
object_in_tile = $bus.get(:object_at, enemy_tile[0], enemy_tile[1]) object_in_tile = $bus.get(:object_at, enemy_tile[0], enemy_tile[1])
@@ -134,7 +134,7 @@ class EnemyAI
@enemy.x += dx @enemy.x += dx
@enemy.y += dy @enemy.y += dy
if $bus.get(:collides?, @enemy.rect)&.include?(:character) if $bus.get_all(:collides?, @enemy.rect)&.include?(:character)
# Attack player # Attack player
end end
end end
+4
View File
@@ -10,6 +10,10 @@ class Enemy
@h = 30 @h = 30
@ai = EnemyAI.new(self, :chase) @ai = EnemyAI.new(self, :chase)
$bus.on(:collides?) do |rect|
next collides?(rect) ? :enemy : nil
end
end end
def rect def rect
+5 -1
View File
@@ -6,9 +6,13 @@ class Maze
@spritesheet = Gosu::Image.load_tiles("assets/images/walls.png", 20, 40, retro: true) @spritesheet = Gosu::Image.load_tiles("assets/images/walls.png", 20, 40, retro: true)
puts @spritesheet.size puts @spritesheet.size
$bus.on_retrievable(:maze_solve) do |x1, y1, x2, y2| $bus.on(:maze_solve) do |x1, y1, x2, y2|
next @maze.solve(x1, y1, x2, y2) next @maze.solve(x1, y1, x2, y2)
end end
$bus.on(:collides?) do |rect|
next collides?(rect) ? :wall : nil
end
end end
def collides?(rect) def collides?(rect)
+33 -9
View File
@@ -12,6 +12,14 @@ class Placeable
@y = y @y = y
@frames_count = 0 @frames_count = 0
@spritesheet = nil @spritesheet = nil
$bus.on(:object_at) do |x, y|
next @x == x && @y == y ? self : nil
end
$bus.on(:collides?) do |rect|
next collides?(rect) ? :object : nil
end
end end
def collides?(rect) def collides?(rect)
@@ -54,26 +62,42 @@ class Radar < Placeable
end end
end end
class Torch < Placeable
def initialize(x, y)
super(x, y)
@frames_count = 4
@spritesheet = Gosu::Image.load_tiles("assets/images/torch.png", 20, 20, retro: true)
$bus.on(:torch_position) do
next rect
end
end
end
class Chest < Placeable
def initialize(x, y)
super(x, y)
@frames_count = 1
@spritesheet = Gosu::Image.load_tiles("assets/images/chest.png", 20, 20, retro: true)
end
end
class ObjectHandler class ObjectHandler
attr_reader :objects attr_reader :objects
def initialize def initialize
start_room_coords = $bus.get(:start_room_coords) start_room_coords = $bus.get(:start_room_coords)
@objects = [Radar.new(start_room_coords[0] / 2 - 1, start_room_coords[1] / 2 - 1)] @objects = [
Torch.new(start_room_coords[0] / 2 - 1, start_room_coords[1] / 2 - 1),
$bus.on_retrievable(:object_at) do |x, y| Torch.new(start_room_coords[0] / 2 + 3, start_room_coords[1] / 2),
next @objects.find { |obj| obj.x == x && obj.y == y } Torch.new(start_room_coords[0] / 2, start_room_coords[1] / 2 - 1),
end ]
end end
def add(object) def add(object)
@objects << object @objects << object
end end
def collides?(rect)
@objects.any? { |obj| obj.collides?(rect) }
end
def update def update
@objects.each(&:update) @objects.each(&:update)
end end
+42 -22
View File
@@ -27,18 +27,9 @@ class Game < Scene
@camera = pos @camera = pos
end end
$bus.on_retrievable(:camera_pos) do $bus.on(:camera_pos) do
next @camera next @camera
end end
$bus.on_retrievable(:collides?) do |rect|
collisions = []
collisions << :wall if @maze.collides?(rect)
collisions << :character if @character.collides?(rect)
collisions << :enemy if @enemies.collides?(rect)
collisions << :object if @objects.collides?(rect)
next collisions
end
end end
def draw def draw
@@ -74,13 +65,19 @@ class Game < Scene
def draw_fog! def draw_fog!
cam_x, cam_y = @camera cam_x, cam_y = @camera
cell = 10 cell = 10
i_radius = 90.0 p_i_radius = 80.0
o_radius = 400.0 p_o_radius = 350.0
t_i_radius = 10.0
o_radius = 160.0
# Player screen position # Player screen position
px = SCREEN_SIZE[0] / 2.0 px = SCREEN_SIZE[0] / 2.0
py = SCREEN_SIZE[1] / 2.0 py = SCREEN_SIZE[1] / 2.0
# Collect all light sources (player + torches)
torches = $bus.get_all(:torch_position)
torch_lights = torches.map { |t| [t[0] - cam_x, t[1] - cam_y] }
tiles_x = (SCREEN_SIZE[0] / cell) + 2 tiles_x = (SCREEN_SIZE[0] / cell) + 2
tiles_y = (SCREEN_SIZE[1] / cell) + 2 tiles_y = (SCREEN_SIZE[1] / cell) + 2
@@ -89,21 +86,44 @@ class Game < Scene
sx = i * cell sx = i * cell
sy = j * cell sy = j * cell
dist = Math.sqrt((sx - px)**2 + (sy - py)**2) light_strength = 0.0
if dist < i_radius d = Math.sqrt((px - sx)**2 + (py - sy)**2)
next # fully clear if d < p_i_radius
elsif dist > o_radius light_strength = 1.0
alpha = 255 elsif d < p_o_radius
t = (d - p_i_radius) / (p_o_radius - p_i_radius) # 0..1
light_strength = 1.0 - t
end
torch_lights.each do |lx, ly|
d = Math.sqrt((sx - lx)**2 + (sy - ly)**2)
if d < t_i_radius
strength = 1.0
elsif d < o_radius
t = (d - t_i_radius) / (o_radius - t_i_radius) # 0..1
strength = 1.0 - t
else else
t = (dist - i_radius) / (o_radius - i_radius) # 0..1 strength = 0.0
end
light_strength += strength
end
light_strength = light_strength.clamp(0.0, 1.0)
t = 1.0 - light_strength
world_x = (sx + cam_x) * 0.005 world_x = (sx + cam_x) * 0.005
world_y = (sy + cam_y) * 0.005 world_y = (sy + cam_y) * 0.005
# Sample noise at world position so it scrolls with camera
# Add time-based offsets for animation noise = (@noise[
noise = (@noise[world_x + Gosu.milliseconds / 5000.0, world_y + Gosu.milliseconds / 6000.0] + 1.0) / 2.0 world_x + Gosu.milliseconds / 5000.0,
world_y + Gosu.milliseconds / 6000.0
] + 1.0) / 2.0
alpha = (t * (1.0 + noise) * 255).to_i.clamp(0, 255) alpha = (t * (1.0 + noise) * 255).to_i.clamp(0, 255)
end
Gosu.draw_rect(sx, sy, cell, cell, Gosu::Color.new(alpha, 0, 0, 0), 10000 + sy) Gosu.draw_rect(sx, sy, cell, cell, Gosu::Color.new(alpha, 0, 0, 0), 10000 + sy)
end end
+31
View File
@@ -4,4 +4,35 @@ class State
# also their health, stealth, and memory bars # also their health, stealth, and memory bars
# it can handle mouse input for inventory management and item usage # it can handle mouse input for inventory management and item usage
# it can also handle keyboard input for quick item usage # it can also handle keyboard input for quick item usage
def initialize
@inventory = {
# basic resources
:wood => 0,
:metal => 0,
:science => 0,
# nuetrals
:torch => 0,
:radar => 0,
# placeable weapons
:landmine => 0,
:shrodingers_mine => 0,
:event_horizon => 0,
# attack weapons
:occams_razor => 0,
:quantum_bow => 0,
:blade_of_recursion => 0,
# utility weapon
:lorentz_field => 0
}
@inventory_selected = :wood
@upgrades = []
@health = 100
@stealth = 100
@memory = 256
end
end end
+1 -1
View File
@@ -20,7 +20,7 @@ class Window < Gosu::Window
$bus.on(:quit_game) { close! } $bus.on(:quit_game) { close! }
$bus.on_retrievable(:mouse_pos) do $bus.on(:mouse_pos) do
next mouse_relative(mouse_x, mouse_y) next mouse_relative(mouse_x, mouse_y)
end end
+18 -15
View File
@@ -2,12 +2,11 @@ class EventBus
def initialize def initialize
# store callbacks with owner: {event => [ {callback:, owner:} ] } # store callbacks with owner: {event => [ {callback:, owner:} ] }
@events = Hash.new { |h, k| h[k] = [] } @events = Hash.new { |h, k| h[k] = [] }
@retrievable_events = {}
end end
# Emit an event # Emit an event
def emit(event, *args) def emit(event, *args)
@events[event].each do |h| @events[event].dup.each do |h|
h[:callback].call(*args) h[:callback].call(*args)
end end
end end
@@ -28,30 +27,34 @@ class EventBus
on(event, owner: owner, &wrapper) on(event, owner: owner, &wrapper)
end end
# Unsubscribe from an event
def off(event, &callback) def off(event, &callback)
@events[event].reject! { |h| h[:callback] == callback } @events[event].reject! { |h| h[:callback] == callback }
end end
# Register a retrievable event (only one allowed) # For events that return a value (e.g. queries), get the first non-nil response
def on_retrievable(event, owner = nil, &callback)
owner ||= callback.binding.eval("self")
@retrievable_events[event] = { callback: callback, owner: owner }
end
# Get data from a retrievable event
def get(event, *args) def get(event, *args)
if @retrievable_events[event] @events[event].dup.each do |h|
@retrievable_events[event][:callback].call(*args) result = h[:callback].call(*args)
else return result unless result.nil?
end
nil nil
end end
# For events that return multiple values (e.g. queries), get all non-nil responses
def get_all(event, *args)
results = []
@events[event].dup.each do |h|
result = h[:callback].call(*args)
results << result unless result.nil?
end
results
end end
# Unsubscribe from an object's events # Unsubscribe all events for a given owner (e.g. when an object is destroyed)
def unlisten_owner(owner) def remove_owner(owner)
@events.each do |event, handlers| @events.each do |event, handlers|
handlers.reject! { |h| h[:owner] == owner } handlers.reject! { |h| h[:owner] == owner }
end end
@retrievable_events.delete_if { |_, h| h[:owner] == owner }
end end
end end
+2 -2
View File
@@ -15,14 +15,14 @@ class MazeData
return if standalone return if standalone
$bus.on_retrievable(:start_room_coords) do $bus.on(:start_room_coords) do
next @start_room ? [@start_room[0] * 2 + 1, @start_room[1] * 2 + 1] : nil next @start_room ? [@start_room[0] * 2 + 1, @start_room[1] * 2 + 1] : nil
end end
return unless DEBUG return unless DEBUG
print_debug print_debug
$bus.on_retrievable(:boss_room_coords) do $bus.on(:boss_room_coords) do
next @boss_rooms.first ? [@boss_rooms.first[0] * 2 + 1, @boss_rooms.first[1] * 2 + 1] : nil next @boss_rooms.first ? [@boss_rooms.first[0] * 2 + 1, @boss_rooms.first[1] * 2 + 1] : nil
end end
end end
+1 -1
View File
@@ -14,6 +14,6 @@ class Scene
end end
def close def close
$bus.unlisten_owner(self) $bus.remove_owner(self)
end end
end end