rgame 0.4.0-arm64-darwin
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 +7 -0
- data/CHANGELOG.md +251 -0
- data/LICENSE +26 -0
- data/README.md +325 -0
- data/docs/api/README.md +224 -0
- data/docs/api/app.md +249 -0
- data/docs/api/assets.md +463 -0
- data/docs/api/audio.md +249 -0
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1112 -0
- data/docs/api/drawing.md +398 -0
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +219 -0
- data/docs/api/images.md +118 -0
- data/docs/api/input.md +476 -0
- data/docs/api/internals.md +289 -0
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +539 -0
- data/docs/api/signals.md +147 -0
- data/docs/api/systems.md +231 -0
- data/docs/api/text.md +133 -0
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +493 -0
- data/docs/api/ui.md +927 -0
- data/docs/api/values.md +316 -0
- data/examples/assets/README.md +322 -0
- data/examples/assets/blip.ogg +0 -0
- data/examples/assets/glyphs.json +5 -0
- data/examples/assets/glyphs.png +0 -0
- data/examples/assets/hero.json +12 -0
- data/examples/assets/hero.png +0 -0
- data/examples/assets/icons.json +13 -0
- data/examples/assets/icons.png +0 -0
- data/examples/assets/music.ogg +0 -0
- data/examples/assets/skills.json +10 -0
- data/examples/assets/skills.png +0 -0
- data/examples/assets/tileset.png +0 -0
- data/examples/assets/tileset.tsx +65 -0
- data/examples/assets/town.tmx +26 -0
- data/examples/assets/ui.json +11 -0
- data/examples/assets/ui.png +0 -0
- data/examples/collision/locales/en.yml +8 -0
- data/examples/collision/main.rb +316 -0
- data/examples/collision_tiles/locales/en.yml +9 -0
- data/examples/collision_tiles/main.rb +274 -0
- data/examples/fullscreen/locales/en.yml +10 -0
- data/examples/fullscreen/main.rb +216 -0
- data/examples/game_menu/locales/en.yml +8 -0
- data/examples/game_menu/main.rb +170 -0
- data/examples/input_glyphs/locales/en.yml +14 -0
- data/examples/input_glyphs/main.rb +213 -0
- data/examples/jump_topdown/locales/en.yml +9 -0
- data/examples/jump_topdown/main.rb +178 -0
- data/examples/localization/locales/de.yml +12 -0
- data/examples/localization/locales/en.yml +13 -0
- data/examples/localization/main.rb +158 -0
- data/examples/menu_navigation/locales/en.yml +23 -0
- data/examples/menu_navigation/main.rb +365 -0
- data/examples/music/locales/en.yml +7 -0
- data/examples/music/main.rb +134 -0
- data/examples/pathfinding/locales/en.yml +17 -0
- data/examples/pathfinding/main.rb +298 -0
- data/examples/pooling/locales/en.yml +7 -0
- data/examples/pooling/main.rb +259 -0
- data/examples/quick_wheel/locales/en.yml +16 -0
- data/examples/quick_wheel/main.rb +184 -0
- data/examples/radial_menu/locales/en.yml +16 -0
- data/examples/radial_menu/main.rb +184 -0
- data/examples/save_load/locales/en.yml +11 -0
- data/examples/save_load/main.rb +207 -0
- data/examples/save_load_ids/locales/en.yml +11 -0
- data/examples/save_load_ids/main.rb +322 -0
- data/examples/scroll_map/locales/en.yml +4 -0
- data/examples/scroll_map/main.rb +140 -0
- data/examples/signals/locales/en.yml +6 -0
- data/examples/signals/main.rb +278 -0
- data/examples/skill_bar/locales/en.yml +14 -0
- data/examples/skill_bar/main.rb +159 -0
- data/examples/sound/locales/en.yml +6 -0
- data/examples/sound/main.rb +122 -0
- data/examples/split_screen/locales/en.yml +9 -0
- data/examples/split_screen/main.rb +304 -0
- data/examples/sprite/locales/en.yml +8 -0
- data/examples/sprite/main.rb +180 -0
- data/examples/timer/locales/en.yml +12 -0
- data/examples/timer/main.rb +273 -0
- data/examples/velocity/locales/en.yml +6 -0
- data/examples/velocity/main.rb +196 -0
- data/examples/walk/locales/en.yml +4 -0
- data/examples/walk/main.rb +99 -0
- data/exe/rgame +9 -0
- data/lib/rgame/boot.rb +3 -0
- data/lib/rgame/cli/new_project.rb +139 -0
- data/lib/rgame/cli/templates/Gemfile.tt +23 -0
- data/lib/rgame/cli/templates/README.md.tt +93 -0
- data/lib/rgame/cli/templates/Rakefile.tt +9 -0
- data/lib/rgame/cli/templates/assets/locales/en.yml.tt +10 -0
- data/lib/rgame/cli/templates/game.rb.tt +23 -0
- data/lib/rgame/cli/templates/gitignore.tt +12 -0
- data/lib/rgame/cli/templates/main.rb.tt +11 -0
- data/lib/rgame/cli/templates/nodes/root.rb.tt +24 -0
- data/lib/rgame/cli/templates/rspec.tt +2 -0
- data/lib/rgame/cli/templates/rubocop.yml.tt +75 -0
- data/lib/rgame/cli/templates/ruby-version.tt +1 -0
- data/lib/rgame/cli/templates/spec/locales_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/nodes/root_spec.rb.tt +18 -0
- data/lib/rgame/cli/templates/spec/spec_helper.rb.tt +37 -0
- data/lib/rgame/cli.rb +66 -0
- data/lib/rgame/core/app.rb +46 -0
- data/lib/rgame/core/asset_manager.rb +206 -0
- data/lib/rgame/core/audio.rb +145 -0
- data/lib/rgame/core/font.rb +46 -0
- data/lib/rgame/core/gamepad.rb +55 -0
- data/lib/rgame/core/image.rb +55 -0
- data/lib/rgame/core/input.rb +71 -0
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +142 -0
- data/lib/rgame/core/recording.rb +54 -0
- data/lib/rgame/core/renderer.rb +354 -0
- data/lib/rgame/core/sprite_sheet.rb +105 -0
- data/lib/rgame/core/tile_map_renderer.rb +172 -0
- data/lib/rgame/core/ui_atlas.rb +101 -0
- data/lib/rgame/core/virtual_gamepad.rb +26 -0
- data/lib/rgame/core.rb +18 -0
- data/lib/rgame/core_ext.bundle +0 -0
- data/lib/rgame/engine/actor_blockers.rb +131 -0
- data/lib/rgame/engine/animation_set.rb +50 -0
- data/lib/rgame/engine/animator.rb +44 -0
- data/lib/rgame/engine/audio_bus.rb +24 -0
- data/lib/rgame/engine/audio_director.rb +59 -0
- data/lib/rgame/engine/bounds_blockers.rb +74 -0
- data/lib/rgame/engine/camera.rb +78 -0
- data/lib/rgame/engine/circle_collider.rb +34 -0
- data/lib/rgame/engine/collision_box.rb +59 -0
- data/lib/rgame/engine/collision_system.rb +132 -0
- data/lib/rgame/engine/component.rb +64 -0
- data/lib/rgame/engine/components/action_trigger.rb +40 -0
- data/lib/rgame/engine/components/animated_sprite.rb +71 -0
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +45 -0
- data/lib/rgame/engine/components/character_body.rb +50 -0
- data/lib/rgame/engine/components/circle_collider.rb +80 -0
- data/lib/rgame/engine/components/collision_world.rb +231 -0
- data/lib/rgame/engine/components/despawn_offscreen.rb +42 -0
- data/lib/rgame/engine/components/feet_collider.rb +61 -0
- data/lib/rgame/engine/components/hop.rb +76 -0
- data/lib/rgame/engine/components/identity.rb +73 -0
- data/lib/rgame/engine/components/mover.rb +285 -0
- data/lib/rgame/engine/components/navigator.rb +145 -0
- data/lib/rgame/engine/components/path_follow.rb +176 -0
- data/lib/rgame/engine/components/player_controller.rb +27 -0
- data/lib/rgame/engine/components/pool.rb +53 -0
- data/lib/rgame/engine/components/screen_wrap.rb +49 -0
- data/lib/rgame/engine/components/sprite.rb +47 -0
- data/lib/rgame/engine/components/targeting.rb +52 -0
- data/lib/rgame/engine/components/thrust_controller.rb +65 -0
- data/lib/rgame/engine/components/tile_world.rb +109 -0
- data/lib/rgame/engine/components/timer.rb +75 -0
- data/lib/rgame/engine/components/velocity.rb +43 -0
- data/lib/rgame/engine/components/wander_controller.rb +64 -0
- data/lib/rgame/engine/components/world.rb +133 -0
- data/lib/rgame/engine/contact_set.rb +74 -0
- data/lib/rgame/engine/culling.rb +45 -0
- data/lib/rgame/engine/debug_overlay.rb +111 -0
- data/lib/rgame/engine/i18n/plural.rb +45 -0
- data/lib/rgame/engine/i18n/plural_rules.rb +82 -0
- data/lib/rgame/engine/i18n/template.rb +59 -0
- data/lib/rgame/engine/i18n.rb +322 -0
- data/lib/rgame/engine/input/action_mapper.rb +100 -0
- data/lib/rgame/engine/input/actions.rb +92 -0
- data/lib/rgame/engine/input/input_map.rb +196 -0
- data/lib/rgame/engine/layout.rb +82 -0
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +555 -0
- data/lib/rgame/engine/path.rb +76 -0
- data/lib/rgame/engine/player.rb +69 -0
- data/lib/rgame/engine/player_layer.rb +70 -0
- data/lib/rgame/engine/players.rb +205 -0
- data/lib/rgame/engine/pool.rb +51 -0
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +87 -0
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/signal.rb +75 -0
- data/lib/rgame/engine/spatial_hash.rb +116 -0
- data/lib/rgame/engine/text.rb +194 -0
- data/lib/rgame/engine/tile_blockers.rb +63 -0
- data/lib/rgame/engine/tile_map.rb +147 -0
- data/lib/rgame/engine/tile_map_layer.rb +82 -0
- data/lib/rgame/engine/tileset.rb +99 -0
- data/lib/rgame/engine/timer.rb +51 -0
- data/lib/rgame/engine/ui/button.rb +248 -0
- data/lib/rgame/engine/ui/column.rb +20 -0
- data/lib/rgame/engine/ui/icon_button.rb +93 -0
- data/lib/rgame/engine/ui/menu.rb +290 -0
- data/lib/rgame/engine/ui/navigation.rb +57 -0
- data/lib/rgame/engine/ui/nine_slice_style.rb +50 -0
- data/lib/rgame/engine/ui/option_button.rb +163 -0
- data/lib/rgame/engine/ui/panel_button.rb +32 -0
- data/lib/rgame/engine/ui/panel_menu.rb +36 -0
- data/lib/rgame/engine/ui/pointing.rb +146 -0
- data/lib/rgame/engine/ui/radial_menu.rb +85 -0
- data/lib/rgame/engine/ui/ring.rb +55 -0
- data/lib/rgame/engine/ui/row.rb +21 -0
- data/lib/rgame/engine/ui/shape_style.rb +102 -0
- data/lib/rgame/engine/ui/stack.rb +58 -0
- data/lib/rgame/engine/ui/stepping.rb +93 -0
- data/lib/rgame/engine/ui/text_button.rb +59 -0
- data/lib/rgame/engine/view.rb +76 -0
- data/lib/rgame/engine/viewports.rb +171 -0
- data/lib/rgame/engine/world_view.rb +71 -0
- data/lib/rgame/engine.rb +86 -0
- data/lib/rgame/fonts/LiberationSans-Regular.ttf +0 -0
- data/lib/rgame/fonts/OFL.txt +102 -0
- data/lib/rgame/game.rb +271 -0
- data/lib/rgame/rubocop/cop/game/draw_in_local_space.rb +103 -0
- data/lib/rgame/rubocop/cop/game/hot_path.rb +36 -0
- data/lib/rgame/rubocop/cop/game/layer_boundary.rb +43 -0
- data/lib/rgame/rubocop/cop/game/no_core_in_engine_layer.rb +100 -0
- data/lib/rgame/rubocop/cop/game/no_engine_in_core_layer.rb +84 -0
- data/lib/rgame/rubocop/cop/game/no_interpolation_in_hot_path.rb +50 -0
- data/lib/rgame/rubocop/cop/game/no_literal_text.rb +41 -0
- data/lib/rgame/rubocop/cop/game/no_needless_allocation.rb +112 -0
- data/lib/rgame/rubocop/default.yml +39 -0
- data/lib/rgame/rubocop/plugin.rb +45 -0
- data/lib/rgame/rubocop.rb +11 -0
- data/lib/rgame/util/color.rb +23 -0
- data/lib/rgame/util/controls.rb +169 -0
- data/lib/rgame/util/route_search.rb +27 -0
- data/lib/rgame/util/save_file.rb +107 -0
- data/lib/rgame/util/solid_grid.rb +37 -0
- data/lib/rgame/util/tensor.rb +3 -0
- data/lib/rgame/util/tile_sweep.rb +36 -0
- data/lib/rgame/util/z.rb +123 -0
- data/lib/rgame/util.rb +10 -0
- data/lib/rgame/util_ext.bundle +0 -0
- data/lib/rgame/version.rb +12 -0
- data/lib/rgame.rb +5 -0
- data/licenses/SDL2/LICENSE.txt +18 -0
- metadata +309 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0e9a8df4b92f3e3a10ea64720724a76e4c896a2010c691149940ca36da734df3
|
|
4
|
+
data.tar.gz: aa2caa71ceba2a1ba39e5c9d043fd423059b624a1da24e5fed7244fc43dfd40b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 19de7a76ea8ec3a39ce6cb8031e679c95a133236fcc101a0a83d4dee4469afcef740e811a46a909906a247ccdc8944fd4506c48970055efc86e036051d4b6d48
|
|
7
|
+
data.tar.gz: ba2b069e3e47c5256de8d896e0f6849751d5c5524444454d7694195bc6760582a7b35b4e8e0c030b83b6205d4ca4eda61edb968a3787afedf203a4606e2337f5
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
6
|
+
this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html) —
|
|
7
|
+
which before 1.0 means the public API can still change in a minor release.
|
|
8
|
+
|
|
9
|
+
Entries describe what changed for someone *using* the engine. The reasoning
|
|
10
|
+
behind a change belongs in the documentation it lands with; this file is the
|
|
11
|
+
index, not the argument.
|
|
12
|
+
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
## [0.4.0] - 2026-09-16
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`gem install rgame` needs no compiler and no SDL2 on a common desktop.**
|
|
20
|
+
Apple Silicon Macs, x86-64 Linux and 64-bit Windows get a gem whose two
|
|
21
|
+
extensions are already built, with SDL2 linked into them. Every other machine,
|
|
22
|
+
and every Ruby but 4.0, installs the source gem and compiles it as before.
|
|
23
|
+
- **A virtual gamepad for tests.** `RGame::Core::VirtualGamepad.new` plugs a
|
|
24
|
+
synthetic controller into a running `App`, which seats it and reads it like a
|
|
25
|
+
real pad; `set_button`, `set_axis` and `detach` drive it. See
|
|
26
|
+
[docs/api/input.md](docs/api/input.md#rgamecorevirtualgamepad).
|
|
27
|
+
|
|
28
|
+
## [0.3.1] - 2026-09-15
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- `require "rgame"` failed under Bundler with `cannot load such file --
|
|
33
|
+
rexml/document`. The gem now declares `rexml` as a dependency, which Ruby 4.0
|
|
34
|
+
no longer loads without one.
|
|
35
|
+
|
|
36
|
+
## [0.3.0] - 2026-09-15
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`rgame new NAME`.** The gem installs an `rgame` command, and `rgame new`
|
|
41
|
+
generates a project: a game class, a root node, an English translation table,
|
|
42
|
+
and a spec suite and RuboCop config that pass. Only `game.rb` loads SDL, so the
|
|
43
|
+
generated specs run without a display. See [docs/api/cli.md](docs/api/cli.md).
|
|
44
|
+
- **RuboCop cops for games.** The gem ships rgame's `Game/` cops as a RuboCop
|
|
45
|
+
plugin, and `rgame new` enables them. They catch per-frame allocations, a node
|
|
46
|
+
drawn at its own position twice, literal text and Core named from headless
|
|
47
|
+
code. See [docs/api/cli.md](docs/api/cli.md#the-generated-rubocop-configuration).
|
|
48
|
+
- **Translation by default.** `RGame::Game` loads every `locales/**/*.yml` and
|
|
49
|
+
picks the player's language from `RGame::Core.preferred_locales`. A node draws
|
|
50
|
+
an `Engine::Text` built from a key, and a UI button's `label:` is a key. See
|
|
51
|
+
[docs/api/localization.md](docs/api/localization.md).
|
|
52
|
+
- **Fullscreen and scaling.** `Game.new` takes `fullscreen:` and `scale_mode:`,
|
|
53
|
+
and both can change while the game runs. `scale_mode:` maps the logical size
|
|
54
|
+
onto the window, and defaults to `:letterbox`. See
|
|
55
|
+
[docs/api/game.md](docs/api/game.md).
|
|
56
|
+
- **Saving.** `Util::SaveFile` reads and writes a game's state in the player's
|
|
57
|
+
save directory. `Components::Identity` gives a node a stable id to save it
|
|
58
|
+
under. See [docs/api/values.md](docs/api/values.md).
|
|
59
|
+
- **Box colliders.** `Components::BoxCollider` is a rectangle in the same
|
|
60
|
+
`CollisionWorld` as `CircleCollider`. `FeetCollider` derives its box from the
|
|
61
|
+
node's size, so a top-down character collides at its feet. See
|
|
62
|
+
[docs/api/components.md](docs/api/components.md).
|
|
63
|
+
- **Movers that something can stop.** `CharacterBody`, `Velocity` and
|
|
64
|
+
`PathFollow` share the base `Components::Mover`. Its `blocked_by:` lists what
|
|
65
|
+
stops a step: solid tiles, the world's edge or collider layers. `on_blocked` and
|
|
66
|
+
`on_unblocked` report the blocker and the axis. See
|
|
67
|
+
[docs/api/components.md](docs/api/components.md).
|
|
68
|
+
- **Pathfinding.** `Engine::NavGrid` finds routes over a tile grid, and
|
|
69
|
+
`TileWorld#nav_grid` hands one out. `Components::Navigator#go_to` walks a node
|
|
70
|
+
there. The search runs in C, in `Util::SolidGrid`, `Util::RouteSearch` and
|
|
71
|
+
`Util::TileSweep`. See [docs/api/toolbox.md](docs/api/toolbox.md).
|
|
72
|
+
- **`Components::World`** gives a scene its bounds without a tile map.
|
|
73
|
+
`ScreenWrap` and `DespawnOffscreen` read the bounds from it or from
|
|
74
|
+
`TileWorld`. See [docs/api/components.md](docs/api/components.md).
|
|
75
|
+
- **`Components::Hop`** lifts a character's picture off the ground through the
|
|
76
|
+
new `Node2D#elevation`. Colliders and cameras ignore the lift. See
|
|
77
|
+
[docs/api/components.md](docs/api/components.md).
|
|
78
|
+
- **New buttons and styles.** `UI::Button` is the base for a button with its
|
|
79
|
+
own look. `TextButton`, `PanelButton`, `OptionButton` and `IconButton` draw
|
|
80
|
+
their background from a `NineSliceStyle` or a `ShapeStyle`. A `hotkey:` presses
|
|
81
|
+
a button without focusing it. See [docs/api/ui.md](docs/api/ui.md).
|
|
82
|
+
- **Menu layouts and navigations.** `UI::Menu` takes a `layout:` (`Column`,
|
|
83
|
+
`Row` or `Ring`) and a `navigation:` (`Stepping`, `Pointing` or `nil`).
|
|
84
|
+
`UI::RadialMenu` focuses whatever the stick points at. `UI::PanelMenu` draws a
|
|
85
|
+
panel that grows with its buttons. See [docs/api/ui.md](docs/api/ui.md).
|
|
86
|
+
- **Menus open and close.** A menu answers `open?`, `open` and `close`, and
|
|
87
|
+
emits `on_opened` and `on_closed`. A closed menu draws nothing and takes no
|
|
88
|
+
input. `trigger:` names an action that holds a menu open while it is down. See
|
|
89
|
+
[docs/api/ui.md](docs/api/ui.md).
|
|
90
|
+
- **A UI atlas can name images.** A descriptor's `images` section cuts
|
|
91
|
+
rectangles from the sheet, and `Renderer#register_ui_atlas` registers each by
|
|
92
|
+
name. `UI::IconButton.new(image: :home)` then draws one.
|
|
93
|
+
- **Input prompts.** `InputMap#button_for(action, device)` returns the bound
|
|
94
|
+
button that device can press. `Controls.gamepad?` and `Controls.pad_button?`
|
|
95
|
+
tell the two kinds of id apart. See [docs/api/input.md](docs/api/input.md).
|
|
96
|
+
- **Sounds by path.** `audio.play_sound` and `play_music` accept a path and
|
|
97
|
+
load it through the asset manager on first use. `RGame::Game` subscribes an
|
|
98
|
+
`AudioDirector`, so `AudioBus` sounds play without setup. See
|
|
99
|
+
[docs/api/audio.md](docs/api/audio.md).
|
|
100
|
+
- **24 examples, one concept each.** Each is a single `main.rb` that runs on its
|
|
101
|
+
own, from `walk` and `sprite` to `pathfinding` and `localization`. See
|
|
102
|
+
[docs/api/examples.md](docs/api/examples.md).
|
|
103
|
+
- `AssetManager#glob` lists the files under the media root that match a pattern.
|
|
104
|
+
- `Util::Color` gains named colours, from `RED` to `DARK_GRAY`.
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
|
|
108
|
+
- **A subclass may not redefine `Node2D`'s or `Component`'s internal methods.**
|
|
109
|
+
Their private and protected machinery now starts with `_`. A subclass that
|
|
110
|
+
defines one of those names raises `NameError` where the class is defined.
|
|
111
|
+
Before, it silently switched that machinery off. See
|
|
112
|
+
[docs/api/scene_graph.md](docs/api/scene_graph.md).
|
|
113
|
+
- **Nodes draw in local space.** `Node2D#draw` pushes the node's transform
|
|
114
|
+
before it calls `on_draw`, so a node draws at its own origin:
|
|
115
|
+
`renderer.rect(0, 0, width, height)`. Drop `abs_x`/`abs_y` from draw calls, or
|
|
116
|
+
the position applies twice. See
|
|
117
|
+
[docs/api/scene_graph.md](docs/api/scene_graph.md).
|
|
118
|
+
- **`abs_x`, `abs_y` and `abs_angle` are now `world_x`, `world_y` and
|
|
119
|
+
`world_angle`.** A node computes them when read, so they are never a tick
|
|
120
|
+
stale. `world_x=` and `world_y=` place a node in world space. See
|
|
121
|
+
[docs/api/scene_graph.md](docs/api/scene_graph.md).
|
|
122
|
+
- **`UI::Menu` is handed its buttons.** `Menu#add(button)` replaces `add_item`,
|
|
123
|
+
`items` is now `buttons`, and `MenuItem` is now `UI::PanelButton`. The item
|
|
124
|
+
size moves to `layout: UI::Column.new(...)`. Confirm activates a button when
|
|
125
|
+
released. See [docs/api/ui.md](docs/api/ui.md).
|
|
126
|
+
- **`CharacterBody` takes its shape from a sibling collider.**
|
|
127
|
+
`CharacterBody.new(speed:, blocked_by:)` replaces `feet_width:` and
|
|
128
|
+
`feet_height:`. A body with no `blocked_by:` moves freely, and `TileWorld#move`
|
|
129
|
+
is gone. See [docs/api/components.md](docs/api/components.md).
|
|
130
|
+
- **Contacts are edges.** `on_hit` fires once when two colliders start to
|
|
131
|
+
overlap, and the new `on_separated` fires once when they part. Before,
|
|
132
|
+
`on_hit` fired on every step of an overlap.
|
|
133
|
+
- **`I18n` reads YAML tables keyed by locale.** A key falls back from `de-AT`
|
|
134
|
+
to `de` to the default. Plurals follow CLDR rules, and `I18n.missing` decides
|
|
135
|
+
what a missing key shows. `load_file` is gone. See
|
|
136
|
+
[docs/api/localization.md](docs/api/localization.md).
|
|
137
|
+
- **`ScreenWrap` and `DespawnOffscreen` work in world space**, so a node under an
|
|
138
|
+
offset parent wraps at the world's edge. Their `width:` and `height:` are now
|
|
139
|
+
optional. A node with two responses to the edge raises when the second
|
|
140
|
+
attaches.
|
|
141
|
+
- **A component raises when its sibling is missing.** `PlayerController`,
|
|
142
|
+
`WanderController` and `AnimatedSprite` name what they need when they attach.
|
|
143
|
+
A node with two movers raises too.
|
|
144
|
+
- **`AnimatedSprite` follows any mover**, and faces along the larger axis of
|
|
145
|
+
its heading.
|
|
146
|
+
- **Engine internals are private.** `Renderer`'s raw `draw_*`, `push_*`, `pop`
|
|
147
|
+
and `*_record` methods, `Recording#draw_at` and `Song#play_looping` are
|
|
148
|
+
private. Call `rect`, `translated`, `record`, `Recording#draw` and `Song#play`
|
|
149
|
+
instead. The Tiled parse helpers are private too.
|
|
150
|
+
|
|
151
|
+
### Removed
|
|
152
|
+
|
|
153
|
+
- `Engine::CachedLabel`. Use `Engine::Text`.
|
|
154
|
+
- `Engine::Body`, `Engine::Actor`, `Engine::Matrix`, `Engine::Resettable` and
|
|
155
|
+
`Engine::PlayerController`. None had a caller in the engine.
|
|
156
|
+
`Components::PlayerController` stays.
|
|
157
|
+
- `Engine::TileCollision`, which is now `Engine::TileBlockers`.
|
|
158
|
+
- The examples `14_asteroids`, `15_tiled_world` and `16_hello_world`. They are
|
|
159
|
+
whole games rather than examples of one concept, and no longer ship.
|
|
160
|
+
|
|
161
|
+
### Fixed
|
|
162
|
+
|
|
163
|
+
- The README's hello-world gave `on_draw` one parameter; it takes two
|
|
164
|
+
(`renderer, view`).
|
|
165
|
+
- A new `UI::Menu` drew its first item without a highlight until the first
|
|
166
|
+
press.
|
|
167
|
+
- `Path#distance_to` returned wrong distances for Integer coordinates.
|
|
168
|
+
|
|
169
|
+
## [0.2.0] - 2026-08-26
|
|
170
|
+
|
|
171
|
+
### Added
|
|
172
|
+
|
|
173
|
+
- **Split-screen.** A game has seats (`RGame::Game.new(players: 2)`), and a
|
|
174
|
+
`RGame::Engine::Player` owns a device, a binding table, a camera and a region
|
|
175
|
+
of the screen. The shared world is updated once and drawn once per viewport by
|
|
176
|
+
a `WorldView`. Which player a node answers to is inherited down the tree like
|
|
177
|
+
its transform, so `ship.input_owner = players[1]` moves a whole subtree.
|
|
178
|
+
New: `Player`, `Players`, `Viewports`, `View`, `WorldView`, `Layout`.
|
|
179
|
+
- **`RGame::Engine::InputMap`** — one binding table per player, written in terms
|
|
180
|
+
of physical ids from `RGame::Util::Controls`, so a game names keys and pad
|
|
181
|
+
buttons in one place and reads named actions everywhere else.
|
|
182
|
+
- **Draw bands** (`RGame::Util::Z`): `:world`, `:hud`, `:overlay` and `:debug`.
|
|
183
|
+
A band beats every `z` in the tree, so nothing in the world can draw over the
|
|
184
|
+
HUD. Inherited down the tree, and set by the nodes that exist to mark one.
|
|
185
|
+
- **Culling** (`RGame::Engine::Culling`, `view.visible?`), which stops being an
|
|
186
|
+
optimisation once the world is drawn once per player.
|
|
187
|
+
- **A bare-bones UI package**: `PlayerLayer` gives a player their own screen, and
|
|
188
|
+
`UI::Menu` / `UI::MenuItem` navigate it by focus and activation — enough for
|
|
189
|
+
keyboard and controller menus. Layout, nesting, scrolling and text entry are
|
|
190
|
+
not in it; see [docs/api/ui.md](docs/api/ui.md).
|
|
191
|
+
- **Pausing** (`Node2D#paused`), which stops `control` and `update` for a node
|
|
192
|
+
and its whole subtree while it keeps drawing — a frozen world under a cutscene
|
|
193
|
+
overlay that goes on animating.
|
|
194
|
+
- `Components::CameraFollow`, and `TileMapLayer` as a node of its own.
|
|
195
|
+
- **`tools/drive_example.rb`** — boots an example unmodified, feeds it a
|
|
196
|
+
scripted input backend and reports what the game actually asked for: draws,
|
|
197
|
+
clips, sounds, scenes, ticks against frames.
|
|
198
|
+
- **macOS and Windows support**, and CI that runs every verification tier on
|
|
199
|
+
all three platforms.
|
|
200
|
+
- This changelog, linked from the gem's RubyGems page through `changelog_uri`.
|
|
201
|
+
|
|
202
|
+
### Changed
|
|
203
|
+
|
|
204
|
+
- `Node2D#draw` and `on_draw` take the viewport being drawn into:
|
|
205
|
+
`on_draw(renderer, view)`. Most nodes ignore it; laying out against the edges
|
|
206
|
+
of a player's region, and culling, need it.
|
|
207
|
+
- `z` orders a node among its **siblings** only. It is never added to anything
|
|
208
|
+
and never reaches the renderer, so a node's whole subtree draws before or
|
|
209
|
+
after a sibling's, never interleaved with it. This replaces the additive
|
|
210
|
+
`abs_z = parent.abs_z + z`, under which a node at z 2 with a child at z 5
|
|
211
|
+
resolved to 7 and overtook a sibling at 4.
|
|
212
|
+
- `F2` quits and `F1` toggles the debug overlay. `Esc` is deliberately left to
|
|
213
|
+
the game, because it is the button a player expects to back out of a menu.
|
|
214
|
+
- Reading an action no `InputMap` declares raises `KeyError` instead of reading
|
|
215
|
+
as "never pressed" forever.
|
|
216
|
+
|
|
217
|
+
### Removed
|
|
218
|
+
|
|
219
|
+
- `RGame::Engine::CameraView`. A camera belongs to a `Player` and is applied by
|
|
220
|
+
the `View` being drawn.
|
|
221
|
+
|
|
222
|
+
## [0.1.0] - 2026-08-20
|
|
223
|
+
|
|
224
|
+
First release, and the first version that runs a game end to end.
|
|
225
|
+
|
|
226
|
+
### Added
|
|
227
|
+
|
|
228
|
+
- **The C engine**, as two Ruby extensions built from one source tree: an SDL2
|
|
229
|
+
window and a fixed-timestep main loop, keyboard and gamepad input with
|
|
230
|
+
hot-plug, a z-sorted batching renderer with transforms, clipping and baked
|
|
231
|
+
recordings, text from a shipped TrueType font, and audio (samples and
|
|
232
|
+
streamed Ogg Vorbis or WAV).
|
|
233
|
+
- **`RGame::Core`** — the half that owns the window, the GPU and the sound
|
|
234
|
+
device: `App`, `Input`, `Gamepad`, `Image`, `Renderer`, `Recording`, `Font`,
|
|
235
|
+
`Audio`, plus the asset layer (`AssetManager`, `SpriteSheet`, `NineSlice`,
|
|
236
|
+
`UIAtlas`, `TileMapRenderer`).
|
|
237
|
+
- **`RGame::Util`** — the graphics-free half, so values can be required with no
|
|
238
|
+
SDL and no OpenGL in the process: `Tensor`, `Color`, `Controls`.
|
|
239
|
+
- **`RGame::Engine`** — the scene graph a game is written in: nodes,
|
|
240
|
+
components, signals, sprites, tile maps, collision and pathfinding. Pure
|
|
241
|
+
Ruby, and unable to name `RGame::Core` at all, which is what lets game logic
|
|
242
|
+
and its specs run with no display.
|
|
243
|
+
- **`RGame::Game`** — the entry point that wires the two halves together.
|
|
244
|
+
- Examples: `14_asteroids`, `15_tiled_world`, `16_hello_world`.
|
|
245
|
+
|
|
246
|
+
[Unreleased]: https://github.com/psuessenb/rgame/compare/v0.4.0...HEAD
|
|
247
|
+
[0.4.0]: https://github.com/psuessenb/rgame/compare/v0.3.1...v0.4.0
|
|
248
|
+
[0.3.1]: https://github.com/psuessenb/rgame/compare/v0.3.0...v0.3.1
|
|
249
|
+
[0.3.0]: https://github.com/psuessenb/rgame/compare/v0.2.0...v0.3.0
|
|
250
|
+
[0.2.0]: https://github.com/psuessenb/rgame/compare/v0.1.0...v0.2.0
|
|
251
|
+
[0.1.0]: https://github.com/psuessenb/rgame/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Paul Süßenbach
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
This project vendors third-party source under ext/rgame_core/vendor/, which
|
|
26
|
+
carries its own licence terms. See ext/rgame_core/vendor/README.md.
|
data/README.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# RGame
|
|
2
|
+
|
|
3
|
+
RGame is a small 2D game engine for Ruby, written in Ruby and C. It's build on top of SDL2, OpenGL and miniaudio. It's built with testability and performance in mind, and aims to be an engine where you can write your whole game code in Ruby, test it as usual with RSpec (or Minitest, or another test framework) and still have acceptable performance.
|
|
4
|
+
|
|
5
|
+
While still a work in progress, RGame aims to be more than a SDL/OpenGL
|
|
6
|
+
binding - it ships with high level features like a scene graph, sprites,
|
|
7
|
+
collision systems, debugging tools and an UI toolkit. You can check out the examples to get a feel for its capabilities.
|
|
8
|
+
|
|
9
|
+
## Why does this exist and should you use it?
|
|
10
|
+
|
|
11
|
+
RGame is the product of both my lazyness and me looking for something that did not exist. Coming from Ruby on Rails, I wanted to write games while not learning a new language. There are Ruby game engines, but none offered me the option to write modern, standard Ruby and proper specs for the game logic. The closest I found was Gosu, and this project initially started as a "high level engine on top of Gosu", but eventually the limitations of Gosu drove me into rewriting this layer myself.
|
|
12
|
+
|
|
13
|
+
RGame puts a lot of emphazis on testing and being testable: It separates the layers that talk to SDL2/OpenGL from the high level engine concepts, so the whole game logic is testable headless.
|
|
14
|
+
|
|
15
|
+
It also tries to marry the beauty of Ruby with the hard performance requirements of games: Hot paths have no per-frame allocation, because garbadge collection is what really slows down Ruby interpreation, and math-heavy use-cases are backed by C code instead of Ruby classes.
|
|
16
|
+
|
|
17
|
+
Should you use it, though? If you're looking for something mature, free, and more battle-tested take a look at Godot instead. If you're looking for something mature and battle-tested _in Ruby land_, take a look at dragonruby instead (it's not free, but it's probably worth the price).
|
|
18
|
+
|
|
19
|
+
If you're just starting with game development and planning on making the next big indie hit, might as well pick this one as the engine for the game you never finish!
|
|
20
|
+
|
|
21
|
+
In all seriousness, though: This is a hobby project of mine, and while it might develop into something actually useful, at the time of writing it's a playground. If you search for something I searched and found nothing - try RGame! If you want to learn how to write games or just need a small prototype, and you really like Ruby - try RGame! If all you know is Rails, but you want to make a game that doesn't run in a browser and don't care about shipping it - try RGame!
|
|
22
|
+
|
|
23
|
+
I would be really happy if someone else actually uses it, but at this point I can't really recommend it for anything else than small projects and/or learning the ropes of game development.
|
|
24
|
+
|
|
25
|
+
## Getting started
|
|
26
|
+
|
|
27
|
+
Installing the gem puts an `rgame` command on your PATH, which you can use to setup a project:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
gem install rgame
|
|
31
|
+
rgame new tictactoe
|
|
32
|
+
|
|
33
|
+
cd tictactoe
|
|
34
|
+
bundle install
|
|
35
|
+
bundle exec rspec # the game logic, headless — no window needed
|
|
36
|
+
ruby main.rb # the game itself
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
It's just a small skeleton — a game class, a root node, a spec and the usual configuration — but it is laid out the way the engine wants to be used: one file loads SDL, everything else stays graphics-free and therefore testable with no display. [The `rgame` command](docs/api/cli.md) explains the layout and the reasoning. This is also sets you up with a Rubocop configuration already geared towards RGame and game development in general.
|
|
40
|
+
|
|
41
|
+
**On a common desktop that first line needs nothing else** — no compiler, no SDL2, no header files. `gem install rgame` fetches a gem whose two C extensions are already built, with SDL2 linked into them:
|
|
42
|
+
|
|
43
|
+
- macOS 11 or later on Apple Silicon
|
|
44
|
+
- x86-64 Linux with glibc 2.29 or later
|
|
45
|
+
- 64-bit Windows, on a RubyInstaller Ruby
|
|
46
|
+
|
|
47
|
+
All three need Ruby 4.0. Anywhere else — an Intel Mac, a Raspberry Pi, Ruby 4.1 — `gem install` falls back to the gem that ships the C and compiles it on your machine. That one needs [a compiler and SDL2](#building-from-source).
|
|
48
|
+
|
|
49
|
+
## Hello world
|
|
50
|
+
|
|
51
|
+
The simplest "game" you can write, all in one file:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require 'rgame/game'
|
|
55
|
+
|
|
56
|
+
class Scene < RGame::Engine::Node2D
|
|
57
|
+
def on_draw(renderer, _view)
|
|
58
|
+
renderer.text('Hello world!', 250, 200)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
game = RGame::Game.new(
|
|
63
|
+
root: Scene.new,
|
|
64
|
+
caption: 'Hello world!'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
game.start
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
You can learn more about how it works in the [documentation](docs/api/README.md).
|
|
71
|
+
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
`examples/` holds one small program per concept, each a single file you can run with `ruby examples/<name>/main.rb`. [The examples page](docs/api/examples.md) describes them in more detail. They're also all full of code comments so you have an easier time learning the concepts.
|
|
75
|
+
|
|
76
|
+
| Example | Shows |
|
|
77
|
+
|---|---|
|
|
78
|
+
| [walk](docs/api/examples.md#walk) | A player-controlled sprite — a node, three components, input as actions |
|
|
79
|
+
| [sprite](docs/api/examples.md#sprite) | One frame drawn at a node, with no animation behind it |
|
|
80
|
+
| [velocity](docs/api/examples.md#velocity) | Movement with nobody driving: a velocity, integrated |
|
|
81
|
+
| [scroll_map](docs/api/examples.md#scroll_map) | A Tiled map larger than the window, scrolled by a camera |
|
|
82
|
+
| [collision](docs/api/examples.md#collision) | Two shapes touching, and who gets told about it |
|
|
83
|
+
| [collision_tiles](docs/api/examples.md#collision_tiles) | Walking into a wall of solid tiles, and sliding along it |
|
|
84
|
+
| [jump_topdown](docs/api/examples.md#jump_topdown) | A hop in a top-down view, where the sprite rises and the feet stay on the ground |
|
|
85
|
+
| [signals](docs/api/examples.md#signals) | A node announcing something happened, to nobody in particular |
|
|
86
|
+
| [timer](docs/api/examples.md#timer) | Things that happen on a clock, with nothing pressed |
|
|
87
|
+
| [pooling](docs/api/examples.md#pooling) | Spawning a lot of things without allocating them |
|
|
88
|
+
| [game_menu](docs/api/examples.md#game_menu) | A menu over a world that keeps running |
|
|
89
|
+
| [menu_navigation](docs/api/examples.md#menu_navigation) | Several screens, and settings that persist |
|
|
90
|
+
| [radial_menu](docs/api/examples.md#radial_menu) | A wheel of icons, chosen by the direction of the stick |
|
|
91
|
+
| [quick_wheel](docs/api/examples.md#quick_wheel) | A wheel held open by a button and chosen by letting go |
|
|
92
|
+
| [skill_bar](docs/api/examples.md#skill_bar) | A row of tools, stepped through or fired by hotkeys |
|
|
93
|
+
| [sound](docs/api/examples.md#sound) | A sound effect fired by a button, and the seam it travels |
|
|
94
|
+
| [music](docs/api/examples.md#music) | A looping track, started and stopped |
|
|
95
|
+
| [split_screen](docs/api/examples.md#split_screen) | Two players in one world, drawn once per viewport |
|
|
96
|
+
| [input_glyphs](docs/api/examples.md#input_glyphs) | Prompts that match the device in your hands |
|
|
97
|
+
| [fullscreen](docs/api/examples.md#fullscreen) | Fullscreen, switched at any time, and the scale modes |
|
|
98
|
+
| [save_load](docs/api/examples.md#save_load) | Writing game state to disk and putting it back |
|
|
99
|
+
| [save_load_ids](docs/api/examples.md#save_load_ids) | A save that has to name things, and why a reference forces ids |
|
|
100
|
+
| [localization](docs/api/examples.md#localization) | The same screen in two languages, switched and remembered |
|
|
101
|
+
| [pathfinding](docs/api/examples.md#pathfinding) | Setting a target for an actor and let if find its way there |
|
|
102
|
+
|
|
103
|
+
## Building from source
|
|
104
|
+
|
|
105
|
+
**This section is for two readers: anyone on a platform the binary gems miss, and anyone working on RGame itself.** Installing on one of the three platforms above needs none of it.
|
|
106
|
+
|
|
107
|
+
Compiling the C engine needs a compiler and the system libraries it links against. A source install of the gem needs the compiler, `pkg-config`, SDL2 and OpenGL from the list below. The rest is for running rgame's own suites.
|
|
108
|
+
|
|
109
|
+
### C engine
|
|
110
|
+
|
|
111
|
+
- A C compiler — `gcc` or `clang`
|
|
112
|
+
- `make`
|
|
113
|
+
- `pkg-config`
|
|
114
|
+
- SDL2 development headers (`sdl2` pkg-config package)
|
|
115
|
+
- OpenGL development headers/libs (provided by Mesa on Linux)
|
|
116
|
+
- [Check](https://libcheck.github.io/check/) (`check` pkg-config package) — C unit test framework, only needed for `make test`
|
|
117
|
+
|
|
118
|
+
PNG decoding, text and audio need no system libraries: `stb_image.h`,
|
|
119
|
+
`stb_truetype.h`, `stb_vorbis.c` and `miniaudio.h` are vendored in
|
|
120
|
+
`ext/rgame_core/vendor/` (public domain / MIT), and the default font ships in
|
|
121
|
+
`lib/rgame/fonts/` (SIL OFL 1.1). miniaudio finds ALSA or PulseAudio at runtime,
|
|
122
|
+
so there is nothing to install for sound either. See the README in
|
|
123
|
+
`ext/rgame_core/vendor/` for all of it.
|
|
124
|
+
|
|
125
|
+
`tools/` holds development tools that are not part of the engine and are not
|
|
126
|
+
built by `make` — currently one, which generates the audio suite's `.ogg`
|
|
127
|
+
fixture and needs `libvorbisenc` to run.
|
|
128
|
+
|
|
129
|
+
### Ruby side
|
|
130
|
+
|
|
131
|
+
- **Ruby 4.0.5**, pinned in `.ruby-version`.
|
|
132
|
+
- **Ruby development headers.** Version-manager builds (mise, rbenv, rvm, asdf) include them. On a distro-packaged Ruby, install `ruby-dev`
|
|
133
|
+
(Debian/Ubuntu). These are what `extconf.rb` compiles against.
|
|
134
|
+
- **Bundler**, then `bundle install` for the dev/test gems (RSpec, RuboCop).
|
|
135
|
+
|
|
136
|
+
Nothing else. The engine's one runtime gem is `rexml`, which Bundler installs with it, and the `Gemfile` holds only development gems.
|
|
137
|
+
|
|
138
|
+
All three platforms below are built and tested on every push by
|
|
139
|
+
[CI](.github/workflows/ci.yml) - with that people have usually at home, so Apple Silicon and not Apple Intel, etc.
|
|
140
|
+
|
|
141
|
+
### Debian / Ubuntu
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
sudo apt install build-essential pkg-config libsdl2-dev libgl1-mesa-dev check
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`rake spec:core` opens real windows and starts its own Xvfb, which needs a few
|
|
148
|
+
more packages — the display itself, the `xwininfo` it polls to know the display
|
|
149
|
+
is up, a software rasteriser (Xvfb has no GPU, and `SDL_GL_CreateContext` fails
|
|
150
|
+
without one) and the XTEST runtime for synthetic keystrokes:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
sudo apt install xvfb x11-utils libgl1-mesa-dri libxtst6
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### macOS (Homebrew)
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
brew install sdl2 pkg-config check
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
OpenGL ships with the Xcode Command Line Tools (`xcode-select --install`) — the
|
|
163
|
+
full Xcode is not needed, and neither is anything else: `rake spec:core` uses
|
|
164
|
+
the native window server, so there is no Xvfb equivalent to set up. Note
|
|
165
|
+
Homebrew's `sdl2` formula now installs **sdl2-compat**, which is SDL2's API
|
|
166
|
+
implemented on top of SDL3; the engine works through it unchanged.
|
|
167
|
+
|
|
168
|
+
### Windows
|
|
169
|
+
|
|
170
|
+
Use a **RubyInstaller-built** Ruby (mise, vfox and rbenv-style managers all
|
|
171
|
+
fetch those), which is what supplies `ridk`. The combined DevKit installer is
|
|
172
|
+
not required — a standalone MSYS2 that `ridk` can find works just as well, and
|
|
173
|
+
`C:\msys64` is one of the places it looks:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
winget install --id MSYS2.MSYS2 -e
|
|
177
|
+
ridk exec pacman -Syu --noconfirm # core update; may need a second pass
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Then the libraries. Note `make` is an **msys** package with no prefix while
|
|
181
|
+
everything else is **ucrt64**-prefixed — that split is the whole Windows story:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
ridk exec pacman -S --needed \
|
|
185
|
+
mingw-w64-ucrt-x86_64-SDL2 \
|
|
186
|
+
mingw-w64-ucrt-x86_64-check \
|
|
187
|
+
mingw-w64-ucrt-x86_64-pkgconf \
|
|
188
|
+
mingw-w64-ucrt-x86_64-gcc \
|
|
189
|
+
make
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**MSYS2 is several environments in one install, and picking the wrong one fails
|
|
193
|
+
in a way that looks like missing packages.** UCRT64 builds native Windows
|
|
194
|
+
binaries, which is what RubyInstaller's Ruby can load; the plain `msys`
|
|
195
|
+
environment builds against a Cygwin-like runtime, which it cannot. Work from
|
|
196
|
+
the "MSYS2 UCRT64" shell or run `ridk enable ucrt64` first, and verify before
|
|
197
|
+
trusting anything:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
which gcc # must be /ucrt64/bin/gcc, NOT /usr/bin/gcc
|
|
201
|
+
pkg-config --cflags sdl2 # must print a ucrt64 include path
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
One more Windows fact worth knowing up front: **Check has no usable fork
|
|
205
|
+
there**, so the first segfault kills the whole test binary and the output stops
|
|
206
|
+
mid-suite. Use a debugger rather than reading the log — `CK_FORK=no gdb --args
|
|
207
|
+
./build/test_rgame`, and `CK_RUN_SUITE=<name>` to run one suite.
|
|
208
|
+
|
|
209
|
+
## Build & run
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
make # builds build/rgame (standalone C binary)
|
|
213
|
+
make run # build and run it
|
|
214
|
+
make test # build and run the Check unit tests (C, pure logic)
|
|
215
|
+
make ext # build both Ruby extensions
|
|
216
|
+
make ext-core # build only ext/rgame_core -> lib/rgame/core_ext.so
|
|
217
|
+
make ext-util # build only ext/rgame_util -> lib/rgame/util_ext.so
|
|
218
|
+
make clean # remove build artifacts, including both extensions'
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`make run` opens a window with one of each drawing primitive in it — a rotating
|
|
222
|
+
square, a clipped rectangle, a circle, a thick line, a baked strip replayed
|
|
223
|
+
every frame, and a line of accented text. `Esc` or closing the window quits.
|
|
224
|
+
`ruby ext/rgame_core/example.rb` is the same scene driven from Ruby, and takes
|
|
225
|
+
an optional sound file — `ruby ext/rgame_core/example.rb theme.ogg` binds Space
|
|
226
|
+
to play it as a sample and Return to start and stop it as looping music. That is
|
|
227
|
+
the only place a real sound device is driven; everything automated runs against
|
|
228
|
+
a null or offline one.
|
|
229
|
+
|
|
230
|
+
The Ruby specs:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
bundle install
|
|
234
|
+
make ext # both extensions; the suites need the compiled .so files
|
|
235
|
+
rake spec # headless specs: RGame::Util, the engine layer, packaging
|
|
236
|
+
rake spec:core # RGame::Core specs; opens real windows, boots its own Xvfb
|
|
237
|
+
rake # everything: make test, rake spec, rake spec:core
|
|
238
|
+
bundle exec rubocop # lint; configured in .rubocop.yml, which also loads the
|
|
239
|
+
# project's own cops from lib/rgame/rubocop/.
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The two Ruby suites are two directories and two processes on purpose. `spec/`
|
|
243
|
+
must never load SDL — the engine layer's whole value is that it can be
|
|
244
|
+
specified with no window — and RSpec loads one root into one process, so a
|
|
245
|
+
single `require "rgame/core"` anywhere would define `RGame::Core` for every
|
|
246
|
+
other example in the run. Separate runners cannot be forgotten the way an
|
|
247
|
+
exclude rule can. `rake spec` needs `make ext-util`; `rake spec:core` needs
|
|
248
|
+
`make ext-core`.
|
|
249
|
+
|
|
250
|
+
Each `make ext-*` target compiles its extension and copies the resulting `.so`
|
|
251
|
+
into `lib/rgame/`, which is where `require "rgame/util_ext"` and `require
|
|
252
|
+
"rgame/core_ext"` look for it. That mirrors how rake-compiler installs a
|
|
253
|
+
compiled extension into `lib/<gem>/`. Without that step the specs can't even
|
|
254
|
+
load, since `RGame::Util::Tensor` now lives in C.
|
|
255
|
+
|
|
256
|
+
`rake spec` needs only `ext-util` — it never touches `RGame::Core`, which is
|
|
257
|
+
what keeps it runnable with no display and no SDL. To drive the *engine* from
|
|
258
|
+
Ruby by hand (opens a real window):
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
make ext-core
|
|
262
|
+
ruby ext/rgame_core/example.rb
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Packaging
|
|
266
|
+
|
|
267
|
+
Both extensions and the Ruby layer ship as one gem, built from `rgame.gemspec` and published at [rubygems.org/gems/rgame](https://rubygems.org/gems/rgame):
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
gem install rgame # from RubyGems; compiles nothing on a covered platform
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Or from a checkout, which builds the source gem locally:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
rake build # package into pkg/rgame-<version>.gem
|
|
277
|
+
gem install pkg/rgame-*.gem # compiles both extensions on this machine
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**RubyGems holds two kinds of gem for each version.** The source gem carries the
|
|
281
|
+
C and compiles on install. Three platform gems carry `core_ext` and `util_ext`
|
|
282
|
+
already built, with SDL2 linked statically into `core_ext`; they ship no `.c`,
|
|
283
|
+
no `extconf.rb` and declare no extensions, so they compile nothing. `gem
|
|
284
|
+
install` picks by platform and Ruby version on its own.
|
|
285
|
+
|
|
286
|
+
CI builds and publishes all four. `tools/platform_gem.rake` builds a platform
|
|
287
|
+
gem, `tools/check_platform_gem.rb` checks it against nine rules before it may be
|
|
288
|
+
published, and the `smoke` job installs it on a runner with no SDL2 and plays
|
|
289
|
+
the examples out of it.
|
|
290
|
+
|
|
291
|
+
Installing the source gem runs each `extconf.rb` and installs the resulting
|
|
292
|
+
`.so` into the gem's own `lib/rgame/`, which is the same layout `make ext`
|
|
293
|
+
produces in a checkout — so `require "rgame"` and `require "rgame/core"` behave
|
|
294
|
+
identically whichever gem you got, including the guarantee that the first of
|
|
295
|
+
those loads no graphics libraries. Its system dependencies are the ones the C
|
|
296
|
+
engine needs (SDL2, OpenGL, pkg-config, a compiler); `extconf.rb` aborts with
|
|
297
|
+
the package to install if one is missing, rather than failing later at the link
|
|
298
|
+
step.
|
|
299
|
+
|
|
300
|
+
What ships is a glob over `lib/`, `ext/` and `docs/api/`, not a hand-written
|
|
301
|
+
list: a new C source or a runtime asset dropped into either tree is packaged
|
|
302
|
+
without being registered anywhere. `spec/packaging_spec.rb` holds that up from
|
|
303
|
+
the other side — it asserts every source, header and data file is in the gem and
|
|
304
|
+
that no build artifact, spec directory or plan is, so the parts of this that
|
|
305
|
+
would otherwise be a checklist fail the suite instead.
|
|
306
|
+
|
|
307
|
+
The version is `RGame::VERSION` in [lib/rgame/version.rb](lib/rgame/version.rb).
|
|
308
|
+
|
|
309
|
+
## Project structure
|
|
310
|
+
|
|
311
|
+
The C lives under `ext/rgame_core/` rather than a top-level `src/`, because`gem install` runs each `extconf.rb` and an extension can only build sources inside its own directory — so one copy of the code serves both the standalone binary and the gem. The Ruby half is split the same way it is namespaced:
|
|
312
|
+
`lib/rgame/util/`, `lib/rgame/core/` and `lib/rgame/engine/`.
|
|
313
|
+
|
|
314
|
+
A file-by-file map of the whole repository is in
|
|
315
|
+
[docs/project_structure.md](docs/project_structure.md).
|
|
316
|
+
|
|
317
|
+
## Roadmap
|
|
318
|
+
|
|
319
|
+
There is currently no roadmap for the next version.
|
|
320
|
+
|
|
321
|
+
## AI clause
|
|
322
|
+
|
|
323
|
+
This project is not vibe-coded, but AI tools were used heavily while
|
|
324
|
+
writing code. If you dislike AI generated code, this project is not for
|
|
325
|
+
you.
|