dama 0.1.0-x86_64-linux
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/LICENSE +21 -0
- data/README.md +227 -0
- data/dama-logo.svg +91 -0
- data/exe/dama +4 -0
- data/lib/dama/animation.rb +66 -0
- data/lib/dama/asset_cache.rb +56 -0
- data/lib/dama/audio.rb +47 -0
- data/lib/dama/auto_loader.rb +54 -0
- data/lib/dama/backend/base.rb +137 -0
- data/lib/dama/backend/native/ffi_bindings.rb +122 -0
- data/lib/dama/backend/native.rb +191 -0
- data/lib/dama/backend/web.rb +199 -0
- data/lib/dama/backend.rb +13 -0
- data/lib/dama/camera.rb +68 -0
- data/lib/dama/cli/new_project.rb +112 -0
- data/lib/dama/cli/release.rb +45 -0
- data/lib/dama/cli.rb +22 -0
- data/lib/dama/colors.rb +30 -0
- data/lib/dama/command_buffer.rb +83 -0
- data/lib/dama/component/attribute_definition.rb +13 -0
- data/lib/dama/component/attribute_set.rb +32 -0
- data/lib/dama/component.rb +28 -0
- data/lib/dama/configuration.rb +18 -0
- data/lib/dama/debug/frame_controller.rb +35 -0
- data/lib/dama/debug/screenshot_tool.rb +19 -0
- data/lib/dama/debug.rb +4 -0
- data/lib/dama/event_bus.rb +47 -0
- data/lib/dama/game/builder.rb +31 -0
- data/lib/dama/game/loop.rb +44 -0
- data/lib/dama/game.rb +88 -0
- data/lib/dama/geometry/circle.rb +28 -0
- data/lib/dama/geometry/rect.rb +16 -0
- data/lib/dama/geometry/sprite.rb +18 -0
- data/lib/dama/geometry/triangle.rb +13 -0
- data/lib/dama/geometry.rb +4 -0
- data/lib/dama/input/keyboard_state.rb +44 -0
- data/lib/dama/input/mouse_state.rb +45 -0
- data/lib/dama/input.rb +38 -0
- data/lib/dama/keys.rb +67 -0
- data/lib/dama/native/libdama_native.so +0 -0
- data/lib/dama/node/component_slot.rb +18 -0
- data/lib/dama/node/draw_context.rb +96 -0
- data/lib/dama/node.rb +139 -0
- data/lib/dama/physics/body.rb +57 -0
- data/lib/dama/physics/collider.rb +152 -0
- data/lib/dama/physics/collision.rb +15 -0
- data/lib/dama/physics/world.rb +125 -0
- data/lib/dama/physics.rb +4 -0
- data/lib/dama/registry/class_resolver.rb +48 -0
- data/lib/dama/registry.rb +21 -0
- data/lib/dama/release/archiver.rb +100 -0
- data/lib/dama/release/defaults/icon.icns +0 -0
- data/lib/dama/release/defaults/icon.ico +0 -0
- data/lib/dama/release/defaults/icon.png +0 -0
- data/lib/dama/release/dylib_relinker.rb +95 -0
- data/lib/dama/release/game_file_copier.rb +35 -0
- data/lib/dama/release/game_metadata.rb +61 -0
- data/lib/dama/release/icon_provider.rb +36 -0
- data/lib/dama/release/native_builder.rb +44 -0
- data/lib/dama/release/packager/linux.rb +62 -0
- data/lib/dama/release/packager/macos.rb +99 -0
- data/lib/dama/release/packager/web.rb +32 -0
- data/lib/dama/release/packager/windows.rb +61 -0
- data/lib/dama/release/packager.rb +9 -0
- data/lib/dama/release/platform_detector.rb +23 -0
- data/lib/dama/release/ruby_bundler.rb +163 -0
- data/lib/dama/release/stdlib_trimmer.rb +133 -0
- data/lib/dama/release/template_renderer.rb +40 -0
- data/lib/dama/release/templates/info_plist.xml.erb +19 -0
- data/lib/dama/release/templates/launcher_linux.sh.erb +10 -0
- data/lib/dama/release/templates/launcher_macos.sh.erb +10 -0
- data/lib/dama/release/templates/launcher_windows.bat.erb +11 -0
- data/lib/dama/release.rb +7 -0
- data/lib/dama/scene/composer.rb +65 -0
- data/lib/dama/scene.rb +233 -0
- data/lib/dama/scene_graph/class_index.rb +26 -0
- data/lib/dama/scene_graph/group_node.rb +27 -0
- data/lib/dama/scene_graph/instance_node.rb +30 -0
- data/lib/dama/scene_graph/path_selector.rb +25 -0
- data/lib/dama/scene_graph/query.rb +34 -0
- data/lib/dama/scene_graph/tag_index.rb +26 -0
- data/lib/dama/scene_graph/tree.rb +65 -0
- data/lib/dama/scene_graph.rb +4 -0
- data/lib/dama/sprite_sheet.rb +36 -0
- data/lib/dama/tween/easing.rb +31 -0
- data/lib/dama/tween/lerp.rb +35 -0
- data/lib/dama/tween/manager.rb +28 -0
- data/lib/dama/tween.rb +4 -0
- data/lib/dama/version.rb +3 -0
- data/lib/dama/vertex_batch.rb +35 -0
- data/lib/dama/web/entry.rb +79 -0
- data/lib/dama/web/static/index.html +142 -0
- data/lib/dama/web_builder.rb +232 -0
- data/lib/dama.rb +42 -0
- metadata +186 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 14c7877e7cada9427d3f09b1e191b57ff91f44e0d7d95bda7967a6d2a0ba6969
|
|
4
|
+
data.tar.gz: 6441a240b6637c45c54fbb1b7418780038ea024a97233a2ac11c883644690a19
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 66edc610afd44d66c6a775d8157f236e1fca18197a4331875dbedd03c4473431e7059872651a9c27e5765c44c51354435b26f66875555f5f6ca5ca29147d357e
|
|
7
|
+
data.tar.gz: 389de5e6386731517d326d284a20369c265f6682f90495eed2b94d4b0f6466b0a89b08c4f1e4111c1528ffc80328eb420fc34c08297e364cb85d5ca9101a078c
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Caiubi Fonseca
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="dama-logo.svg" alt="dama-rb" width="120">
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">dama-rb</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
A cross-platform 2D game engine with a Ruby DSL and Rust rendering backend.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<strong>Write games in Ruby. Render with wgpu. Run native or in the browser.</strong>
|
|
13
|
+
<br />
|
|
14
|
+
<a href="https://github.com/caiubi/dama-rb">View Demo</a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
dama-rb lets you build 2D games using an expressive Ruby DSL. The engine handles rendering via a Rust/wgpu backend (Metal, Vulkan, DX12, WebGPU), so your game code is pure Ruby while GPU-accelerated graphics run at native speed.
|
|
20
|
+
|
|
21
|
+
The name "dama" is inspired by the Japanese word for "orb" (玉) and the Portuguese word for the "checkers" game (dama).
|
|
22
|
+
|
|
23
|
+
## Screenshots
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<img src="docs/checkers_screenshot.png" alt="Checkers" width="380">
|
|
27
|
+
<img src="docs/breakout_screenshot.png" alt="Breakout" width="380">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Ruby DSL** — Standard CRuby 3.4 — bring your gems, your tools, your workflow
|
|
33
|
+
- **Rust/wgpu renderer** — GPU-accelerated shapes, text, sprites, shaders, and textures
|
|
34
|
+
- **Cross-platform** — Native (macOS, Linux, Windows) and web (WebGPU via ruby.wasm)
|
|
35
|
+
- **Input handling** — Keyboard, mouse (gamepad support is on the [roadmap](#roadmap))
|
|
36
|
+
- **Sprite rendering** — Draw textures from your assets folder
|
|
37
|
+
- **Shader support** 🔥 — Write custom fragment shaders in WGSL (basic functions supported)
|
|
38
|
+
- **Sound effects** — Play audio files within your game
|
|
39
|
+
- **Physics** — AABB collision detection with a simple API (Rapier2D integration is on the [roadmap](#roadmap))
|
|
40
|
+
- **HiDPI/Retina** — Sharp rendering on high-density displays
|
|
41
|
+
- **100% test coverage** — RSpec with headless native backend integration tests (Web build pipeline coverage is on the [roadmap](#roadmap))
|
|
42
|
+
- **MIT licensed** — Every feature, for everyone, forever
|
|
43
|
+
|
|
44
|
+
## Build Your First Game
|
|
45
|
+
|
|
46
|
+
### 1. Create a project
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
mkdir my_game && cd my_game
|
|
50
|
+
bundle init
|
|
51
|
+
bundle add dama
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Scaffold the game
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle exec dama new
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This generates a playable starter project:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
my_game/
|
|
64
|
+
├── bin/dama # game launcher
|
|
65
|
+
├── config.rb # game settings (resolution, title, start scene)
|
|
66
|
+
├── game/
|
|
67
|
+
│ ├── components/transform.rb # position data
|
|
68
|
+
│ ├── nodes/player.rb # entity with drawing
|
|
69
|
+
│ └── scenes/main_scene.rb # composes nodes, handles input
|
|
70
|
+
└── assets/ # images, sounds, shaders
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Run it
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bin/dama # native window
|
|
77
|
+
bin/dama web # browser (WebGPU)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
You'll see a red circle you can move with the arrow keys.
|
|
81
|
+
|
|
82
|
+
### 4. How it works
|
|
83
|
+
|
|
84
|
+
dama-rb uses a **Component → Node → Scene** architecture:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
# Components hold pure data
|
|
88
|
+
class Transform < Dama::Component
|
|
89
|
+
attribute :x, default: 0.0
|
|
90
|
+
attribute :y, default: 0.0
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Nodes are entities — they own components and define drawing
|
|
94
|
+
class Player < Dama::Node
|
|
95
|
+
component Transform, as: :transform, x: 400.0, y: 300.0
|
|
96
|
+
|
|
97
|
+
draw do
|
|
98
|
+
circle(transform.x, transform.y, 20.0, **Dama::Colors::RED.to_h)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Scenes compose nodes and handle game logic
|
|
103
|
+
class MainScene < Dama::Scene
|
|
104
|
+
compose do
|
|
105
|
+
add Player, as: :hero
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
update do |dt, input|
|
|
109
|
+
hero.transform.x += 200.0 * dt if input.right?
|
|
110
|
+
hero.transform.x -= 200.0 * dt if input.left?
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# config.rb boots the game — must define a GAME constant
|
|
115
|
+
GAME = Dama::Game.new do
|
|
116
|
+
settings resolution: [800, 600], title: "My Game"
|
|
117
|
+
start_scene MainScene
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 5. Make it yours
|
|
122
|
+
|
|
123
|
+
- Edit `game/scenes/main_scene.rb` to change game logic
|
|
124
|
+
- Add new nodes in `game/nodes/`
|
|
125
|
+
- Add new components in `game/components/`
|
|
126
|
+
- Put images, sounds, and shaders in `assets/`
|
|
127
|
+
- See the [examples](examples/) for physics, sprites, shaders, and scene transitions
|
|
128
|
+
|
|
129
|
+
## Architecture
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Ruby DSL (your game code)
|
|
133
|
+
↓
|
|
134
|
+
Dama Engine (Ruby)
|
|
135
|
+
↓ FFI / JS bridge
|
|
136
|
+
Rust Backend (wgpu)
|
|
137
|
+
↓
|
|
138
|
+
GPU (Metal / Vulkan / WebGPU)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **Ruby** handles game logic, scene graph, components, input, and the update loop
|
|
142
|
+
- **Rust** handles window management (winit), GPU rendering (wgpu), text (glyphon), and screenshots
|
|
143
|
+
- **Native**: Ruby calls Rust via FFI (cdylib)
|
|
144
|
+
- **Web**: Ruby runs in ruby.wasm, calls Rust wasm via JS bridge
|
|
145
|
+
|
|
146
|
+
## Drawing Primitives
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
draw do
|
|
150
|
+
rect(x, y, w, h, r:, g:, b:, a:)
|
|
151
|
+
circle(cx, cy, radius, r:, g:, b:, a:)
|
|
152
|
+
triangle(x1, y1, x2, y2, x3, y3, r:, g:, b:, a:)
|
|
153
|
+
text("Hello", x, y, size: 24.0, r:, g:, b:, a:)
|
|
154
|
+
sprite(texture_handle, x, y, w, h)
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Examples
|
|
159
|
+
|
|
160
|
+
- **[demo](examples/demo/)** — Shapes, sprites, FPS overlay, keyboard input
|
|
161
|
+
- **[checkers](examples/checkers/)** — Full 8x8 checkers game with piece selection, captures, king promotion, and scene transitions
|
|
162
|
+
- **[breakout](examples/breakout/)** — Brick breaker with physics, custom shaders, and score tracking
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
### Prerequisites
|
|
167
|
+
|
|
168
|
+
- Ruby 3.4+
|
|
169
|
+
- Rust (stable, via rustup)
|
|
170
|
+
- wasm-bindgen-cli (`cargo install wasm-bindgen-cli`)
|
|
171
|
+
- npm (for downloading ruby.wasm base binary — web builds only)
|
|
172
|
+
|
|
173
|
+
> **Note:** Web builds require `npm` (to download the ruby.wasm base binary) and `wasm-bindgen-cli`.
|
|
174
|
+
|
|
175
|
+
### Setup
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
git clone https://github.com/caiubi/dama-rb
|
|
179
|
+
cd dama-rb
|
|
180
|
+
bundle install
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Tests
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
bundle exec rspec # Ruby specs (builds Rust automatically)
|
|
187
|
+
bundle exec rubocop # Ruby linting
|
|
188
|
+
cd ext/dama_native && cargo test # Rust tests
|
|
189
|
+
cd ext/dama_native && cargo clippy # Rust linting
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Run examples
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
cd examples/checkers && bin/dama # Native
|
|
196
|
+
cd examples/checkers && bin/dama web # Browser
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Roadmap
|
|
200
|
+
|
|
201
|
+
### Rapier2D Physics
|
|
202
|
+
- [ ] Replace AABB with Rapier2D behind Cargo feature flag
|
|
203
|
+
- [ ] Continuous collision detection, joints, raycasting
|
|
204
|
+
|
|
205
|
+
### Game Framework Features
|
|
206
|
+
- [ ] Particle system
|
|
207
|
+
- [ ] UI framework (Button, Label, Panel)
|
|
208
|
+
- [ ] Tilemap (Tiled JSON format)
|
|
209
|
+
- [ ] Scene transitions (fade in/out)
|
|
210
|
+
- [ ] Save/Load (JSON, localStorage on web)
|
|
211
|
+
|
|
212
|
+
### Polish & Community
|
|
213
|
+
- [ ] Gamepad support
|
|
214
|
+
- [ ] Documentation site (YARD + GitHub Pages)
|
|
215
|
+
- [x] Project generator (`dama new my_game`)
|
|
216
|
+
- [ ] Hot reload
|
|
217
|
+
- [ ] Debug overlay
|
|
218
|
+
- [ ] Publish gem to RubyGems
|
|
219
|
+
|
|
220
|
+
### Web Integration Testing
|
|
221
|
+
- [ ] Capybara-like + Cuprite browser tests
|
|
222
|
+
- [ ] Game state assertion helpers
|
|
223
|
+
- [ ] CI integration
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
[MIT](LICENSE)
|
data/dama-logo.svg
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="60 -50 500 500" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">dama-rb logo</title>
|
|
3
|
+
<desc id="desc">A glossy red orb over a circular checkerboard base.</desc>
|
|
4
|
+
|
|
5
|
+
<defs>
|
|
6
|
+
<radialGradient id="orbFill" cx="38%" cy="28%" r="65%">
|
|
7
|
+
<stop offset="0%" stop-color="#ff8d8d"/>
|
|
8
|
+
<stop offset="25%" stop-color="#ff2323"/>
|
|
9
|
+
<stop offset="70%" stop-color="#c30012"/>
|
|
10
|
+
<stop offset="100%" stop-color="#6c0008"/>
|
|
11
|
+
</radialGradient>
|
|
12
|
+
|
|
13
|
+
<radialGradient id="orbGlow" cx="50%" cy="50%" r="50%">
|
|
14
|
+
<stop offset="0%" stop-color="#ff4d4d" stop-opacity="0.55"/>
|
|
15
|
+
<stop offset="100%" stop-color="#ff4d4d" stop-opacity="0"/>
|
|
16
|
+
</radialGradient>
|
|
17
|
+
|
|
18
|
+
<linearGradient id="ringTop" x1="0" y1="0" x2="0" y2="1">
|
|
19
|
+
<stop offset="0%" stop-color="#3d434a"/>
|
|
20
|
+
<stop offset="100%" stop-color="#111418"/>
|
|
21
|
+
</linearGradient>
|
|
22
|
+
|
|
23
|
+
<linearGradient id="ringSide" x1="0" y1="0" x2="0" y2="1">
|
|
24
|
+
<stop offset="0%" stop-color="#20252b"/>
|
|
25
|
+
<stop offset="100%" stop-color="#050607"/>
|
|
26
|
+
</linearGradient>
|
|
27
|
+
|
|
28
|
+
<filter id="softShadow" x="-30%" y="-30%" width="160%" height="160%">
|
|
29
|
+
<feDropShadow dx="0" dy="14" stdDeviation="14" flood-color="#000000" flood-opacity="0.28"/>
|
|
30
|
+
</filter>
|
|
31
|
+
|
|
32
|
+
<clipPath id="iconClip">
|
|
33
|
+
<rect x="60" y="-50" width="500" height="500" rx="90"/>
|
|
34
|
+
</clipPath>
|
|
35
|
+
|
|
36
|
+
<clipPath id="topEllipseClip">
|
|
37
|
+
<ellipse cx="310" cy="250" rx="190" ry="74"/>
|
|
38
|
+
</clipPath>
|
|
39
|
+
</defs>
|
|
40
|
+
|
|
41
|
+
<!-- White rounded-corner background -->
|
|
42
|
+
<rect x="60" y="-50" width="500" height="500" rx="90" fill="#ffffff"/>
|
|
43
|
+
|
|
44
|
+
<g clip-path="url(#iconClip)">
|
|
45
|
+
<g filter="url(#softShadow)">
|
|
46
|
+
<!-- Base side -->
|
|
47
|
+
<ellipse cx="310" cy="286" rx="190" ry="74" fill="url(#ringSide)"/>
|
|
48
|
+
|
|
49
|
+
<!-- Checkerboard top clipped to ellipse -->
|
|
50
|
+
<g clip-path="url(#topEllipseClip)">
|
|
51
|
+
<rect x="120" y="176" width="380" height="148" fill="#f4f4f4"/>
|
|
52
|
+
<g fill="#111111">
|
|
53
|
+
<rect x="120" y="176" width="47.5" height="37"/>
|
|
54
|
+
<rect x="215" y="176" width="47.5" height="37"/>
|
|
55
|
+
<rect x="310" y="176" width="47.5" height="37"/>
|
|
56
|
+
<rect x="405" y="176" width="47.5" height="37"/>
|
|
57
|
+
|
|
58
|
+
<rect x="167.5" y="213" width="47.5" height="37"/>
|
|
59
|
+
<rect x="262.5" y="213" width="47.5" height="37"/>
|
|
60
|
+
<rect x="357.5" y="213" width="47.5" height="37"/>
|
|
61
|
+
<rect x="452.5" y="213" width="47.5" height="37"/>
|
|
62
|
+
|
|
63
|
+
<rect x="120" y="250" width="47.5" height="37"/>
|
|
64
|
+
<rect x="215" y="250" width="47.5" height="37"/>
|
|
65
|
+
<rect x="310" y="250" width="47.5" height="37"/>
|
|
66
|
+
<rect x="405" y="250" width="47.5" height="37"/>
|
|
67
|
+
|
|
68
|
+
<rect x="167.5" y="287" width="47.5" height="37"/>
|
|
69
|
+
<rect x="262.5" y="287" width="47.5" height="37"/>
|
|
70
|
+
<rect x="357.5" y="287" width="47.5" height="37"/>
|
|
71
|
+
<rect x="452.5" y="287" width="47.5" height="37"/>
|
|
72
|
+
</g>
|
|
73
|
+
<ellipse cx="310" cy="250" rx="190" ry="74" fill="url(#ringTop)" opacity="0.16"/>
|
|
74
|
+
</g>
|
|
75
|
+
|
|
76
|
+
<!-- Rim -->
|
|
77
|
+
<ellipse cx="310" cy="250" rx="190" ry="74" fill="none" stroke="#2c3138" stroke-width="16"/>
|
|
78
|
+
|
|
79
|
+
<!-- Orb shadow on board -->
|
|
80
|
+
<ellipse cx="290" cy="250" rx="95" ry="28" fill="#3c0008" opacity="0.28"/>
|
|
81
|
+
|
|
82
|
+
<!-- Orb -->
|
|
83
|
+
<circle cx="255" cy="170" r="118" fill="url(#orbGlow)" opacity="0.9"/>
|
|
84
|
+
<circle cx="255" cy="170" r="102" fill="url(#orbFill)"/>
|
|
85
|
+
<ellipse cx="222" cy="130" rx="32" ry="22" fill="#ffffff" opacity="0.78" transform="rotate(-22 222 130)"/>
|
|
86
|
+
<ellipse cx="294" cy="197" rx="13" ry="10" fill="#ffb2b2" opacity="0.36"/>
|
|
87
|
+
<ellipse cx="322" cy="178" rx="18" ry="14" fill="#ff6b6b" opacity="0.22"/>
|
|
88
|
+
<circle cx="255" cy="170" r="102" fill="none" stroke="#8d000d" stroke-width="4" opacity="0.55"/>
|
|
89
|
+
</g>
|
|
90
|
+
</g>
|
|
91
|
+
</svg>
|
data/exe/dama
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Dama
|
|
2
|
+
# Cycles through sprite sheet frames over time.
|
|
3
|
+
# Supports looping and one-shot animations.
|
|
4
|
+
class Animation
|
|
5
|
+
attr_reader :fps
|
|
6
|
+
|
|
7
|
+
def initialize(frames:, fps:, loop: true, on_complete: nil)
|
|
8
|
+
@frame_indices = frames.to_a
|
|
9
|
+
@fps = fps.to_f
|
|
10
|
+
@looping = loop
|
|
11
|
+
@on_complete = on_complete
|
|
12
|
+
@elapsed = 0.0
|
|
13
|
+
@frame_position = 0
|
|
14
|
+
@completed = false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update(delta_time:)
|
|
18
|
+
return if completed
|
|
19
|
+
|
|
20
|
+
@elapsed += delta_time
|
|
21
|
+
frame_duration = 1.0 / fps
|
|
22
|
+
frames_advanced = (elapsed / frame_duration).to_i
|
|
23
|
+
|
|
24
|
+
return unless frames_advanced > frame_position
|
|
25
|
+
|
|
26
|
+
@frame_position = frames_advanced
|
|
27
|
+
handle_frame_advancement
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def complete?
|
|
31
|
+
completed
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reset
|
|
35
|
+
@elapsed = 0.0
|
|
36
|
+
@frame_position = 0
|
|
37
|
+
@completed = false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def current_frame
|
|
41
|
+
frame_indices[frame_position % frame_indices.size]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
attr_reader :frame_indices, :looping, :on_complete, :elapsed, :frame_position, :completed
|
|
47
|
+
|
|
48
|
+
def handle_frame_advancement
|
|
49
|
+
return if frame_position < frame_indices.size
|
|
50
|
+
|
|
51
|
+
finish_animation
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def finish_animation
|
|
55
|
+
return loop_animation if looping
|
|
56
|
+
|
|
57
|
+
@frame_position = frame_indices.size - 1
|
|
58
|
+
@completed = true
|
|
59
|
+
on_complete&.call
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def loop_animation
|
|
63
|
+
@frame_position = frame_position % frame_indices.size
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Dama
|
|
2
|
+
# Reference-counted texture cache. Textures are loaded once and shared
|
|
3
|
+
# across all nodes that declare the same path. When the last node using
|
|
4
|
+
# a texture is removed, the texture is unloaded from the GPU.
|
|
5
|
+
class AssetCache
|
|
6
|
+
def initialize(backend:)
|
|
7
|
+
@backend = backend
|
|
8
|
+
@entries = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Acquire a texture handle for the given path. Loads from disk on first
|
|
12
|
+
# use; subsequent calls increment the reference count and return the
|
|
13
|
+
# same handle.
|
|
14
|
+
def acquire(path:)
|
|
15
|
+
entry = entries[path]
|
|
16
|
+
|
|
17
|
+
return increment_and_return(entry) if entry
|
|
18
|
+
|
|
19
|
+
handle = backend.load_texture_file(path:)
|
|
20
|
+
entries[path] = { handle:, ref_count: 1 }
|
|
21
|
+
handle
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Release one reference to the texture at the given path. When the
|
|
25
|
+
# reference count reaches zero, the texture is unloaded from the GPU.
|
|
26
|
+
def release(path:)
|
|
27
|
+
entry = entries[path]
|
|
28
|
+
return unless entry
|
|
29
|
+
|
|
30
|
+
entry[:ref_count] -= 1
|
|
31
|
+
return unless entry[:ref_count] <= 0
|
|
32
|
+
|
|
33
|
+
backend.unload_texture(handle: entry.fetch(:handle))
|
|
34
|
+
entries.delete(path)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Release all textures regardless of reference count.
|
|
38
|
+
def release_all
|
|
39
|
+
entries.each_value { |entry| backend.unload_texture(handle: entry.fetch(:handle)) }
|
|
40
|
+
entries.clear
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def handle_for(path:)
|
|
44
|
+
entries.dig(path, :handle)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
attr_reader :backend, :entries
|
|
50
|
+
|
|
51
|
+
def increment_and_return(entry)
|
|
52
|
+
entry[:ref_count] += 1
|
|
53
|
+
entry.fetch(:handle)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/dama/audio.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Dama
|
|
2
|
+
# High-level audio interface for loading and playing sounds.
|
|
3
|
+
# Manages sound handles with reference counting (like AssetCache).
|
|
4
|
+
#
|
|
5
|
+
# Usage in a Node:
|
|
6
|
+
# sound :jump, path: "assets/jump.wav"
|
|
7
|
+
#
|
|
8
|
+
# Usage in update:
|
|
9
|
+
# Audio.play(:jump)
|
|
10
|
+
# Audio.play(:theme, volume: 0.5, loop: true)
|
|
11
|
+
class Audio
|
|
12
|
+
attr_reader :backend
|
|
13
|
+
|
|
14
|
+
def initialize(backend:)
|
|
15
|
+
@backend = backend
|
|
16
|
+
@sounds = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load(name:, path:)
|
|
20
|
+
handle = backend.load_sound(path:)
|
|
21
|
+
sounds[name] = handle
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def play(name, volume: 1.0, loop: false)
|
|
25
|
+
handle = sounds.fetch(name)
|
|
26
|
+
backend.play_sound(handle:, volume:, loop:)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop_all
|
|
30
|
+
backend.stop_all_sounds
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def unload(name)
|
|
34
|
+
handle = sounds.delete(name)
|
|
35
|
+
backend.unload_sound(handle:) if handle
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def unload_all
|
|
39
|
+
sounds.each_value { |handle| backend.unload_sound(handle:) }
|
|
40
|
+
sounds.clear
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
attr_reader :sounds
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Dama
|
|
2
|
+
# Discovers and loads all Ruby files in a game project directory.
|
|
3
|
+
# Handles dependency ordering automatically by retrying files
|
|
4
|
+
# that fail due to undefined constants.
|
|
5
|
+
class AutoLoader
|
|
6
|
+
MAX_PASSES = 10
|
|
7
|
+
|
|
8
|
+
def initialize(game_dir:)
|
|
9
|
+
@game_dir = game_dir
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def load_all
|
|
13
|
+
files = discover_files
|
|
14
|
+
load_with_retries(files:)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :game_dir
|
|
20
|
+
|
|
21
|
+
def discover_files
|
|
22
|
+
Dir[File.join(game_dir, "**", "*.rb")]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Repeatedly attempt to load files, retrying those that fail
|
|
26
|
+
# with NameError (undefined constant — dependency not yet loaded).
|
|
27
|
+
# Stops when all files are loaded or no progress is made.
|
|
28
|
+
def load_with_retries(files:)
|
|
29
|
+
remaining = files.dup
|
|
30
|
+
|
|
31
|
+
MAX_PASSES.times do
|
|
32
|
+
failed = []
|
|
33
|
+
|
|
34
|
+
remaining.each do |file|
|
|
35
|
+
require file
|
|
36
|
+
rescue NameError
|
|
37
|
+
failed << file
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
return if failed.empty?
|
|
41
|
+
|
|
42
|
+
# No progress — the remaining files have unresolvable errors.
|
|
43
|
+
raise_load_error(failed:) if failed.size == remaining.size
|
|
44
|
+
|
|
45
|
+
remaining = failed
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def raise_load_error(failed:)
|
|
50
|
+
# Try loading each failed file one more time to get the real error message.
|
|
51
|
+
failed.each { |file| require file }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|