oriverse-engine 0.1.1
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.
- package/LICENSE.md +30 -0
- package/README.md +76 -0
- package/dist/AGENT_ONBOARDING.md +74 -0
- package/dist/CODEX_PROMPT.md +108 -0
- package/dist/GAME_AUTHORING.md +512 -0
- package/dist/examples/coin_collector.weave +70 -0
- package/dist/examples/dodge_blocks.weave +70 -0
- package/dist/examples/target_range.weave +82 -0
- package/dist/ktx_bridge.js +121 -0
- package/dist/ktx_lib.js +5236 -0
- package/dist/ktx_lib.wasm +0 -0
- package/dist/oriverse_wgpu.d.ts +174 -0
- package/dist/oriverse_wgpu.js +3361 -0
- package/dist/oriverse_wgpu_bg.wasm +0 -0
- package/dist/oriverse_wgpu_bg.wasm.d.ts +73 -0
- package/dist/snippets/oriverse_market_client-0cd3013288f3c49c/inline0.js +30 -0
- package/dist/snippets/oriverse_networking_shared-e43478064e11e46c/inline0.js +18 -0
- package/dist/snippets/oriverse_wgpu-255445ce6d9a0851/inline0.js +60 -0
- package/dist/snippets/oriverse_wgpu-255445ce6d9a0851/inline1.js +21 -0
- package/dist/starter/AGENTS.md +32 -0
- package/dist/starter/CLAUDE.md +32 -0
- package/dist/starter/game.html +64 -0
- package/dist/templates/game.html +63 -0
- package/dist/templates/game_cdn.html +64 -0
- package/dist/weave_documentation.txt +6258 -0
- package/package.json +18 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# Oriverse game authoring guide (artifact / CDN build)
|
|
2
|
+
|
|
3
|
+
How to write a complete small game in Weave for the Oriverse artifact engine.
|
|
4
|
+
Everything here works offline and fully procedurally — no market assets, no network.
|
|
5
|
+
Three complete, engine-tested example games are at the end; pattern-match from them.
|
|
6
|
+
The full language/API reference is `weave_documentation.txt` (same folder/URL as this file).
|
|
7
|
+
|
|
8
|
+
## 1. The shape of a game
|
|
9
|
+
|
|
10
|
+
A game is ONE block of Weave text (the "world description") placed inside the
|
|
11
|
+
`<script type="text/weave" id="oriverse-weave">` tag of your `game.html`. It has two layers:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
#class SomeClass <- class code: events + functions (one block per class)
|
|
15
|
+
...weave statements...
|
|
16
|
+
#end
|
|
17
|
+
#texture SomeTexture <- optional procedural texture DSL
|
|
18
|
+
#end
|
|
19
|
+
#mesh SomeMesh <- optional procedural mesh DSL
|
|
20
|
+
#end
|
|
21
|
+
|
|
22
|
+
saved thing { ... } <- spawnable-later object templates
|
|
23
|
+
object Floor { ... } <- scene objects placed at boot
|
|
24
|
+
display hint { ... } <- visual-only object (no collision/interaction)
|
|
25
|
+
physics crate { ... } <- dynamic (gravity) object
|
|
26
|
+
region goal { ... } <- invisible overlap-test volume
|
|
27
|
+
screenCanvas HUD { ... } <- 2D screen-space UI
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The engine boots straight into play mode. A player spawns at `Origin()` (0,0,0) with a
|
|
31
|
+
third-person camera. If you do NOT define `#class player`, a default controller is
|
|
32
|
+
injected: WASD camera-relative movement + Space jump. Define `#class player ... #end`
|
|
33
|
+
only to customize movement/camera/abilities.
|
|
34
|
+
|
|
35
|
+
## 2. World description basics
|
|
36
|
+
|
|
37
|
+
- Units: 1 unit = 1 cm. Z is up. A tile is 100x100 cm. The player is 180 cm tall, 40 wide.
|
|
38
|
+
- Primitive models: `"cube"`, `"sphere"`, `"cylinder"`, `"capsule"`, `"cone"`, `"wedge"`.
|
|
39
|
+
Base cube is 100x100x100; sphere is 100 diameter.
|
|
40
|
+
- `position = (x, y, z)` for mesh-backed objects is the BOTTOM-CENTER pivot (an object at
|
|
41
|
+
z=0 stands on the ground plane). `size = (x, y, z)` stretches; scalar `size = 80` is uniform.
|
|
42
|
+
- For axis-aligned walls/floors prefer exact corners:
|
|
43
|
+
`object NorthWall { model="cube" bounds_min=(-1500,1100,0) bounds_max=(1500,1180,280) }`
|
|
44
|
+
- `color = (r, g, b)` channels are 0-1 floats (or `#RRGGBB`). FOOTGUN: 0-255 style values
|
|
45
|
+
clamp every channel to 1 and render untinted white.
|
|
46
|
+
- Attach a class with `:` — `object Tower : TowerClass { ... }`. Override class variables
|
|
47
|
+
from the scene: `object boss : Enemy { override MaxHP = 500 }` (Number/Bool/Vector only).
|
|
48
|
+
- Tags need no declaration: `tag = Tag.Coin` in a clause, `AddTag(Tag.Coin)` in code.
|
|
49
|
+
- Object kinds: `object` (static, collides), `physics` (dynamic, falls), `display`
|
|
50
|
+
(visual-only: no collision, no Touched/Click events), `crowd` (NPC with avoidance),
|
|
51
|
+
`region` (invisible overlap volume), `saved` (template for runtime spawning).
|
|
52
|
+
- A hidden "manager" object is the standard place for game state and loops:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
display manager : GameManager { visible = false }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 3. Class code: events, loops, timers
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
#class GameManager
|
|
62
|
+
var Score = 0 // class variables persist; readable/writable cross-object
|
|
63
|
+
event OnPreSpawn { // runs before OnSpawned; put AddTag(...) here
|
|
64
|
+
AddTag(Tag.Manager)
|
|
65
|
+
}
|
|
66
|
+
event OnSpawned { // main entry; runs once when the object spawns
|
|
67
|
+
var tmr = Timer() // Timer starts in the Done state
|
|
68
|
+
forever { // once per frame (implicit WaitNextFrame between iterations)
|
|
69
|
+
if tmr.IsDone() {
|
|
70
|
+
tmr.StartCountDown(0.5)
|
|
71
|
+
// ... do something every 0.5 s ...
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
event KeyboardDown[R] { // any player pressed R (use ...ByThisPlayer only in #class player)
|
|
76
|
+
RestartGame()
|
|
77
|
+
}
|
|
78
|
+
#end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- `forever { ... }` = per-frame loop; `Wait(seconds)` sleeps inside event code;
|
|
82
|
+
`continue` skips to the next frame inside `forever`.
|
|
83
|
+
- At most ONE continuous move and ONE continuous rotate stick per object per frame
|
|
84
|
+
(`Move*Continuously`, `RotateToward*` — the last call wins). Teleports win over moves.
|
|
85
|
+
- `GetPosition()` returns the transform from the START of the current frame.
|
|
86
|
+
- Cross-object access works on variables and functions: `other.Score += 1`, `other.Pop()`.
|
|
87
|
+
- Text concat: `"Score: " + Score`. Random: `RandomNumber(from, to)` (deterministic per tick).
|
|
88
|
+
- Debug: `Print("...")` to console, `Say("...")` as a speech bubble,
|
|
89
|
+
`SpawnFloatingText("+1", Vector(0,1,0))` for damage/pickup popups.
|
|
90
|
+
|
|
91
|
+
## 4. The player class
|
|
92
|
+
|
|
93
|
+
Default (injected when you don't write one):
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
#class player
|
|
97
|
+
event OnSpawned {
|
|
98
|
+
forever {
|
|
99
|
+
var Direction = GetPlayerInputDirectionCamera(Key.W, Key.S, Key.A, Key.D)
|
|
100
|
+
MoveInDirectionContinuouslyFacing(Direction, 900)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
event KeyboardDownByThisPlayer[Space] {
|
|
104
|
+
if IsOnGround() {
|
|
105
|
+
Jump(1000)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
#end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Customization patterns (all inside `#class player`):
|
|
112
|
+
|
|
113
|
+
- Speed/feel: change `900` (cm/s); prefer `GetMoveVectorCamera()` for gameplay movement
|
|
114
|
+
(it also handles mobile thumbsticks). `Jump(1400)` for a higher jump.
|
|
115
|
+
- Actions: `event ActionDownByThisPlayer[Primary]` (left click / tap), `[Secondary]`,
|
|
116
|
+
`[Interact]` (E), `[Jump]`. `IsActionPressedByThisPlayer(Action.Primary)` for held state.
|
|
117
|
+
- Aim at the crosshair: `GetMouseTargetObjectWithTag(Tag.Target)` returns the object under
|
|
118
|
+
the crosshair (or `None` — always check `.Exists()`).
|
|
119
|
+
- Dash (compose motion yourself; last continuous call in a frame wins):
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
var DashTmr : Timer
|
|
123
|
+
event KeyboardDownByThisPlayer[LeftShift] {
|
|
124
|
+
DashTmr.StartCountDown(0.15)
|
|
125
|
+
}
|
|
126
|
+
event OnSpawned {
|
|
127
|
+
forever {
|
|
128
|
+
var dir = GetMoveVectorCamera()
|
|
129
|
+
if not DashTmr.IsDone() {
|
|
130
|
+
MoveInDirectionContinuouslyFacing(dir, 2800)
|
|
131
|
+
} else {
|
|
132
|
+
MoveInDirectionContinuouslyFacing(dir, 900)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- IMPORTANT: `#class player` is auto-attached to every player. Never declare a player
|
|
139
|
+
`object` in the scene, and never spawn one.
|
|
140
|
+
|
|
141
|
+
## 5. Spawning and despawning at runtime
|
|
142
|
+
|
|
143
|
+
Declare a template with `saved`, then spawn instances from code:
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
saved coin {
|
|
147
|
+
display coin : Coin { model = "cylinder" size = (60, 60, 12) color = (1, 0.85, 0.2) }
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- `SpawnObject(SavedObject.coin, Position(x, y, z))` -> returns the new Object.
|
|
152
|
+
- `Destroy()` removes the calling object (or `other.Destroy()`).
|
|
153
|
+
- Census by tag: `GetAllObjectsWithTag(Tag.Coin).Count()`, `NearestObjectWithTag(Tag.X)`,
|
|
154
|
+
`DestroyObjectsWithTag(Tag.X)`.
|
|
155
|
+
- A `saved` clause must contain exactly one root object (or one root group).
|
|
156
|
+
|
|
157
|
+
## 6. Collision and proximity interactions
|
|
158
|
+
|
|
159
|
+
Two robust patterns (see the frame model in the docs for exact timing):
|
|
160
|
+
|
|
161
|
+
1. Proximity poll (best for pickups/damage auras; works with `display` objects too):
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
forever {
|
|
165
|
+
var p = NearestPlayer()
|
|
166
|
+
if p.Exists() and SurfaceGapTo(p) < 70 {
|
|
167
|
+
p.SpawnFloatingText("+1", Vector(1, 0.9, 0.2))
|
|
168
|
+
Destroy()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
2. Contact events on colliding objects (`object`/`physics`, NOT `display`):
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
event Touched {
|
|
177
|
+
if Event.TouchedObject.IsPlayer() { ... }
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Regions fire `event EnterRegion` / `ExitRegion` with `Event.TouchedObject` — use a
|
|
182
|
+
`region` for goal areas, kill floors below the map, checkpoints.
|
|
183
|
+
|
|
184
|
+
## 7. Score and UI text
|
|
185
|
+
|
|
186
|
+
Declare a screen canvas in the world description, then update labels from code:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
screenCanvas HUD {
|
|
190
|
+
textLabel ScoreText { offset = (20, 20) text = "Score: 0" color = (1, 1, 0.85) }
|
|
191
|
+
textLabel MidText { align = (center, center) text = "" color = (1, 1, 0.3) }
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- UI base units assume a 1600x900 window and scale with resize.
|
|
196
|
+
- From code: `ResolveObjectByNameOnce("ScoreText").SetText("Score: " + Score)` — resolve
|
|
197
|
+
once into a var outside the `forever` loop, then call `SetText` on it.
|
|
198
|
+
- A centered label with empty text doubles as the win/lose banner.
|
|
199
|
+
- For panels/menus prefer auto-layout containers (`vStack`/`hStack`/`grid`); every direct
|
|
200
|
+
child of a stack needs an explicit `size`. See the `<ui>` docs section.
|
|
201
|
+
|
|
202
|
+
## 8. Win / lose / restart
|
|
203
|
+
|
|
204
|
+
The manager pattern (used by all three examples):
|
|
205
|
+
|
|
206
|
+
- Keep `var GameOver = false` (or `RoundOver`) on the tagged manager.
|
|
207
|
+
- Losing conditions write it cross-object: `NearestObjectWithTag(Tag.Manager).GameOver = true`.
|
|
208
|
+
- The manager's `forever` loop shows the banner and freezes scoring when set.
|
|
209
|
+
- `event KeyboardDown[R] { RestartGame() }` reloads the whole world (fresh state).
|
|
210
|
+
|
|
211
|
+
## 9. Procedural textures and meshes (quickstarts)
|
|
212
|
+
|
|
213
|
+
`#texture` defines a material usable as `material = "Name"` on any object (verbatim
|
|
214
|
+
docs example; see `<texture_dsl_examples>` in the docs for concrete/wood/metal/brick):
|
|
215
|
+
|
|
216
|
+
```text
|
|
217
|
+
#texture StylizedGrass
|
|
218
|
+
tile_m = 3.0;
|
|
219
|
+
wrap = repeat;
|
|
220
|
+
let n = noise.fbm(uv * 4.0, octaves: 4, seed: 5, period: 4);
|
|
221
|
+
out.color = ramp(n, [0.0: vec3(0.337, 0.376, 0.165), 1.0: vec3(0.408, 0.459, 0.212)]);
|
|
222
|
+
out.orm = orm(occlusion: 0.8, roughness: 0.55, metallic: 0.0);
|
|
223
|
+
#end
|
|
224
|
+
object Ground { model = "cube" bounds_min = (-2000, -2000, -100) bounds_max = (2000, 2000, 0) material = "StylizedGrass" }
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`#mesh` defines a static model usable as `model = "mesh.Name"`:
|
|
228
|
+
|
|
229
|
+
```text
|
|
230
|
+
#mesh Rock
|
|
231
|
+
let base = scale(sphere(r: 90, segments: 10, material: "Stone"), (1.4, 0.85, 0.55));
|
|
232
|
+
out.geo = displace(base, amount: 18, seed: 4);
|
|
233
|
+
#end
|
|
234
|
+
object RockA { model = "mesh.Rock" position = (300, 400, 0) }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Mesh DSL statements end with `;` and use `let`; pair material slots with a flat
|
|
238
|
+
`#texture Stone color=#8B8B84` for quick color-blocking. Full generator list is in the
|
|
239
|
+
`<mesh_dsl_usage>` docs section.
|
|
240
|
+
|
|
241
|
+
## 10. Footguns (each one has burned an AI before)
|
|
242
|
+
|
|
243
|
+
- Colors are 0-1 floats. `color = (255, 200, 50)` renders WHITE, silently.
|
|
244
|
+
- Everything must be procedural. `Ori.model.*` / market URLs hard-error in this build.
|
|
245
|
+
- `display` objects never get `Touched`/`ClickThisObject` events and don't collide.
|
|
246
|
+
Use the proximity-poll pattern, or use `object`/`physics`/`region` kinds.
|
|
247
|
+
- One continuous move per object per frame; the last call wins. Compose speeds yourself
|
|
248
|
+
(see the dash pattern) instead of calling `Move*` twice.
|
|
249
|
+
- `None` is only valid for Object variables; always guard with `.Exists()` before use.
|
|
250
|
+
- `KeyboardDownByThisPlayer` / `ActionDownByThisPlayer` / `IsKeyPressedByThisPlayer` /
|
|
251
|
+
`GetMoveVectorCamera` belong in `#class player` only; plain `KeyboardDown[..]` fires
|
|
252
|
+
for ANY player and belongs in manager-style classes.
|
|
253
|
+
- `for i in 0..8` excludes 8. Statements can share a line with `;`.
|
|
254
|
+
- Don't declare or spawn player objects; `#class player` auto-attaches to each player.
|
|
255
|
+
- Class-level `var T : Timer` declares a timer field; local `var t = Timer()` also works.
|
|
256
|
+
New timers start Done — call `StartCountDown(s)` to arm.
|
|
257
|
+
- Don't rely on cross-object `OnSpawned` ordering; it is unspecified.
|
|
258
|
+
|
|
259
|
+
## 11. Complete example games (engine-tested)
|
|
260
|
+
|
|
261
|
+
Each example below ships as a `.weave` file in `examples/` (same folder/URL as this
|
|
262
|
+
file), is compiled and run by the engine's native test suite on every build, and is
|
|
263
|
+
kept under ~100 lines. To play one: replace the contents of the `#oriverse-weave`
|
|
264
|
+
script tag in `game.html` with the example source.
|
|
265
|
+
|
|
266
|
+
### 11a. Coin Collector (`examples/coin_collector.weave`)
|
|
267
|
+
|
|
268
|
+
Collect 10 spinning coins; HUD counter; win banner + R to restart. Demonstrates: saved
|
|
269
|
+
objects + runtime spawning, tags, proximity pickup, default player controller.
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
// Coin Collector — walk into the 10 spinning coins to collect them all. Press R to replay.
|
|
273
|
+
// Demonstrates: saved objects + runtime spawning, tags, proximity pickup, HUD score text,
|
|
274
|
+
// win + restart flow. Movement uses the engine's default player controller (no #class player).
|
|
275
|
+
|
|
276
|
+
#class Coin
|
|
277
|
+
event OnPreSpawn {
|
|
278
|
+
AddTag(Tag.Coin)
|
|
279
|
+
}
|
|
280
|
+
event OnSpawned {
|
|
281
|
+
forever {
|
|
282
|
+
RotateLeftInstantly(4)
|
|
283
|
+
var p = NearestPlayer()
|
|
284
|
+
if p.Exists() and SurfaceGapTo(p) < 70 {
|
|
285
|
+
p.SpawnFloatingText("+1", Vector(1, 0.9, 0.2))
|
|
286
|
+
Destroy()
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
#end
|
|
291
|
+
|
|
292
|
+
#class GameManager
|
|
293
|
+
var TotalCoins = 10
|
|
294
|
+
var SeenCoins = false
|
|
295
|
+
event OnSpawned {
|
|
296
|
+
for i in 0..TotalCoins {
|
|
297
|
+
var pos = Position(RandomNumber(-1400, 1400), RandomNumber(-1400, 1400), 80)
|
|
298
|
+
SpawnObject(SavedObject.coin, pos)
|
|
299
|
+
}
|
|
300
|
+
var scoreLabel = ResolveObjectByNameOnce("ScoreText")
|
|
301
|
+
var midLabel = ResolveObjectByNameOnce("MidText")
|
|
302
|
+
forever {
|
|
303
|
+
// Spawned coins register their tag on the next tick, so wait until the census
|
|
304
|
+
// has seen at least one coin before treating "0 left" as a win.
|
|
305
|
+
var left = GetAllObjectsWithTag(Tag.Coin).Count()
|
|
306
|
+
var collected = TotalCoins - left
|
|
307
|
+
scoreLabel.SetText("Coins: " + collected + " / " + TotalCoins)
|
|
308
|
+
if left == 0 and collected == TotalCoins and SeenCoins {
|
|
309
|
+
midLabel.SetText("YOU WIN! Press R to play again")
|
|
310
|
+
} else {
|
|
311
|
+
midLabel.SetText("")
|
|
312
|
+
}
|
|
313
|
+
if left > 0 {
|
|
314
|
+
SeenCoins = true
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
event KeyboardDown[R] {
|
|
319
|
+
RestartGame()
|
|
320
|
+
}
|
|
321
|
+
#end
|
|
322
|
+
|
|
323
|
+
saved coin {
|
|
324
|
+
display coin : Coin { model = "cylinder" size = (60, 60, 12) rotation = (90, 0, 0) color = (1, 0.85, 0.2) }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
object Ground {
|
|
328
|
+
model = "cube"
|
|
329
|
+
position = (0, 0, -50)
|
|
330
|
+
size = (4000, 4000, 100)
|
|
331
|
+
color = (0.32, 0.5, 0.34)
|
|
332
|
+
}
|
|
333
|
+
object PillarA { position = (400, 400, 150) size = (100, 100, 300) color = (0.62, 0.58, 0.52) }
|
|
334
|
+
object PillarB { position = (-600, 300, 150) size = (100, 100, 300) color = (0.62, 0.58, 0.52) }
|
|
335
|
+
object PillarC { position = (200, -700, 150) size = (100, 100, 300) color = (0.62, 0.58, 0.52) }
|
|
336
|
+
display manager : GameManager { visible = false }
|
|
337
|
+
|
|
338
|
+
screenCanvas HUD {
|
|
339
|
+
textLabel ScoreText { offset = (20, 20) text = "Coins: 0 / 10" color = (1, 1, 0.85) }
|
|
340
|
+
textLabel MidText { align = (center, center) text = "" color = (1, 1, 0.3) }
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### 11b. Dodge the Falling Blocks (`examples/dodge_blocks.weave`)
|
|
345
|
+
|
|
346
|
+
Red physics cubes rain down; touching one ends the run; survival-seconds score.
|
|
347
|
+
Demonstrates: timed spawner, dynamic physics objects, cross-object state writes,
|
|
348
|
+
lose + restart flow.
|
|
349
|
+
|
|
350
|
+
```text
|
|
351
|
+
// Dodge the Falling Blocks — red cubes rain down; touching one ends the run. Press R to retry.
|
|
352
|
+
// Demonstrates: a hidden manager object, timed spawning with Timer, dynamic physics objects,
|
|
353
|
+
// cross-object variable writes (block -> manager), survival-seconds score, lose + restart flow.
|
|
354
|
+
// Movement uses the engine's default player controller (no #class player).
|
|
355
|
+
|
|
356
|
+
#class FallingBlock
|
|
357
|
+
var LifeTmr : Timer
|
|
358
|
+
event OnSpawned {
|
|
359
|
+
LifeTmr.StartCountDown(8) // despawn landed blocks so the arena stays playable
|
|
360
|
+
forever {
|
|
361
|
+
if LifeTmr.IsDone() {
|
|
362
|
+
Destroy()
|
|
363
|
+
}
|
|
364
|
+
var p = NearestPlayer()
|
|
365
|
+
if p.Exists() and SurfaceGapTo(p) < 5 {
|
|
366
|
+
NearestObjectWithTag(Tag.Manager).GameOver = true
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
#end
|
|
371
|
+
|
|
372
|
+
#class GameManager
|
|
373
|
+
var GameOver = false
|
|
374
|
+
var Score = 0
|
|
375
|
+
event OnPreSpawn {
|
|
376
|
+
AddTag(Tag.Manager)
|
|
377
|
+
}
|
|
378
|
+
event OnSpawned {
|
|
379
|
+
var secondTmr = Timer()
|
|
380
|
+
var dropTmr = Timer()
|
|
381
|
+
var scoreLabel = ResolveObjectByNameOnce("ScoreText")
|
|
382
|
+
var midLabel = ResolveObjectByNameOnce("MidText")
|
|
383
|
+
forever {
|
|
384
|
+
if GameOver {
|
|
385
|
+
midLabel.SetText("GAME OVER — survived " + Score + " s. Press R to retry")
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
388
|
+
if secondTmr.IsDone() {
|
|
389
|
+
secondTmr.StartCountDown(1)
|
|
390
|
+
Score += 1
|
|
391
|
+
scoreLabel.SetText("Survived: " + Score + " s")
|
|
392
|
+
}
|
|
393
|
+
if dropTmr.IsDone() {
|
|
394
|
+
dropTmr.StartCountDown(0.35)
|
|
395
|
+
var pos = Position(RandomNumber(-900, 900), RandomNumber(-900, 900), 1300)
|
|
396
|
+
SpawnObject(SavedObject.block, pos)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
event KeyboardDown[R] {
|
|
401
|
+
RestartGame()
|
|
402
|
+
}
|
|
403
|
+
#end
|
|
404
|
+
|
|
405
|
+
saved block {
|
|
406
|
+
physics block : FallingBlock { model = "cube" size = (120, 120, 120) color = (0.85, 0.25, 0.2) }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
object Ground {
|
|
410
|
+
model = "cube"
|
|
411
|
+
position = (0, 0, -50)
|
|
412
|
+
size = (2200, 2200, 100)
|
|
413
|
+
color = (0.4, 0.42, 0.5)
|
|
414
|
+
}
|
|
415
|
+
display manager : GameManager { visible = false }
|
|
416
|
+
|
|
417
|
+
screenCanvas HUD {
|
|
418
|
+
textLabel ScoreText { offset = (20, 20) text = "Survived: 0 s" color = (1, 1, 1) }
|
|
419
|
+
textLabel MidText { align = (center, center) text = "" color = (1, 0.45, 0.35) }
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### 11c. Target Range (`examples/target_range.weave`)
|
|
424
|
+
|
|
425
|
+
Click the floating spheres through the crosshair; 30-second round. Demonstrates: custom
|
|
426
|
+
`#class player` (movement + jump + shooting), crosshair ray queries, user-defined funcs
|
|
427
|
+
called cross-object, TeleportTo respawn, countdown timer.
|
|
428
|
+
|
|
429
|
+
```text
|
|
430
|
+
// Target Range — aim at the floating spheres and left-click to score. 30-second round, R restarts.
|
|
431
|
+
// Demonstrates: a custom #class player (movement + jump + click-to-shoot with the crosshair ray),
|
|
432
|
+
// user-defined funcs called cross-object, TeleportTo respawn, countdown round timer, HUD score.
|
|
433
|
+
|
|
434
|
+
#class player
|
|
435
|
+
event OnSpawned {
|
|
436
|
+
forever {
|
|
437
|
+
var dir = GetMoveVectorCamera()
|
|
438
|
+
MoveInDirectionContinuouslyFacing(dir, 900)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
event KeyboardDownByThisPlayer[Space] {
|
|
442
|
+
if IsOnGround() {
|
|
443
|
+
Jump(1000)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
event ActionDownByThisPlayer[Primary] {
|
|
447
|
+
var t = GetMouseTargetObjectWithTag(Tag.Target)
|
|
448
|
+
if t.Exists() and not NearestObjectWithTag(Tag.Manager).RoundOver {
|
|
449
|
+
t.Pop()
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
#end
|
|
453
|
+
|
|
454
|
+
#class Target
|
|
455
|
+
func Pop() {
|
|
456
|
+
SpawnFloatingText("+1", Vector(0.3, 1, 0.4))
|
|
457
|
+
NearestObjectWithTag(Tag.Manager).Hits += 1
|
|
458
|
+
TeleportTo(Position(RandomNumber(-1200, 1200), RandomNumber(500, 1600), RandomNumber(100, 500)))
|
|
459
|
+
}
|
|
460
|
+
event OnPreSpawn {
|
|
461
|
+
AddTag(Tag.Target)
|
|
462
|
+
}
|
|
463
|
+
#end
|
|
464
|
+
|
|
465
|
+
#class GameManager
|
|
466
|
+
var Hits = 0
|
|
467
|
+
var TimeLeft = 30
|
|
468
|
+
var RoundOver = false
|
|
469
|
+
event OnPreSpawn {
|
|
470
|
+
AddTag(Tag.Manager)
|
|
471
|
+
}
|
|
472
|
+
event OnSpawned {
|
|
473
|
+
var secondTmr = Timer()
|
|
474
|
+
secondTmr.StartCountDown(1)
|
|
475
|
+
var scoreLabel = ResolveObjectByNameOnce("ScoreText")
|
|
476
|
+
var midLabel = ResolveObjectByNameOnce("MidText")
|
|
477
|
+
forever {
|
|
478
|
+
scoreLabel.SetText("Hits: " + Hits + " Time: " + TimeLeft)
|
|
479
|
+
if RoundOver {
|
|
480
|
+
continue
|
|
481
|
+
}
|
|
482
|
+
if secondTmr.IsDone() {
|
|
483
|
+
secondTmr.StartCountDown(1)
|
|
484
|
+
TimeLeft -= 1
|
|
485
|
+
if TimeLeft <= 0 {
|
|
486
|
+
RoundOver = true
|
|
487
|
+
midLabel.SetText("TIME! Final score: " + Hits + " — press R to retry")
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
event KeyboardDown[R] {
|
|
493
|
+
RestartGame()
|
|
494
|
+
}
|
|
495
|
+
#end
|
|
496
|
+
|
|
497
|
+
object Ground {
|
|
498
|
+
model = "cube"
|
|
499
|
+
position = (0, 0, -50)
|
|
500
|
+
size = (4000, 4000, 100)
|
|
501
|
+
color = (0.45, 0.4, 0.35)
|
|
502
|
+
}
|
|
503
|
+
object TargetA : Target { model = "sphere" position = (-600, 900, 250) size = 90 color = (1, 0.3, 0.3) }
|
|
504
|
+
object TargetB : Target { model = "sphere" position = (0, 1200, 350) size = 90 color = (1, 0.3, 0.3) }
|
|
505
|
+
object TargetC : Target { model = "sphere" position = (700, 800, 200) size = 90 color = (1, 0.3, 0.3) }
|
|
506
|
+
display manager : GameManager { visible = false }
|
|
507
|
+
|
|
508
|
+
screenCanvas HUD {
|
|
509
|
+
textLabel ScoreText { offset = (20, 20) text = "Hits: 0 Time: 30" color = (1, 1, 0.85) }
|
|
510
|
+
textLabel MidText { align = (center, center) text = "" color = (0.4, 1, 0.5) }
|
|
511
|
+
}
|
|
512
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Coin Collector — walk into the 10 spinning coins to collect them all. Press R to replay.
|
|
2
|
+
// Demonstrates: saved objects + runtime spawning, tags, proximity pickup, HUD score text,
|
|
3
|
+
// win + restart flow. Movement uses the engine's default player controller (no #class player).
|
|
4
|
+
|
|
5
|
+
#class Coin
|
|
6
|
+
event OnPreSpawn {
|
|
7
|
+
AddTag(Tag.Coin)
|
|
8
|
+
}
|
|
9
|
+
event OnSpawned {
|
|
10
|
+
forever {
|
|
11
|
+
RotateLeftInstantly(4)
|
|
12
|
+
var p = NearestPlayer()
|
|
13
|
+
if p.Exists() and SurfaceGapTo(p) < 70 {
|
|
14
|
+
p.SpawnFloatingText("+1", Vector(1, 0.9, 0.2))
|
|
15
|
+
Destroy()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
#end
|
|
20
|
+
|
|
21
|
+
#class GameManager
|
|
22
|
+
var TotalCoins = 10
|
|
23
|
+
var SeenCoins = false
|
|
24
|
+
event OnSpawned {
|
|
25
|
+
for i in 0..TotalCoins {
|
|
26
|
+
var pos = Position(RandomNumber(-1400, 1400), RandomNumber(-1400, 1400), 80)
|
|
27
|
+
SpawnObject(SavedObject.coin, pos)
|
|
28
|
+
}
|
|
29
|
+
var scoreLabel = ResolveObjectByNameOnce("ScoreText")
|
|
30
|
+
var midLabel = ResolveObjectByNameOnce("MidText")
|
|
31
|
+
forever {
|
|
32
|
+
// Spawned coins register their tag on the next tick, so wait until the census
|
|
33
|
+
// has seen at least one coin before treating "0 left" as a win.
|
|
34
|
+
var left = GetAllObjectsWithTag(Tag.Coin).Count()
|
|
35
|
+
var collected = TotalCoins - left
|
|
36
|
+
scoreLabel.SetText("Coins: " + collected + " / " + TotalCoins)
|
|
37
|
+
if left == 0 and collected == TotalCoins and SeenCoins {
|
|
38
|
+
midLabel.SetText("YOU WIN! Press R to play again")
|
|
39
|
+
} else {
|
|
40
|
+
midLabel.SetText("")
|
|
41
|
+
}
|
|
42
|
+
if left > 0 {
|
|
43
|
+
SeenCoins = true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
event KeyboardDown[R] {
|
|
48
|
+
RestartGame()
|
|
49
|
+
}
|
|
50
|
+
#end
|
|
51
|
+
|
|
52
|
+
saved coin {
|
|
53
|
+
display coin : Coin { model = "cylinder" size = (60, 60, 12) rotation = (90, 0, 0) color = (1, 0.85, 0.2) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
object Ground {
|
|
57
|
+
model = "cube"
|
|
58
|
+
position = (0, 0, -50)
|
|
59
|
+
size = (4000, 4000, 100)
|
|
60
|
+
color = (0.32, 0.5, 0.34)
|
|
61
|
+
}
|
|
62
|
+
object PillarA { position = (400, 400, 150) size = (100, 100, 300) color = (0.62, 0.58, 0.52) }
|
|
63
|
+
object PillarB { position = (-600, 300, 150) size = (100, 100, 300) color = (0.62, 0.58, 0.52) }
|
|
64
|
+
object PillarC { position = (200, -700, 150) size = (100, 100, 300) color = (0.62, 0.58, 0.52) }
|
|
65
|
+
display manager : GameManager { visible = false }
|
|
66
|
+
|
|
67
|
+
screenCanvas HUD {
|
|
68
|
+
textLabel ScoreText { offset = (20, 20) text = "Coins: 0 / 10" color = (1, 1, 0.85) }
|
|
69
|
+
textLabel MidText { align = (center, center) text = "" color = (1, 1, 0.3) }
|
|
70
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Dodge the Falling Blocks — red cubes rain down; touching one ends the run. Press R to retry.
|
|
2
|
+
// Demonstrates: a hidden manager object, timed spawning with Timer, dynamic physics objects,
|
|
3
|
+
// cross-object variable writes (block -> manager), survival-seconds score, lose + restart flow.
|
|
4
|
+
// Movement uses the engine's default player controller (no #class player).
|
|
5
|
+
|
|
6
|
+
#class FallingBlock
|
|
7
|
+
var LifeTmr : Timer
|
|
8
|
+
event OnSpawned {
|
|
9
|
+
LifeTmr.StartCountDown(8) // despawn landed blocks so the arena stays playable
|
|
10
|
+
forever {
|
|
11
|
+
if LifeTmr.IsDone() {
|
|
12
|
+
Destroy()
|
|
13
|
+
}
|
|
14
|
+
var p = NearestPlayer()
|
|
15
|
+
if p.Exists() and SurfaceGapTo(p) < 5 {
|
|
16
|
+
NearestObjectWithTag(Tag.Manager).GameOver = true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
#end
|
|
21
|
+
|
|
22
|
+
#class GameManager
|
|
23
|
+
var GameOver = false
|
|
24
|
+
var Score = 0
|
|
25
|
+
event OnPreSpawn {
|
|
26
|
+
AddTag(Tag.Manager)
|
|
27
|
+
}
|
|
28
|
+
event OnSpawned {
|
|
29
|
+
var secondTmr = Timer()
|
|
30
|
+
var dropTmr = Timer()
|
|
31
|
+
var scoreLabel = ResolveObjectByNameOnce("ScoreText")
|
|
32
|
+
var midLabel = ResolveObjectByNameOnce("MidText")
|
|
33
|
+
forever {
|
|
34
|
+
if GameOver {
|
|
35
|
+
midLabel.SetText("GAME OVER — survived " + Score + " s. Press R to retry")
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
if secondTmr.IsDone() {
|
|
39
|
+
secondTmr.StartCountDown(1)
|
|
40
|
+
Score += 1
|
|
41
|
+
scoreLabel.SetText("Survived: " + Score + " s")
|
|
42
|
+
}
|
|
43
|
+
if dropTmr.IsDone() {
|
|
44
|
+
dropTmr.StartCountDown(0.35)
|
|
45
|
+
var pos = Position(RandomNumber(-900, 900), RandomNumber(-900, 900), 1300)
|
|
46
|
+
SpawnObject(SavedObject.block, pos)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
event KeyboardDown[R] {
|
|
51
|
+
RestartGame()
|
|
52
|
+
}
|
|
53
|
+
#end
|
|
54
|
+
|
|
55
|
+
saved block {
|
|
56
|
+
physics block : FallingBlock { model = "cube" size = (120, 120, 120) color = (0.85, 0.25, 0.2) }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
object Ground {
|
|
60
|
+
model = "cube"
|
|
61
|
+
position = (0, 0, -50)
|
|
62
|
+
size = (2200, 2200, 100)
|
|
63
|
+
color = (0.4, 0.42, 0.5)
|
|
64
|
+
}
|
|
65
|
+
display manager : GameManager { visible = false }
|
|
66
|
+
|
|
67
|
+
screenCanvas HUD {
|
|
68
|
+
textLabel ScoreText { offset = (20, 20) text = "Survived: 0 s" color = (1, 1, 1) }
|
|
69
|
+
textLabel MidText { align = (center, center) text = "" color = (1, 0.45, 0.35) }
|
|
70
|
+
}
|