Class: Quake::Game::Engine
- Inherits:
-
Object
- Object
- Quake::Game::Engine
- Defined in:
- lib/quake/game/engine.rb
Overview
Encapsulates the entire game state and per-frame loop, allowing both interactive (bin/quake) and scripted (bin/quake-debug) execution.
Instance Attribute Summary collapse
-
#brush_game ⇒ Object
readonly
Returns the value of attribute brush_game.
-
#camera ⇒ Object
readonly
Returns the value of attribute camera.
-
#entities ⇒ Object
readonly
Returns the value of attribute entities.
-
#game_time ⇒ Object
Returns the value of attribute game_time.
-
#hud ⇒ Object
readonly
Returns the value of attribute hud.
-
#item_pickups ⇒ Object
readonly
Returns the value of attribute item_pickups.
-
#level ⇒ Object
readonly
Returns the value of attribute level.
-
#pak ⇒ Object
readonly
Returns the value of attribute pak.
-
#palette ⇒ Object
readonly
Returns the value of attribute palette.
-
#particles ⇒ Object
readonly
Returns the value of attribute particles.
-
#player ⇒ Object
readonly
Returns the value of attribute player.
-
#player_state ⇒ Object
readonly
Returns the value of attribute player_state.
-
#sound_events ⇒ Object
readonly
Returns the value of attribute sound_events.
-
#viewmodel ⇒ Object
readonly
Returns the value of attribute viewmodel.
-
#wad ⇒ Object
readonly
Returns the value of attribute wad.
-
#window ⇒ Object
readonly
Returns the value of attribute window.
Instance Method Summary collapse
- #clear_keys ⇒ Object
- #dump_state ⇒ Object
-
#initialize(pak_path:, window: nil, window_visible: true, window_width: nil, window_height: nil, enable_sound: true, enable_render: true) ⇒ Engine
constructor
A new instance of Engine.
- #keys ⇒ Object
-
#load_map(map_name) ⇒ Object
Load a BSP map and (re)build all per-level state.
- #render ⇒ Object
-
#screenshot(filename) ⇒ Object
———————- debug output ———————–.
-
#set_key(scancode, pressed) ⇒ Object
———————- input control ———————-.
- #set_keys(keys_hash) ⇒ Object
- #shutdown ⇒ Object
- #swap_buffers ⇒ Object
-
#teleport(x, y, z, yaw: nil, pitch: nil) ⇒ Object
———————- player control ———————.
-
#tick(dt) ⇒ Object
Update + render one frame.
Constructor Details
#initialize(pak_path:, window: nil, window_visible: true, window_width: nil, window_height: nil, enable_sound: true, enable_render: true) ⇒ Engine
Returns a new instance of Engine.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/quake/game/engine.rb', line 16 def initialize(pak_path:, window: nil, window_visible: true, window_width: nil, window_height: nil, enable_sound: true, enable_render: true) @enable_render = enable_render # PAK + assets @pak = Pak::Reader.new(pak_path) @palette = Palette.new(@pak.read("gfx/palette.lmp")) wad_data = @pak.read("gfx.wad") @wad = Wad::Reader.new(wad_data) if wad_data # Sound (optional) if enable_sound @sound_mixer = Sound::Mixer.new(@pak) @sound_mixer.open end @sound_events = Sound::Events.new(@sound_mixer) # Window if window @window = window @owns_window = false else opts = { visible: window_visible } opts[:width] = window_width if window_width opts[:height] = window_height if window_height @window = Window.new(**opts) @owns_window = true end @keys = {} @game_time = 0.0 end |
Instance Attribute Details
#brush_game ⇒ Object (readonly)
Returns the value of attribute brush_game.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def brush_game @brush_game end |
#camera ⇒ Object (readonly)
Returns the value of attribute camera.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def camera @camera end |
#entities ⇒ Object (readonly)
Returns the value of attribute entities.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def entities @entities end |
#game_time ⇒ Object
Returns the value of attribute game_time.
14 15 16 |
# File 'lib/quake/game/engine.rb', line 14 def game_time @game_time end |
#hud ⇒ Object (readonly)
Returns the value of attribute hud.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def hud @hud end |
#item_pickups ⇒ Object (readonly)
Returns the value of attribute item_pickups.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def item_pickups @item_pickups end |
#level ⇒ Object (readonly)
Returns the value of attribute level.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def level @level end |
#pak ⇒ Object (readonly)
Returns the value of attribute pak.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def pak @pak end |
#palette ⇒ Object (readonly)
Returns the value of attribute palette.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def palette @palette end |
#particles ⇒ Object (readonly)
Returns the value of attribute particles.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def particles @particles end |
#player ⇒ Object (readonly)
Returns the value of attribute player.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def player @player end |
#player_state ⇒ Object (readonly)
Returns the value of attribute player_state.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def player_state @player_state end |
#sound_events ⇒ Object (readonly)
Returns the value of attribute sound_events.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def sound_events @sound_events end |
#viewmodel ⇒ Object (readonly)
Returns the value of attribute viewmodel.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def viewmodel @viewmodel end |
#wad ⇒ Object (readonly)
Returns the value of attribute wad.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def wad @wad end |
#window ⇒ Object (readonly)
Returns the value of attribute window.
10 11 12 |
# File 'lib/quake/game/engine.rb', line 10 def window @window end |
Instance Method Details
#clear_keys ⇒ Object
186 187 188 |
# File 'lib/quake/game/engine.rb', line 186 def clear_keys @keys = {} end |
#dump_state ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/quake/game/engine.rb', line 212 def dump_state { time: @game_time, player: { position: vec_to_a(@player.position), velocity: vec_to_a(@player.velocity), yaw: @player.yaw, pitch: @player.pitch, on_ground: @player.on_ground, water_level: @player.water_level, noclip: @player.noclip }, stats: { health: @player_state.health, armor: @player_state.armor, armor_type: @player_state.armor_type, current_weapon: @player_state.current_weapon, ammo: @player_state.ammo.dup }, brush_entities: brush_entity_summaries, particles: @particles ? @particles.particle_count : 0 } end |
#keys ⇒ Object
190 191 192 |
# File 'lib/quake/game/engine.rb', line 190 def keys @keys end |
#load_map(map_name) ⇒ Object
Load a BSP map and (re)build all per-level state.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/quake/game/engine.rb', line 52 def load_map(map_name) bsp_data = @pak.read(map_name) raise "Map not found: #{map_name}" unless bsp_data @level = Bsp::Reader.new(bsp_data).parse @entities = EntityParser.parse(@level.entities) @target_map = EntityParser.build_target_map(@entities) # Find player start player_ent = @entities.find { |e| e.classname == "info_player_start" } @player_start = player_ent&.position || Math::Vec3::ORIGIN @player_yaw = player_ent&.angle || 0.0 # Load MDL models referenced by entities @mdl_cache ||= {} @entities.each do |ent| model_path = ent["model"] next unless model_path&.end_with?(".mdl") next if @mdl_cache.key?(model_path) begin mdl_data = @pak.read(model_path) @mdl_cache[model_path] = Mdl::Reader.new(mdl_data).parse rescue => e warn "Failed to load #{model_path}: #{e.}" @mdl_cache[model_path] = nil end end # Persistent player state across level loads @player_state ||= PlayerState.new @item_pickups = ItemPickups.new(@entities) @brush_game = BrushEntities.new(@entities, @level, @target_map) # Player + camera (reset position to start) @player = Physics::Player.new(position: @player_start, yaw: @player_yaw) @camera = Camera.new(position: @player.eye_position, yaw: @player_yaw) build_renderers if @enable_render end |
#render ⇒ Object
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/quake/game/engine.rb', line 143 def render @world_renderer.render(@camera, @window.aspect_ratio) @brush_renderer&.render(@entities) @entities.each do |ent| next if @item_pickups.picked_up?(ent) model_path = ent["model"] next unless model_path&.end_with?(".mdl") gl_model = @mdl_renderers[model_path] next unless gl_model frame_rate = 10.0 frame = (@game_time * frame_rate).to_i lerp = (@game_time * frame_rate) % 1.0 gl_model.render( frame_index: frame, lerp: lerp, position: ent.position, yaw: ent.angle ) end @particles&.render @viewmodel&.render(@camera, @window.aspect_ratio) @hud&.render(@player_state) end |
#screenshot(filename) ⇒ Object
———————- debug output ———————–
208 209 210 |
# File 'lib/quake/game/engine.rb', line 208 def screenshot(filename) Debug::Screenshot.save(filename, @window.width, @window.height) end |
#set_key(scancode, pressed) ⇒ Object
———————- input control ———————-
178 179 180 |
# File 'lib/quake/game/engine.rb', line 178 def set_key(scancode, pressed) @keys[scancode] = pressed end |
#set_keys(keys_hash) ⇒ Object
182 183 184 |
# File 'lib/quake/game/engine.rb', line 182 def set_keys(keys_hash) @keys = keys_hash.dup end |
#shutdown ⇒ Object
236 237 238 239 240 241 242 |
# File 'lib/quake/game/engine.rb', line 236 def shutdown @sound_mixer&.close if @owns_window @window&.close end @pak.close end |
#swap_buffers ⇒ Object
172 173 174 |
# File 'lib/quake/game/engine.rb', line 172 def swap_buffers @window.swap end |
#teleport(x, y, z, yaw: nil, pitch: nil) ⇒ Object
———————- player control ———————
196 197 198 199 200 201 202 203 204 |
# File 'lib/quake/game/engine.rb', line 196 def teleport(x, y, z, yaw: nil, pitch: nil) @player.position = Math::Vec3.new(x.to_f, y.to_f, z.to_f) @player.velocity = Math::Vec3::ORIGIN @player.instance_variable_set(:@yaw, yaw.to_f) if yaw @player.instance_variable_set(:@pitch, pitch.to_f) if pitch @camera.position = @player.eye_position @camera.yaw = @player.yaw @camera.pitch = @player.pitch end |
#tick(dt) ⇒ Object
Update + render one frame.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/quake/game/engine.rb', line 93 def tick(dt) @game_time += dt # Brush entities first (so collision uses current positions) if @brush_game @brush_game.update(dt, @player.position) @brush_game.check_triggers(@player.position) do |trigger| handle_trigger(trigger) end snapped = @brush_game.snap_to_platform(@player.position) if snapped @player.position = snapped @player.on_ground = true @player.velocity = Math::Vec3.new(@player.velocity.x, @player.velocity.y, 0.0) end end solid_brush = @entities.select(&:brush_entity?) @player.update(dt, @level, @keys, brush_entities: solid_brush) # Item pickups events = @item_pickups.check_pickups(@player.position, @player_state) events.each do |evt| @sound_events&.on_pickup(evt) @particles&.pickup_effect(evt[:entity].position) @viewmodel&.set_weapon(@player_state.current_weapon_model) end # Viewmodel bob (before camera so bob is applied to camera too) if @viewmodel speed = ::Math.sqrt(@player.velocity.x**2 + @player.velocity.y**2) @viewmodel.update(dt, speed) end # Sync camera eye = @player.eye_position bob = @viewmodel ? @viewmodel.bob : 0.0 @camera.position = Math::Vec3.new(eye.x, eye.y, eye.z + bob) @camera.yaw = @player.yaw @camera.pitch = @player.pitch return unless @enable_render @world_renderer.update(dt) if @world_renderer.respond_to?(:update) @particles&.update(dt) render end |