rgame 0.2.0 → 0.3.0
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 +135 -1
- data/README.md +67 -65
- data/docs/api/README.md +86 -82
- data/docs/api/app.md +125 -68
- data/docs/api/assets.md +203 -165
- data/docs/api/audio.md +130 -89
- data/docs/api/cli.md +259 -0
- data/docs/api/components.md +1032 -265
- data/docs/api/drawing.md +160 -160
- data/docs/api/examples.md +263 -0
- data/docs/api/game.md +149 -50
- data/docs/api/images.md +39 -39
- data/docs/api/input.md +226 -148
- data/docs/api/internals.md +241 -62
- data/docs/api/localization.md +285 -0
- data/docs/api/scene_graph.md +397 -244
- data/docs/api/signals.md +81 -76
- data/docs/api/systems.md +186 -73
- data/docs/api/text.md +58 -41
- data/docs/api/tile_maps.md +237 -0
- data/docs/api/toolbox.md +395 -146
- data/docs/api/ui.md +883 -54
- data/docs/api/values.md +216 -33
- 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/ext/rgame_core/app/app.c +33 -3
- data/ext/rgame_core/app/locale.c +67 -0
- data/ext/rgame_core/app/locale.h +28 -0
- data/ext/rgame_core/audio/audio.c +39 -2
- data/ext/rgame_core/example.rb +0 -49
- data/ext/rgame_core/extconf.rb +0 -125
- data/ext/rgame_core/include/rgame/core.h +38 -2
- data/ext/rgame_core/ruby/audio_ext.c +10 -5
- data/ext/rgame_core/ruby/core_ext.c +30 -7
- data/ext/rgame_core/ruby/core_ext.h +3 -0
- data/ext/rgame_core/ruby/locale_ext.c +44 -0
- data/ext/rgame_core/ruby/recording_ext.c +1 -1
- data/ext/rgame_core/ruby/renderer_ext.c +20 -20
- data/ext/rgame_util/extconf.rb +2 -20
- data/ext/rgame_util/route_search.c +305 -0
- data/ext/rgame_util/route_search.h +86 -0
- data/ext/rgame_util/route_search_ext.c +150 -0
- data/ext/rgame_util/solid_grid.c +58 -0
- data/ext/rgame_util/solid_grid.h +49 -0
- data/ext/rgame_util/solid_grid_ext.c +161 -0
- data/ext/rgame_util/tile_sweep.c +164 -0
- data/ext/rgame_util/tile_sweep.h +62 -0
- data/ext/rgame_util/tile_sweep_ext.c +155 -0
- data/ext/rgame_util/util_ext.c +3 -0
- data/ext/rgame_util/util_ext.h +15 -0
- data/lib/rgame/boot.rb +0 -10
- 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 +6 -44
- data/lib/rgame/core/asset_manager.rb +13 -31
- data/lib/rgame/core/audio.rb +37 -16
- data/lib/rgame/core/font.rb +0 -3
- data/lib/rgame/core/locale.rb +22 -0
- data/lib/rgame/core/nine_slice.rb +0 -21
- data/lib/rgame/core/renderer.rb +6 -63
- data/lib/rgame/core/sprite_sheet.rb +0 -3
- data/lib/rgame/core/tile_map_renderer.rb +2 -19
- data/lib/rgame/core/ui_atlas.rb +28 -13
- data/lib/rgame/core.rb +1 -8
- data/lib/rgame/engine/actor_blockers.rb +131 -0
- data/lib/rgame/engine/animation_set.rb +1 -0
- data/lib/rgame/engine/audio_director.rb +36 -6
- data/lib/rgame/engine/bounds_blockers.rb +74 -0
- data/lib/rgame/engine/camera.rb +3 -3
- data/lib/rgame/engine/circle_collider.rb +4 -2
- data/lib/rgame/engine/collision_box.rb +26 -1
- data/lib/rgame/engine/collision_system.rb +110 -22
- data/lib/rgame/engine/component.rb +34 -10
- data/lib/rgame/engine/components/action_trigger.rb +0 -1
- data/lib/rgame/engine/components/animated_sprite.rb +27 -25
- data/lib/rgame/engine/components/box_collider.rb +99 -0
- data/lib/rgame/engine/components/camera_follow.rb +6 -5
- data/lib/rgame/engine/components/character_body.rb +20 -61
- data/lib/rgame/engine/components/circle_collider.rb +47 -11
- data/lib/rgame/engine/components/collision_world.rb +159 -31
- data/lib/rgame/engine/components/despawn_offscreen.rb +24 -8
- 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 +123 -31
- data/lib/rgame/engine/components/player_controller.rb +5 -2
- data/lib/rgame/engine/components/pool.rb +1 -1
- data/lib/rgame/engine/components/screen_wrap.rb +33 -11
- data/lib/rgame/engine/components/sprite.rb +12 -6
- data/lib/rgame/engine/components/targeting.rb +9 -11
- data/lib/rgame/engine/components/thrust_controller.rb +1 -1
- data/lib/rgame/engine/components/tile_world.rb +41 -13
- data/lib/rgame/engine/components/timer.rb +1 -1
- data/lib/rgame/engine/components/velocity.rb +23 -7
- data/lib/rgame/engine/components/wander_controller.rb +6 -2
- data/lib/rgame/engine/components/world.rb +133 -0
- data/lib/rgame/engine/contact_set.rb +74 -0
- data/lib/rgame/engine/culling.rb +4 -6
- data/lib/rgame/engine/debug_overlay.rb +5 -11
- 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 +276 -51
- data/lib/rgame/engine/input/action_mapper.rb +0 -26
- data/lib/rgame/engine/input/actions.rb +2 -8
- data/lib/rgame/engine/input/input_map.rb +39 -21
- data/lib/rgame/engine/nav_grid.rb +87 -0
- data/lib/rgame/engine/node2d.rb +248 -133
- data/lib/rgame/engine/path.rb +4 -6
- data/lib/rgame/engine/players.rb +6 -13
- data/lib/rgame/engine/presentation.rb +171 -0
- data/lib/rgame/engine/scene/scene_stack.rb +4 -4
- data/lib/rgame/engine/sealed_privates.rb +54 -0
- data/lib/rgame/engine/spatial_hash.rb +51 -19
- data/lib/rgame/engine/text.rb +194 -0
- data/lib/rgame/engine/tile_blockers.rb +63 -0
- data/lib/rgame/engine/tile_map.rb +2 -3
- data/lib/rgame/engine/tile_map_layer.rb +0 -2
- data/lib/rgame/engine/tileset.rb +2 -4
- data/lib/rgame/engine/timer.rb +2 -2
- 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 +246 -71
- 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/viewports.rb +2 -5
- data/lib/rgame/engine/world_view.rb +5 -4
- data/lib/rgame/engine.rb +31 -25
- data/lib/rgame/game.rb +99 -27
- 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 +20 -24
- data/lib/rgame/util/controls.rb +14 -28
- 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 +0 -9
- data/lib/rgame/util/tile_sweep.rb +36 -0
- data/lib/rgame/util/z.rb +4 -14
- data/lib/rgame/util.rb +4 -3
- data/lib/rgame/version.rb +1 -1
- data/lib/rgame.rb +0 -15
- metadata +152 -11
- data/lib/rgame/engine/actor.rb +0 -53
- data/lib/rgame/engine/body.rb +0 -49
- data/lib/rgame/engine/cached_label.rb +0 -33
- data/lib/rgame/engine/input/player_controller.rb +0 -14
- data/lib/rgame/engine/matrix.rb +0 -32
- data/lib/rgame/engine/resettable.rb +0 -67
- data/lib/rgame/engine/tile_collision.rb +0 -78
- data/lib/rgame/engine/ui/menu_item.rb +0 -84
data/docs/api/drawing.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Drawing
|
|
2
2
|
|
|
3
|
-
`RGame::Core::Renderer
|
|
4
|
-
|
|
3
|
+
Everything on screen goes through `RGame::Core::Renderer`. **In an
|
|
4
|
+
`RGame::Game`, you never build one.** `Game` builds it and passes it to every
|
|
5
|
+
node's `on_draw(renderer, view)`. The examples on this page build one on a plain
|
|
6
|
+
`App` instead, to show the calls without a scene graph:
|
|
5
7
|
|
|
6
8
|
```ruby
|
|
7
9
|
require 'rgame'
|
|
@@ -26,15 +28,16 @@ end
|
|
|
26
28
|
MyGame.new.run
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
Know two rules before anything else.
|
|
30
32
|
|
|
31
|
-
**
|
|
32
|
-
from a constructor raises. The frame is not open
|
|
33
|
-
|
|
33
|
+
**Draw only inside `draw`**, or a node's `on_draw`. A drawing call from `update`
|
|
34
|
+
or from a constructor raises. The frame is not open then, so the call would vanish without a trace.
|
|
35
|
+
A loud failure beats an invisible one. `renderer.drawing?` returns whether a frame
|
|
36
|
+
is open.
|
|
34
37
|
|
|
35
|
-
**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
**The renderer draws nothing immediately.** It collects calls, sorts the frame
|
|
39
|
+
and sends it to the GPU once, after `draw` returns. So call order does not decide
|
|
40
|
+
what ends up on top; the scene tree does. See "Draw order" below.
|
|
38
41
|
|
|
39
42
|
## Coordinates, colours and z
|
|
40
43
|
|
|
@@ -45,37 +48,36 @@ not decide what ends up on top — the scene tree does. See "Draw order" below.
|
|
|
45
48
|
| `z:` | Where this call sits among **this node's own** drawing. −512…511. |
|
|
46
49
|
| `color:` | `nil` (white), `[r, g, b]`, `[r, g, b, a]`, or a `RGame::Util::Color`. |
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
node's panel under its label and its shadow under its sprite
|
|
50
|
-
|
|
51
|
+
**`z:` is an offset inside the current layer**, not a global number. It puts a
|
|
52
|
+
node's panel under its label and its shadow under its sprite. It cannot reach
|
|
53
|
+
anything else. A value outside −512…511 raises.
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
z
|
|
55
|
-
|
|
56
|
-
as flicker.
|
|
55
|
+
Shapes default to `50`, text to `10` and images to `0`. A debug box or a health
|
|
56
|
+
bar without a `z:` therefore lands on top of *that node's* sprite. Calls with
|
|
57
|
+
equal z keep their call order. Without that rule, two sprites on one layer could
|
|
58
|
+
swap places from frame to frame, and players would see flicker.
|
|
57
59
|
|
|
58
60
|
## Draw order
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
**The renderer orders a frame in three steps, coarsest first.** A drawing call
|
|
63
|
+
passes a number only for the last step.
|
|
62
64
|
|
|
63
|
-
1. **The band
|
|
64
|
-
Everything in one band
|
|
65
|
-
2. **The slot.** The scene tree
|
|
66
|
-
order
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
3. **The offset
|
|
65
|
+
1. **The band**: `:world` (the default), `:hud`, `:overlay` or `:debug`.
|
|
66
|
+
Everything in one band lies under everything in the next.
|
|
67
|
+
2. **The slot.** The traversal walks the scene tree depth-first, siblings in `z`
|
|
68
|
+
order. Each node takes the next slot in its band when the walk reaches it.
|
|
69
|
+
Draw order is therefore **tree order**. A node's subtree forms one unbroken
|
|
70
|
+
run and cannot straddle a sibling.
|
|
71
|
+
3. **The offset**: the `z:` above, inside one node's slot.
|
|
70
72
|
|
|
71
|
-
A scene graph arranges all of this
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
A scene graph arranges all of this. `RGame::Engine::Node2D#draw` opens a layer per
|
|
74
|
+
node, so a game sets `z` on nodes and a `band` on the few that start one. See
|
|
75
|
+
[scene_graph.md](scene_graph.md), "Draw order".
|
|
74
76
|
|
|
75
77
|
### Opening a layer by hand
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
Code that draws outside the scene tree opens its own layer. The debug overlay, a
|
|
80
|
+
spec and a script all do:
|
|
79
81
|
|
|
80
82
|
```ruby
|
|
81
83
|
renderer.layered(:hud) do
|
|
@@ -84,41 +86,39 @@ renderer.layered(:hud) do
|
|
|
84
86
|
end
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
`layered` takes the next slot in that band
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
passes.
|
|
89
|
+
`layered` takes the next slot in that band and measures every `z:` inside the
|
|
90
|
+
block from it. Afterwards it restores the previous base, even when the block
|
|
91
|
+
raises. Nesting *replaces* the base; it does not add to it. A node's slot depends
|
|
92
|
+
on where the traversal reached it, not on its ancestors' picks. Outside any block
|
|
93
|
+
the base is 0, so a bare script gets exactly the z it passes.
|
|
93
94
|
|
|
94
|
-
`renderer.layer`
|
|
95
|
+
`renderer.layer` returns the base in effect.
|
|
95
96
|
|
|
96
|
-
### Why bands exist
|
|
97
|
+
### Why bands exist
|
|
97
98
|
|
|
98
99
|
A frame holds three kinds of content:
|
|
99
100
|
|
|
100
|
-
- **World
|
|
101
|
-
- **A player's own screen space
|
|
102
|
-
|
|
103
|
-
- **Global screen space
|
|
101
|
+
- **World**: inside a `WorldView`, drawn once per viewport, under a camera.
|
|
102
|
+
- **A player's own screen space**: their HUD and menu, drawn once and clipped to
|
|
103
|
+
their viewport (`PlayerLayer`).
|
|
104
|
+
- **Global screen space**: a cutscene or a results panel, drawn once across the
|
|
104
105
|
whole window.
|
|
105
106
|
|
|
106
|
-
The
|
|
107
|
-
`WorldView`
|
|
108
|
-
|
|
107
|
+
The world is a different *space* from the other two, and the tree enforces that:
|
|
108
|
+
`WorldView` draws its subtree once per viewport. The other two share one space,
|
|
109
|
+
and the band tells them apart.
|
|
109
110
|
|
|
110
|
-
Two viewports
|
|
111
|
-
different clips and land on different pixels. Order *within* one viewport
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
convention. See `RGame::Util::Z`.
|
|
111
|
+
Two viewports may interleave in the sort without harm. Their commands carry
|
|
112
|
+
different clips and land on different pixels. Order *within* one viewport
|
|
113
|
+
matters, and drawing a HUD after the world does not put it on top. Its band
|
|
114
|
+
does. Bands lie `2**40` apart and a `z:` spans 1024, so no offset can carry a
|
|
115
|
+
call into the next band. See `RGame::Util::Z`.
|
|
116
116
|
|
|
117
117
|
### Colours and allocation
|
|
118
118
|
|
|
119
|
-
Passing a `Color` allocates nothing
|
|
120
|
-
|
|
121
|
-
call
|
|
119
|
+
**Passing a `Color` allocates nothing.** A `Color` is a frozen value, so every
|
|
120
|
+
sprite can share the same one. Passing an array allocates a colour on every
|
|
121
|
+
call. That is fine at setup and wasteful sixty times a second:
|
|
122
122
|
|
|
123
123
|
```ruby
|
|
124
124
|
RED = RGame::Util::Color.new(224, 64, 64) # once
|
|
@@ -139,20 +139,17 @@ renderer.circle(cx, cy, radius, z: 50, color: nil, segments: 64)
|
|
|
139
139
|
renderer.debug_box(x, y, width, height, z: 50)
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
A **quad
|
|
143
|
-
bottom-right, bottom-left for a rectangle.
|
|
144
|
-
hourglass.
|
|
142
|
+
A **quad** takes its four points in loop order: top-left, top-right,
|
|
143
|
+
bottom-right, bottom-left for a rectangle. Points in Z order give an hourglass.
|
|
145
144
|
|
|
146
|
-
A **line** has real thickness because
|
|
147
|
-
|
|
148
|
-
worth seeing has to be a shape.
|
|
145
|
+
A **line** has real thickness, because the renderer draws it as a quad. Drivers
|
|
146
|
+
may ignore OpenGL's own line width above one pixel.
|
|
149
147
|
|
|
150
|
-
A **circle** is a fan of triangles
|
|
151
|
-
|
|
152
|
-
for the rare case where 64 is too many or too few.
|
|
148
|
+
A **circle** is a fan of triangles in one batch. It needs no cached texture and
|
|
149
|
+
no configuration. Adjust `segments:` if 64 is too many or too few.
|
|
153
150
|
|
|
154
|
-
`debug_box`
|
|
155
|
-
|
|
151
|
+
`debug_box` draws a translucent red rectangle to show a collision box. A scene
|
|
152
|
+
can ask for one without choosing a debug colour.
|
|
156
153
|
|
|
157
154
|
## Images
|
|
158
155
|
|
|
@@ -162,63 +159,65 @@ renderer.image_at(image, x, y, scale_x: 1, scale_y: 1, z: 0, color: nil)
|
|
|
162
159
|
renderer.background(image, x = 0, y = 0, z: 0, color: nil)
|
|
163
160
|
```
|
|
164
161
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
162
|
+
Each method anchors the image differently:
|
|
163
|
+
|
|
164
|
+
- `image` **centres** the image on the position and rotates it about that
|
|
165
|
+
centre. Use it for sprites.
|
|
166
|
+
- `image_at` places the **top-left** corner and scales each axis separately. Use
|
|
167
|
+
it for tiles, nine-slice corners and sheet frames.
|
|
168
|
+
- `background` is `image_at` at natural size.
|
|
169
169
|
|
|
170
170
|
### Mirroring
|
|
171
171
|
|
|
172
|
-
A negative scale on `image_at` mirrors the image
|
|
173
|
-
It does not move
|
|
172
|
+
**A negative scale on `image_at` mirrors the image inside the same rectangle.**
|
|
173
|
+
It does not move the image:
|
|
174
174
|
|
|
175
175
|
```ruby
|
|
176
176
|
renderer.image_at(frame, x, y, scale_x: facing_left ? -1 : 1)
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
Both calls cover the same pixels; only the picture is reversed.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
nothing to forget.
|
|
179
|
+
Both calls cover the same pixels; only the picture is reversed. `(x, y)` stays
|
|
180
|
+
the top-left corner whatever the scale's sign, so a mirrored sprite stays put.
|
|
181
|
+
Mirroring about the anchor would shift the image one width to the left. Every
|
|
182
|
+
flipped draw would then have to add that width back.
|
|
184
183
|
|
|
185
184
|
A scale of `0` draws nothing.
|
|
186
185
|
|
|
187
|
-
`color:` tints
|
|
188
|
-
|
|
186
|
+
`color:` tints the image by multiplying its pixels. White leaves the image
|
|
187
|
+
unchanged, and a colour with alpha fades it.
|
|
189
188
|
|
|
190
|
-
|
|
189
|
+
[Images](images.md) covers loading files and slicing tiles; [Assets](assets.md#sprite-sheets) covers sprite sheets.
|
|
191
190
|
|
|
192
|
-
**
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
is nearly all of them — this never comes up.
|
|
191
|
+
**Only the app that loaded an image can draw it.** A GPU texture belongs to one
|
|
192
|
+
window's OpenGL context. Drawing another app's image would sample nothing and
|
|
193
|
+
paint a plain white rectangle, so the renderer raises `ArgumentError` instead. A
|
|
194
|
+
one-window game never meets this.
|
|
197
195
|
|
|
198
196
|
## Drawing by id
|
|
199
197
|
|
|
200
|
-
Game logic names an asset; it does not hold one
|
|
201
|
-
|
|
202
|
-
|
|
198
|
+
**Game logic names an asset; it does not hold one.** The scene layer may hold
|
|
199
|
+
`RGame::Util` values but no `RGame::Core` handle. A Symbol or a path is the only
|
|
200
|
+
thing a node *can* carry.
|
|
203
201
|
|
|
204
|
-
An id is normally a **root-relative path
|
|
205
|
-
[asset manager](assets.md) and
|
|
202
|
+
An id is normally a **root-relative path**. The renderer resolves it through the
|
|
203
|
+
app's [asset manager](assets.md) and remembers the result:
|
|
206
204
|
|
|
207
205
|
```ruby
|
|
208
|
-
renderer.sprite('
|
|
206
|
+
renderer.sprite('hero.json', row, col, x, y, flip_x: false, z: 0)
|
|
209
207
|
renderer.image('space.png', cx, cy, angle: 0, scale: 1)
|
|
210
208
|
renderer.background('space.png')
|
|
211
|
-
renderer.tilemap('map/island.tmx', layer,
|
|
209
|
+
renderer.tilemap('map/island.tmx', layer, cull_x, cull_y, cull_w, cull_h, elapsed: 0.0) # draws in world coordinates
|
|
210
|
+
renderer.nine_slice(:panel, x, y, width, height, z: 0, tint: nil)
|
|
212
211
|
```
|
|
213
212
|
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
Paths need no setup. `Renderer.new(app)` uses the app's own manager.
|
|
214
|
+
`Renderer.new(app, assets: other)` uses a different one.
|
|
216
215
|
|
|
217
216
|
### Registering
|
|
218
217
|
|
|
219
|
-
`register_*`
|
|
220
|
-
manager.
|
|
221
|
-
|
|
218
|
+
`register_*` binds an id to an object you choose, and takes priority over the
|
|
219
|
+
asset manager. Use it for what a path cannot name: an id that is not a file, and
|
|
220
|
+
an object the game built itself.
|
|
222
221
|
|
|
223
222
|
```ruby
|
|
224
223
|
renderer.register_image(:space, app.assets.image('space.png'))
|
|
@@ -230,10 +229,10 @@ renderer.register_ui_atlas(atlas) # every element under its own name
|
|
|
230
229
|
renderer.image(:space, 100, 100)
|
|
231
230
|
```
|
|
232
231
|
|
|
233
|
-
**Nine-slices
|
|
234
|
-
|
|
232
|
+
**Nine-slices must be registered.** Their ids name an *element of an atlas*, not
|
|
233
|
+
a file, so an asset manager has nothing to resolve.
|
|
235
234
|
|
|
236
|
-
###
|
|
235
|
+
### How the renderer resolves an id
|
|
237
236
|
|
|
238
237
|
| Given | |
|
|
239
238
|
|---|---|
|
|
@@ -243,19 +242,18 @@ not a file, so there is nothing for a manager to resolve them to.
|
|
|
243
242
|
| A `Symbol` that is not registered | `KeyError`, naming the id and the type |
|
|
244
243
|
| `nil` | `TypeError` |
|
|
245
244
|
|
|
246
|
-
|
|
247
|
-
path.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
wanting a different fix.
|
|
245
|
+
The renderer never offers a Symbol to the asset manager, because only a String
|
|
246
|
+
can be a path. A mistyped Symbol therefore raises "no sheet registered for
|
|
247
|
+
`:heor`". A broken *file* raises its own `LoadError` naming the file. The two
|
|
248
|
+
errors point at two different fixes.
|
|
251
249
|
|
|
252
|
-
|
|
253
|
-
neither
|
|
250
|
+
The renderer resolves each id once and keeps the answer. Per-frame drawing
|
|
251
|
+
neither resolves again nor allocates a lookup key.
|
|
254
252
|
|
|
255
253
|
## Transform blocks
|
|
256
254
|
|
|
257
|
-
Each
|
|
258
|
-
|
|
255
|
+
Each block applies to everything drawn inside it and undoes itself afterwards,
|
|
256
|
+
even when the block raises.
|
|
259
257
|
|
|
260
258
|
```ruby
|
|
261
259
|
renderer.translated(dx, dy) { ... }
|
|
@@ -265,7 +263,7 @@ renderer.clipped(x, y, width, height) { ... }
|
|
|
265
263
|
renderer.layered(band) { ... } # see "Draw order" above
|
|
266
264
|
```
|
|
267
265
|
|
|
268
|
-
|
|
266
|
+
Blocks nest and compose in the order you open them:
|
|
269
267
|
|
|
270
268
|
```ruby
|
|
271
269
|
renderer.translated(-camera.x, -camera.y) do # world space -> screen space
|
|
@@ -275,18 +273,24 @@ renderer.translated(-camera.x, -camera.y) do # world space -> screen space
|
|
|
275
273
|
end
|
|
276
274
|
```
|
|
277
275
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
276
|
+
**A camera is a `translated` block.** Because the offset applies at draw time,
|
|
277
|
+
the same world can be drawn twice under two different offsets. That is
|
|
278
|
+
split-screen.
|
|
279
|
+
|
|
280
|
+
`rotated(0, …)`, `translated(0, 0)` and `scaled(1)` cost nothing. They skip the
|
|
281
|
+
transform and run the block, so unrotated drawing pays nothing.
|
|
281
282
|
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
**Inside a scene graph you rarely open a transform block yourself.** The examples
|
|
284
|
+
on this page drive the renderer from an `App`, in window coordinates. For a
|
|
285
|
+
`Node2D`, the traversal pushes the node's transform before it calls `on_draw`. A
|
|
286
|
+
node therefore draws at *its own* origin, and passing its position would apply it
|
|
287
|
+
twice. See [Scene graph](scene_graph.md#drawing-happens-in-local-space).
|
|
284
288
|
|
|
285
289
|
### Clipping and split-screen
|
|
286
290
|
|
|
287
|
-
A clip
|
|
288
|
-
|
|
289
|
-
|
|
291
|
+
**A clip narrows.** A nested clip intersects with its parent, so a child never
|
|
292
|
+
draws outside the region its parent allowed. Two clipped blocks make a split
|
|
293
|
+
screen:
|
|
290
294
|
|
|
291
295
|
```ruby
|
|
292
296
|
def draw
|
|
@@ -300,17 +304,17 @@ def draw
|
|
|
300
304
|
end
|
|
301
305
|
```
|
|
302
306
|
|
|
303
|
-
**A game does not write that.**
|
|
304
|
-
[`RGame::Engine::WorldView`](scene_graph.md#view-transforms-and-the-camera) does
|
|
305
|
-
once per active player, with the rectangles
|
|
306
|
-
|
|
307
|
-
|
|
307
|
+
**A game does not write that.**
|
|
308
|
+
[`RGame::Engine::WorldView`](scene_graph.md#view-transforms-and-the-camera) does it
|
|
309
|
+
once per active player, with the layout's rectangles and each player's camera.
|
|
310
|
+
Call `clipped` directly for a region of your own, such as a minimap or a
|
|
311
|
+
scrolling list.
|
|
308
312
|
|
|
309
313
|
## Recordings: bake once, replay cheaply
|
|
310
314
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
315
|
+
**`record` bakes a block of drawing, and a replay costs one call per texture.** A
|
|
316
|
+
tile layer holds a few thousand quads that stay the same once the level loads, so
|
|
317
|
+
it is the typical case:
|
|
314
318
|
|
|
315
319
|
```ruby
|
|
316
320
|
def draw
|
|
@@ -322,58 +326,58 @@ def draw
|
|
|
322
326
|
end
|
|
323
327
|
```
|
|
324
328
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
`initialize`.
|
|
329
|
+
Baking draws nothing; the block's output goes into the recording, not the frame.
|
|
330
|
+
`record` must run inside `draw` like every other call. The example therefore
|
|
331
|
+
bakes on the first frame, not in `initialize`.
|
|
329
332
|
|
|
330
333
|
```ruby
|
|
331
334
|
baked.draw(x = 0, y = 0, z: 0, color: nil)
|
|
332
335
|
baked.batch_count # GL calls one replay costs
|
|
333
|
-
baked.
|
|
336
|
+
baked.vertex_count # vertices baked in
|
|
337
|
+
baked.width # the size of what was baked, with #height
|
|
334
338
|
baked.empty?
|
|
335
339
|
```
|
|
336
340
|
|
|
337
|
-
**
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
+
**A recording bakes in positions, texture coordinates, colours and any
|
|
342
|
+
transforms inside the block.** The transform in effect at replay applies on top.
|
|
343
|
+
A baked layer scrolls under a camera without a rebuild, and one recording can be
|
|
344
|
+
stamped in several places:
|
|
341
345
|
|
|
342
346
|
```ruby
|
|
343
347
|
5.times { |i| @bush.draw(i * 120, 300) }
|
|
344
348
|
```
|
|
345
349
|
|
|
346
|
-
**`color:` tints the replay
|
|
347
|
-
|
|
350
|
+
**`color:` tints the replay.** The renderer multiplies each recorded colour by
|
|
351
|
+
it, so you can fade a whole baked layer at once.
|
|
348
352
|
|
|
349
|
-
**
|
|
350
|
-
clip
|
|
351
|
-
|
|
352
|
-
replay instead, which is what was meant anyway:
|
|
353
|
+
**A recording cannot contain a clip.** Clipping happens at rasterisation, so a
|
|
354
|
+
clip captured in one place would be wrong everywhere else the recording is drawn.
|
|
355
|
+
Pushing a clip inside a `record` block raises. Clip the replay instead:
|
|
353
356
|
|
|
354
357
|
```ruby
|
|
355
358
|
@renderer.clipped(0, 0, 400, 600) { @ground.draw(-@camera.x, -@camera.y) }
|
|
356
359
|
```
|
|
357
360
|
|
|
358
|
-
Recordings do not nest
|
|
359
|
-
behind. A recording keeps
|
|
360
|
-
|
|
361
|
+
Recordings do not nest. A block that raises leaves no half-built recording
|
|
362
|
+
behind. A recording keeps its baked images alive, so dropping a sprite sheet
|
|
363
|
+
after baking does not free its texture.
|
|
361
364
|
|
|
362
365
|
## Testing what a scene draws
|
|
363
366
|
|
|
364
|
-
|
|
365
|
-
receives
|
|
366
|
-
instead and asserts on the calls
|
|
367
|
+
**Treat the renderer as an interface, not a class your game names.** Game logic
|
|
368
|
+
receives a renderer and calls its methods. A headless spec passes a recording
|
|
369
|
+
fake instead and asserts on the calls. rgame's own suite uses `FakeRenderer`
|
|
370
|
+
from `spec/support/`:
|
|
367
371
|
|
|
368
372
|
```ruby
|
|
369
373
|
renderer = FakeRenderer.new
|
|
370
|
-
health_bar.
|
|
374
|
+
health_bar.on_draw(renderer, nil)
|
|
371
375
|
|
|
372
376
|
expect(renderer.calls_to(:rect).map(&:args)).to eq([[10, 10, 64, 8]])
|
|
373
377
|
```
|
|
374
378
|
|
|
375
|
-
|
|
376
|
-
|
|
379
|
+
The fake also records recordings, and keeps two questions apart: what was baked,
|
|
380
|
+
and where it was replayed.
|
|
377
381
|
|
|
378
382
|
```ruby
|
|
379
383
|
ground = renderer.record { ... } # => a FakeRecording
|
|
@@ -382,17 +386,13 @@ expect(ground.calls.size).to eq(tiles.size) # baked once, not per frame
|
|
|
382
386
|
expect(ground.draws.map(&:args)).to eq([[-camera.x, -camera.y]])
|
|
383
387
|
```
|
|
384
388
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
+
These specs run with no window, no GPU and no clock. One shared contract,
|
|
390
|
+
`spec/support/shared_examples/a_renderer.rb`, checks both the fake and the real
|
|
391
|
+
renderer. A fake that drifted from the real renderer would keep the suite green
|
|
392
|
+
while the game stopped drawing.
|
|
389
393
|
|
|
390
394
|
## Text
|
|
391
395
|
|
|
392
396
|
`renderer.text(string, x, y)` draws a line of text, and `text_width` measures
|
|
393
|
-
one.
|
|
394
|
-
|
|
395
|
-
## What is not here yet
|
|
396
|
-
|
|
397
|
-
Audio and drawing by asset id (`sprite(:hero, row, col, …)`) are still to come.
|
|
398
|
-
Today an image is passed as an object rather than looked up in a registry.
|
|
397
|
+
one. Both take a String or an [`Engine::Text`](toolbox.md#text--the-string-a-node-draws). [Text](text.md) covers fonts, the shipped default and the characters it
|
|
398
|
+
covers.
|