Class: Example
- Inherits:
-
RGame::Core::App
- Object
- RGame::Core::App
- Example
- Defined in:
- ext/rgame_core/example.rb
Overview
An App subclass overrides only the hooks it needs; the rest are inherited no-ops, so this class is the smallest useful driver of the engine.
Constant Summary collapse
- Controls =
RGame::Util::Controls
- Color =
RGame::Util::Color
- RED =
Color.new(224, 64, 64)
- GREEN =
Color.new(64, 224, 96)
- BLUE =
Color.new(64, 96, 224)
- YELLOW =
Color.new(224, 192, 64)
- TRANSLUCENT_WHITE =
Color.new(255, 255, 255, 128)
- AUDIO_HINT_NONE =
Frozen and chosen between rather than built: #draw runs sixty times a second, and a string per frame is a garbage collection waiting to happen.
'audio: pass a sound file on the command line to try it'- AUDIO_HINT_STOPPED =
'audio: Space plays a sample, Return starts the music'- AUDIO_HINT_PLAYING =
'audio: Space plays a sample, Return stops the music'
Instance Attribute Summary collapse
-
#cursor_x ⇒ Object
readonly
Returns the value of attribute cursor_x.
-
#cursor_y ⇒ Object
readonly
Returns the value of attribute cursor_y.
-
#frames ⇒ Object
readonly
Returns the value of attribute frames.
-
#ticks ⇒ Object
readonly
Returns the value of attribute ticks.
Attributes inherited from RGame::Core::App
Instance Method Summary collapse
- #audio_status ⇒ Object
- #button_down(id) ⇒ Object
-
#draw ⇒ Object
One of everything, so a look at the window checks the whole drawing path.
-
#frame_begin ⇒ Object
Once per frame, before the tick batch: pick which device to read.
-
#gamepad_connected(slot) ⇒ Object
The callbacks are for reacting to the change; the polling above is for reading the current state.
- #gamepad_disconnected(slot) ⇒ Object
-
#initialize(sound_path = nil) ⇒ Example
constructor
A new instance of Example.
- #resize(width, height) ⇒ Object
-
#toggle_music ⇒ Object
Return is a toggle, so one key covers both transitions and the "play after stop starts from the beginning" behaviour is audible by pressing it twice.
-
#update(dt) ⇒ Object
One fixed simulation tick.
Methods inherited from RGame::Core::App
#assets, #audio, #button_up, #caption, #caption=, #close, #fps, #gamepad_count, #gamepad_name, #gamepad_present?, #height, #input_axis, #input_down?, #needs_redraw?, #run, #ticks_ms, #width
Constructor Details
#initialize(sound_path = nil) ⇒ Example
Returns a new instance of Example.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'ext/rgame_core/example.rb', line 52 def initialize(sound_path = nil) super(width: 800, height: 600, caption: 'rgame via Ruby') @input = RGame::Core::Input.new(self) @pads = RGame::Core::Gamepad.new(self) @renderer = RGame::Core::Renderer.new(self) @frames = 0 @ticks = 0 @cursor_x = 400.0 @cursor_y = 300.0 @spin = 0.0 @baked = nil @device = Controls::KEYBOARD # The device is opened whether or not there is anything to play through it, # so that starting with no sound card takes the same path as starting with # one. Nothing here is tied to the window: audio has no GL context and # survives one being recreated. @audio = RGame::Core::Audio.new puts "audio backend: #{@audio.backend}" return unless sound_path @sample = RGame::Core::Sample.new(@audio, sound_path) @song = RGame::Core::Song.new(@audio, sound_path) end |
Instance Attribute Details
#cursor_x ⇒ Object (readonly)
Returns the value of attribute cursor_x.
50 51 52 |
# File 'ext/rgame_core/example.rb', line 50 def cursor_x @cursor_x end |
#cursor_y ⇒ Object (readonly)
Returns the value of attribute cursor_y.
50 51 52 |
# File 'ext/rgame_core/example.rb', line 50 def cursor_y @cursor_y end |
#frames ⇒ Object (readonly)
Returns the value of attribute frames.
50 51 52 |
# File 'ext/rgame_core/example.rb', line 50 def frames @frames end |
#ticks ⇒ Object (readonly)
Returns the value of attribute ticks.
50 51 52 |
# File 'ext/rgame_core/example.rb', line 50 def ticks @ticks end |
Instance Method Details
#audio_status ⇒ Object
162 163 164 165 166 |
# File 'ext/rgame_core/example.rb', line 162 def audio_status return AUDIO_HINT_NONE unless @song @song. ? AUDIO_HINT_PLAYING : AUDIO_HINT_STOPPED end |
#button_down(id) ⇒ Object
144 145 146 147 148 149 150 151 152 |
# File 'ext/rgame_core/example.rb', line 144 def (id) case id when Controls::KEY_ESCAPE then close # Held down, this is the overlap check: each press is another voice rather # than a restart, so a fast run of them should pile up rather than stutter. when Controls::KEY_SPACE then @sample&.play when Controls::KEY_RETURN then toggle_music end end |
#draw ⇒ Object
One of everything, so a look at the window checks the whole drawing path. Colours are RGame::Util::Color values rather than arrays: a Color is frozen and allocates nothing when it is drawn, which is what per-frame code wants.
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 142 |
# File 'ext/rgame_core/example.rb', line 104 def draw r = @renderer # Baked on the first frame and replayed after that: forty rectangles cost # one call per frame instead of forty. A real game bakes its tile layers # this way. Recording has to happen inside #draw, which is why it is here # rather than in initialize. @baked ||= r.record do 40.times { |i| r.rect(i * 18, 0, 12, 12, color: GREEN, z: 0) } end # Scrolls with the same value the spinning square turns by, so it is # visibly the *replay* moving and not a rebake. @baked.draw((@spin % 18) - 18, 560) r.rect(40, 40, 160, 100, color: RED, z: 0) # Higher z wins wherever they overlap, whatever order the calls came in. r.rect(120, 90, 160, 100, color: TRANSLUCENT_WHITE, z: 1) r.triangle(400, 40, 480, 180, 320, 180, color: GREEN, z: 0) r.circle(620, 110, 70, color: BLUE, z: 0) r.line(40, 240, 760, 240, thickness: 6, color: YELLOW, z: 0) # The transform stack: a square turning about its own centre. r.rotated(@spin, 400, 380) { r.rect(340, 320, 120, 120, color: GREEN, z: 0) } # The clip stack: the rectangle is twice the size of what shows. r.clipped(60, 480, 200, 80) { r.rect(60, 440, 400, 160, color: RED, z: 0) } # The cursor the arrow keys move. r.debug_box(@cursor_x - 8, @cursor_y - 8, 16, 16) # Text, including accents and punctuation the shipped font has to cover, so # a look at the window checks kerning and coverage at once. The second line # is centred using text_width, which is what a UI actually does with it. r.text('rgame — Grüße, œuvre, 5 €', 40, 270, color: YELLOW) label = format('%d fps', fps) r.text(label, 400 - (r.text_width(label) / 2), 270 + r.text_height) r.text(audio_status, 40, 270 + (r.text_height * 2), color: YELLOW) @frames += 1 end |
#frame_begin ⇒ Object
Once per frame, before the tick batch: pick which device to read. Doing it here rather than per tick is the pattern the engine is shaped around — one sample per frame, reused by however many catch-up ticks follow.
80 81 82 83 84 |
# File 'ext/rgame_core/example.rb', line 80 def frame_begin # Poll rather than track: Gamepad answers straight from the engine, so # there is no local copy of the connection state to keep in step. @device = @pads.connected?(0) ? @pads.device(0) : Controls::KEYBOARD end |
#gamepad_connected(slot) ⇒ Object
The callbacks are for reacting to the change; the polling above is for reading the current state. Slots are stable across a replug, so a pad that falls out and returns comes back as the same player.
171 172 173 |
# File 'ext/rgame_core/example.rb', line 171 def gamepad_connected(slot) puts "controller connected in slot #{slot}: #{@pads.name(slot)}" end |
#gamepad_disconnected(slot) ⇒ Object
175 176 177 |
# File 'ext/rgame_core/example.rb', line 175 def gamepad_disconnected(slot) puts "controller disconnected from slot #{slot}" end |
#resize(width, height) ⇒ Object
179 180 181 |
# File 'ext/rgame_core/example.rb', line 179 def resize(width, height) puts "resized to #{width}x#{height}" end |
#toggle_music ⇒ Object
Return is a toggle, so one key covers both transitions and the "play after stop starts from the beginning" behaviour is audible by pressing it twice.
156 157 158 159 160 |
# File 'ext/rgame_core/example.rb', line 156 def toggle_music return unless @song @song. ? @song.stop : @song.play(looping: true) end |
#update(dt) ⇒ Object
One fixed simulation tick. dt is always the engine's fixed step.
87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'ext/rgame_core/example.rb', line 87 def update(dt) @ticks += 1 @spin += dt * 45.0 speed = 200.0 * dt @cursor_x -= speed if @input.down?(:left, device: @device) @cursor_x += speed if @input.down?(:right, device: @device) @cursor_y -= speed if @input.down?(:up, device: @device) @cursor_y += speed if @input.down?(:down, device: @device) # Analog sticks are additive on top of the dpad; the keyboard reads 0.0. @cursor_x += @input.axis(:move_x, device: @device) * speed @cursor_y += @input.axis(:move_y, device: @device) * speed end |