ruby-sfml 3.0.0.1 → 3.0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +81 -2
- data/README.md +86 -48
- data/lib/sfml/app.rb +257 -0
- data/lib/sfml/c/graphics.rb +5 -2
- data/lib/sfml/c/window.rb +30 -0
- data/lib/sfml/graphics/render_window.rb +36 -5
- data/lib/sfml/keybindings.rb +28 -0
- data/lib/sfml/scene.rb +82 -0
- data/lib/sfml/version.rb +1 -1
- data/lib/sfml/window/context_settings.rb +121 -0
- data/lib/sfml.rb +4 -1
- metadata +5 -2
- data/lib/sfml/game.rb +0 -101
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8136e14f0f8d0f777f7649e15a61e6017e386f7e80e023d5a2b8000191db9213
|
|
4
|
+
data.tar.gz: f6fed33adc435dcbe67777499d0aae14e58ae670a7a04fc94cb58b23805e8fc8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62d6d8965915bfe6e95161ae8580a3b2d84eca6334a33387238073de069839dae4f08829f82f42042f43850899ed24329e3d49610ac6ec95092b53685ed49d2e
|
|
7
|
+
data.tar.gz: a4f6805ba848b00c623ae4f1b0029b97423f154058e472e3208c48873c9f2be229707aed0ac2934483256c03f048fc06bf3f6c9c8001712ab7e764d8aa23bba4
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,85 @@ ruby-sfml's own patch level.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [3.0.0.2] — 2026-05-09
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **`SFML::App`** — subclass-friendly main loop. Removes the
|
|
15
|
+
boilerplate of window creation, event pumping, dt management,
|
|
16
|
+
and clear/display so a small app fits in a few methods.
|
|
17
|
+
- Class-level configuration DSL for `SFML::App`. Defaults that
|
|
18
|
+
used to be passed to `.new` can now be declared in the class
|
|
19
|
+
body and inherited:
|
|
20
|
+
```ruby
|
|
21
|
+
class MyApp < SFML::App
|
|
22
|
+
width 800
|
|
23
|
+
height 600
|
|
24
|
+
title "Smooth"
|
|
25
|
+
framerate 120
|
|
26
|
+
antialiasing 4
|
|
27
|
+
background SFML::Color["#1a1a1a"]
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
Per-instance kwargs to `.new` still override on a case-by-case
|
|
31
|
+
basis. Available macros: `width`, `height`, `title`, `framerate`,
|
|
32
|
+
`vsync`, `background`, `style`, `fullscreen`, `antialiasing`,
|
|
33
|
+
`context`.
|
|
34
|
+
- **`SFML::ContextSettings`** — configures the OpenGL context the
|
|
35
|
+
window backs onto: anti-aliasing level, depth/stencil bits, GL
|
|
36
|
+
version. The fifth argument to `sfRenderWindow_create` is no
|
|
37
|
+
longer `NULL` by default; pass `antialiasing: 4` (or
|
|
38
|
+
`context: SFML::ContextSettings.new(...)`) to
|
|
39
|
+
`RenderWindow.new` / `SFML::App.new` to turn on MSAA. Read back
|
|
40
|
+
what the driver actually gave you with
|
|
41
|
+
`window.context_settings`. Wraps `sfContextSettings` and
|
|
42
|
+
`sfRenderWindow_getSettings`.
|
|
43
|
+
- **`on_key`** — class-level keybinding DSL. Bind a key to an
|
|
44
|
+
instance method, a Proc, or a block; bindings inherit through
|
|
45
|
+
subclasses with later definitions shadowing earlier ones:
|
|
46
|
+
```ruby
|
|
47
|
+
class MyApp < SFML::App
|
|
48
|
+
on_key :escape, :quit
|
|
49
|
+
on_key :f11, :toggle_fullscreen
|
|
50
|
+
on_key :p do |app| app.toggle_pause end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
Bindings live as the class's `key_handlers` Hash; the same DSL
|
|
54
|
+
is also available on `SFML::Scene`, where scene-level bindings
|
|
55
|
+
shadow app-level ones for the active scene.
|
|
56
|
+
- **`pause` / `resume` / `toggle_pause` / `paused?`** on
|
|
57
|
+
`SFML::App` — while paused, `update(dt)` is skipped but `draw`
|
|
58
|
+
continues, so a pause overlay can be drawn on top of a frozen
|
|
59
|
+
scene.
|
|
60
|
+
- **`on_resize(width, height)`** hook on `SFML::App` and
|
|
61
|
+
`SFML::Scene` — replaces the `case event in {type: :resized,
|
|
62
|
+
size: {x:, y:}}` boilerplate that used to live inside
|
|
63
|
+
`on_event`. Default forwards to the active scene.
|
|
64
|
+
- **`SFML::Scene`** — base class for stateful screens (menu,
|
|
65
|
+
gameplay, results screen, settings overlay, etc.). Lifecycle
|
|
66
|
+
hooks (`setup` / `update` / `draw` / `on_event` / `on_resize` /
|
|
67
|
+
`teardown`), its own `on_key` DSL, and a `switch_to(other)`
|
|
68
|
+
shortcut that delegates to the host app. The host app picks a
|
|
69
|
+
starting scene with the `initial_scene SomeScene` class macro;
|
|
70
|
+
`App.switch_to` tears down the previous scene before calling
|
|
71
|
+
`setup` on the new one.
|
|
72
|
+
- New example
|
|
73
|
+
[24_scenes](examples/24_scenes/scenes.rb) — title → play scene
|
|
74
|
+
pair built on `SFML::Scene` + `initial_scene`.
|
|
75
|
+
- New example
|
|
76
|
+
[04_app_class](examples/04_app_class/app_class.rb) — bouncing
|
|
77
|
+
ball on top of `SFML::App` with class-level config and
|
|
78
|
+
`on_key` bindings (replaces the old `04_game_class` example).
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
- `SFML::App._dispatch` no longer auto-quits on Esc. Apps that
|
|
82
|
+
want it bind it explicitly with `on_key :escape, :quit`. The
|
|
83
|
+
window-close button (`:closed`) still always quits.
|
|
84
|
+
- Audio specs (anything under `spec/sfml/audio/`) are now
|
|
85
|
+
auto-tagged `:audio` and skipped by default on macOS, where
|
|
86
|
+
CoreAudio + the CSFML OpenAL backend occasionally hang the
|
|
87
|
+
test group. Opt-in with `bundle exec rspec --tag audio`.
|
|
88
|
+
Linux runs the full suite as before.
|
|
89
|
+
|
|
11
90
|
## [3.0.0.1] — 2026-05-07
|
|
12
91
|
|
|
13
92
|
### Added
|
|
@@ -202,8 +281,8 @@ RBS signatures, hosted RDoc).
|
|
|
202
281
|
`Net::HTTP` / `Net::FTP` — they're nicer)
|
|
203
282
|
|
|
204
283
|
### Helpers
|
|
205
|
-
- `
|
|
206
|
-
`draw` / `on_event` hooks.
|
|
284
|
+
- `App` — subclass-friendly main loop with `setup` / `update(dt)` /
|
|
285
|
+
`draw` / `on_event` hooks.
|
|
207
286
|
- `Assets` — cached, search-path-driven loader.
|
|
208
287
|
`SFML::Assets.font("DejaVuSans")`, `.texture(name)`, `.sound(name)`,
|
|
209
288
|
`.music(name)`. Default search root is `<dir of $0>/assets/`.
|
data/README.md
CHANGED
|
@@ -1,54 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
<h1>
|
|
2
|
+
<img src="ruby-sfml.png" alt="" height="48" align="absmiddle">
|
|
3
|
+
ruby-sfml
|
|
4
|
+
</h1>
|
|
2
5
|
|
|
3
6
|
Modern, idiomatic Ruby bindings for [SFML 3.x](https://www.sfml-dev.org/) via [CSFML](https://github.com/SFML/CSFML) and [Ruby FFI](https://github.com/ffi/ffi).
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
[](https://rubygems.org/gems/ruby-sfml)
|
|
9
|
+
[](https://www.rubydoc.info/gems/ruby-sfml)
|
|
10
|
+
|
|
11
|
+
> **Status:** API surface complete for SFML 3.0 — system, window, graphics (incl. stencil buffer + VBOs), audio (incl. 3D positional + custom DSP + procedural streams), network (incl. HTTP / FTP / socket selector), input (keyboard, mouse, joystick, touch, sensors), plus the higher-level `App` / `Scene` / `Assets` helpers. 410 RSpec examples, 24 runnable example folders.
|
|
6
12
|
|
|
7
13
|
## Why
|
|
8
14
|
|
|
9
15
|
The original [rbSFML](https://github.com/Groogy/rbSFML) is unmaintained and only works against SFML 2 and Ruby 2.2. `ruby-sfml` targets the current SFML 3.x line, modern Ruby (3.2+), and a Ruby-first API — blocks instead of polling loops, symbols instead of enums, operators on vectors, automatic resource cleanup via GC.
|
|
10
16
|
|
|
11
|
-
##
|
|
17
|
+
## Installation
|
|
12
18
|
|
|
13
|
-
- Ruby
|
|
14
|
-
|
|
19
|
+
ruby-sfml needs **Ruby ≥ 3.2** and **CSFML 3.0** (or compatible 3.x) on
|
|
20
|
+
the system. Install in two steps:
|
|
15
21
|
|
|
16
|
-
### Install CSFML
|
|
22
|
+
### 1. Install dependencies (CSFML)
|
|
17
23
|
|
|
18
|
-
| OS | Command | Notes
|
|
19
|
-
| ------------------------ | ---------------------------------------- |
|
|
20
|
-
| Ubuntu 25.04+ / Debian | `sudo apt install libcsfml-dev` | Ships CSFML 3
|
|
24
|
+
| OS | Command | Notes |
|
|
25
|
+
| ------------------------ | ---------------------------------------- | ------------------------------------------------------------------------ |
|
|
26
|
+
| Ubuntu 25.04+ / Debian | `sudo apt install libcsfml-dev` | Ships CSFML 3 |
|
|
21
27
|
| Ubuntu 22.04 / 24.04 | repo too old (CSFML 2.5) | Build from [3.0.0 release](https://github.com/SFML/CSFML/releases/tag/3.0.0) |
|
|
22
|
-
| macOS (brew) | `brew install csfml` | Currently 3.x
|
|
23
|
-
| Arch Linux | `sudo pacman -S csfml` | Currently 3.x
|
|
24
|
-
| Windows | https://www.sfml-dev.org/download/csfml/ | Pick the 3.0 tarball
|
|
28
|
+
| macOS (brew) | `brew install csfml` | Currently 3.x |
|
|
29
|
+
| Arch Linux | `sudo pacman -S csfml` | Currently 3.x |
|
|
30
|
+
| Windows | https://www.sfml-dev.org/download/csfml/ | Pick the 3.0 tarball |
|
|
31
|
+
|
|
32
|
+
### 2. Install the gem
|
|
33
|
+
|
|
34
|
+
In a Bundler-managed project, add to your `Gemfile`:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
gem "ruby-sfml", "~> 3.0"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
then `bundle install`.
|
|
41
|
+
|
|
42
|
+
Or drop it in directly:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
gem install ruby-sfml
|
|
46
|
+
```
|
|
25
47
|
|
|
26
|
-
ruby-sfml
|
|
48
|
+
Hosted on [rubygems.org](https://rubygems.org/gems/ruby-sfml); HTML
|
|
49
|
+
docs auto-generate at
|
|
50
|
+
[rubydoc.info/gems/ruby-sfml](https://www.rubydoc.info/gems/ruby-sfml).
|
|
27
51
|
|
|
28
|
-
|
|
29
|
-
- **At `require "sfml"`** — same probe runs as a runtime sanity check, in case libraries were swapped between install and use.
|
|
52
|
+
### How the CSFML check happens
|
|
30
53
|
|
|
31
|
-
|
|
54
|
+
ruby-sfml verifies the linked CSFML in two places so a missing or
|
|
55
|
+
out-of-date library never falls through to a cryptic segfault:
|
|
32
56
|
|
|
33
|
-
|
|
57
|
+
- **At `gem install`** — `extconf.rb` checks for the five
|
|
58
|
+
`libcsfml-*` libraries plus a CSFML 3.0+ symbol
|
|
59
|
+
(`sfClock_isRunning`). Aborts with a clear message if the system
|
|
60
|
+
has CSFML 2.x.
|
|
61
|
+
- **At `require "sfml"`** — the same probe runs as a runtime sanity
|
|
62
|
+
check, in case libraries were swapped between install and use.
|
|
63
|
+
|
|
64
|
+
## A 13-line app
|
|
34
65
|
|
|
35
66
|
```ruby
|
|
36
67
|
require "sfml"
|
|
37
68
|
|
|
38
|
-
class Hello < SFML::
|
|
69
|
+
class Hello < SFML::App
|
|
70
|
+
title "Hello"
|
|
71
|
+
background SFML::Color.cornflower_blue
|
|
72
|
+
antialiasing 4
|
|
73
|
+
|
|
39
74
|
def setup
|
|
40
75
|
@ball = SFML::CircleShape.new(radius: 30, fill_color: SFML::Color.white,
|
|
41
76
|
position: [200, 200])
|
|
42
77
|
end
|
|
43
78
|
|
|
44
|
-
def update(dt)
|
|
45
|
-
def draw
|
|
79
|
+
def update(dt) = @ball.move(60 * dt.as_seconds * SFML::Vector2[1, 0])
|
|
80
|
+
def draw = window.draw(@ball)
|
|
46
81
|
end
|
|
47
82
|
|
|
48
|
-
Hello.new
|
|
83
|
+
Hello.new.run
|
|
49
84
|
```
|
|
50
85
|
|
|
51
|
-
`SFML::
|
|
86
|
+
`SFML::App` handles window creation, the main loop, event pumping, dt, and the Esc/close-button quit. Override `setup` / `update` / `draw` / `on_event`. Class-level macros (`title`, `framerate`, `antialiasing`, `background`, ...) set per-class defaults; per-instance kwargs to `.new` still override on a case-by-case basis. Drop into the manual loop style any time you want full control.
|
|
52
87
|
|
|
53
88
|
## A 5-line manual loop
|
|
54
89
|
|
|
@@ -79,44 +114,36 @@ end
|
|
|
79
114
|
| Window | `RenderWindow`, `Window` (bare, GL-only), `VideoMode`, `Event`, `Keyboard`, `Mouse`, `Joystick`, `Touch`, `Sensor`, `Cursor`, `Clipboard` |
|
|
80
115
|
| Graphics | `Color`, `Image`, `Texture`, `RenderTexture`, `Sprite`, `CircleShape`, `RectangleShape`, `ConvexShape`, `Vertex`, `VertexArray`, `VertexBuffer`, `Font`, `Text`, `View`, `BlendMode`, `StencilMode`, `RenderStates`, `Shader`, `Transform` |
|
|
81
116
|
| Audio | `SoundBuffer`, `Sound`, `Music`, `Listener`, `SoundCone`, `SoundStream`, `SoundRecorder`, `SoundBufferRecorder` (3D positional + cones + Doppler + custom DSP via `effect_processor=`) |
|
|
82
|
-
| Helpers | `Assets` (search-path + cache), `
|
|
117
|
+
| Helpers | `Assets` (search-path + cache), `App` (lifecycle main loop with class-level config + `on_key` DSL + `pause` / `on_resize`), `Scene` (stateful screens + `switch_to` between them), `ContextSettings` (MSAA / GL version) |
|
|
83
118
|
|
|
84
119
|
**Network**: `IpAddress`, `TcpSocket`, `TcpListener`, `UdpSocket`, `SocketSelector` for stream / datagram networking, plus the niche `Http` and `Ftp` clients (use Ruby's `Net::HTTP` / `Net::FTP` if you have the choice — these exist for parity with CSFML).
|
|
85
120
|
|
|
86
121
|
## What's intentionally *not* wrapped
|
|
87
122
|
|
|
88
|
-
|
|
123
|
+
Three corners of CSFML 3 deliberately stay out:
|
|
89
124
|
|
|
90
|
-
- **Geometry shaders** — CSFML doesn't expose them at all (only
|
|
91
|
-
and fragment stages); nothing
|
|
125
|
+
- **Geometry shaders** — CSFML doesn't expose them at all (only
|
|
126
|
+
vertex and fragment stages); there's nothing on the C side to
|
|
127
|
+
wrap.
|
|
92
128
|
- **Raw `sf::SoundRecorder`** (per-buffer callbacks on the audio
|
|
93
|
-
thread) —
|
|
94
|
-
|
|
95
|
-
|
|
129
|
+
thread) — fights the GVL too hard for what it gives. Use
|
|
130
|
+
`SFML::SoundBufferRecorder` for the common "record into memory,
|
|
131
|
+
save on stop" path.
|
|
96
132
|
- **Custom `sf::InputStream`** for loading assets from non-file
|
|
97
|
-
sources — Ruby
|
|
98
|
-
byte-string constructors.
|
|
99
|
-
|
|
100
|
-
**An aside on `Http` / `Ftp` / `SocketSelector`** — these *are*
|
|
101
|
-
wrapped (matches CSFML for parity), but for any non-trivial use
|
|
102
|
-
Ruby's stdlib `Net::HTTP` / `Net::FTP` and `IO.select` are better
|
|
103
|
-
tools.
|
|
133
|
+
sources — Ruby's `IO` covers this. Read the bytes yourself and
|
|
134
|
+
feed them into the byte-string constructors.
|
|
104
135
|
|
|
105
|
-
|
|
106
|
-
wrapped, but their callbacks run on the SFML audio thread and
|
|
107
|
-
must reacquire the GVL each invocation. Fine for trivial DSP;
|
|
108
|
-
expect glitches for anything heavier.
|
|
136
|
+
If anything else is missing or blocking you, **open an issue**.
|
|
109
137
|
|
|
110
|
-
|
|
138
|
+
## Other Ruby bindings worth knowing about
|
|
111
139
|
|
|
112
140
|
- SFML 2.x is *not* covered. The previous-generation gem
|
|
113
141
|
[rbSFML](https://github.com/Groogy/rbSFML) targets SFML 2; it's
|
|
114
142
|
unmaintained and only works with Ruby ≤ 2.2.
|
|
115
|
-
-
|
|
116
|
-
the CSFML C API directly. If you want raw
|
|
117
|
-
Ruby layer, no auto-cleanup), check that
|
|
118
|
-
|
|
119
|
-
If anything missing here is blocking you, **open an issue**.
|
|
143
|
+
- [RubySFML3](https://github.com/gAndy50/RubySFML3) — a thin FFI
|
|
144
|
+
wrapper that exposes the CSFML C API directly. If you want raw
|
|
145
|
+
bindings (no idiomatic Ruby layer, no auto-cleanup), check that
|
|
146
|
+
project instead.
|
|
120
147
|
|
|
121
148
|
## Examples
|
|
122
149
|
|
|
@@ -133,7 +160,7 @@ bundle exec ruby examples/<NN_name>/<name>.rb
|
|
|
133
160
|
| 01 | [hello_window](examples/01_hello_window/hello_window.rb) | Empty window, manual event loop |
|
|
134
161
|
| 02 | [events_demo](examples/02_events_demo/events_demo.rb) | Pattern matching on input events |
|
|
135
162
|
| 03 | [bouncing_ball](examples/03_bouncing_ball/bouncing_ball.rb) | dt-based physics, `CircleShape` + `RectangleShape` |
|
|
136
|
-
| 04 | [
|
|
163
|
+
| 04 | [app_class](examples/04_app_class/app_class.rb) | Same idea on top of `SFML::App` |
|
|
137
164
|
| 05 | [mouse_demo](examples/05_mouse_demo/mouse_demo.rb) | Polling vs. events; paint with the mouse |
|
|
138
165
|
| 06 | [pong](examples/06_pong/pong.rb) | Two-player Pong with in-window score (`Text`) and bounce `Sound` |
|
|
139
166
|
| 07 | [scrolling_world](examples/07_scrolling_world/scrolling_world.rb) | `View` as a 2D camera: drag-pan, wheel-zoom around cursor, FPS HUD |
|
|
@@ -219,7 +246,7 @@ When SFML 3.1 / CSFML 3.1 ships, only the bottom layer typically needs to move.
|
|
|
219
246
|
|
|
220
247
|
```sh
|
|
221
248
|
bundle install
|
|
222
|
-
bundle exec rspec #
|
|
249
|
+
bundle exec rspec # 410 examples (all subsystems on Linux)
|
|
223
250
|
bundle exec rake rdoc # generate HTML docs in doc/ (Aliki theme via RDoc 7)
|
|
224
251
|
```
|
|
225
252
|
|
|
@@ -227,6 +254,17 @@ The spec suite hits real CSFML for everything that isn't pure Ruby — `Clock` r
|
|
|
227
254
|
|
|
228
255
|
CI runs the full suite on Ubuntu and macOS × Ruby 3.2 / 3.3 / 3.4. Linux builds CSFML 3 from source (cached), then runs specs under `xvfb-run` so the headless runner has an X server for `RenderWindow`.
|
|
229
256
|
|
|
257
|
+
### Audio specs on macOS
|
|
258
|
+
|
|
259
|
+
CoreAudio + the CSFML OpenAL backend occasionally hang an audio test group on macOS. To keep a darwin run reliable, every spec under `spec/sfml/audio/` is auto-tagged `:audio` and **excluded by default on darwin**. Force them in when you do want to run them:
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
bundle exec rspec --tag audio # only audio specs
|
|
263
|
+
bundle exec rspec spec/sfml/audio/sound_spec.rb --tag audio # one file
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
On Linux the `:audio` filter doesn't fire — the whole suite runs by default.
|
|
267
|
+
|
|
230
268
|
## License
|
|
231
269
|
|
|
232
270
|
MIT. See [LICENSE.txt](LICENSE.txt).
|
data/lib/sfml/app.rb
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Subclass-friendly main loop. Removes the boilerplate of window
|
|
3
|
+
# creation, event pumping, clock management, and clear/display so
|
|
4
|
+
# a small app fits in a few methods.
|
|
5
|
+
#
|
|
6
|
+
# class MyApp < SFML::App
|
|
7
|
+
# antialiasing 4 # class-level config — applies to every instance
|
|
8
|
+
# framerate 120
|
|
9
|
+
# background SFML::Color.new(18, 20, 28)
|
|
10
|
+
#
|
|
11
|
+
# def setup
|
|
12
|
+
# @ball = SFML::CircleShape.new(radius: 30, position: [200, 200],
|
|
13
|
+
# fill_color: SFML::Color.white)
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# def update(dt)
|
|
17
|
+
# @ball.move([60 * dt.as_seconds, 30 * dt.as_seconds])
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# def draw
|
|
21
|
+
# window.draw(@ball)
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# MyApp.new(title: "Hello").run
|
|
26
|
+
#
|
|
27
|
+
# The loop auto-handles :closed and :key_pressed/:escape by calling
|
|
28
|
+
# #quit; everything else is forwarded to #on_event. Override that
|
|
29
|
+
# to handle keys, mouse, etc.
|
|
30
|
+
#
|
|
31
|
+
# ## Configuration
|
|
32
|
+
#
|
|
33
|
+
# Every constructor kwarg is also a class-level macro: declare
|
|
34
|
+
# defaults with `antialiasing 4` etc. inside the class body, and
|
|
35
|
+
# `MyApp.new` picks them up. Per-instance kwargs still win on a
|
|
36
|
+
# case-by-case basis. Subclasses inherit their parent's settings
|
|
37
|
+
# — set one in a base class, override in a subclass.
|
|
38
|
+
class App
|
|
39
|
+
CONFIG_KEYS = %i[
|
|
40
|
+
width height title
|
|
41
|
+
framerate vsync background
|
|
42
|
+
style fullscreen
|
|
43
|
+
antialiasing context
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
CONFIG_KEYS.each do |key|
|
|
48
|
+
# Each macro doubles as a reader (no args) and a writer
|
|
49
|
+
# (one arg). When reading, walks up the inheritance chain so
|
|
50
|
+
# a subclass picks up the parent's value if it hasn't
|
|
51
|
+
# overridden it.
|
|
52
|
+
define_method(key) do |*args|
|
|
53
|
+
ivar = "@#{key}"
|
|
54
|
+
if args.empty?
|
|
55
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
|
56
|
+
return superclass.send(key) if superclass.respond_to?(key)
|
|
57
|
+
|
|
58
|
+
nil
|
|
59
|
+
else
|
|
60
|
+
instance_variable_set(ivar, args.first)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
include Keybindings # provides `on_key` + `key_handlers`
|
|
66
|
+
|
|
67
|
+
# Set the scene class the app should switch into automatically
|
|
68
|
+
# at `setup` time. Inheritable: a subclass that doesn't set
|
|
69
|
+
# one inherits the parent's choice.
|
|
70
|
+
def initial_scene(klass = nil)
|
|
71
|
+
@initial_scene = klass if klass
|
|
72
|
+
return @initial_scene if instance_variable_defined?(:@initial_scene)
|
|
73
|
+
superclass.respond_to?(:initial_scene) ? superclass.initial_scene : nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
attr_reader :window
|
|
78
|
+
attr_accessor :background_color
|
|
79
|
+
|
|
80
|
+
# Per-instance kwargs override anything set at the class level.
|
|
81
|
+
# Anything not given here OR at the class level falls back to
|
|
82
|
+
# the hard-coded defaults below.
|
|
83
|
+
def initialize(**opts)
|
|
84
|
+
cfg = self.class
|
|
85
|
+
|
|
86
|
+
width = opts[:width] || cfg.width || 800
|
|
87
|
+
height = opts[:height] || cfg.height || 600
|
|
88
|
+
title = opts[:title] || cfg.title || cfg.name
|
|
89
|
+
framerate = opts[:framerate] || cfg.framerate || 60
|
|
90
|
+
vsync = opts.fetch(:vsync, cfg.vsync)
|
|
91
|
+
background = opts[:background] || cfg.background || Color::BLACK
|
|
92
|
+
style = opts.fetch(:style, cfg.style)
|
|
93
|
+
fullscreen = opts.fetch(:fullscreen, cfg.fullscreen)
|
|
94
|
+
fullscreen = false if fullscreen.nil?
|
|
95
|
+
antialiasing = opts.fetch(:antialiasing, cfg.antialiasing)
|
|
96
|
+
context = opts.fetch(:context, cfg.context)
|
|
97
|
+
|
|
98
|
+
window_opts = { framerate: framerate, fullscreen: fullscreen }
|
|
99
|
+
window_opts[:vsync] = vsync unless vsync.nil?
|
|
100
|
+
window_opts[:style] = style unless style.nil?
|
|
101
|
+
window_opts[:antialiasing] = antialiasing unless antialiasing.nil?
|
|
102
|
+
window_opts[:context] = context unless context.nil?
|
|
103
|
+
|
|
104
|
+
@window = RenderWindow.new(width, height, title, **window_opts)
|
|
105
|
+
@background_color = background
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Width and height shortcuts that always reflect the current
|
|
109
|
+
# window size — matters once the user is allowed to resize.
|
|
110
|
+
def width = @window.size.x
|
|
111
|
+
def height = @window.size.y
|
|
112
|
+
|
|
113
|
+
# Close the window. The main loop exits at the start of the
|
|
114
|
+
# next frame.
|
|
115
|
+
def quit
|
|
116
|
+
@window.close
|
|
117
|
+
self
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# ---- Scenes ----
|
|
121
|
+
#
|
|
122
|
+
# `current_scene` is whatever the app last activated via
|
|
123
|
+
# `switch_to`. When non-nil, App's default `update` / `draw` /
|
|
124
|
+
# `on_event` / `on_resize` forward to it; key bindings on the
|
|
125
|
+
# scene class shadow ones on the app class while the scene is
|
|
126
|
+
# active.
|
|
127
|
+
|
|
128
|
+
attr_reader :current_scene
|
|
129
|
+
|
|
130
|
+
# Activate `scene_or_class`. Tears down the previous scene
|
|
131
|
+
# before calling `setup` on the new one. Accepts either:
|
|
132
|
+
# * a Scene subclass — instantiated with `self` as host
|
|
133
|
+
# * an existing Scene instance — used as-is
|
|
134
|
+
def switch_to(scene_or_class)
|
|
135
|
+
new_scene =
|
|
136
|
+
case scene_or_class
|
|
137
|
+
when Class then scene_or_class.new(self)
|
|
138
|
+
else scene_or_class
|
|
139
|
+
end
|
|
140
|
+
@current_scene&.teardown
|
|
141
|
+
@current_scene = new_scene
|
|
142
|
+
@current_scene&.setup
|
|
143
|
+
@current_scene
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# ---- Pause / resume ----
|
|
147
|
+
#
|
|
148
|
+
# While paused, `update(dt)` is *not* called — the world stops
|
|
149
|
+
# advancing. `draw` keeps running so the window still updates
|
|
150
|
+
# (handy for pause overlays that need to be drawn over a
|
|
151
|
+
# frozen scene).
|
|
152
|
+
|
|
153
|
+
def pause = (@paused = true)
|
|
154
|
+
def resume = (@paused = false)
|
|
155
|
+
def toggle_pause = (@paused = !paused?)
|
|
156
|
+
def paused? = @paused == true
|
|
157
|
+
|
|
158
|
+
# The main entry point. Calls #setup once, then runs the
|
|
159
|
+
# per-frame loop until the window closes.
|
|
160
|
+
def run
|
|
161
|
+
setup
|
|
162
|
+
clock = Clock.new
|
|
163
|
+
while @window.open?
|
|
164
|
+
dt = clock.restart
|
|
165
|
+
@window.each_event { |event| _dispatch(event) }
|
|
166
|
+
update(dt) unless paused?
|
|
167
|
+
@window.clear(@background_color)
|
|
168
|
+
draw
|
|
169
|
+
@window.display
|
|
170
|
+
end
|
|
171
|
+
teardown
|
|
172
|
+
self
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# ---- Override these in subclasses --------------------------------------
|
|
176
|
+
#
|
|
177
|
+
# Defaults forward to the current scene when one is active. The
|
|
178
|
+
# `initial_scene` class macro auto-instantiates a scene at
|
|
179
|
+
# `setup` time so subclasses with `initial_scene Foo` don't
|
|
180
|
+
# have to define `setup` themselves.
|
|
181
|
+
|
|
182
|
+
def setup
|
|
183
|
+
if (klass = self.class.initial_scene)
|
|
184
|
+
switch_to(klass)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def update(dt)
|
|
189
|
+
@current_scene&.update(dt)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def draw
|
|
193
|
+
@current_scene&.draw
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The framework consumes:
|
|
197
|
+
# * `:closed` — closes the window (always)
|
|
198
|
+
# * `:resized` — forwarded to `on_resize`
|
|
199
|
+
# * `:key_pressed` whose code matches a scene- or app-level
|
|
200
|
+
# `on_key` binding
|
|
201
|
+
# Everything else lands here. Override to handle game-specific
|
|
202
|
+
# input. By default forwards to the current scene's `on_event`.
|
|
203
|
+
def on_event(event)
|
|
204
|
+
@current_scene&.on_event(event)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Default: forward to the current scene. Override to additionally
|
|
208
|
+
# do app-wide layout fixups; call `super` to keep the scene in
|
|
209
|
+
# the loop.
|
|
210
|
+
def on_resize(width, height)
|
|
211
|
+
@current_scene&.on_resize(width, height)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Called once after the main loop exits. Tears down the active
|
|
215
|
+
# scene by default.
|
|
216
|
+
def teardown
|
|
217
|
+
@current_scene&.teardown
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def _dispatch(event)
|
|
223
|
+
case event
|
|
224
|
+
in {type: :closed}
|
|
225
|
+
quit
|
|
226
|
+
in {type: :resized, size: {x:, y:}}
|
|
227
|
+
on_resize(x, y)
|
|
228
|
+
in {type: :key_pressed, code:}
|
|
229
|
+
# Scene-level bindings win over app-level (CSS-style cascade:
|
|
230
|
+
# the more-specific layer overrides the more-general one).
|
|
231
|
+
if @current_scene && (h = @current_scene.class.key_handlers[code])
|
|
232
|
+
_invoke_scene_key(h)
|
|
233
|
+
elsif (h = self.class.key_handlers[code])
|
|
234
|
+
_invoke_key_handler(h)
|
|
235
|
+
else
|
|
236
|
+
on_event(event)
|
|
237
|
+
end
|
|
238
|
+
else
|
|
239
|
+
on_event(event)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def _invoke_key_handler(handler)
|
|
244
|
+
case handler
|
|
245
|
+
when Symbol then send(handler)
|
|
246
|
+
when Proc then handler.call(self)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def _invoke_scene_key(handler)
|
|
251
|
+
case handler
|
|
252
|
+
when Symbol then @current_scene.send(handler)
|
|
253
|
+
when Proc then handler.call(@current_scene)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
data/lib/sfml/c/graphics.rb
CHANGED
|
@@ -79,12 +79,15 @@ module SFML
|
|
|
79
79
|
|
|
80
80
|
typedef :pointer, :render_window_t
|
|
81
81
|
|
|
82
|
-
# See CSFML/Graphics/RenderWindow.h.
|
|
83
|
-
#
|
|
82
|
+
# See CSFML/Graphics/RenderWindow.h. The fifth param is a
|
|
83
|
+
# `const sfContextSettings*` — pass `nil` for SFML defaults
|
|
84
|
+
# or the pointer to a populated ContextSettings struct for
|
|
85
|
+
# MSAA / depth-buffer / GL-version tuning.
|
|
84
86
|
attach_function :sfRenderWindow_create,
|
|
85
87
|
[Window::VideoMode.by_value, :string, :uint32, :int, :pointer],
|
|
86
88
|
:render_window_t
|
|
87
89
|
|
|
90
|
+
attach_function :sfRenderWindow_getSettings, [:render_window_t], Window::ContextSettings.by_value
|
|
88
91
|
attach_function :sfRenderWindow_destroy, [:render_window_t], :void
|
|
89
92
|
attach_function :sfRenderWindow_close, [:render_window_t], :void
|
|
90
93
|
attach_function :sfRenderWindow_isOpen, [:render_window_t], :bool
|
data/lib/sfml/c/window.rb
CHANGED
|
@@ -10,6 +10,36 @@ module SFML
|
|
|
10
10
|
:bits_per_pixel, :uint32
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# sfContextSettings — passed to sfWindow_create / sfRenderWindow_create
|
|
14
|
+
# to configure the underlying OpenGL context. Field order and types
|
|
15
|
+
# match CSFML/Window/Window.h exactly:
|
|
16
|
+
#
|
|
17
|
+
# typedef struct {
|
|
18
|
+
# unsigned int depthBits;
|
|
19
|
+
# unsigned int stencilBits;
|
|
20
|
+
# unsigned int antiAliasingLevel;
|
|
21
|
+
# unsigned int majorVersion;
|
|
22
|
+
# unsigned int minorVersion;
|
|
23
|
+
# uint32_t attributeFlags;
|
|
24
|
+
# bool sRgbCapable;
|
|
25
|
+
# } sfContextSettings;
|
|
26
|
+
class ContextSettings < FFI::Struct
|
|
27
|
+
layout :depth_bits, :uint32,
|
|
28
|
+
:stencil_bits, :uint32,
|
|
29
|
+
:anti_aliasing_level,:uint32,
|
|
30
|
+
:major_version, :uint32,
|
|
31
|
+
:minor_version, :uint32,
|
|
32
|
+
:attribute_flags, :uint32,
|
|
33
|
+
:s_rgb_capable, :bool
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Context attribute bitmask values — match SFML's enum.
|
|
37
|
+
module ContextAttribute
|
|
38
|
+
DEFAULT = 0
|
|
39
|
+
CORE = 1 << 0
|
|
40
|
+
DEBUG = 1 << 2
|
|
41
|
+
end
|
|
42
|
+
|
|
13
43
|
# sfStyle is a bitmask. sfWindowState is a plain enum.
|
|
14
44
|
module Style
|
|
15
45
|
NONE = 0
|
|
@@ -24,31 +24,51 @@ module SFML
|
|
|
24
24
|
# The second form takes (video_mode, title, **opts) for full control.
|
|
25
25
|
#
|
|
26
26
|
# Options:
|
|
27
|
-
# style:
|
|
28
|
-
# fullscreen:
|
|
29
|
-
# framerate:
|
|
30
|
-
# vsync:
|
|
27
|
+
# style: bitmask of SFML::C::Window::Style constants
|
|
28
|
+
# fullscreen: true to use sfFullscreen state instead of sfWindowed
|
|
29
|
+
# framerate: cap to N FPS via sfRenderWindow_setFramerateLimit
|
|
30
|
+
# vsync: enable vertical sync
|
|
31
|
+
# antialiasing: shorthand — MSAA level (2 / 4 / 8). Same as
|
|
32
|
+
# passing `context: ContextSettings.new(antialiasing: N)`
|
|
33
|
+
# context: a SFML::ContextSettings for full GL-context control
|
|
31
34
|
def initialize(*args, **opts)
|
|
32
35
|
mode, title = parse_args(args)
|
|
33
36
|
style = opts.fetch(:style, DEFAULT_STYLE)
|
|
34
37
|
state = opts[:fullscreen] ? :fullscreen : :windowed
|
|
35
38
|
|
|
39
|
+
settings = _resolve_context_settings(opts)
|
|
40
|
+
# Hold a reference for the duration of the C call so the
|
|
41
|
+
# struct's memory survives until CSFML has copied it.
|
|
42
|
+
@ctx_struct = settings ? settings.to_native : nil
|
|
43
|
+
ctx_ptr = @ctx_struct ? @ctx_struct.to_ptr : nil
|
|
44
|
+
|
|
36
45
|
ptr = C::Graphics.sfRenderWindow_create(
|
|
37
46
|
mode.to_native,
|
|
38
47
|
title.to_s,
|
|
39
48
|
style,
|
|
40
49
|
C::Window::State[state],
|
|
41
|
-
|
|
50
|
+
ctx_ptr,
|
|
42
51
|
)
|
|
43
52
|
raise Error, "sfRenderWindow_create returned NULL" if ptr.null?
|
|
44
53
|
|
|
45
54
|
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfRenderWindow_destroy))
|
|
46
55
|
@event_buffer = C::Window::Event.new
|
|
56
|
+
@requested_context = settings
|
|
47
57
|
|
|
48
58
|
self.framerate_limit = opts[:framerate] if opts[:framerate]
|
|
49
59
|
self.vsync = opts[:vsync] unless opts[:vsync].nil?
|
|
50
60
|
end
|
|
51
61
|
|
|
62
|
+
# The actual ContextSettings the driver gave us. May differ
|
|
63
|
+
# from what we requested — the driver picks the closest level
|
|
64
|
+
# of MSAA / GL version it supports.
|
|
65
|
+
def context_settings
|
|
66
|
+
ContextSettings.from_native(C::Graphics.sfRenderWindow_getSettings(@handle))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# What we asked for at creation time, if anything (otherwise nil).
|
|
70
|
+
attr_reader :requested_context
|
|
71
|
+
|
|
52
72
|
def open?
|
|
53
73
|
C::Graphics.sfRenderWindow_isOpen(@handle)
|
|
54
74
|
end
|
|
@@ -182,6 +202,17 @@ module SFML
|
|
|
182
202
|
|
|
183
203
|
private
|
|
184
204
|
|
|
205
|
+
def _resolve_context_settings(opts)
|
|
206
|
+
if opts[:context]
|
|
207
|
+
unless opts[:context].is_a?(ContextSettings)
|
|
208
|
+
raise ArgumentError, "context: must be a SFML::ContextSettings"
|
|
209
|
+
end
|
|
210
|
+
opts[:context]
|
|
211
|
+
elsif opts[:antialiasing]
|
|
212
|
+
ContextSettings.new(antialiasing: opts[:antialiasing])
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
185
216
|
def _vec2u_or_nil(value)
|
|
186
217
|
return nil if value.nil?
|
|
187
218
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Class-level `on_key` DSL — shared by `SFML::App` and `SFML::Scene`.
|
|
3
|
+
# Each class that extends this module gets:
|
|
4
|
+
#
|
|
5
|
+
# * `on_key(code, :method_name)` — bind a key to an instance method
|
|
6
|
+
# * `on_key(code, ->(receiver) { ... })` — bind to a Proc
|
|
7
|
+
# * `on_key(code) { |receiver| ... }` — bind to a block
|
|
8
|
+
#
|
|
9
|
+
# Inheritance: a subclass's bindings layer on top of the parent's,
|
|
10
|
+
# so `class Sub < Parent; on_key :x, :foo; end` keeps the parent's
|
|
11
|
+
# other bindings while overriding (or adding) `:x`.
|
|
12
|
+
module Keybindings
|
|
13
|
+
def on_key(code, target = nil, &block)
|
|
14
|
+
@key_handlers ||= {}
|
|
15
|
+
handler = block || target
|
|
16
|
+
raise ArgumentError, "on_key needs a target Symbol/Proc or a block" unless handler
|
|
17
|
+
@key_handlers[code.to_sym] = handler
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# `Hash{key_code => handler}` — own bindings layered over the
|
|
21
|
+
# parent's (own keys win).
|
|
22
|
+
def key_handlers
|
|
23
|
+
own = (@key_handlers ||= {})
|
|
24
|
+
parent = superclass.respond_to?(:key_handlers) ? superclass.key_handlers : {}
|
|
25
|
+
parent.merge(own)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/sfml/scene.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# A chunk of game state with its own lifecycle — menu, gameplay,
|
|
3
|
+
# game-over, settings overlay, etc. The host `SFML::App` keeps a
|
|
4
|
+
# `current_scene` and forwards `update` / `draw` / `on_event` /
|
|
5
|
+
# `on_resize` to it. Switch between scenes with `app.switch_to`
|
|
6
|
+
# (or `switch_to` from inside a scene, which delegates).
|
|
7
|
+
#
|
|
8
|
+
# class TitleScene < SFML::Scene
|
|
9
|
+
# on_key :enter, :start_game
|
|
10
|
+
#
|
|
11
|
+
# def setup
|
|
12
|
+
# @gui = SFML::GUI::App.new(window: window, stylesheet: SHEET)
|
|
13
|
+
# # ... build UI ...
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# def update(dt) = @gui.update(dt)
|
|
17
|
+
# def draw = @gui.draw
|
|
18
|
+
#
|
|
19
|
+
# def start_game
|
|
20
|
+
# switch_to PlayScene
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# class PlayScene < SFML::Scene
|
|
25
|
+
# # …
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# class MyApp < SFML::App
|
|
29
|
+
# initial_scene TitleScene # auto-switches to it on app setup
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# `SFML::Scene` carries its own `on_key` DSL — scene-level
|
|
33
|
+
# bindings shadow app-level bindings while the scene is active.
|
|
34
|
+
class Scene
|
|
35
|
+
class << self
|
|
36
|
+
include Keybindings
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :app
|
|
40
|
+
|
|
41
|
+
def initialize(app)
|
|
42
|
+
@app = app
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Convenience accessors that match `SFML::App`'s.
|
|
46
|
+
def window = @app.window
|
|
47
|
+
def width = @app.width
|
|
48
|
+
def height = @app.height
|
|
49
|
+
|
|
50
|
+
# Switch the host app to a new scene from inside this one.
|
|
51
|
+
# Accepts a Scene class (instantiated with `app`) or an already-
|
|
52
|
+
# constructed Scene instance.
|
|
53
|
+
def switch_to(scene_or_class)
|
|
54
|
+
@app.switch_to(scene_or_class)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# ---- Override these in subclasses --------------------------------------
|
|
58
|
+
|
|
59
|
+
# Called once when the host app activates this scene (replacing
|
|
60
|
+
# whatever scene was active before). Build per-scene state here.
|
|
61
|
+
def setup; end
|
|
62
|
+
|
|
63
|
+
# Called every frame the scene is active and the app isn't paused.
|
|
64
|
+
def update(_dt); end
|
|
65
|
+
|
|
66
|
+
# Called every frame after #update.
|
|
67
|
+
def draw; end
|
|
68
|
+
|
|
69
|
+
# Called for every event the framework didn't auto-handle (closed,
|
|
70
|
+
# resized, and on_key bindings). Override to handle game-specific
|
|
71
|
+
# input patterns.
|
|
72
|
+
def on_event(_event); end
|
|
73
|
+
|
|
74
|
+
# Window resize. Default: no-op.
|
|
75
|
+
def on_resize(_width, _height); end
|
|
76
|
+
|
|
77
|
+
# Called once when the host app switches away (or when the app
|
|
78
|
+
# loop exits while this scene is active). Free per-scene
|
|
79
|
+
# resources here.
|
|
80
|
+
def teardown; end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/sfml/version.rb
CHANGED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Configuration for the underlying OpenGL context that backs a
|
|
3
|
+
# `Window` / `RenderWindow`. Passed at window-creation time to
|
|
4
|
+
# request anti-aliasing, depth/stencil buffers, or a specific
|
|
5
|
+
# OpenGL version.
|
|
6
|
+
#
|
|
7
|
+
# settings = SFML::ContextSettings.new(antialiasing: 4)
|
|
8
|
+
# window = SFML::RenderWindow.new(800, 600, "Smooth", context: settings)
|
|
9
|
+
#
|
|
10
|
+
# Or use the `RenderWindow.new` shortcut directly:
|
|
11
|
+
#
|
|
12
|
+
# window = SFML::RenderWindow.new(800, 600, "Smooth", antialiasing: 4)
|
|
13
|
+
#
|
|
14
|
+
# Anti-aliasing is the most common knob — typical values are 0
|
|
15
|
+
# (off), 2, 4, or 8. The driver picks the closest level it
|
|
16
|
+
# actually supports; query `window.context_settings` after
|
|
17
|
+
# creation to see what you got.
|
|
18
|
+
class ContextSettings
|
|
19
|
+
DEFAULT_DEPTH_BITS = 0
|
|
20
|
+
DEFAULT_STENCIL_BITS = 0
|
|
21
|
+
DEFAULT_AA_LEVEL = 0
|
|
22
|
+
DEFAULT_MAJOR_VERSION = 1
|
|
23
|
+
DEFAULT_MINOR_VERSION = 1
|
|
24
|
+
|
|
25
|
+
ATTRIBUTE_FLAGS = {
|
|
26
|
+
default: C::Window::ContextAttribute::DEFAULT,
|
|
27
|
+
core: C::Window::ContextAttribute::CORE,
|
|
28
|
+
debug: C::Window::ContextAttribute::DEBUG,
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
attr_reader :depth_bits, :stencil_bits, :antialiasing,
|
|
32
|
+
:major_version, :minor_version, :attribute_flags,
|
|
33
|
+
:srgb_capable
|
|
34
|
+
|
|
35
|
+
def initialize(depth_bits: DEFAULT_DEPTH_BITS,
|
|
36
|
+
stencil_bits: DEFAULT_STENCIL_BITS,
|
|
37
|
+
antialiasing: DEFAULT_AA_LEVEL,
|
|
38
|
+
major_version: DEFAULT_MAJOR_VERSION,
|
|
39
|
+
minor_version: DEFAULT_MINOR_VERSION,
|
|
40
|
+
attributes: :default,
|
|
41
|
+
srgb_capable: false)
|
|
42
|
+
@depth_bits = Integer(depth_bits)
|
|
43
|
+
@stencil_bits = Integer(stencil_bits)
|
|
44
|
+
@antialiasing = Integer(antialiasing)
|
|
45
|
+
@major_version = Integer(major_version)
|
|
46
|
+
@minor_version = Integer(minor_version)
|
|
47
|
+
@attribute_flags = _attribute_mask(attributes)
|
|
48
|
+
@srgb_capable = !!srgb_capable
|
|
49
|
+
freeze
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Build the matching CSFML struct on the heap. Returned struct
|
|
53
|
+
# is owned by the caller (FFI::AutoPointer is *not* set — pass
|
|
54
|
+
# by reference into a window-creation call and let it copy).
|
|
55
|
+
def to_native
|
|
56
|
+
s = C::Window::ContextSettings.new
|
|
57
|
+
s[:depth_bits] = @depth_bits
|
|
58
|
+
s[:stencil_bits] = @stencil_bits
|
|
59
|
+
s[:anti_aliasing_level] = @antialiasing
|
|
60
|
+
s[:major_version] = @major_version
|
|
61
|
+
s[:minor_version] = @minor_version
|
|
62
|
+
s[:attribute_flags] = @attribute_flags
|
|
63
|
+
s[:s_rgb_capable] = @srgb_capable
|
|
64
|
+
s
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.from_native(struct)
|
|
68
|
+
attrs = ATTRIBUTE_FLAGS.find { |_, v| v == struct[:attribute_flags] }&.first || :default
|
|
69
|
+
new(
|
|
70
|
+
depth_bits: struct[:depth_bits],
|
|
71
|
+
stencil_bits: struct[:stencil_bits],
|
|
72
|
+
antialiasing: struct[:anti_aliasing_level],
|
|
73
|
+
major_version: struct[:major_version],
|
|
74
|
+
minor_version: struct[:minor_version],
|
|
75
|
+
attributes: attrs,
|
|
76
|
+
srgb_capable: struct[:s_rgb_capable],
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def ==(other)
|
|
81
|
+
other.is_a?(ContextSettings) &&
|
|
82
|
+
depth_bits == other.depth_bits &&
|
|
83
|
+
stencil_bits == other.stencil_bits &&
|
|
84
|
+
antialiasing == other.antialiasing &&
|
|
85
|
+
major_version == other.major_version &&
|
|
86
|
+
minor_version == other.minor_version &&
|
|
87
|
+
attribute_flags == other.attribute_flags &&
|
|
88
|
+
srgb_capable == other.srgb_capable
|
|
89
|
+
end
|
|
90
|
+
alias eql? ==
|
|
91
|
+
|
|
92
|
+
def hash = [@depth_bits, @stencil_bits, @antialiasing,
|
|
93
|
+
@major_version, @minor_version, @attribute_flags,
|
|
94
|
+
@srgb_capable].hash
|
|
95
|
+
|
|
96
|
+
def to_s
|
|
97
|
+
"ContextSettings(aa=#{@antialiasing}, depth=#{@depth_bits}, " \
|
|
98
|
+
"stencil=#{@stencil_bits}, gl=#{@major_version}.#{@minor_version})"
|
|
99
|
+
end
|
|
100
|
+
alias inspect to_s
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def _attribute_mask(attrs)
|
|
105
|
+
case attrs
|
|
106
|
+
when Integer then attrs
|
|
107
|
+
when Symbol then ATTRIBUTE_FLAGS.fetch(attrs) do
|
|
108
|
+
raise ArgumentError, "unknown attribute :#{attrs}; one of #{ATTRIBUTE_FLAGS.keys}"
|
|
109
|
+
end
|
|
110
|
+
when Array
|
|
111
|
+
attrs.reduce(0) do |mask, name|
|
|
112
|
+
mask | ATTRIBUTE_FLAGS.fetch(name) do
|
|
113
|
+
raise ArgumentError, "unknown attribute :#{name}; one of #{ATTRIBUTE_FLAGS.keys}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
else
|
|
117
|
+
raise ArgumentError, "expected Symbol/Array/Integer, got #{attrs.class}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
data/lib/sfml.rb
CHANGED
|
@@ -80,6 +80,7 @@ require "sfml/window/cursor"
|
|
|
80
80
|
require "sfml/window/clipboard"
|
|
81
81
|
require "sfml/window/video_mode"
|
|
82
82
|
require "sfml/window/event"
|
|
83
|
+
require "sfml/window/context_settings"
|
|
83
84
|
require "sfml/window/window"
|
|
84
85
|
require "sfml/graphics/color"
|
|
85
86
|
require "sfml/graphics/transformable"
|
|
@@ -120,4 +121,6 @@ require "sfml/network/socket_selector"
|
|
|
120
121
|
require "sfml/network/http"
|
|
121
122
|
require "sfml/network/ftp"
|
|
122
123
|
require "sfml/assets"
|
|
123
|
-
require "sfml/
|
|
124
|
+
require "sfml/keybindings"
|
|
125
|
+
require "sfml/scene"
|
|
126
|
+
require "sfml/app"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-sfml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.0.
|
|
4
|
+
version: 3.0.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mykhailo Melnyk
|
|
@@ -79,6 +79,7 @@ files:
|
|
|
79
79
|
- README.md
|
|
80
80
|
- ext/ruby-sfml/extconf.rb
|
|
81
81
|
- lib/sfml.rb
|
|
82
|
+
- lib/sfml/app.rb
|
|
82
83
|
- lib/sfml/assets.rb
|
|
83
84
|
- lib/sfml/assets/fonts/DejaVuSans.LICENSE.txt
|
|
84
85
|
- lib/sfml/assets/fonts/DejaVuSans.ttf
|
|
@@ -97,7 +98,6 @@ files:
|
|
|
97
98
|
- lib/sfml/c/network.rb
|
|
98
99
|
- lib/sfml/c/system.rb
|
|
99
100
|
- lib/sfml/c/window.rb
|
|
100
|
-
- lib/sfml/game.rb
|
|
101
101
|
- lib/sfml/graphics/blend_mode.rb
|
|
102
102
|
- lib/sfml/graphics/circle_shape.rb
|
|
103
103
|
- lib/sfml/graphics/color.rb
|
|
@@ -120,6 +120,7 @@ files:
|
|
|
120
120
|
- lib/sfml/graphics/vertex_array.rb
|
|
121
121
|
- lib/sfml/graphics/vertex_buffer.rb
|
|
122
122
|
- lib/sfml/graphics/view.rb
|
|
123
|
+
- lib/sfml/keybindings.rb
|
|
123
124
|
- lib/sfml/network/ftp.rb
|
|
124
125
|
- lib/sfml/network/http.rb
|
|
125
126
|
- lib/sfml/network/ip_address.rb
|
|
@@ -127,6 +128,7 @@ files:
|
|
|
127
128
|
- lib/sfml/network/tcp_listener.rb
|
|
128
129
|
- lib/sfml/network/tcp_socket.rb
|
|
129
130
|
- lib/sfml/network/udp_socket.rb
|
|
131
|
+
- lib/sfml/scene.rb
|
|
130
132
|
- lib/sfml/system/clock.rb
|
|
131
133
|
- lib/sfml/system/rect.rb
|
|
132
134
|
- lib/sfml/system/time.rb
|
|
@@ -134,6 +136,7 @@ files:
|
|
|
134
136
|
- lib/sfml/system/vector3.rb
|
|
135
137
|
- lib/sfml/version.rb
|
|
136
138
|
- lib/sfml/window/clipboard.rb
|
|
139
|
+
- lib/sfml/window/context_settings.rb
|
|
137
140
|
- lib/sfml/window/cursor.rb
|
|
138
141
|
- lib/sfml/window/event.rb
|
|
139
142
|
- lib/sfml/window/joystick.rb
|
data/lib/sfml/game.rb
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
module SFML
|
|
2
|
-
# Subclass-friendly main loop. Removes the boilerplate of window creation,
|
|
3
|
-
# event pumping, clock management, and clear/display so a small game fits
|
|
4
|
-
# in a few methods.
|
|
5
|
-
#
|
|
6
|
-
# class MyGame < SFML::Game
|
|
7
|
-
# def setup
|
|
8
|
-
# @ball = SFML::CircleShape.new(radius: 30, position: [200, 200],
|
|
9
|
-
# fill_color: SFML::Color.white)
|
|
10
|
-
# end
|
|
11
|
-
#
|
|
12
|
-
# def update(dt)
|
|
13
|
-
# @ball.move([60 * dt.as_seconds, 30 * dt.as_seconds])
|
|
14
|
-
# end
|
|
15
|
-
#
|
|
16
|
-
# def draw
|
|
17
|
-
# window.draw(@ball)
|
|
18
|
-
# end
|
|
19
|
-
# end
|
|
20
|
-
#
|
|
21
|
-
# MyGame.new(title: "Hello").run
|
|
22
|
-
#
|
|
23
|
-
# The loop auto-handles :closed and :key_pressed/:escape by calling #quit;
|
|
24
|
-
# everything else is forwarded to #on_event. Override that to handle keys,
|
|
25
|
-
# mouse, etc.
|
|
26
|
-
class Game
|
|
27
|
-
attr_reader :window
|
|
28
|
-
attr_accessor :background_color
|
|
29
|
-
|
|
30
|
-
def initialize(
|
|
31
|
-
width: 800, height: 600, title: nil,
|
|
32
|
-
framerate: 60, vsync: nil,
|
|
33
|
-
background: Color::BLACK,
|
|
34
|
-
style: nil, fullscreen: false
|
|
35
|
-
)
|
|
36
|
-
window_opts = { framerate: framerate, fullscreen: fullscreen }
|
|
37
|
-
window_opts[:vsync] = vsync unless vsync.nil?
|
|
38
|
-
window_opts[:style] = style unless style.nil?
|
|
39
|
-
|
|
40
|
-
@window = RenderWindow.new(width, height, title || self.class.name, **window_opts)
|
|
41
|
-
@background_color = background
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Width and height shortcuts that always reflect the current window size,
|
|
45
|
-
# which matters once the user is allowed to resize.
|
|
46
|
-
def width = @window.size.x
|
|
47
|
-
def height = @window.size.y
|
|
48
|
-
|
|
49
|
-
# Close the window. The main loop exits at the start of the next frame.
|
|
50
|
-
def quit
|
|
51
|
-
@window.close
|
|
52
|
-
self
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# The main entry point. Calls #setup once, then runs the per-frame loop
|
|
56
|
-
# until the window closes.
|
|
57
|
-
def run
|
|
58
|
-
setup
|
|
59
|
-
clock = Clock.new
|
|
60
|
-
while @window.open?
|
|
61
|
-
dt = clock.restart
|
|
62
|
-
@window.each_event { |event| _dispatch(event) }
|
|
63
|
-
update(dt)
|
|
64
|
-
@window.clear(@background_color)
|
|
65
|
-
draw
|
|
66
|
-
@window.display
|
|
67
|
-
end
|
|
68
|
-
teardown
|
|
69
|
-
self
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# ---- Override these in subclasses --------------------------------------
|
|
73
|
-
|
|
74
|
-
# Called once, just before the first frame. Build initial state here.
|
|
75
|
-
def setup; end
|
|
76
|
-
|
|
77
|
-
# Called every frame. `dt` is a SFML::Time covering the previous frame.
|
|
78
|
-
def update(dt); end
|
|
79
|
-
|
|
80
|
-
# Called every frame after #update. Use window.draw(...) for any drawables.
|
|
81
|
-
def draw; end
|
|
82
|
-
|
|
83
|
-
# Called for events the framework didn't auto-handle (everything except
|
|
84
|
-
# :closed and the Esc key). Call #quit from here to exit on a custom key.
|
|
85
|
-
def on_event(event); end
|
|
86
|
-
|
|
87
|
-
# Called once after the window closes. Useful for saving state. Default
|
|
88
|
-
# is a no-op.
|
|
89
|
-
def teardown; end
|
|
90
|
-
|
|
91
|
-
private
|
|
92
|
-
|
|
93
|
-
def _dispatch(event)
|
|
94
|
-
case event
|
|
95
|
-
in {type: :closed} then quit
|
|
96
|
-
in {type: :key_pressed, code: :escape} then quit
|
|
97
|
-
else on_event(event)
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|