Class: SFML::App
- Inherits:
-
Object
- Object
- SFML::App
- Extended by:
- InputActions, Keybindings
- Includes:
- InputQueries
- Defined in:
- lib/sfml/app.rb
Overview
Subclass-friendly main loop. Removes the boilerplate of window creation, event pumping, clock management, and clear/display so a small app fits in a few methods.
class MyApp < SFML::App
antialiasing 4 # class-level config — applies to every instance
framerate 120
background SFML::Color.new(18, 20, 28)
def setup
@ball = SFML::CircleShape.new(radius: 30, position: [200, 200],
fill_color: SFML::Color.white)
end
def update(dt)
@ball.move([60 * dt.as_seconds, 30 * dt.as_seconds])
end
def draw
window.draw(@ball)
end
end
MyApp.new(title: "Hello").run
The loop auto-handles :closed and :key_pressed/:escape by calling #quit; everything else is forwarded to #on_event. Override that to handle keys, mouse, etc.
Configuration
Every constructor kwarg is also a class-level macro: declare
defaults with antialiasing 4 etc. inside the class body, and
MyApp.new picks them up. Per-instance kwargs still win on a
case-by-case basis. Subclasses inherit their parent's settings
— set one in a base class, override in a subclass.
Constant Summary collapse
- CONFIG_KEYS =
Names of every class-level configuration macro on
SFML::App. %i[ width height title framerate vsync background style fullscreen antialiasing context fixed_timestep ].freeze
- FIXED_TIMESTEP_MAX_CATCHUP =
Cap on catch-up update calls per frame when running with
fixed_timestep. Prevents the "spiral of death" where a slow frame queues so many physics steps that the next frame is even slower. After this many steps, residual time is dropped. 5
Instance Attribute Summary collapse
-
#background_color ⇒ Object
The background color.
-
#current_scene ⇒ Object
readonly
---- Scenes ----.
-
#interpolation_alpha ⇒ Object
readonly
Fraction of a fixed timestep accumulated since the last
update. -
#window ⇒ Object
readonly
The Backing SFML::RenderWindow.
Class Method Summary collapse
-
.initial_scene(klass = nil) ⇒ Object
Set the scene class the app should switch into automatically at
setuptime.
Instance Method Summary collapse
-
#draw ⇒ Object
Returns the draw.
-
#height ⇒ Object
Returns the height.
-
#initialize(**opts) ⇒ App
constructor
Per-instance kwargs override anything set at the class level.
-
#on_event(event) ⇒ Object
The framework consumes: *
:closed— closes the window (always) *:resized— forwarded toon_resize*:key_pressedwhose code matches a scene- or app-levelon_keybinding Everything else lands here. -
#on_resize(width, height) ⇒ Object
Default: forward to the current scene.
-
#pause ⇒ Object
Pause the simulation.
-
#paused? ⇒ Boolean
truewhile the simulation is paused. -
#quit ⇒ Object
Close the window.
-
#resume ⇒ Object
Resume the simulation.
-
#run ⇒ Object
The main entry point.
-
#setup ⇒ Object
---- Override these in subclasses --------------------------------------.
-
#switch_to(scene_or_class) ⇒ Object
Activate
scene_or_class. -
#teardown ⇒ Object
Called once after the main loop exits.
-
#toggle_pause ⇒ Object
Flip the pause state.
-
#update(dt) ⇒ Object
Returns the update.
-
#width ⇒ Object
Width and height shortcuts that always reflect the current window size — matters once the user is allowed to resize.
Methods included from InputActions
Methods included from Keybindings
Methods included from InputQueries
Constructor Details
#initialize(**opts) ⇒ App
Per-instance kwargs override anything set at the class level. Anything not given here OR at the class level falls back to the hard-coded defaults below.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/sfml/app.rb', line 96 def initialize(**opts) cfg = self.class width = opts[:width] || cfg.width || 800 height = opts[:height] || cfg.height || 600 title = opts[:title] || cfg.title || cfg.name framerate = opts[:framerate] || cfg.framerate || 60 vsync = opts.fetch(:vsync, cfg.vsync) background = opts[:background] || cfg.background || Color::BLACK style = opts.fetch(:style, cfg.style) fullscreen = opts.fetch(:fullscreen, cfg.fullscreen) fullscreen = false if fullscreen.nil? antialiasing = opts.fetch(:antialiasing, cfg.antialiasing) context = opts.fetch(:context, cfg.context) window_opts = { framerate: framerate, fullscreen: fullscreen } window_opts[:vsync] = vsync unless vsync.nil? window_opts[:style] = style unless style.nil? window_opts[:antialiasing] = antialiasing unless antialiasing.nil? window_opts[:context] = context unless context.nil? @window = RenderWindow.new(width, height, title, **window_opts) @background_color = background end |
Instance Attribute Details
#background_color ⇒ Object
The background color.
91 92 93 |
# File 'lib/sfml/app.rb', line 91 def background_color @background_color end |
#current_scene ⇒ Object (readonly)
---- Scenes ----
current_scene is whatever the app last activated via
switch_to. When non-nil, App's default update / draw /
on_event / on_resize forward to it; key bindings on the
scene class shadow ones on the app class while the scene is
active.
142 143 144 |
# File 'lib/sfml/app.rb', line 142 def current_scene @current_scene end |
#interpolation_alpha ⇒ Object (readonly)
Fraction of a fixed timestep accumulated since the last
update. In range [0.0, 1.0). Use it in #draw to
interpolate between the previous and current world state:
def draw
pos = @prev_pos.lerp(@curr_pos, interpolation_alpha)
...
end
Only meaningful when fixed_timestep is set; otherwise 0.
186 187 188 |
# File 'lib/sfml/app.rb', line 186 def interpolation_alpha @interpolation_alpha end |
#window ⇒ Object (readonly)
The Backing SFML::RenderWindow.
89 90 91 |
# File 'lib/sfml/app.rb', line 89 def window @window end |
Class Method Details
.initial_scene(klass = nil) ⇒ Object
Set the scene class the app should switch into automatically
at setup time. Inheritable: a subclass that doesn't set
one inherits the parent's choice.
79 80 81 82 83 |
# File 'lib/sfml/app.rb', line 79 def initial_scene(klass = nil) @initial_scene = klass if klass return @initial_scene if instance_variable_defined?(:@initial_scene) superclass.respond_to?(:initial_scene) ? superclass.initial_scene : nil end |
Instance Method Details
#draw ⇒ Object
Returns the draw.
253 254 255 |
# File 'lib/sfml/app.rb', line 253 def draw @current_scene&.draw end |
#height ⇒ Object
Returns the height.
125 |
# File 'lib/sfml/app.rb', line 125 def height = @window.size.y |
#on_event(event) ⇒ Object
The framework consumes:
* `:closed` — closes the window (always)
* `:resized` — forwarded to `on_resize`
* `:key_pressed` whose code matches a scene- or app-level
`on_key` binding
Everything else lands here. Override to handle game-specific
input. By default forwards to the current scene's on_event.
264 265 266 |
# File 'lib/sfml/app.rb', line 264 def on_event(event) @current_scene&.on_event(event) end |
#on_resize(width, height) ⇒ Object
Default: forward to the current scene. Override to additionally
do app-wide layout fixups; call super to keep the scene in
the loop.
271 272 273 |
# File 'lib/sfml/app.rb', line 271 def on_resize(width, height) @current_scene&.on_resize(width, height) end |
#pause ⇒ Object
Pause the simulation. #draw keeps firing; #update doesn't.
168 169 |
# File 'lib/sfml/app.rb', line 168 def pause = (@paused = true) # Resume the simulation. |
#paused? ⇒ Boolean
true while the simulation is paused.
174 |
# File 'lib/sfml/app.rb', line 174 def paused? = @paused == true |
#quit ⇒ Object
Close the window. The main loop exits at the start of the next frame.
129 130 131 132 |
# File 'lib/sfml/app.rb', line 129 def quit @window.close self end |
#resume ⇒ Object
Resume the simulation.
170 171 |
# File 'lib/sfml/app.rb', line 170 def resume = (@paused = false) # Flip the pause state. |
#run ⇒ Object
The main entry point. Calls #setup once, then runs the per-frame loop until the window closes.
When fixed_timestep N is set on the class, update(dt) is
called exactly N times per second (with a fixed dt), and
rendering runs as fast as vsync/framerate allows. This is
the standard pattern for deterministic physics — without it,
large-dt frames produce different results than small-dt frames.
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/sfml/app.rb', line 196 def run setup clock = Clock.new ts = self.class.fixed_timestep step_seconds = ts && (1.0 / ts) dt_fixed = ts && Time.seconds(step_seconds) accumulator = 0.0 @interpolation_alpha = 0.0 while @window.open? frame_dt = clock.restart @window.each_event { |event| _dispatch(event) } if dt_fixed accumulator += frame_dt.as_seconds steps = 0 while accumulator >= step_seconds && steps < FIXED_TIMESTEP_MAX_CATCHUP update(dt_fixed) unless paused? accumulator -= step_seconds steps += 1 end # If we hit the catch-up cap, drop residual time — better # to slightly slow the simulation than spiral into longer # and longer frames. accumulator = 0.0 if steps == FIXED_TIMESTEP_MAX_CATCHUP @interpolation_alpha = accumulator / step_seconds else update(frame_dt) unless paused? end @window.clear(@background_color) draw @window.display end teardown self end |
#setup ⇒ Object
---- Override these in subclasses --------------------------------------
Defaults forward to the current scene when one is active. The
initial_scene class macro auto-instantiates a scene at
setup time so subclasses with initial_scene Foo don't
have to define setup themselves.
241 242 243 244 245 |
# File 'lib/sfml/app.rb', line 241 def setup if (klass = self.class.initial_scene) switch_to(klass) end end |
#switch_to(scene_or_class) ⇒ Object
Activate scene_or_class. Tears down the previous scene
before calling setup on the new one. Accepts either:
* a Scene subclass — instantiated with `self` as host
* an existing Scene instance — used as-is
148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/sfml/app.rb', line 148 def switch_to(scene_or_class) new_scene = case scene_or_class when Class then scene_or_class.new(self) else scene_or_class end @current_scene&.teardown @current_scene = new_scene @current_scene&.setup @current_scene end |
#teardown ⇒ Object
Called once after the main loop exits. Tears down the active scene by default.
277 278 279 |
# File 'lib/sfml/app.rb', line 277 def teardown @current_scene&.teardown end |
#toggle_pause ⇒ Object
Flip the pause state.
172 173 |
# File 'lib/sfml/app.rb', line 172 def toggle_pause = (@paused = !paused?) # `true` while the simulation is paused. |
#update(dt) ⇒ Object
Returns the update.
248 249 250 |
# File 'lib/sfml/app.rb', line 248 def update(dt) @current_scene&.update(dt) end |
#width ⇒ Object
Width and height shortcuts that always reflect the current window size — matters once the user is allowed to resize.
123 124 |
# File 'lib/sfml/app.rb', line 123 def width = @window.size.x # Returns the height. |