@origingame/engine 0.0.0-stage → 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/bin/og.mjs +25 -0
- package/dist/web/origin-engine.d.ts +1233 -0
- package/dist/web/origin-engine.js +8628 -0
- package/dist/web/origin-engine_bg.wasm +0 -0
- package/dist/web/origin-engine_bg.wasm.d.ts +366 -0
- package/dist/web-publish/origin-engine-lanes.js +8509 -0
- package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish/origin-engine.d.ts +1176 -0
- package/dist/web-publish/origin-engine.js +8313 -0
- package/dist/web-publish/origin-engine_bg.wasm +0 -0
- package/dist/web-publish/origin-engine_bg.wasm.d.ts +347 -0
- package/dist/web-publish/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
- package/dist/web-publish-2d/origin-engine-lanes.js +8509 -0
- package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine.d.ts +1176 -0
- package/dist/web-publish-2d/origin-engine.js +8313 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +347 -0
- package/dist/web-publish-2d/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
- package/knowledge/README.md +31 -0
- package/knowledge/knowledge.json +13 -0
- package/knowledge/looks/catalogue.json +1871 -0
- package/knowledge/package.json +33 -0
- package/knowledge/rules/platform.json +329 -0
- package/knowledge/scaffold/README.md +27 -0
- package/knowledge/scaffold/distribution.json +48 -0
- package/knowledge/scaffold/origin-engine.json +19 -0
- package/knowledge/scaffold/template/.origin/setup +14 -0
- package/knowledge/scaffold/template/LICENSES.md +23 -0
- package/knowledge/scaffold/template/README.md +37 -0
- package/knowledge/scaffold/template/assets/ai/guard.ogt +48 -0
- package/knowledge/scaffold/template/assets/audio/click.ogt +9 -0
- package/knowledge/scaffold/template/assets/fonts/NotoSansSC-Regular-subset.ttf.ogt +12 -0
- package/knowledge/scaffold/template/assets/fonts/OFL.txt +93 -0
- package/knowledge/scaffold/template/assets/looks/origin-game.ogt +12 -0
- package/knowledge/scaffold/template/assets/nav/yard.ogt +27 -0
- package/knowledge/scaffold/template/assets/scenes/main.ogt +34 -0
- package/knowledge/scaffold/template/assets/strings/en.ogt +14 -0
- package/knowledge/scaffold/template/assets/textures/stone_n.png.ogt +7 -0
- package/knowledge/scaffold/template/gitattributes +22 -0
- package/knowledge/scaffold/template/gitignore +8 -0
- package/knowledge/scaffold/template/package.json +18 -0
- package/knowledge/scaffold/template/project.ogt +25 -0
- package/knowledge/scaffold/template/src/main.ts +246 -0
- package/knowledge/scaffold/template/tests/guard.og.ts +67 -0
- package/knowledge/scaffold/template/tests/smoke.og.ts +43 -0
- package/knowledge/scaffold/template/tsconfig.json +17 -0
- package/knowledge/scripts/lib.ts +207 -0
- package/knowledge/scripts/mount.ts +210 -0
- package/knowledge/scripts/pack.ts +25 -0
- package/knowledge/scripts/scaffold.ts +72 -0
- package/knowledge/scripts/validate.ts +749 -0
- package/knowledge/skills/og-ai/SKILL.md +386 -0
- package/knowledge/skills/og-audio/SKILL.md +191 -0
- package/knowledge/skills/og-build/SKILL.md +330 -0
- package/knowledge/skills/og-core/SKILL.md +285 -0
- package/knowledge/skills/og-feel/SKILL.md +385 -0
- package/knowledge/skills/og-game/SKILL.md +1479 -0
- package/knowledge/skills/og-i18n/SKILL.md +220 -0
- package/knowledge/skills/og-platform-sdk/SKILL.md +199 -0
- package/knowledge/skills/og-taste/SKILL.md +537 -0
- package/knowledge/skills/og-ui/SKILL.md +444 -0
- package/knowledge/wiki/ai.md +627 -0
- package/knowledge/wiki/anim.md +1187 -0
- package/knowledge/wiki/atmosphere.md +636 -0
- package/knowledge/wiki/audio.md +732 -0
- package/knowledge/wiki/camera.md +711 -0
- package/knowledge/wiki/commands.md +357 -0
- package/knowledge/wiki/game.md +220 -0
- package/knowledge/wiki/i18n.md +213 -0
- package/knowledge/wiki/index.md +1027 -0
- package/knowledge/wiki/input.md +1714 -0
- package/knowledge/wiki/load.md +1280 -0
- package/knowledge/wiki/math.md +166 -0
- package/knowledge/wiki/nav.md +1154 -0
- package/knowledge/wiki/physics.md +3142 -0
- package/knowledge/wiki/platform.md +857 -0
- package/knowledge/wiki/probe.md +1692 -0
- package/knowledge/wiki/reflect.md +285 -0
- package/knowledge/wiki/render.md +3511 -0
- package/knowledge/wiki/rng.md +393 -0
- package/knowledge/wiki/stream.md +376 -0
- package/knowledge/wiki/system.md +197 -0
- package/knowledge/wiki/systems.md +43 -0
- package/knowledge/wiki/terrain.md +727 -0
- package/knowledge/wiki/tilemap.md +422 -0
- package/knowledge/wiki/time.md +150 -0
- package/knowledge/wiki/transform.md +230 -0
- package/knowledge/wiki/tween.md +580 -0
- package/knowledge/wiki/ui.md +8000 -0
- package/knowledge/wiki/world.md +909 -0
- package/package.json +68 -4
- package/schema/api.schema.json +29675 -0
- package/shell/README.md +22 -0
- package/shell/brand/README.md +23 -0
- package/shell/brand/icon-192.png +0 -0
- package/shell/brand/icon-512.png +0 -0
- package/shell/brand/mark-on-paper.svg +6 -0
- package/shell/brand/origin-mark.svg +5 -0
- package/shell/brand/origin-motion.css +251 -0
- package/shell/brand/tokens.css +20 -0
- package/shell/brand.ts +132 -0
- package/shell/page.ts +715 -0
- package/shell/platform.ts +203 -0
- package/shell/player.ts +238 -0
- package/shell/shell.css +185 -0
- package/shell/sw.ts +177 -0
- package/shell/transport.ts +515 -0
- package/shell/wire.ts +369 -0
- package/src/e6.ts +69 -0
- package/src/env.d.ts +100 -0
- package/src/generated/abi.ts +980 -0
- package/src/index.ts +193 -0
- package/src/node/audio.ts +86 -0
- package/src/node/build.ts +109 -0
- package/src/node/device.ts +981 -0
- package/src/node/doctor.ts +60 -0
- package/src/node/index.ts +102 -0
- package/src/node/platform.ts +24 -0
- package/src/node/playtest-run.ts +249 -0
- package/src/node/png.ts +130 -0
- package/src/node/polish.ts +976 -0
- package/src/node/run.ts +373 -0
- package/src/node/web-build.ts +808 -0
- package/src/p2.ts +35 -0
- package/src/platform-local.ts +211 -0
- package/src/playtest.ts +131 -0
- package/src/runtime.ts +1509 -0
- package/src/systems/cameras.ts +247 -0
- package/src/ui-kit/bind.ts +141 -0
- package/src/ui-kit/huds.ts +303 -0
- package/src/ui-kit/index.ts +36 -0
- package/src/ui-kit/menus.ts +187 -0
- package/src/ui-kit/settings.ts +131 -0
- package/src/ui-kit/store.ts +222 -0
- package/src/ui-kit/story.ts +89 -0
- package/src/ui-kit/util.ts +106 -0
- package/src/web/a11y.ts +429 -0
- package/src/web/audio-worklet.ts +193 -0
- package/src/web/audio.ts +307 -0
- package/src/web/basis.ts +239 -0
- package/src/web/gamepad.ts +123 -0
- package/src/web/host.ts +166 -0
- package/src/web/input.ts +237 -0
- package/src/web/lanes.ts +21 -0
- package/src/web/play.ts +20 -0
- package/src/web/rpc-ring.ts +110 -0
- package/src/web/settle.ts +122 -0
- package/src/web/shell.ts +643 -0
- package/src/web/text-input.ts +248 -0
- package/src/web/worker.ts +1002 -0
- package/types/bindings.json +13654 -0
- package/types/index.d.ts +20090 -0
- package/vendor/basis/LICENSE +201 -0
- package/vendor/basis/NOTICE +15 -0
- package/vendor/basis/README.md +18 -0
- package/vendor/basis/basis_transcoder.js +19 -0
- package/vendor/basis/basis_transcoder.wasm +0 -0
- package/README.md +0 -3
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: og-feel
|
|
3
|
+
description: Game feel for an AI making a game on Origin Engine — input that answers on the same tick, anticipation and recovery, hit stop by impact weight, screen shake trauma and when not to shake, squash and stretch amounts, easing by intent, floating text, camera look-ahead, dead zones and zones, 2D frame counts and rates by action, sprite sorting, parallax ratios, platformer and character controller tunables by genre, and a checklist proved with og run --commands captures and world dumps. Use when a game works but feels flat, when adding hits, jumps, pickups or a camera, or before calling a 2D game done.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
A game feels right when every input shows an answer at once, every impact
|
|
7
|
+
has weight, and the camera helps the player read what comes next. On
|
|
8
|
+
Origin Engine all of it is deterministic data: tweens, flipbooks, the time
|
|
9
|
+
scale, camera trauma and floating texts live in the world's state blocks,
|
|
10
|
+
so a replay shows the same juice and `og test` can check it. This skill's
|
|
11
|
+
modules are `reference/time.md` (time scale, hit stop), `reference/tween.md`,
|
|
12
|
+
`reference/camera.md` and `reference/systems.md` (the camera controllers the
|
|
13
|
+
engine ships in TypeScript); sprites, flipbooks, tilemaps and floating text
|
|
14
|
+
are og-game's (§8, §10). This skill is how much of each to use, and how to
|
|
15
|
+
prove it.
|
|
16
|
+
|
|
17
|
+
## What works
|
|
18
|
+
|
|
19
|
+
In Hollowbell (`examples/hollowbell`, a 2D platformer written from this
|
|
20
|
+
package alone) `og polish` measured every input answered within a tick and every damage moment with hit stop,
|
|
21
|
+
shake, a number and a sound (feel 92; Lantern Vale, a 3D adventure, 90).
|
|
22
|
+
An `og.CameraZone` held its boss arena, and flipbook frame events put the
|
|
23
|
+
footsteps and the strikes on their exact frames. The Kestrel Stair
|
|
24
|
+
(`examples/kestrel-stair`, a 3D third-person game) measured the same in
|
|
25
|
+
`og polish` (input latency and hit feedback 100; feel 79, the rest lost to
|
|
26
|
+
frame time): hit stop by weight, a shake, a number and a sound on every
|
|
27
|
+
strike landing on its clip's `hit` marker, and the 3D character controller
|
|
28
|
+
at full speed every tick (3.2 m/s walking) over level ground, an 18° ramp,
|
|
29
|
+
a 34° path and stairs, refusing a 63° scree.
|
|
30
|
+
|
|
31
|
+
## 1. Answer on the same tick
|
|
32
|
+
|
|
33
|
+
- Read input in a system and act in **the same tick**: start the jump, the
|
|
34
|
+
swing, the step. Never queue a press for "the next frame" or wait for an
|
|
35
|
+
animation to finish before moving. At 60 Hz one tick is 17 ms; two ticks
|
|
36
|
+
of delay are felt.
|
|
37
|
+
- Buffer early presses (a jump pressed up to 6 ticks before landing jumps
|
|
38
|
+
on landing) and allow coyote time (a jump up to 6 ticks after leaving a
|
|
39
|
+
ledge). Keep the counters in a component so snapshots carry them.
|
|
40
|
+
- Move gameplay by `time.scaledDt` (the systems' `time`): slow motion and
|
|
41
|
+
hit stop then reach your code the same way they reach physics, og.anim,
|
|
42
|
+
flipbooks and tweens. Use `time.dt` only for what must keep its pace
|
|
43
|
+
(menus, input buffers, the UI).
|
|
44
|
+
- Prove it: `0.5:key_down:Space;0.517:dump_entity:hero:a.json` — the dump
|
|
45
|
+
of the tick after the press already shows the new velocity or clip.
|
|
46
|
+
|
|
47
|
+
## 2. Anticipation, action, recovery
|
|
48
|
+
|
|
49
|
+
Every action has three beats; the action itself is the shortest.
|
|
50
|
+
|
|
51
|
+
| Beat | Frames at 60 Hz | How |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| Anticipation | 0 for player-driven moves (latency wins), 4–12 for enemies and big attacks | a wind-up clip or `og.tween.to` with `backIn` on scale or position |
|
|
54
|
+
| Action | 1–4 | the frame the hit lands: fire the hit on a flipbook `event` (`event 2 "hit"`), not on a timer |
|
|
55
|
+
| Recovery | 6–20 | a recover clip, `og.tween.squash` easing back, a settle with `backOut` |
|
|
56
|
+
|
|
57
|
+
The player's own attacks skip anticipation; telegraph enemy attacks with
|
|
58
|
+
it (a flash, a crouch, a 0.25–0.5 s wind-up) so the player can react.
|
|
59
|
+
|
|
60
|
+
## 3. Hit stop by impact weight
|
|
61
|
+
|
|
62
|
+
`og.time.hitStop(world, ticks)` freezes physics, og.anim, flipbooks and
|
|
63
|
+
scaled tweens while input, the UI, unscaled tweens and camera shake keep
|
|
64
|
+
moving. It never shortens one already running.
|
|
65
|
+
|
|
66
|
+
| Impact | Ticks at 60 Hz |
|
|
67
|
+
|---|---|
|
|
68
|
+
| Light (a jab, a coin, a small enemy hit) | 2–3 |
|
|
69
|
+
| Medium (a sword hit, a stomp, a 4-match) | 4–6 |
|
|
70
|
+
| Heavy (a charged attack, a boss hit, a big cascade) | 8–12 |
|
|
71
|
+
| Finishing blow, a death | 12–20, paired with slow motion |
|
|
72
|
+
|
|
73
|
+
- Pair a hit stop with a flash (`og.tween.flash`, 0.08–0.15 s) and a small
|
|
74
|
+
shake; the stop is what sells the contact. The flash takes a sprite, a
|
|
75
|
+
mesh or a glTF instance (its og.Model: that instance alone flashes, the
|
|
76
|
+
others sharing its materials do not). On an og.Model the tween sets and fades
|
|
77
|
+
its `flash` (the lit colour mixed towards `flashColor`); its `tint`
|
|
78
|
+
colours that one instance for as long as it is set.
|
|
79
|
+
- Pair it with a knock-back too: on the hit's tick set the
|
|
80
|
+
character's `og.PlatformerController.impulse` to `{ x: away * 6, y: 7 }`
|
|
81
|
+
(m/s; taken once on the next step, so it flies when the stop ends) and
|
|
82
|
+
hold its `moveX` at 0 for 10–15 ticks; a dynamic body takes it as
|
|
83
|
+
`og.Velocity` (`linear` away and up).
|
|
84
|
+
- Slow motion: `og.time.setScale(world, 0.3)` for a last kill or a near
|
|
85
|
+
miss, then back to 1 after 0.5–1 s of real time (count ticks, not
|
|
86
|
+
scaled time). Never leave the scale below 1 by accident: assert it in a
|
|
87
|
+
test.
|
|
88
|
+
- Where it lives: `world.resource(og.Time)` and a system's `time` carry
|
|
89
|
+
`scale`, `hitStop`, `scaledDt` and `scaledElapsed` beside `tick`, `dt`,
|
|
90
|
+
`elapsed` and `rate`. The world keeps the scale in a second resource,
|
|
91
|
+
`og.TimeScale` (`scale`, `hitStop`, `elapsed`), which is what a world
|
|
92
|
+
dump lists; read og.Time in code, and `get:og.Time` (every field above)
|
|
93
|
+
or `set:og.TimeScale.scale=0.5` from a script.
|
|
94
|
+
- Do not hit-stop on every bullet of a fast weapon or every coin of a
|
|
95
|
+
stream; cap it to one stop per 10 ticks.
|
|
96
|
+
|
|
97
|
+
## 4. Screen shake: trauma, not offsets
|
|
98
|
+
|
|
99
|
+
`og.camera.shake(world, trauma)` adds trauma (0..1) to og.Camera, which
|
|
100
|
+
decays by `traumaDecay` (1.5 per second) on the unscaled tick. The offset
|
|
101
|
+
and roll grow with trauma squared, so small values stay subtle.
|
|
102
|
+
|
|
103
|
+
| Event | Trauma |
|
|
104
|
+
|---|---|
|
|
105
|
+
| A light hit, landing from a high jump | 0.1–0.2 |
|
|
106
|
+
| A medium hit taken, a big match | 0.25–0.4 |
|
|
107
|
+
| An explosion, a boss slam, the player's death | 0.5–0.8 |
|
|
108
|
+
| 1.0 | almost never: it is nauseating |
|
|
109
|
+
|
|
110
|
+
- Shake the camera for what hurts or hits hard; **never** for UI, pickups,
|
|
111
|
+
menus, steady events (engine hum, rain), or every shot of a rapid weapon.
|
|
112
|
+
A game that shakes all the time has no shake left for the moments that
|
|
113
|
+
count. A refused press or a wrong answer shakes the element, not the
|
|
114
|
+
camera (`og.ui.shake`, og-ui §6).
|
|
115
|
+
- Prefer `og.camera.kick(world, direction, distance)` (0.1–0.3 units in the
|
|
116
|
+
direction of the force) for directional impacts and recoil, and
|
|
117
|
+
`og.camera.punch(world, 0.05–0.15)` for a zoom-in on a crit or a slam.
|
|
118
|
+
They are visual only (og.Camera's position stays put, the frame moves),
|
|
119
|
+
so they never change the simulation.
|
|
120
|
+
- Offer a reduced-motion setting that scales trauma, kick and punch by 0.3.
|
|
121
|
+
|
|
122
|
+
## 5. Squash and stretch
|
|
123
|
+
|
|
124
|
+
`og.tween.squash(world, { entity, impact, duration, ease })` sets
|
|
125
|
+
og.Transform.scale to rest × impact on the tick it is called and eases
|
|
126
|
+
back (default backOut, 0.18 s). Keep the area roughly constant: x × y ≈ 1.
|
|
127
|
+
On a 3D model give z too, bulging with x so the volume holds: a hit taken
|
|
128
|
+
is `{ x: 1.15, y: 0.8, z: 1.15 }`. Left out (or 0), z stays at rest, which
|
|
129
|
+
is what a sprite wants.
|
|
130
|
+
|
|
131
|
+
| Moment | impact (x, y) | duration |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| Landing (small hop) | (1.15, 0.85) | 0.12 s |
|
|
134
|
+
| Landing (big fall) | (1.3, 0.7) | 0.18 s |
|
|
135
|
+
| Take-off | (0.85, 1.15) | 0.1 s |
|
|
136
|
+
| Hit taken | (1.2, 0.8) | 0.15 s, with a flash |
|
|
137
|
+
| Pickup collected (on the pickup) | (1.4, 1.4) then shrink to 0 | 0.15 s |
|
|
138
|
+
| UI button press (the skin's `press` does it for og.ui) | (0.92, 0.92) | 0.08 s, unscaled |
|
|
139
|
+
|
|
140
|
+
Do not squash rigid things (crates, walls, the camera's target in a
|
|
141
|
+
cutscene) or anything more than once per 6 ticks.
|
|
142
|
+
|
|
143
|
+
## 6. Easing by intent
|
|
144
|
+
|
|
145
|
+
`og.tween.to(world, { entity, component, field: "position.y", to: [2],
|
|
146
|
+
duration, ease, delay, loops, yoyo })` animates any numeric component field;
|
|
147
|
+
`og.tween.sequence` and `og.tween.parallel` chain them, `og.TweenCompleted`
|
|
148
|
+
reports the end, and `unscaled: true` keeps a tween going through slow
|
|
149
|
+
motion and hit stop (UI motion). `og.tween.squash` (§5) and
|
|
150
|
+
`og.tween.flash` are the presets.
|
|
151
|
+
|
|
152
|
+
| Intent | Ease |
|
|
153
|
+
|---|---|
|
|
154
|
+
| Things arriving and settling (UI in, a gem landing) | `cubicOut`, `quartOut` |
|
|
155
|
+
| Things leaving (UI out, a dying enemy fading) | `cubicIn`, `expoIn` |
|
|
156
|
+
| A deliberate move from A to B (camera, a swap) | `cubicInOut` |
|
|
157
|
+
| A pop, a button, squash recovery | `backOut` |
|
|
158
|
+
| A wind-up (anticipation) | `backIn` |
|
|
159
|
+
| Springy, cartoon impact | `elasticOut` (sparingly: it rings for a while) |
|
|
160
|
+
| A drop that lands | `bounceOut` |
|
|
161
|
+
| Constant motion, timers, scrolling, colour cycles | `linear` |
|
|
162
|
+
| Most small motion when unsure | `quadOut` (the default) |
|
|
163
|
+
|
|
164
|
+
Durations: 0.1–0.2 s for feedback, 0.12–0.3 s for UI (the skin's
|
|
165
|
+
durations, og-taste §5), 0.2–0.35 s for swaps, 0.4–0.8 s for camera moves,
|
|
166
|
+
reveals and screen transitions (up to 0.7 s). Longer feels sluggish unless
|
|
167
|
+
it is a cutscene or a reward.
|
|
168
|
+
|
|
169
|
+
## 7. Floating text
|
|
170
|
+
|
|
171
|
+
`og.render.floatingText(world, { text, position, ... })` pops a text that
|
|
172
|
+
rises, drifts and fades by itself (world state, deterministic).
|
|
173
|
+
|
|
174
|
+
- Damage and score numbers above the hit point, not on the HUD: the eye is
|
|
175
|
+
already there. Pickups: what was gained (`+1`, `key`), short.
|
|
176
|
+
- Colour by meaning and keep it consistent: white damage dealt, red damage
|
|
177
|
+
taken, yellow gold, green heal. Always an outline (the default 0.6) so it
|
|
178
|
+
reads on any background.
|
|
179
|
+
- Crits and combos: `crit: true` (critScale 1.6 and a stronger pop); the
|
|
180
|
+
biggest number on screen should be the most important.
|
|
181
|
+
- Lifetime 0.6–1.0 s, rise 0.8–1.5 units, spread 0.2–0.4 so stacked hits do
|
|
182
|
+
not overlap. At most 6–8 on screen: sum rapid hits into one number.
|
|
183
|
+
|
|
184
|
+
## 8. Camera: look-ahead, dead zones, zones, rails
|
|
185
|
+
|
|
186
|
+
- `og.Follow2D` with a dead zone of 0.5–1.5 units horizontally (smaller
|
|
187
|
+
vertically) so small moves do not swim the screen; `smoothTime` 0.1–0.2 s,
|
|
188
|
+
`smoothTimeY` 0.2–0.35 s in platformers (follow jumps lazily, land
|
|
189
|
+
firmly).
|
|
190
|
+
- Look-ahead: `lookAhead` 0.15–0.3 s with `lookAheadMax` 2–4 units in the
|
|
191
|
+
running direction; the player should see more of where they go than of
|
|
192
|
+
where they were. Runners and shooters lean hard ahead; top-down games
|
|
193
|
+
lightly.
|
|
194
|
+
- `og.CameraZone` per room or arena keeps the view inside it (applied to
|
|
195
|
+
the frame, from the real aspect, so the simulation stays independent of
|
|
196
|
+
the screen); a zone smaller than the view centres it. Its `scrollTime`
|
|
197
|
+
(0.3–0.6 s, eased in and out) scrolls the view from the room the player
|
|
198
|
+
left into this one when they cross; 0 cuts at once. og.Camera's
|
|
199
|
+
`zoneScroll` says how far a transition has come.
|
|
200
|
+
- `og.CameraRail` for corridors and set pieces: the camera rides a
|
|
201
|
+
Catmull-Rom spline to the point nearest the player.
|
|
202
|
+
- 3D: `og.OrbitCamera` (drag to turn, wheel to zoom) and `og.FollowCamera`
|
|
203
|
+
on the entity the camera orbits or follows, driven by
|
|
204
|
+
`og.systems.cameras` listed in `GameDesc.systems`.
|
|
205
|
+
- The camera never moves on its own during play except for shake, kick,
|
|
206
|
+
punch and a zone's scroll. Do not tween the camera while the player has
|
|
207
|
+
control.
|
|
208
|
+
|
|
209
|
+
## 9. 2D animation: frames and rates
|
|
210
|
+
|
|
211
|
+
Flipbooks (`og.Flipbook` on an entity with `og.Sprite`) play by scaled time,
|
|
212
|
+
fire `og.FlipbookEvent` on frames named in the sheet, and loop, ping-pong
|
|
213
|
+
or play once.
|
|
214
|
+
|
|
215
|
+
| Action | Frames | fps |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| Idle (breathing) | 2–4 | 4–6 (or ping-pong) |
|
|
218
|
+
| Walk | 4–8 | 8–12 |
|
|
219
|
+
| Run | 6–8 | 12–16 |
|
|
220
|
+
| Jump (up, apex, fall) | 1 each, chosen by vertical speed | — |
|
|
221
|
+
| Attack (wind-up, hit, recover) | 3–6, once | 12–20, `event` on the hit frame |
|
|
222
|
+
| Hurt | 1–2, once | 8–12, with a flash |
|
|
223
|
+
| Death | 4–8, once | 8–12 |
|
|
224
|
+
| Pickups, coins | 4–8 loop | 8–12 |
|
|
225
|
+
|
|
226
|
+
- Put footsteps and hits on `event` lines, never on timers: they stay in
|
|
227
|
+
step under slow motion and speed changes.
|
|
228
|
+
- Match the walk rate to the move speed (the feet should not skate):
|
|
229
|
+
`Flipbook.speed = speed / walkSpeed`.
|
|
230
|
+
- Face with `og.Sprite.flipX` instead of drawing left-facing frames; keep
|
|
231
|
+
the pivot at the feet (`pivot 0.5 0`).
|
|
232
|
+
- Pixel art: `texture { kind pixel }` import settings (lossless, nearest)
|
|
233
|
+
and whole-pixel scales: ortho `height` = visible tiles; one tile one unit.
|
|
234
|
+
- Skinned 3D characters can hold poses the same way: `og.AnimationPlayer`'s
|
|
235
|
+
`stepFps` 12 animates on twos and 8 on threes, so clay, puppet and paper
|
|
236
|
+
styles read as stop motion while the character still moves smoothly
|
|
237
|
+
through the world (only the pose steps; events and root motion stay
|
|
238
|
+
continuous). Keep the camera and effects smooth: stepping everything
|
|
239
|
+
reads as a low frame rate, not a style.
|
|
240
|
+
|
|
241
|
+
## 10. Sprite sorting and layers
|
|
242
|
+
|
|
243
|
+
- Layers by role: tiles −10…−1 (`og.Tilemap.layer`), shadows and the floor
|
|
244
|
+
decals 0, characters and props 0 with `sortY`, effects 10–20, floating
|
|
245
|
+
text 100, the HUD in screen space.
|
|
246
|
+
- Top-down: `sortY: true` on everything that stands (characters, trees,
|
|
247
|
+
pillars) and the pivot at the feet, so lower on the screen draws in
|
|
248
|
+
front. Wall tops and roofs go in a tile layer above the characters.
|
|
249
|
+
- Blob shadows (`og.Sprite.shadow` 0.3–0.45) ground characters and
|
|
250
|
+
pickups; they draw under every sprite of their layer.
|
|
251
|
+
- Trails on 2D sprites: `og.Trail` with `layered: true` and the layer
|
|
252
|
+
below the sprite's. Afterimages (`og.Afterimage`, 3–5 ghosts every 2–3
|
|
253
|
+
ticks) for dashes and speed bursts only.
|
|
254
|
+
|
|
255
|
+
## 11. Parallax
|
|
256
|
+
|
|
257
|
+
Tile layers scroll by their `parallax` (1 with the world, 0 with the
|
|
258
|
+
camera); repeat far layers along x (`repeat x`).
|
|
259
|
+
|
|
260
|
+
| Layer | parallax x | parallax y |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| Sky, far gradient | 0–0.05 | 0 |
|
|
263
|
+
| Distant skyline, mountains | 0.1–0.25 | 0.05–0.1 |
|
|
264
|
+
| Mid buildings, hills | 0.4–0.6 | 0.2–0.4 |
|
|
265
|
+
| Play layer | 1 | 1 |
|
|
266
|
+
| Foreground (out of focus, sparse) | 1.2–1.5 | 1–1.2 |
|
|
267
|
+
|
|
268
|
+
Keep parallax backgrounds darker and less saturated than the play layer,
|
|
269
|
+
and never put gameplay on a layer whose parallax is not 1.
|
|
270
|
+
|
|
271
|
+
## 12. Controllers by genre
|
|
272
|
+
|
|
273
|
+
Use `og.PlatformerController` (2D), `og.CharacterController` (3D) and
|
|
274
|
+
`og.Vehicle` (cars) instead of moving bodies by hand: the controllers
|
|
275
|
+
already have coyote time, a jump buffer, variable jump height and moving
|
|
276
|
+
platforms, the car its suspension, grip and steering. Tune by feel,
|
|
277
|
+
starting from these (the defaults are the first column):
|
|
278
|
+
|
|
279
|
+
| `og.PlatformerController` | precise (Celeste-like) | floaty (kid-friendly) | heavy (Castlevania-like) |
|
|
280
|
+
|---|---|---|---|
|
|
281
|
+
| `maxSpeed` (m/s) | 8 | 6 | 5 |
|
|
282
|
+
| `acceleration` / `deceleration` | 70 / 80 | 30 / 25 | 120 / 160 (snappy, committed) |
|
|
283
|
+
| `airAcceleration` / `airDeceleration` | 45 / 25 | 20 / 10 | 15 / 5 |
|
|
284
|
+
| `jumpHeight` (m) / `timeToApex` (s) | 3.2 / 0.4 | 3.5 / 0.55 | 2.4 / 0.35 |
|
|
285
|
+
| `fallGravity` (× rising) | 1.8 | 1.2 | 2.4 |
|
|
286
|
+
| `jumpCut` (× gravity after release) | 2.5 | 1.5 | 1 (no cut: a fixed arc) |
|
|
287
|
+
| `apexGravity` / `apexThreshold` | 0.6 / 1.5 | 0.4 / 2 | 1 / 0 |
|
|
288
|
+
| `coyoteTime` / `jumpBuffer` (s) | 0.1 / 0.12 | 0.15 / 0.2 | 0.05 / 0.08 |
|
|
289
|
+
| `airJumps`, `wallJumps` | 0, 1 | 1, 0 | 0, 0 |
|
|
290
|
+
|
|
291
|
+
- Size the jump to the level: `jumpHeight` 0.5 m above the highest ledge a
|
|
292
|
+
player must reach, so a perfect press is not required.
|
|
293
|
+
- `cornerCorrection` (0.15 m) forgives head bumps; keep it under a third of
|
|
294
|
+
the character's width.
|
|
295
|
+
- 3D characters: `maxSlope` 45° (55° for arcade), `stepHeight` a third of
|
|
296
|
+
the capsule's height, `snapDistance` 0.2–0.3 m so stairs down do not
|
|
297
|
+
bounce, `jumpSpeed` from the height you want: `sqrt(2 × 9.81 × h)`.
|
|
298
|
+
- Read `og.CharacterEvent` for juice: `CHARACTER_LANDED` with its `speed`
|
|
299
|
+
scales the landing squash and dust; `CHARACTER_JUMPED` plays the jump
|
|
300
|
+
stretch (§5) and sound on the same tick.
|
|
301
|
+
- Ragdolls: `blendTime` 0.15–0.3 s from the animation to the bones; set
|
|
302
|
+
`weight` 1 on the killing blow's tick, with its hit stop, so the fall
|
|
303
|
+
starts from the hit pose.
|
|
304
|
+
- Cars: `og.Vehicle` on a dynamic chassis whose `og.RigidBody.mass` is the
|
|
305
|
+
car's (the defaults are a 1,000 kg road car; a car-sized box at density 1
|
|
306
|
+
weighs a few kilograms and the newtons below would throw it). The
|
|
307
|
+
suspension and `grip` scale with the mass; `engineForce` (per driven
|
|
308
|
+
wheel) and `brakeForce` (per wheel) are newtons and do not, so a heavier
|
|
309
|
+
car takes proportionally more. Measured on flat ground, 60 Hz:
|
|
310
|
+
|
|
311
|
+
| `og.Vehicle` | road car (the defaults) | arcade racer | truck | kart |
|
|
312
|
+
|---|---|---|---|---|
|
|
313
|
+
| chassis mass (kg), box (m) | 1,000, 1.8 × 0.6 × 4 | 1,000, 1.8 × 0.4 × 4 | 4,000, 2.4 × 1.6 × 6 | 150, 1 × 0.3 × 1.6 |
|
|
314
|
+
| `centerOfMass` y (m) | 0 | -0.3 | -0.4 | 0 |
|
|
315
|
+
| `wheelBase` / `track` / `wheelRadius` | 2.6 / 1.6 / 0.4 | 2.6 / 1.6 / 0.4 | 4 / 2 / 0.5 | 1.2 / 1 / 0.15 |
|
|
316
|
+
| `engineForce` (N a wheel), `drive` | 2,500, rear | 5,200, rear | 3,000, all | 450, rear |
|
|
317
|
+
| `brakeForce` (N a wheel) | 2,000 | 2,600 | 5,000 | 350 |
|
|
318
|
+
| `maxSpeed` (m/s) | 40 | 46 | 25 | 22 |
|
|
319
|
+
| `grip` | 2 (holds the line, slides at full lock) | 0.62 (slides on the throttle) | 2 | 2.5 |
|
|
320
|
+
| `maxSteer` (rad) | 0.55 | 0.5 | 0.45 | 0.6 |
|
|
321
|
+
| measured: after 1 s, braking | 4.7 m/s, 8 m/s² | 9.3 m/s, 10.5 m/s² | 2.8 m/s, 5 m/s² | 5 m/s, 10 m/s² |
|
|
322
|
+
|
|
323
|
+
`rollInfluence` 0 (the default) keeps every one of them on its wheels at
|
|
324
|
+
full lock at top speed; rapier's own 0.1 tips the road car onto its roof
|
|
325
|
+
at full lock above about 20 m/s. For body roll raise it to 0.05 with the
|
|
326
|
+
centre of mass lowered below the wheel mounts (`og.RigidBody.centerOfMass`).
|
|
327
|
+
Shorten the lock with speed (a quarter of it at top speed) and ease the
|
|
328
|
+
wheel to it over 0.1–0.2 s, as a pad's stick does; a little downforce
|
|
329
|
+
(`og.physics.applyForce(world, car, { x: 0, y: -0.004 * v * v * mass, z: 0 })`
|
|
330
|
+
every tick with the wheels down) keeps a fast car planted over crests.
|
|
331
|
+
Smoke, skid marks and the squeal read `og.Vehicle.slip` (the largest of
|
|
332
|
+
the wheels', 0 gripping to 1) or each wheel's `slip` and `sideSpeed` from
|
|
333
|
+
`og.physics.vehicleWheels`: start smoke and marks above about 0.2, fade
|
|
334
|
+
the squeal in with it. Prove it: with the throttle held from rest,
|
|
335
|
+
`get:car.og.Vehicle.speed` two seconds later reads about 8.9 m/s for the
|
|
336
|
+
defaults (4.7 after one).
|
|
337
|
+
- Prove it: `0.5:key_down:Space;0.9:dump_entity:hero:apex.json` — the dump
|
|
338
|
+
shows `og.PlatformerController.jumping` and a height within 10 % of
|
|
339
|
+
`jumpHeight` at `timeToApex`. `templates/archetypes/runner`'s courier is a
|
|
340
|
+
PlatformerController with its tuning in a `Tuning` resource;
|
|
341
|
+
`templates/archetypes/topdown`'s hero is a CharacterController in its
|
|
342
|
+
top-down mode.
|
|
343
|
+
|
|
344
|
+
## 13. The checklist, proved
|
|
345
|
+
|
|
346
|
+
Make each a playtest (`tests/*.og.ts`) or at least a scripted run whose
|
|
347
|
+
outputs you read. A world dump lists resources by name only; read their
|
|
348
|
+
values with `get:` (og-build §1), which logs the value and writes it as JSON:
|
|
349
|
+
|
|
350
|
+
1. **Same-tick response**: `t:key_down:X;t+1/60:dump_entity:hero:a.json`
|
|
351
|
+
shows the new state on the next tick.
|
|
352
|
+
2. **Hit lands with weight**: during a hit, `dump_entity:enemy` shows
|
|
353
|
+
`og.Sprite.flash` > 0 and its position equal across the hit-stop ticks;
|
|
354
|
+
`get:og.Time` shows `hitStop` > 0 on the stop's first tick.
|
|
355
|
+
3. **Shake decays**: `get:og.Camera.trauma` on the hit and about a second
|
|
356
|
+
later shows it rising, then back to 0.
|
|
357
|
+
4. **Feet do not skate, steps are heard**: count og.FlipbookEvent `step`
|
|
358
|
+
events over a scripted walk (a game resource or a fact).
|
|
359
|
+
5. **Floating text appears**: a `screenshot` 0.2 s after a hit shows the
|
|
360
|
+
number above the hit point (read the PNG).
|
|
361
|
+
6. **The camera leads**: two screenshots while running show more space ahead
|
|
362
|
+
of the hero than behind; a dump shows `og.Follow2D.lead` non-zero.
|
|
363
|
+
7. **Sorting**: a screenshot where a character walks behind and in front of
|
|
364
|
+
a pillar.
|
|
365
|
+
8. **Determinism**: a playtest with `runs: 2` and
|
|
366
|
+
`t.equal(run.hashes[1], run.hashes[0], ...)` over a session with hits,
|
|
367
|
+
pickups and a hit stop: the juice must replay exactly.
|
|
368
|
+
9. **No stray slow motion**: `get:og.Time.scale` at the end of the run is 1.
|
|
369
|
+
10. **Rooms scroll**: `get:og.Camera` on the ticks after the hero crosses a
|
|
370
|
+
doorway shows `zoneScroll` rising from 0 to 1 (topdown's `door.og.ts`).
|
|
371
|
+
|
|
372
|
+
`templates/archetypes/topdown`, `runner` and `match3` do all of this, and
|
|
373
|
+
`tower-defense`, `roguelike`, `idle` and `rhythm` too (each README's feel
|
|
374
|
+
table names where); read their `src/` and `tests/` for working patterns.
|
|
375
|
+
For a 3D action game, `templates/archetypes/third-person` is the pattern: a
|
|
376
|
+
CharacterController over a level, clips retargeted at cook and run time
|
|
377
|
+
through blend trees, and navmesh enemies on behaviour machines whose
|
|
378
|
+
grabs land with hit stop and shake.
|
|
379
|
+
|
|
380
|
+
## 14. Feel, measured: `og polish`
|
|
381
|
+
|
|
382
|
+
`og polish .` scores this checklist's measurable part (input latency, hit
|
|
383
|
+
feedback, idle frames, sound on input, frame time) next to og-taste's;
|
|
384
|
+
og-build §5 is the one list of its measures, thresholds and fixes. Expose
|
|
385
|
+
`hp`, `hits` or `kills` as facts (og-game §3) or the hit measures stay n/a.
|