@origingame/create-game 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/README.md +57 -2
- package/archetypes/README.md +99 -0
- package/archetypes/idle/.origin/setup +14 -0
- package/archetypes/idle/LICENSES.md +12 -0
- package/archetypes/idle/README.md +234 -0
- package/archetypes/idle/assets/audio/buy.wav +0 -0
- package/archetypes/idle/assets/audio/milestone.wav +0 -0
- package/archetypes/idle/assets/audio/nova.wav +0 -0
- package/archetypes/idle/assets/audio/orbit.wav +0 -0
- package/archetypes/idle/assets/audio/tap.wav +0 -0
- package/archetypes/idle/assets/audio/ui-close.ogt +9 -0
- package/archetypes/idle/assets/audio/ui-error.ogt +9 -0
- package/archetypes/idle/assets/audio/ui-open.ogt +9 -0
- package/archetypes/idle/assets/audio/ui-press.ogt +9 -0
- package/archetypes/idle/assets/audio/ui-success.ogt +9 -0
- package/archetypes/idle/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/idle/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/idle/assets/fonts/OFL.txt +93 -0
- package/archetypes/idle/assets/fx/drift.ogt +21 -0
- package/archetypes/idle/assets/fx/milestone.ogt +21 -0
- package/archetypes/idle/assets/fx/nova.ogt +22 -0
- package/archetypes/idle/assets/fx/stars.ogt +20 -0
- package/archetypes/idle/assets/fx/tap.ogt +23 -0
- package/archetypes/idle/assets/looks/idle.ogt +17 -0
- package/archetypes/idle/assets/skins/sparkorb/ChakraPetch-BoldItalic.ttf +0 -0
- package/archetypes/idle/assets/skins/sparkorb/ChakraPetch-BoldItalic.ttf.ogt +9 -0
- package/archetypes/idle/assets/skins/sparkorb/LICENSES.md +19 -0
- package/archetypes/idle/assets/skins/sparkorb/OFL.txt +95 -0
- package/archetypes/idle/assets/skins/sparkorb/Orbitron-Medium.ttf +0 -0
- package/archetypes/idle/assets/skins/sparkorb/Orbitron-Medium.ttf.ogt +9 -0
- package/archetypes/idle/assets/skins/sparkorb/Rajdhani-Bold.ttf +0 -0
- package/archetypes/idle/assets/skins/sparkorb/Rajdhani-Bold.ttf.ogt +9 -0
- package/archetypes/idle/assets/skins/sparkorb/atlas.png +0 -0
- package/archetypes/idle/assets/skins/sparkorb/atlas.png.ogt +7 -0
- package/archetypes/idle/assets/skins/sparkorb/icons.png +0 -0
- package/archetypes/idle/assets/skins/sparkorb/icons.png.ogt +7 -0
- package/archetypes/idle/assets/skins/sparkorb/skin.ogt +113 -0
- package/archetypes/idle/assets/sprites/glow.png +0 -0
- package/archetypes/idle/assets/sprites/glow.png.ogt +8 -0
- package/archetypes/idle/assets/sprites/mote-crystal.png +0 -0
- package/archetypes/idle/assets/sprites/mote-crystal.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/mote-ember.png +0 -0
- package/archetypes/idle/assets/sprites/mote-ember.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/mote-star.png +0 -0
- package/archetypes/idle/assets/sprites/mote-star.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/mote-storm.png +0 -0
- package/archetypes/idle/assets/sprites/mote-storm.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/orb.png +0 -0
- package/archetypes/idle/assets/sprites/orb.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/spark.png +0 -0
- package/archetypes/idle/assets/sprites/spark.png.ogt +7 -0
- package/archetypes/idle/assets/strings/en.ogt +62 -0
- package/archetypes/idle/assets/strings/zh-CN.ogt +62 -0
- package/archetypes/idle/gitattributes +22 -0
- package/archetypes/idle/gitignore +8 -0
- package/archetypes/idle/host.d.ts +46 -0
- package/archetypes/idle/package.json +18 -0
- package/archetypes/idle/project.ogt +14 -0
- package/archetypes/idle/references/metal/card.png +0 -0
- package/archetypes/idle/references/metal/orbit.png +0 -0
- package/archetypes/idle/references/metal/title.png +0 -0
- package/archetypes/idle/src/components.ts +139 -0
- package/archetypes/idle/src/main.ts +247 -0
- package/archetypes/idle/src/numbers.ts +126 -0
- package/archetypes/idle/src/systems.ts +340 -0
- package/archetypes/idle/src/ui.ts +412 -0
- package/archetypes/idle/tests/away.og.ts +42 -0
- package/archetypes/idle/tests/flow.og.ts +57 -0
- package/archetypes/idle/tests/frames.og.ts +33 -0
- package/archetypes/idle/tests/frames.ts +134 -0
- package/archetypes/idle/tests/lib.ts +74 -0
- package/archetypes/idle/tests/milestone.og.ts +32 -0
- package/archetypes/idle/tests/nova.og.ts +52 -0
- package/archetypes/idle/tests/offline.og.ts +60 -0
- package/archetypes/idle/tests/pad.og.ts +55 -0
- package/archetypes/idle/tests/pause.og.ts +68 -0
- package/archetypes/idle/tests/phone.og.ts +50 -0
- package/archetypes/idle/tests/replay.og.ts +28 -0
- package/archetypes/idle/tools/make-assets.py +448 -0
- package/archetypes/idle/tools/oglite.py +285 -0
- package/archetypes/idle/tsconfig.json +17 -0
- package/archetypes/match3/.origin/setup +14 -0
- package/archetypes/match3/LICENSES.md +7 -0
- package/archetypes/match3/README.md +205 -0
- package/archetypes/match3/assets/audio/cascade.wav +0 -0
- package/archetypes/match3/assets/audio/clear.wav +0 -0
- package/archetypes/match3/assets/audio/lost.wav +0 -0
- package/archetypes/match3/assets/audio/refused.wav +0 -0
- package/archetypes/match3/assets/audio/select.wav +0 -0
- package/archetypes/match3/assets/audio/swap.wav +0 -0
- package/archetypes/match3/assets/audio/won.wav +0 -0
- package/archetypes/match3/assets/fonts/OFL.txt +93 -0
- package/archetypes/match3/assets/fonts/ui.ttf +0 -0
- package/archetypes/match3/assets/fonts/ui.ttf.ogt +11 -0
- package/archetypes/match3/assets/scenes/main.ogt +24 -0
- package/archetypes/match3/assets/sprites/backdrop.png +0 -0
- package/archetypes/match3/assets/sprites/board.ogt +21 -0
- package/archetypes/match3/assets/sprites/cells.ogt +7 -0
- package/archetypes/match3/assets/sprites/cells.png +0 -0
- package/archetypes/match3/assets/sprites/cells.png.ogt +7 -0
- package/archetypes/match3/assets/sprites/gems.ogt +75 -0
- package/archetypes/match3/assets/sprites/gems.png +0 -0
- package/archetypes/match3/assets/sprites/panel.png +0 -0
- package/archetypes/match3/assets/sprites/ring.png +0 -0
- package/archetypes/match3/gitattributes +22 -0
- package/archetypes/match3/gitignore +8 -0
- package/archetypes/match3/host.d.ts +46 -0
- package/archetypes/match3/package.json +18 -0
- package/archetypes/match3/project.ogt +18 -0
- package/archetypes/match3/references/metal/cascade.png +0 -0
- package/archetypes/match3/references/metal/clearing.png +0 -0
- package/archetypes/match3/references/metal/dealt.png +0 -0
- package/archetypes/match3/src/art.ts +54 -0
- package/archetypes/match3/src/board.ts +112 -0
- package/archetypes/match3/src/components.ts +95 -0
- package/archetypes/match3/src/layout.ts +25 -0
- package/archetypes/match3/src/main.ts +199 -0
- package/archetypes/match3/src/systems.ts +631 -0
- package/archetypes/match3/tests/drag.og.ts +31 -0
- package/archetypes/match3/tests/endings.og.ts +40 -0
- package/archetypes/match3/tests/frames.og.ts +49 -0
- package/archetypes/match3/tests/frames.ts +134 -0
- package/archetypes/match3/tests/hidpi.og.ts +41 -0
- package/archetypes/match3/tests/lib.ts +64 -0
- package/archetypes/match3/tests/replay.og.ts +28 -0
- package/archetypes/match3/tests/sounds.og.ts +50 -0
- package/archetypes/match3/tests/swap.og.ts +67 -0
- package/archetypes/match3/tools/make-assets.py +404 -0
- package/archetypes/match3/tools/oglite.py +285 -0
- package/archetypes/match3/tsconfig.json +17 -0
- package/archetypes/rhythm/.origin/setup +14 -0
- package/archetypes/rhythm/LICENSES.md +13 -0
- package/archetypes/rhythm/README.md +235 -0
- package/archetypes/rhythm/assets/audio/bass0.wav +0 -0
- package/archetypes/rhythm/assets/audio/bass1.wav +0 -0
- package/archetypes/rhythm/assets/audio/bass2.wav +0 -0
- package/archetypes/rhythm/assets/audio/bass3.wav +0 -0
- package/archetypes/rhythm/assets/audio/chords0.wav +0 -0
- package/archetypes/rhythm/assets/audio/chords1.wav +0 -0
- package/archetypes/rhythm/assets/audio/chords2.wav +0 -0
- package/archetypes/rhythm/assets/audio/chords3.wav +0 -0
- package/archetypes/rhythm/assets/audio/clear.wav +0 -0
- package/archetypes/rhythm/assets/audio/click.wav +0 -0
- package/archetypes/rhythm/assets/audio/drums.wav +0 -0
- package/archetypes/rhythm/assets/audio/fail.wav +0 -0
- package/archetypes/rhythm/assets/audio/leadA.wav +0 -0
- package/archetypes/rhythm/assets/audio/leadB.wav +0 -0
- package/archetypes/rhythm/assets/audio/miss.wav +0 -0
- package/archetypes/rhythm/assets/audio/tap0.wav +0 -0
- package/archetypes/rhythm/assets/audio/tap1.wav +0 -0
- package/archetypes/rhythm/assets/audio/tap2.wav +0 -0
- package/archetypes/rhythm/assets/audio/ui-close.ogt +9 -0
- package/archetypes/rhythm/assets/audio/ui-error.ogt +9 -0
- package/archetypes/rhythm/assets/audio/ui-open.ogt +9 -0
- package/archetypes/rhythm/assets/audio/ui-press.ogt +9 -0
- package/archetypes/rhythm/assets/audio/ui-success.ogt +9 -0
- package/archetypes/rhythm/assets/charts/neon-pulse.ogt +919 -0
- package/archetypes/rhythm/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/rhythm/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/rhythm/assets/fonts/OFL.txt +93 -0
- package/archetypes/rhythm/assets/fx/hit.ogt +22 -0
- package/archetypes/rhythm/assets/skins/lanebeat/ChakraPetch-Medium.ttf +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/ChakraPetch-Medium.ttf.ogt +9 -0
- package/archetypes/rhythm/assets/skins/lanebeat/JetBrainsMono-Regular.ttf +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/JetBrainsMono-Regular.ttf.ogt +9 -0
- package/archetypes/rhythm/assets/skins/lanebeat/LICENSES.md +17 -0
- package/archetypes/rhythm/assets/skins/lanebeat/OFL.txt +95 -0
- package/archetypes/rhythm/assets/skins/lanebeat/Orbitron-Bold.ttf +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/Orbitron-Bold.ttf.ogt +9 -0
- package/archetypes/rhythm/assets/skins/lanebeat/atlas.png +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/atlas.png.ogt +7 -0
- package/archetypes/rhythm/assets/skins/lanebeat/icons.png +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/icons.png.ogt +7 -0
- package/archetypes/rhythm/assets/skins/lanebeat/skin.ogt +110 -0
- package/archetypes/rhythm/assets/sprites/lane.png +0 -0
- package/archetypes/rhythm/assets/sprites/lane.png.ogt +7 -0
- package/archetypes/rhythm/assets/sprites/note.png +0 -0
- package/archetypes/rhythm/assets/sprites/note.png.ogt +7 -0
- package/archetypes/rhythm/assets/sprites/ring.png +0 -0
- package/archetypes/rhythm/assets/sprites/ring.png.ogt +7 -0
- package/archetypes/rhythm/assets/sprites/spark.png +0 -0
- package/archetypes/rhythm/assets/sprites/spark.png.ogt +7 -0
- package/archetypes/rhythm/assets/strings/en.ogt +61 -0
- package/archetypes/rhythm/assets/strings/zh-CN.ogt +61 -0
- package/archetypes/rhythm/gitattributes +22 -0
- package/archetypes/rhythm/gitignore +8 -0
- package/archetypes/rhythm/host.d.ts +46 -0
- package/archetypes/rhythm/package.json +18 -0
- package/archetypes/rhythm/project.ogt +14 -0
- package/archetypes/rhythm/references/metal/highway.png +0 -0
- package/archetypes/rhythm/references/metal/results.png +0 -0
- package/archetypes/rhythm/references/metal/title.png +0 -0
- package/archetypes/rhythm/src/components.ts +147 -0
- package/archetypes/rhythm/src/main.ts +202 -0
- package/archetypes/rhythm/src/systems.ts +490 -0
- package/archetypes/rhythm/src/ui.ts +421 -0
- package/archetypes/rhythm/tests/band.og.ts +45 -0
- package/archetypes/rhythm/tests/calibrate.og.ts +49 -0
- package/archetypes/rhythm/tests/clear.og.ts +51 -0
- package/archetypes/rhythm/tests/fail.og.ts +40 -0
- package/archetypes/rhythm/tests/flow.og.ts +54 -0
- package/archetypes/rhythm/tests/frames.og.ts +35 -0
- package/archetypes/rhythm/tests/frames.ts +134 -0
- package/archetypes/rhythm/tests/lib.ts +123 -0
- package/archetypes/rhythm/tests/pad.og.ts +41 -0
- package/archetypes/rhythm/tests/pause.og.ts +77 -0
- package/archetypes/rhythm/tests/phone.og.ts +44 -0
- package/archetypes/rhythm/tests/replay.og.ts +27 -0
- package/archetypes/rhythm/tests/timing.og.ts +37 -0
- package/archetypes/rhythm/tools/make-assets.py +383 -0
- package/archetypes/rhythm/tools/oglite.py +285 -0
- package/archetypes/rhythm/tsconfig.json +17 -0
- package/archetypes/roguelike/.origin/setup +14 -0
- package/archetypes/roguelike/LICENSES.md +14 -0
- package/archetypes/roguelike/README.md +256 -0
- package/archetypes/roguelike/assets/ai/foe.ogt +28 -0
- package/archetypes/roguelike/assets/ai/guardian.ogt +26 -0
- package/archetypes/roguelike/assets/audio/bump.wav +0 -0
- package/archetypes/roguelike/assets/audio/drink.wav +0 -0
- package/archetypes/roguelike/assets/audio/fall.wav +0 -0
- package/archetypes/roguelike/assets/audio/hit.wav +0 -0
- package/archetypes/roguelike/assets/audio/hurt.wav +0 -0
- package/archetypes/roguelike/assets/audio/item.wav +0 -0
- package/archetypes/roguelike/assets/audio/kill.wav +0 -0
- package/archetypes/roguelike/assets/audio/pickup.wav +0 -0
- package/archetypes/roguelike/assets/audio/roar.wav +0 -0
- package/archetypes/roguelike/assets/audio/stairs.wav +0 -0
- package/archetypes/roguelike/assets/audio/step.ogt +9 -0
- package/archetypes/roguelike/assets/audio/telegraph.wav +0 -0
- package/archetypes/roguelike/assets/audio/tomb.wav +0 -0
- package/archetypes/roguelike/assets/audio/ui-close.ogt +9 -0
- package/archetypes/roguelike/assets/audio/ui-error.ogt +9 -0
- package/archetypes/roguelike/assets/audio/ui-open.ogt +9 -0
- package/archetypes/roguelike/assets/audio/ui-press.ogt +9 -0
- package/archetypes/roguelike/assets/audio/ui-success.ogt +9 -0
- package/archetypes/roguelike/assets/audio/won.wav +0 -0
- package/archetypes/roguelike/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/roguelike/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/roguelike/assets/fonts/OFL.txt +93 -0
- package/archetypes/roguelike/assets/maps/dive.ogt +106 -0
- package/archetypes/roguelike/assets/skins/lampdive/DotGothic16-Regular.ttf +0 -0
- package/archetypes/roguelike/assets/skins/lampdive/DotGothic16-Regular.ttf.ogt +9 -0
- package/archetypes/roguelike/assets/skins/lampdive/LICENSES.md +16 -0
- package/archetypes/roguelike/assets/skins/lampdive/OFL.txt +93 -0
- package/archetypes/roguelike/assets/skins/lampdive/atlas.png +0 -0
- package/archetypes/roguelike/assets/skins/lampdive/atlas.png.ogt +7 -0
- package/archetypes/roguelike/assets/skins/lampdive/icons.png +0 -0
- package/archetypes/roguelike/assets/skins/lampdive/icons.png.ogt +7 -0
- package/archetypes/roguelike/assets/skins/lampdive/skin.ogt +115 -0
- package/archetypes/roguelike/assets/sprites/dive.ogt +50 -0
- package/archetypes/roguelike/assets/sprites/dive.png +0 -0
- package/archetypes/roguelike/assets/sprites/dive.png.ogt +7 -0
- package/archetypes/roguelike/assets/sprites/glow.png +0 -0
- package/archetypes/roguelike/assets/sprites/glow.png.ogt +7 -0
- package/archetypes/roguelike/assets/strings/en.ogt +57 -0
- package/archetypes/roguelike/assets/strings/zh-CN.ogt +57 -0
- package/archetypes/roguelike/assets/tiles/tomb.ogt +31 -0
- package/archetypes/roguelike/assets/tiles/tomb.png +0 -0
- package/archetypes/roguelike/assets/tiles/tomb.png.ogt +7 -0
- package/archetypes/roguelike/gitattributes +22 -0
- package/archetypes/roguelike/gitignore +8 -0
- package/archetypes/roguelike/host.d.ts +46 -0
- package/archetypes/roguelike/package.json +18 -0
- package/archetypes/roguelike/project.ogt +14 -0
- package/archetypes/roguelike/references/metal/dive.png +0 -0
- package/archetypes/roguelike/references/metal/over.png +0 -0
- package/archetypes/roguelike/references/metal/title.png +0 -0
- package/archetypes/roguelike/src/art.ts +23 -0
- package/archetypes/roguelike/src/components.ts +169 -0
- package/archetypes/roguelike/src/dive.ts +144 -0
- package/archetypes/roguelike/src/foes.ts +174 -0
- package/archetypes/roguelike/src/main.ts +258 -0
- package/archetypes/roguelike/src/systems.ts +620 -0
- package/archetypes/roguelike/src/ui.ts +350 -0
- package/archetypes/roguelike/tests/flow.og.ts +54 -0
- package/archetypes/roguelike/tests/foes.og.ts +45 -0
- package/archetypes/roguelike/tests/frames.og.ts +32 -0
- package/archetypes/roguelike/tests/frames.ts +134 -0
- package/archetypes/roguelike/tests/lib.ts +83 -0
- package/archetypes/roguelike/tests/lose.og.ts +44 -0
- package/archetypes/roguelike/tests/pad.og.ts +54 -0
- package/archetypes/roguelike/tests/pause.og.ts +59 -0
- package/archetypes/roguelike/tests/phone.og.ts +54 -0
- package/archetypes/roguelike/tests/replay.og.ts +24 -0
- package/archetypes/roguelike/tests/win.og.ts +60 -0
- package/archetypes/roguelike/tools/make-assets.py +518 -0
- package/archetypes/roguelike/tools/oglite.py +285 -0
- package/archetypes/roguelike/tsconfig.json +17 -0
- package/archetypes/runner/.origin/setup +14 -0
- package/archetypes/runner/LICENSES.md +10 -0
- package/archetypes/runner/README.md +225 -0
- package/archetypes/runner/assets/audio/coin.wav +0 -0
- package/archetypes/runner/assets/audio/crash.wav +0 -0
- package/archetypes/runner/assets/audio/hold.ogt +9 -0
- package/archetypes/runner/assets/audio/jump.wav +0 -0
- package/archetypes/runner/assets/audio/start.wav +0 -0
- package/archetypes/runner/assets/fonts/OFL.txt +93 -0
- package/archetypes/runner/assets/fonts/ui.ttf +0 -0
- package/archetypes/runner/assets/fonts/ui.ttf.ogt +11 -0
- package/archetypes/runner/assets/maps/far.ogt +25 -0
- package/archetypes/runner/assets/maps/near.ogt +25 -0
- package/archetypes/runner/assets/maps/roofs.ogt +19 -0
- package/archetypes/runner/assets/scenes/main.ogt +35 -0
- package/archetypes/runner/assets/sprites/coin.ogt +11 -0
- package/archetypes/runner/assets/sprites/coin.png +0 -0
- package/archetypes/runner/assets/sprites/coin.png.ogt +7 -0
- package/archetypes/runner/assets/sprites/courier.ogt +32 -0
- package/archetypes/runner/assets/sprites/courier.png +0 -0
- package/archetypes/runner/assets/sprites/courier.png.ogt +7 -0
- package/archetypes/runner/assets/sprites/props.ogt +21 -0
- package/archetypes/runner/assets/sprites/props.png +0 -0
- package/archetypes/runner/assets/sprites/props.png.ogt +7 -0
- package/archetypes/runner/assets/sprites/sky.png +0 -0
- package/archetypes/runner/assets/sprites/sky.png.ogt +7 -0
- package/archetypes/runner/assets/tiles/city.ogt +18 -0
- package/archetypes/runner/assets/tiles/city.png +0 -0
- package/archetypes/runner/assets/tiles/city.png.ogt +7 -0
- package/archetypes/runner/gitattributes +22 -0
- package/archetypes/runner/gitignore +8 -0
- package/archetypes/runner/host.d.ts +46 -0
- package/archetypes/runner/package.json +18 -0
- package/archetypes/runner/project.ogt +18 -0
- package/archetypes/runner/references/metal/frames-coin.png +0 -0
- package/archetypes/runner/references/metal/frames-crash.png +0 -0
- package/archetypes/runner/references/metal/frames-title.png +0 -0
- package/archetypes/runner/src/art.ts +57 -0
- package/archetypes/runner/src/components.ts +94 -0
- package/archetypes/runner/src/main.ts +241 -0
- package/archetypes/runner/src/systems.ts +553 -0
- package/archetypes/runner/tests/coins.og.ts +30 -0
- package/archetypes/runner/tests/flow.og.ts +72 -0
- package/archetypes/runner/tests/frames.og.ts +37 -0
- package/archetypes/runner/tests/frames.ts +134 -0
- package/archetypes/runner/tests/jump.og.ts +44 -0
- package/archetypes/runner/tests/lib.ts +42 -0
- package/archetypes/runner/tests/records.og.ts +43 -0
- package/archetypes/runner/tests/replay.og.ts +36 -0
- package/archetypes/runner/tests/sounds.og.ts +43 -0
- package/archetypes/runner/tests/tune.og.ts +36 -0
- package/archetypes/runner/tools/make-assets.py +603 -0
- package/archetypes/runner/tools/oglite.py +285 -0
- package/archetypes/runner/tsconfig.json +17 -0
- package/archetypes/third-person/.origin/setup +14 -0
- package/archetypes/third-person/LICENSES.md +23 -0
- package/archetypes/third-person/README.md +249 -0
- package/archetypes/third-person/assets/ai/sentinel-east.ogt +69 -0
- package/archetypes/third-person/assets/ai/sentinel-north.ogt +69 -0
- package/archetypes/third-person/assets/ai/sentinel-west.ogt +69 -0
- package/archetypes/third-person/assets/anims/humanoid-clips.glb +0 -0
- package/archetypes/third-person/assets/audio/alarm.wav +0 -0
- package/archetypes/third-person/assets/audio/dawn.wav +0 -0
- package/archetypes/third-person/assets/audio/grab.wav +0 -0
- package/archetypes/third-person/assets/audio/ignite.wav +0 -0
- package/archetypes/third-person/assets/audio/jump.wav +0 -0
- package/archetypes/third-person/assets/audio/night.wav +0 -0
- package/archetypes/third-person/assets/audio/splash.wav +0 -0
- package/archetypes/third-person/assets/audio/step.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-close.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-error.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-open.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-press.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-success.ogt +9 -0
- package/archetypes/third-person/assets/audio/vale.wav +0 -0
- package/archetypes/third-person/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/third-person/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/third-person/assets/fonts/OFL.txt +93 -0
- package/archetypes/third-person/assets/fx/burst.ogt +25 -0
- package/archetypes/third-person/assets/fx/fire.ogt +24 -0
- package/archetypes/third-person/assets/fx/smoke.ogt +24 -0
- package/archetypes/third-person/assets/fx/splash.ogt +24 -0
- package/archetypes/third-person/assets/luts/teal-orange.cube +4917 -0
- package/archetypes/third-person/assets/models/stout.glb +0 -0
- package/archetypes/third-person/assets/models/strider.glb +0 -0
- package/archetypes/third-person/assets/models/strider.glb.ogt +9 -0
- package/archetypes/third-person/assets/nav/vale.ogt +43 -0
- package/archetypes/third-person/assets/rigs/strider.ogt +6 -0
- package/archetypes/third-person/assets/skins/ember/Cinzel-Bold.ttf +0 -0
- package/archetypes/third-person/assets/skins/ember/Cinzel-Bold.ttf.ogt +9 -0
- package/archetypes/third-person/assets/skins/ember/LICENSES.md +13 -0
- package/archetypes/third-person/assets/skins/ember/Newsreader-Medium.ttf +0 -0
- package/archetypes/third-person/assets/skins/ember/Newsreader-Medium.ttf.ogt +9 -0
- package/archetypes/third-person/assets/skins/ember/OFL.txt +94 -0
- package/archetypes/third-person/assets/skins/ember/atlas.png +0 -0
- package/archetypes/third-person/assets/skins/ember/atlas.png.ogt +7 -0
- package/archetypes/third-person/assets/skins/ember/icons.png +0 -0
- package/archetypes/third-person/assets/skins/ember/icons.png.ogt +7 -0
- package/archetypes/third-person/assets/skins/ember/skin.ogt +109 -0
- package/archetypes/third-person/assets/strings/en.ogt +47 -0
- package/archetypes/third-person/assets/strings/zh-CN.ogt +47 -0
- package/archetypes/third-person/assets/terrain/vale-control.png +0 -0
- package/archetypes/third-person/assets/terrain/vale.ogt +42 -0
- package/archetypes/third-person/assets/terrain/vale.png +0 -0
- package/archetypes/third-person/assets/textures/decals.png +0 -0
- package/archetypes/third-person/assets/textures/flame.png +0 -0
- package/archetypes/third-person/assets/textures/smoke.png +0 -0
- package/archetypes/third-person/assets/textures/spark.png +0 -0
- package/archetypes/third-person/assets/textures/terrain-grass.png +0 -0
- package/archetypes/third-person/assets/textures/terrain-path.png +0 -0
- package/archetypes/third-person/assets/textures/terrain-rock.png +0 -0
- package/archetypes/third-person/assets/textures/terrain-sand.png +0 -0
- package/archetypes/third-person/gitattributes +22 -0
- package/archetypes/third-person/gitignore +8 -0
- package/archetypes/third-person/host.d.ts +46 -0
- package/archetypes/third-person/package.json +18 -0
- package/archetypes/third-person/project.ogt +14 -0
- package/archetypes/third-person/references/metal/beacon.png +0 -0
- package/archetypes/third-person/references/metal/dawn.png +0 -0
- package/archetypes/third-person/references/metal/title.png +0 -0
- package/archetypes/third-person/references/webgl2/beacon.png +0 -0
- package/archetypes/third-person/src/anim.ts +87 -0
- package/archetypes/third-person/src/beacons.ts +80 -0
- package/archetypes/third-person/src/components.ts +148 -0
- package/archetypes/third-person/src/hero.ts +293 -0
- package/archetypes/third-person/src/main.ts +164 -0
- package/archetypes/third-person/src/sentinels.ts +159 -0
- package/archetypes/third-person/src/sound.ts +66 -0
- package/archetypes/third-person/src/ui.ts +451 -0
- package/archetypes/third-person/src/world.ts +212 -0
- package/archetypes/third-person/tests/flow.og.ts +70 -0
- package/archetypes/third-person/tests/frames.og.ts +35 -0
- package/archetypes/third-person/tests/frames.ts +134 -0
- package/archetypes/third-person/tests/hitstop.og.ts +26 -0
- package/archetypes/third-person/tests/lib.ts +87 -0
- package/archetypes/third-person/tests/lose.og.ts +44 -0
- package/archetypes/third-person/tests/pause.og.ts +64 -0
- package/archetypes/third-person/tests/phone.og.ts +65 -0
- package/archetypes/third-person/tests/replay.og.ts +32 -0
- package/archetypes/third-person/tests/restart.og.ts +28 -0
- package/archetypes/third-person/tests/sentinels.og.ts +48 -0
- package/archetypes/third-person/tests/win.og.ts +62 -0
- package/archetypes/third-person/tools/make-assets.py +353 -0
- package/archetypes/third-person/tools/oglite.py +285 -0
- package/archetypes/third-person/tsconfig.json +17 -0
- package/archetypes/topdown/.origin/setup +14 -0
- package/archetypes/topdown/LICENSES.md +10 -0
- package/archetypes/topdown/README.md +235 -0
- package/archetypes/topdown/assets/ai/slime.ogt +49 -0
- package/archetypes/topdown/assets/audio/coin.wav +0 -0
- package/archetypes/topdown/assets/audio/door.wav +0 -0
- package/archetypes/topdown/assets/audio/hit.wav +0 -0
- package/archetypes/topdown/assets/audio/hurt.wav +0 -0
- package/archetypes/topdown/assets/audio/key.wav +0 -0
- package/archetypes/topdown/assets/audio/step.ogt +9 -0
- package/archetypes/topdown/assets/fonts/OFL.txt +93 -0
- package/archetypes/topdown/assets/fonts/ui.ttf +0 -0
- package/archetypes/topdown/assets/fonts/ui.ttf.ogt +9 -0
- package/archetypes/topdown/assets/maps/tomb.tmj +2183 -0
- package/archetypes/topdown/assets/sprites/hero.ogt +58 -0
- package/archetypes/topdown/assets/sprites/hero.png +0 -0
- package/archetypes/topdown/assets/sprites/hero.png.ogt +7 -0
- package/archetypes/topdown/assets/sprites/items.ogt +15 -0
- package/archetypes/topdown/assets/sprites/items.png +0 -0
- package/archetypes/topdown/assets/sprites/items.png.ogt +7 -0
- package/archetypes/topdown/assets/sprites/slime.ogt +17 -0
- package/archetypes/topdown/assets/sprites/slime.png +0 -0
- package/archetypes/topdown/assets/sprites/slime.png.ogt +7 -0
- package/archetypes/topdown/assets/tiles/dungeon.ogt +65 -0
- package/archetypes/topdown/assets/tiles/dungeon.png +0 -0
- package/archetypes/topdown/assets/tiles/dungeon.png.ogt +7 -0
- package/archetypes/topdown/gitattributes +22 -0
- package/archetypes/topdown/gitignore +8 -0
- package/archetypes/topdown/host.d.ts +46 -0
- package/archetypes/topdown/package.json +18 -0
- package/archetypes/topdown/project.ogt +14 -0
- package/archetypes/topdown/references/metal/crit.png +0 -0
- package/archetypes/topdown/references/metal/dash.png +0 -0
- package/archetypes/topdown/references/metal/east.png +0 -0
- package/archetypes/topdown/references/metal/start.png +0 -0
- package/archetypes/topdown/src/art.ts +48 -0
- package/archetypes/topdown/src/components.ts +128 -0
- package/archetypes/topdown/src/main.ts +275 -0
- package/archetypes/topdown/src/systems.ts +503 -0
- package/archetypes/topdown/tests/ai.og.ts +64 -0
- package/archetypes/topdown/tests/combat.og.ts +72 -0
- package/archetypes/topdown/tests/door.og.ts +62 -0
- package/archetypes/topdown/tests/flee.og.ts +49 -0
- package/archetypes/topdown/tests/frames.og.ts +45 -0
- package/archetypes/topdown/tests/frames.ts +134 -0
- package/archetypes/topdown/tests/lib.ts +71 -0
- package/archetypes/topdown/tests/replay.og.ts +40 -0
- package/archetypes/topdown/tests/walk.og.ts +46 -0
- package/archetypes/topdown/tools/make-assets.py +623 -0
- package/archetypes/topdown/tools/oglite.py +285 -0
- package/archetypes/topdown/tsconfig.json +17 -0
- package/archetypes/tower-defense/.origin/setup +14 -0
- package/archetypes/tower-defense/LICENSES.md +15 -0
- package/archetypes/tower-defense/README.md +240 -0
- package/archetypes/tower-defense/assets/ai/plant.ogt +16 -0
- package/archetypes/tower-defense/assets/audio/clear.wav +0 -0
- package/archetypes/tower-defense/assets/audio/frost.wav +0 -0
- package/archetypes/tower-defense/assets/audio/leak.wav +0 -0
- package/archetypes/tower-defense/assets/audio/lost.wav +0 -0
- package/archetypes/tower-defense/assets/audio/night.wav +0 -0
- package/archetypes/tower-defense/assets/audio/plant.wav +0 -0
- package/archetypes/tower-defense/assets/audio/pop.wav +0 -0
- package/archetypes/tower-defense/assets/audio/thorn.wav +0 -0
- package/archetypes/tower-defense/assets/audio/ui-close.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/ui-error.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/ui-open.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/ui-press.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/ui-success.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/wave.wav +0 -0
- package/archetypes/tower-defense/assets/audio/won.wav +0 -0
- package/archetypes/tower-defense/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/tower-defense/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/tower-defense/assets/fonts/OFL.txt +93 -0
- package/archetypes/tower-defense/assets/fx/fireflies.ogt +21 -0
- package/archetypes/tower-defense/assets/fx/pop.ogt +21 -0
- package/archetypes/tower-defense/assets/maps/garden.ogt +91 -0
- package/archetypes/tower-defense/assets/skins/pathguard/Fredoka-SemiBold.ttf +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/Fredoka-SemiBold.ttf.ogt +9 -0
- package/archetypes/tower-defense/assets/skins/pathguard/LICENSES.md +13 -0
- package/archetypes/tower-defense/assets/skins/pathguard/LilitaOne-Regular.ttf +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/LilitaOne-Regular.ttf.ogt +9 -0
- package/archetypes/tower-defense/assets/skins/pathguard/OFL.txt +94 -0
- package/archetypes/tower-defense/assets/skins/pathguard/atlas.png +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/atlas.png.ogt +7 -0
- package/archetypes/tower-defense/assets/skins/pathguard/icons.png +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/icons.png.ogt +7 -0
- package/archetypes/tower-defense/assets/skins/pathguard/skin.ogt +111 -0
- package/archetypes/tower-defense/assets/sprites/garden.ogt +66 -0
- package/archetypes/tower-defense/assets/sprites/garden.png +0 -0
- package/archetypes/tower-defense/assets/sprites/garden.png.ogt +7 -0
- package/archetypes/tower-defense/assets/sprites/glow.png +0 -0
- package/archetypes/tower-defense/assets/sprites/glow.png.ogt +7 -0
- package/archetypes/tower-defense/assets/sprites/ring.png +0 -0
- package/archetypes/tower-defense/assets/sprites/ring.png.ogt +7 -0
- package/archetypes/tower-defense/assets/sprites/spark.png +0 -0
- package/archetypes/tower-defense/assets/sprites/spark.png.ogt +7 -0
- package/archetypes/tower-defense/assets/strings/en.ogt +47 -0
- package/archetypes/tower-defense/assets/strings/zh-CN.ogt +47 -0
- package/archetypes/tower-defense/assets/tiles/garden.ogt +10 -0
- package/archetypes/tower-defense/assets/tiles/garden.png +0 -0
- package/archetypes/tower-defense/assets/tiles/garden.png.ogt +7 -0
- package/archetypes/tower-defense/gitattributes +22 -0
- package/archetypes/tower-defense/gitignore +8 -0
- package/archetypes/tower-defense/host.d.ts +46 -0
- package/archetypes/tower-defense/package.json +18 -0
- package/archetypes/tower-defense/project.ogt +14 -0
- package/archetypes/tower-defense/references/metal/end.png +0 -0
- package/archetypes/tower-defense/references/metal/title.png +0 -0
- package/archetypes/tower-defense/references/metal/wave.png +0 -0
- package/archetypes/tower-defense/src/art.ts +24 -0
- package/archetypes/tower-defense/src/components.ts +156 -0
- package/archetypes/tower-defense/src/main.ts +253 -0
- package/archetypes/tower-defense/src/systems.ts +658 -0
- package/archetypes/tower-defense/src/ui.ts +382 -0
- package/archetypes/tower-defense/tests/flow.og.ts +56 -0
- package/archetypes/tower-defense/tests/frames.og.ts +31 -0
- package/archetypes/tower-defense/tests/frames.ts +134 -0
- package/archetypes/tower-defense/tests/lib.ts +81 -0
- package/archetypes/tower-defense/tests/lose.og.ts +40 -0
- package/archetypes/tower-defense/tests/maze.og.ts +53 -0
- package/archetypes/tower-defense/tests/pad.og.ts +41 -0
- package/archetypes/tower-defense/tests/pause.og.ts +59 -0
- package/archetypes/tower-defense/tests/phone.og.ts +49 -0
- package/archetypes/tower-defense/tests/replay.og.ts +28 -0
- package/archetypes/tower-defense/tests/win.og.ts +58 -0
- package/archetypes/tower-defense/tools/make-assets.py +640 -0
- package/archetypes/tower-defense/tools/oglite.py +285 -0
- package/archetypes/tower-defense/tsconfig.json +17 -0
- package/bin/create.ts +503 -0
- package/looks/catalogue.json +1871 -0
- package/package.json +31 -4
- package/template/.origin/setup +14 -0
- package/template/LICENSES.md +23 -0
- package/template/README.md +37 -0
- package/template/assets/ai/guard.ogt +48 -0
- package/template/assets/audio/blip.wav +0 -0
- package/template/assets/audio/click.ogt +9 -0
- package/template/assets/fonts/NotoSansSC-Regular-subset.ttf +0 -0
- package/template/assets/fonts/NotoSansSC-Regular-subset.ttf.ogt +12 -0
- package/template/assets/fonts/OFL.txt +93 -0
- package/template/assets/looks/origin-game.ogt +12 -0
- package/template/assets/models/dropship.glb +0 -0
- package/template/assets/models/walker.glb +0 -0
- package/template/assets/nav/yard.ogt +27 -0
- package/template/assets/scenes/main.ogt +34 -0
- package/template/assets/strings/en.ogt +14 -0
- package/template/assets/textures/stone.png +0 -0
- package/template/assets/textures/stone_n.png +0 -0
- package/template/assets/textures/stone_n.png.ogt +7 -0
- package/template/gitattributes +22 -0
- package/template/gitignore +8 -0
- package/template/package.json +18 -0
- package/template/project.ogt +25 -0
- package/template/src/main.ts +246 -0
- package/template/tests/guard.og.ts +67 -0
- package/template/tests/smoke.og.ts +43 -0
- package/template/tsconfig.json +17 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
// The runner's systems, in tick order: Director (phases and pace), Jump (the
|
|
2
|
+
// runner's body: og.PlatformerController's jump, fed the press), Scroll (the world moves left past a runner who stays put),
|
|
3
|
+
// Spawn (obstacle and coin patterns from the dice), Collide (coins and
|
|
4
|
+
// crashes, with their feel), Voices (the jump, coin, crash and start
|
|
5
|
+
// sounds the systems before it asked for), Dust (puffs on the run clip's footfalls),
|
|
6
|
+
// Animate (the courier's clip and afterimage) and Hud (text).
|
|
7
|
+
//
|
|
8
|
+
// Each system sees the actor rows through its typed views. The runner row
|
|
9
|
+
// (Actor.kind === Kind.Runner) carries the run's state and tuning for every
|
|
10
|
+
// system (src/components.ts says why). Motion uses Time.scaledDt, so a hit
|
|
11
|
+
// stop freezes the world while the camera still shakes.
|
|
12
|
+
|
|
13
|
+
import { og, defineSystem, CHARACTER_JUMPED, CHARACTER_LANDED } from "@origingame/engine";
|
|
14
|
+
import type { ArchetypeView, Entity, Query, World } from "@origingame/engine";
|
|
15
|
+
import { COURIER } from "./art.ts";
|
|
16
|
+
import { Actor, Hud, Kind, Phase, Run, Sfx, Sound, Sounds, Tuning } from "./components.ts";
|
|
17
|
+
|
|
18
|
+
/** The ground's top: the runner's feet rest here. */
|
|
19
|
+
export const GROUND_Y = 0;
|
|
20
|
+
/** Where the runner stands; the world scrolls past this x. */
|
|
21
|
+
export const RUNNER_X = -5;
|
|
22
|
+
/** The runner sprite's size. Its position is its feet; its hit box is centred RUNNER_SIZE.y / 2 above them. */
|
|
23
|
+
export const RUNNER_SIZE = { x: 1.3, y: 1.625 } as const;
|
|
24
|
+
/** Ticks the game-over card holds before a press restarts. */
|
|
25
|
+
const OVER_HOLD = 30;
|
|
26
|
+
/** Where a pooled row waits while parked. */
|
|
27
|
+
const PARKED_X = -1000;
|
|
28
|
+
/**
|
|
29
|
+
* Patterns spawn at SPAWN_X and pooled rows retire past RETIRE_X: fixed
|
|
30
|
+
* numbers beyond the widest view (21:9 at the 10 m height is 23.3 m wide),
|
|
31
|
+
* so the play surface's shape never changes when an obstacle arrives.
|
|
32
|
+
*/
|
|
33
|
+
export const SPAWN_X = 13;
|
|
34
|
+
export const RETIRE_X = -13;
|
|
35
|
+
/** Clouds wrap over this span: wider than the widest view plus a piece. */
|
|
36
|
+
export const SCENERY_SPAN = 30;
|
|
37
|
+
|
|
38
|
+
/** The feel: tuned by eye, not by the tests (except that they never change the rules). */
|
|
39
|
+
const FEEL = {
|
|
40
|
+
/** The take-off stretch and the landing squash (x, y factors at the impact). */
|
|
41
|
+
takeOff: { x: 0.78, y: 1.28 },
|
|
42
|
+
landing: { x: 1.3, y: 0.72 },
|
|
43
|
+
/** A crash: ticks of hit stop, screen shake trauma, how far the courier is knocked back and up. */
|
|
44
|
+
hitStop: 8,
|
|
45
|
+
shake: 0.5,
|
|
46
|
+
knockBack: 0.9,
|
|
47
|
+
knockUp: 6,
|
|
48
|
+
/** The red flash of a crash. */
|
|
49
|
+
crashFlash: { r: 1, g: 0.12, b: 0.08 },
|
|
50
|
+
/** The afterimage shows within this much of the top speed. */
|
|
51
|
+
afterimageBelowMax: 0.75,
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
interface Row {
|
|
55
|
+
readonly a: ArchetypeView;
|
|
56
|
+
readonly i: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The runner row among the system's archetypes, or null before `start` placed it. */
|
|
60
|
+
function runnerRow(q: Query): Row | null {
|
|
61
|
+
for (const a of q.archetypes()) {
|
|
62
|
+
const kind = a.column(Actor).kind;
|
|
63
|
+
for (let i = 0; i < a.count; i++) if (kind[i] === Kind.Runner) return { a, i };
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function pressed(i: { pressed(k: string): boolean; buttonPressed(b: "left"): boolean }): boolean {
|
|
69
|
+
return i.pressed("Space") || i.pressed("ArrowUp") || i.pressed("KeyW") || i.buttonPressed("left");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function held(i: { down(k: string): boolean; buttonDown(b: "left"): boolean }): boolean {
|
|
73
|
+
return i.down("Space") || i.down("ArrowUp") || i.down("KeyW") || i.buttonDown("left");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function pooled(kind: number | undefined): boolean {
|
|
77
|
+
return kind === Kind.Crate || kind === Kind.Fence || kind === Kind.Coin || kind === Kind.Dust;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Retire every pooled row: inactive and parked. */
|
|
81
|
+
function retirePools(q: Query): void {
|
|
82
|
+
for (const a of q.archetypes()) {
|
|
83
|
+
const actor = a.column(Actor);
|
|
84
|
+
const p = a.column(og.Transform).position;
|
|
85
|
+
for (let i = 0; i < a.count; i++) {
|
|
86
|
+
if (!pooled(actor.kind[i])) continue;
|
|
87
|
+
actor.active[i] = 0;
|
|
88
|
+
p[i * 3] = PARKED_X;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Phases and pace: a press starts a round from the title or after a crash; the speed ramps while running. */
|
|
94
|
+
export const Director = defineSystem(
|
|
95
|
+
"Director",
|
|
96
|
+
{ reads: [og.Input, Tuning], writes: [Run, Actor, og.Transform, og.Sprite, Sounds] },
|
|
97
|
+
(q, time, world) => {
|
|
98
|
+
const r = runnerRow(q);
|
|
99
|
+
const input = world.resource(og.Input);
|
|
100
|
+
if (r === null || !input.ok) return;
|
|
101
|
+
const run = r.a.column(Run);
|
|
102
|
+
const tune = r.a.column(Tuning);
|
|
103
|
+
const i = r.i;
|
|
104
|
+
run.phaseTicks[i] = run.phaseTicks[i]! + 1;
|
|
105
|
+
const phase = run.phase[i];
|
|
106
|
+
const go = pressed(input.value);
|
|
107
|
+
if (phase === Phase.Running) {
|
|
108
|
+
run.speed[i] = Math.min(tune.speedMax[i]!, run.speed[i]! + tune.speedRamp[i]! * time.scaledDt);
|
|
109
|
+
run.distance[i] = run.distance[i]! + run.speed[i]! * time.scaledDt;
|
|
110
|
+
run.score[i] = Math.floor(run.distance[i]!) + run.coins[i]! * tune.coinValue[i]!;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// The title scrolls the city slowly; the game-over card holds the world still.
|
|
114
|
+
run.speed[i] = phase === Phase.Title ? 1.5 : 0;
|
|
115
|
+
if (!go) return;
|
|
116
|
+
if (phase === Phase.Over && run.phaseTicks[i]! < OVER_HOLD) {
|
|
117
|
+
// The card holds, but the press is heard: never ignore an input silently.
|
|
118
|
+
ask(world, Sound.Hold);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
run.phase[i] = Phase.Running;
|
|
122
|
+
run.phaseTicks[i] = 0;
|
|
123
|
+
run.speed[i] = tune.speed[i]!;
|
|
124
|
+
run.distance[i] = 0;
|
|
125
|
+
run.coins[i] = 0;
|
|
126
|
+
run.score[i] = 0;
|
|
127
|
+
run.rounds[i] = run.rounds[i]! + 1;
|
|
128
|
+
ask(world, Sound.Start);
|
|
129
|
+
run.spawnIn[i] = Math.max(1, tune.spawnInterval[i]! * 1.5);
|
|
130
|
+
run.grounded[i] = 1;
|
|
131
|
+
run.startHeld[i] = 1;
|
|
132
|
+
// Jump hands the body this velocity (0: standing) on the next tick's step.
|
|
133
|
+
run.kick[i] = 1;
|
|
134
|
+
r.a.column(Actor).vy[i] = 0;
|
|
135
|
+
// The crash's knock-back, squash and flash end before the card's hold; stop them anyway and stand the courier up.
|
|
136
|
+
og.tween.cancelEntity(world, r.a.entity(i));
|
|
137
|
+
const t = r.a.column(og.Transform);
|
|
138
|
+
t.position[i * 3] = RUNNER_X;
|
|
139
|
+
t.position[i * 3 + 1] = GROUND_Y;
|
|
140
|
+
t.scale[i * 3] = 1;
|
|
141
|
+
t.scale[i * 3 + 1] = 1;
|
|
142
|
+
r.a.column(og.Sprite).flash[i] = 0;
|
|
143
|
+
retirePools(q);
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The runner's body is an og.PlatformerController that never runs (moveX 0;
|
|
149
|
+
* the world scrolls past it): this system holds its jump while the press is
|
|
150
|
+
* held (the press that started the round excepted), copies its tunables from
|
|
151
|
+
* Tuning, and turns its events into feel. The controller buffers an early
|
|
152
|
+
* press, cuts a released jump, hangs at the apex and lands on the roof's
|
|
153
|
+
* collider; take-off stretches and landing squashes, a tick after the step
|
|
154
|
+
* that did them (og.CharacterEvent).
|
|
155
|
+
*/
|
|
156
|
+
export const Jump = defineSystem(
|
|
157
|
+
"Jump",
|
|
158
|
+
{ reads: [og.Input, Tuning, og.CharacterEvent], writes: [Run, Actor, og.PlatformerController, Sounds], after: ["Director"] },
|
|
159
|
+
(q, _time, world) => {
|
|
160
|
+
const r = runnerRow(q);
|
|
161
|
+
const input = world.resource(og.Input);
|
|
162
|
+
if (r === null || !input.ok) return;
|
|
163
|
+
const { a, i } = r;
|
|
164
|
+
const run = a.column(Run);
|
|
165
|
+
const tune = a.column(Tuning);
|
|
166
|
+
const me = a.entity(i);
|
|
167
|
+
const body = a.column(og.PlatformerController);
|
|
168
|
+
const actor = a.column(Actor);
|
|
169
|
+
// Director (a new round) and Collide (a crash's knock up) set the body's
|
|
170
|
+
// vertical speed through Actor.vy and Run.kick; otherwise the body owns it.
|
|
171
|
+
if (run.kick[i]) {
|
|
172
|
+
run.kick[i] = 0;
|
|
173
|
+
body.velocity[i * 3] = 0;
|
|
174
|
+
body.velocity[i * 3 + 1] = actor.vy[i]!;
|
|
175
|
+
run.grounded[i] = actor.vy[i]! > 0 ? 0 : 1;
|
|
176
|
+
} else {
|
|
177
|
+
actor.vy[i] = body.velocity[i * 3 + 1]!;
|
|
178
|
+
run.grounded[i] = body.grounded[i]!;
|
|
179
|
+
}
|
|
180
|
+
for (const e of world.events(og.CharacterEvent)) {
|
|
181
|
+
if (e.entity !== me) continue;
|
|
182
|
+
if (e.kind === CHARACTER_JUMPED) {
|
|
183
|
+
og.tween.squash(world, { entity: me, impact: FEEL.takeOff, duration: 0.22 });
|
|
184
|
+
ask(world, Sound.Jump);
|
|
185
|
+
} else if (e.kind === CHARACTER_LANDED && run.phase[i] === Phase.Running) {
|
|
186
|
+
og.tween.squash(world, { entity: me, impact: FEEL.landing, duration: 0.2 });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const down = held(input.value);
|
|
190
|
+
if (!down) run.startHeld[i] = 0;
|
|
191
|
+
body.jump[i] = run.phase[i] === Phase.Running && down && !run.startHeld[i] ? 1 : 0;
|
|
192
|
+
body.jumpHeight[i] = tune.jumpHeight[i]!;
|
|
193
|
+
body.timeToApex[i] = tune.timeToApex[i]!;
|
|
194
|
+
body.jumpCut[i] = tune.jumpCut[i]!;
|
|
195
|
+
body.apexGravity[i] = tune.apexGravity[i]!;
|
|
196
|
+
body.fallGravity[i] = tune.fallGravity[i]!;
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
/** The world scrolls left at the run speed times each row's parallax; scenery wraps, pooled rows retire behind the left edge. */
|
|
201
|
+
export const Scroll = defineSystem(
|
|
202
|
+
"Scroll",
|
|
203
|
+
{ reads: [Run], writes: [Actor, og.Transform], after: ["Jump"] },
|
|
204
|
+
(q, time) => {
|
|
205
|
+
const r = runnerRow(q);
|
|
206
|
+
if (r === null) return;
|
|
207
|
+
const step = r.a.column(Run).speed[r.i]! * time.scaledDt;
|
|
208
|
+
if (step === 0) return;
|
|
209
|
+
for (const a of q.archetypes()) {
|
|
210
|
+
const actor = a.column(Actor);
|
|
211
|
+
const p = a.column(og.Transform).position;
|
|
212
|
+
for (let i = 0; i < a.count; i++) {
|
|
213
|
+
const k = actor.kind[i];
|
|
214
|
+
if (k === Kind.Runner || actor.parallax[i] === 0) continue;
|
|
215
|
+
const wrap = actor.wrap[i]!;
|
|
216
|
+
if (wrap > 0) {
|
|
217
|
+
let x = p[i * 3]! - step * actor.parallax[i]!;
|
|
218
|
+
if (x < -wrap / 2) x += wrap;
|
|
219
|
+
p[i * 3] = x;
|
|
220
|
+
} else if (p[i * 3]! > PARKED_X / 2) {
|
|
221
|
+
// In play, or a collected coin or a dust puff finishing its tween: carried along until it retires.
|
|
222
|
+
const x = p[i * 3]! - step;
|
|
223
|
+
if (x < RETIRE_X) {
|
|
224
|
+
actor.active[i] = 0;
|
|
225
|
+
p[i * 3] = PARKED_X;
|
|
226
|
+
} else {
|
|
227
|
+
p[i * 3] = x;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
/** Takes an inactive pooled row of a kind and puts it in play at (x, y), whole and opaque; false when the pool is spent. */
|
|
236
|
+
function place(q: Query, world: World, kind: number, x: number, y: number): boolean {
|
|
237
|
+
for (const a of q.archetypes()) {
|
|
238
|
+
const actor = a.column(Actor);
|
|
239
|
+
for (let i = 0; i < a.count; i++) {
|
|
240
|
+
if (actor.kind[i] !== kind || actor.active[i]) continue;
|
|
241
|
+
og.tween.cancelEntity(world, a.entity(i));
|
|
242
|
+
actor.active[i] = 1;
|
|
243
|
+
const t = a.column(og.Transform);
|
|
244
|
+
t.position[i * 3] = x;
|
|
245
|
+
t.position[i * 3 + 1] = y;
|
|
246
|
+
t.scale[i * 3] = 1;
|
|
247
|
+
t.scale[i * 3 + 1] = 1;
|
|
248
|
+
a.column(og.Sprite).opacity[i] = 1;
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Where each kind rests: its centre height on the roof. */
|
|
256
|
+
export const REST_Y: Readonly<Record<number, number>> = { [Kind.Crate]: 0.4, [Kind.Fence]: 0.8, [Kind.Coin]: 0.6 };
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* A pattern of obstacles and coins, placed from x0 rightwards; returns its length in metres.
|
|
260
|
+
* 0 crate, 1 crate under a coin arc, 2 a line of coins, 3 a fence, 4 a fence under a high coin, 5 two crates.
|
|
261
|
+
*/
|
|
262
|
+
export function placePattern(q: Query, world: World, pattern: number, x0: number): number {
|
|
263
|
+
const at = (kind: number, dx: number, y: number) => place(q, world, kind, x0 + dx, y);
|
|
264
|
+
const coin = (dx: number, y: number) => at(Kind.Coin, dx, y);
|
|
265
|
+
switch (pattern) {
|
|
266
|
+
case 0:
|
|
267
|
+
at(Kind.Crate, 0, REST_Y[Kind.Crate]!);
|
|
268
|
+
return 1;
|
|
269
|
+
case 1:
|
|
270
|
+
at(Kind.Crate, 0, REST_Y[Kind.Crate]!);
|
|
271
|
+
coin(-0.9, 1.7);
|
|
272
|
+
coin(0, 2.2);
|
|
273
|
+
coin(0.9, 1.7);
|
|
274
|
+
return 2;
|
|
275
|
+
case 2:
|
|
276
|
+
for (let k = 0; k < 4; k++) coin(k * 0.9, REST_Y[Kind.Coin]!);
|
|
277
|
+
return 3;
|
|
278
|
+
case 3:
|
|
279
|
+
at(Kind.Fence, 0, REST_Y[Kind.Fence]!);
|
|
280
|
+
return 1;
|
|
281
|
+
case 4:
|
|
282
|
+
at(Kind.Fence, 0, REST_Y[Kind.Fence]!);
|
|
283
|
+
coin(0, 2.9);
|
|
284
|
+
return 1;
|
|
285
|
+
default:
|
|
286
|
+
at(Kind.Crate, 0, REST_Y[Kind.Crate]!);
|
|
287
|
+
at(Kind.Crate, 4.2, REST_Y[Kind.Crate]!);
|
|
288
|
+
return 5;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Obstacle and coin patterns from the dice, one every Tuning.spawnInterval seconds (×0.85–1.25); fences after 60 m, double crates after 150 m. */
|
|
293
|
+
export const Spawn = defineSystem(
|
|
294
|
+
"Spawn",
|
|
295
|
+
{ reads: [Tuning], writes: [og.Rng, Run, Actor, og.Transform, og.Sprite], after: ["Scroll"] },
|
|
296
|
+
(q, time, world) => {
|
|
297
|
+
const r = runnerRow(q);
|
|
298
|
+
const rng = world.resource(og.Rng);
|
|
299
|
+
if (r === null || !rng.ok) return;
|
|
300
|
+
const run = r.a.column(Run);
|
|
301
|
+
const i = r.i;
|
|
302
|
+
if (run.phase[i] !== Phase.Running) return;
|
|
303
|
+
run.spawnIn[i] = run.spawnIn[i]! - time.scaledDt;
|
|
304
|
+
if (run.spawnIn[i]! > 0) return;
|
|
305
|
+
const distance = run.distance[i]!;
|
|
306
|
+
const top = distance < 60 ? 2 : distance < 150 ? 4 : 5;
|
|
307
|
+
const pattern = rng.value.integer(0, top);
|
|
308
|
+
const length = placePattern(q, world, pattern, SPAWN_X);
|
|
309
|
+
// Read the tuning after placing: the views stay valid within the call.
|
|
310
|
+
const interval = r.a.column(Tuning).spawnInterval[i]!;
|
|
311
|
+
run.spawnIn[i] = interval * rng.value.range(0.85, 1.25) + length / Math.max(1, run.speed[i]!);
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const GOLD = { r: 1, g: 0.66, b: 0.05 };
|
|
316
|
+
|
|
317
|
+
/** A collected coin: "+10" rises from it while it pops (grows, lifts and fades). */
|
|
318
|
+
function coinPop(world: World, coin: Entity, x: number, y: number, value: number): void {
|
|
319
|
+
og.render.floatingText(world, { text: `+${value}`, position: { x, y: y + 0.35, z: 5 }, size: 0.5, color: GOLD, rise: 1.1, spread: 0.15, lifetime: 0.7, pop: 0.6 });
|
|
320
|
+
og.tween.parallel(world, [
|
|
321
|
+
{ entity: coin, field: "scale", to: [1.8, 1.8, 1], duration: 0.22, ease: "quadOut" },
|
|
322
|
+
{ entity: coin, field: "position.y", to: [0.5], relative: true, duration: 0.22, ease: "quadOut" },
|
|
323
|
+
{ entity: coin, component: og.Sprite, field: "opacity", to: [0], duration: 0.22, ease: "quadIn" },
|
|
324
|
+
]);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** A crash: hit stop, a shake and a kick, the courier knocked back and up, flashing red. */
|
|
328
|
+
function crashFeel(world: World, runner: Entity): void {
|
|
329
|
+
og.time.hitStop(world, FEEL.hitStop);
|
|
330
|
+
og.camera.shake(world, FEEL.shake);
|
|
331
|
+
og.camera.kick(world, { x: -1, y: 0, z: 0 }, 0.3);
|
|
332
|
+
og.tween.to(world, { entity: runner, field: "position.x", to: [-FEEL.knockBack], relative: true, duration: 0.3, ease: "quartOut" });
|
|
333
|
+
// The red holds through the hit stop, then fades.
|
|
334
|
+
og.tween.flash(world, { entity: runner, color: FEEL.crashFlash, amount: 0.85, duration: 0.4 });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Overlap tests against the runner's hit box: a coin is collected, a crate or a fence ends the round. */
|
|
338
|
+
export const Collide = defineSystem(
|
|
339
|
+
"Collide",
|
|
340
|
+
{ reads: [og.Transform, Tuning], writes: [Run, Actor, Sounds], after: ["Spawn"] },
|
|
341
|
+
(q, _time, world) => {
|
|
342
|
+
const r = runnerRow(q);
|
|
343
|
+
if (r === null) return;
|
|
344
|
+
const run = r.a.column(Run);
|
|
345
|
+
const ri = r.i;
|
|
346
|
+
if (run.phase[ri] !== Phase.Running) return;
|
|
347
|
+
const rp = r.a.column(og.Transform).position;
|
|
348
|
+
const ra = r.a.column(Actor);
|
|
349
|
+
const value = r.a.column(Tuning).coinValue[ri]!;
|
|
350
|
+
const rx = rp[ri * 3]!, ry = rp[ri * 3 + 1]! + RUNNER_SIZE.y / 2, rw = ra.w[ri]! / 2, rh = ra.h[ri]! / 2;
|
|
351
|
+
let crashed = false;
|
|
352
|
+
for (const a of q.archetypes()) {
|
|
353
|
+
const actor = a.column(Actor);
|
|
354
|
+
const p = a.column(og.Transform).position;
|
|
355
|
+
for (let i = 0; i < a.count; i++) {
|
|
356
|
+
if (!actor.active[i]) continue;
|
|
357
|
+
const k = actor.kind[i];
|
|
358
|
+
if (k !== Kind.Coin && k !== Kind.Crate && k !== Kind.Fence) continue;
|
|
359
|
+
const overlap =
|
|
360
|
+
Math.abs(p[i * 3]! - rx) < rw + actor.w[i]! / 2 && Math.abs(p[i * 3 + 1]! - ry) < rh + actor.h[i]! / 2;
|
|
361
|
+
if (!overlap) continue;
|
|
362
|
+
if (k === Kind.Coin) {
|
|
363
|
+
actor.active[i] = 0;
|
|
364
|
+
run.coins[ri] = run.coins[ri]! + 1;
|
|
365
|
+
coinPop(world, a.entity(i), p[i * 3]!, p[i * 3 + 1]!, value);
|
|
366
|
+
ask(world, Sound.Coin);
|
|
367
|
+
} else {
|
|
368
|
+
crashed = true;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
run.score[ri] = Math.floor(run.distance[ri]!) + run.coins[ri]! * value;
|
|
373
|
+
if (crashed) {
|
|
374
|
+
run.phase[ri] = Phase.Over;
|
|
375
|
+
run.phaseTicks[ri] = 0;
|
|
376
|
+
const score = run.score[ri]!;
|
|
377
|
+
if (score > run.best[ri]!) {
|
|
378
|
+
run.best[ri] = score;
|
|
379
|
+
// The best survives a reload: the player's store (the Player's save, or this browser's).
|
|
380
|
+
og.platform.storeSet(world, { key: "best", value: score });
|
|
381
|
+
}
|
|
382
|
+
og.platform.leaderboardSubmit(world, { board: "main", score });
|
|
383
|
+
og.platform.telemetry(world, "round.over", { score, coins: run.coins[ri]!, distance: Math.round(run.distance[ri]!) });
|
|
384
|
+
run.grounded[ri] = 0;
|
|
385
|
+
ra.vy[ri] = FEEL.knockUp;
|
|
386
|
+
run.kick[ri] = 1;
|
|
387
|
+
crashFeel(world, r.a.entity(ri));
|
|
388
|
+
ask(world, Sound.Crash);
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
/** Ask for a sound this tick (Voices plays it). */
|
|
394
|
+
function ask(world: World, bit: number): void {
|
|
395
|
+
const sounds = world.resource(Sounds);
|
|
396
|
+
if (sounds.ok) sounds.value.sfx |= bit;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** Plays each requested sound once: a voice already playing is stopped this tick and restarted the next. */
|
|
400
|
+
export const Voices = defineSystem("Voices", { reads: [Sfx], writes: [Sounds, og.AudioSource], after: ["Collide"] }, (q, _time, world) => {
|
|
401
|
+
const sounds = world.resource(Sounds);
|
|
402
|
+
if (!sounds.ok || sounds.value.sfx === 0) return;
|
|
403
|
+
const s = sounds.value;
|
|
404
|
+
let left = s.sfx;
|
|
405
|
+
for (const a of q.archetypes()) {
|
|
406
|
+
const bit = a.column(Sfx).bit;
|
|
407
|
+
const src = a.column(og.AudioSource);
|
|
408
|
+
for (let i = 0; i < a.count; i++) {
|
|
409
|
+
const b = bit[i]!;
|
|
410
|
+
if ((s.sfx & b) === 0) continue;
|
|
411
|
+
if (src.playing[i]) {
|
|
412
|
+
src.playing[i] = 0;
|
|
413
|
+
} else {
|
|
414
|
+
src.playing[i] = 1;
|
|
415
|
+
left &= ~b;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
s.sfx = left;
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
/** On each footfall of the run clip (its "step" events) a dust puff leaves the courier's feet. */
|
|
423
|
+
export const Dust = defineSystem(
|
|
424
|
+
"Dust",
|
|
425
|
+
{ reads: [og.FlipbookEvent, Run], writes: [Actor, og.Transform], after: ["Collide"] },
|
|
426
|
+
(q, _time, world) => {
|
|
427
|
+
const r = runnerRow(q);
|
|
428
|
+
if (r === null) return;
|
|
429
|
+
const run = r.a.column(Run);
|
|
430
|
+
if (run.phase[r.i] !== Phase.Running || !run.grounded[r.i]) return;
|
|
431
|
+
const runner = r.a.entity(r.i);
|
|
432
|
+
const rx = r.a.column(og.Transform).position[r.i * 3]!;
|
|
433
|
+
for (const e of world.events(og.FlipbookEvent)) {
|
|
434
|
+
if (e.entity !== runner || e.name !== "step") continue;
|
|
435
|
+
// The oldest puff (the leftmost; parked ones first) is reused.
|
|
436
|
+
let best: Row | null = null;
|
|
437
|
+
let bestX = Infinity;
|
|
438
|
+
for (const a of q.archetypes()) {
|
|
439
|
+
const actor = a.column(Actor);
|
|
440
|
+
const p = a.column(og.Transform).position;
|
|
441
|
+
for (let i = 0; i < a.count; i++) {
|
|
442
|
+
if (actor.kind[i] === Kind.Dust && p[i * 3]! < bestX) {
|
|
443
|
+
best = { a, i };
|
|
444
|
+
bestX = p[i * 3]!;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (best === null) return;
|
|
449
|
+
const puff = best.a.entity(best.i);
|
|
450
|
+
og.tween.cancelEntity(world, puff);
|
|
451
|
+
const t = best.a.column(og.Transform);
|
|
452
|
+
t.position[best.i * 3] = rx - 0.25;
|
|
453
|
+
t.position[best.i * 3 + 1] = GROUND_Y + 0.08;
|
|
454
|
+
og.tween.parallel(world, [
|
|
455
|
+
{ entity: puff, field: "scale", from: [1, 1, 1], to: [0.2, 0.2, 1], duration: 0.3, ease: "quadIn" },
|
|
456
|
+
{ entity: puff, field: "position.y", to: [0.3], relative: true, duration: 0.3, ease: "quadOut" },
|
|
457
|
+
{ entity: puff, component: og.Sprite, field: "opacity", from: [0.7], to: [0], duration: 0.3, ease: "linear" },
|
|
458
|
+
]);
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
/** The courier's clip from the phase and the body (idle, run, jump, fall, crash), the run clip's pace from the speed, and the afterimage near top speed. */
|
|
464
|
+
export const Animate = defineSystem(
|
|
465
|
+
"Animate",
|
|
466
|
+
{ reads: [Run, Actor, Tuning], writes: [og.Flipbook, og.Afterimage], after: ["Dust"] },
|
|
467
|
+
(q) => {
|
|
468
|
+
for (const a of q.archetypes()) {
|
|
469
|
+
const run = a.column(Run);
|
|
470
|
+
const actor = a.column(Actor);
|
|
471
|
+
const flip = a.column(og.Flipbook);
|
|
472
|
+
const ghost = a.column(og.Afterimage);
|
|
473
|
+
const speedMax = a.column(Tuning).speedMax;
|
|
474
|
+
for (let i = 0; i < a.count; i++) {
|
|
475
|
+
if (actor.kind[i] !== Kind.Runner) continue;
|
|
476
|
+
const phase = run.phase[i];
|
|
477
|
+
let clip: number = COURIER.idle;
|
|
478
|
+
if (phase === Phase.Over) clip = COURIER.crash;
|
|
479
|
+
else if (phase === Phase.Running) clip = run.grounded[i] ? COURIER.run : actor.vy[i]! > 0 ? COURIER.jump : COURIER.fall;
|
|
480
|
+
flip.clip[i] = clip;
|
|
481
|
+
flip.speed[i] = clip === COURIER.run ? Math.min(1.6, Math.max(0.8, run.speed[i]! / 7)) : 1;
|
|
482
|
+
ghost.active[i] = phase === Phase.Running && run.speed[i]! >= speedMax[i]! - FEEL.afterimageBelowMax ? 1 : 0;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
/** A rough advance for centring: Latin at 0.56 em, CJK at 1 em (the API has no text measure yet). */
|
|
489
|
+
function width(text: string, size: number): number {
|
|
490
|
+
let w = 0;
|
|
491
|
+
for (const c of text) w += c.charCodeAt(0) > 0x2e80 ? 1 : c === " " ? 0.3 : 0.56;
|
|
492
|
+
return w * size;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** The HUD: score, coins and best while running; the title card and the game-over card. */
|
|
496
|
+
export const HudText = defineSystem("Hud", { reads: [Run, Hud, Tuning], after: ["Animate"] }, (q, _time, world) => {
|
|
497
|
+
for (const a of q.archetypes()) {
|
|
498
|
+
const run = a.column(Run);
|
|
499
|
+
const hud = a.column(Hud);
|
|
500
|
+
for (let i = 0; i < a.count; i++) {
|
|
501
|
+
const font = hud.font[i] as never;
|
|
502
|
+
const left = -hud.halfWidth[i]!;
|
|
503
|
+
const top = hud.top[i]!;
|
|
504
|
+
const text = (t: string, x: number, y: number, size: number, color = WHITE) =>
|
|
505
|
+
og.render.text(world, { font, text: t, position: { x, y, z: 10 }, size, color, layer: 10 });
|
|
506
|
+
const centred = (t: string, y: number, size: number, color = WHITE) => text(t, -width(t, size) / 2, y, size, color);
|
|
507
|
+
const phase = run.phase[i];
|
|
508
|
+
if (phase === Phase.Title) {
|
|
509
|
+
centred("ROOFTOP RUN", top - 2, 1.3, GOLD);
|
|
510
|
+
centred("屋顶快跑", top - 3, 0.6, WHITE);
|
|
511
|
+
if (Math.floor(run.phaseTicks[i]! / 30) % 2 === 0) centred("Tap or press Space to run", top - 3.8, 0.45, MINT);
|
|
512
|
+
centred("点击或按空格开始", top - 4.4, 0.4, MINT);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
text(`SCORE ${run.score[i]}`, left + 0.4, top - 0.8, 0.55);
|
|
516
|
+
text(`COINS ${run.coins[i]}`, left + 0.4, top - 1.5, 0.45, GOLD);
|
|
517
|
+
const best = `BEST ${run.best[i]}`;
|
|
518
|
+
text(best, hud.halfWidth[i]! - 0.4 - width(best, 0.45), top - 0.8, 0.45, MINT);
|
|
519
|
+
if (phase === Phase.Running && run.phaseTicks[i]! < 240) {
|
|
520
|
+
centred("Tap: hop Hold: high jump", top - 2.4, 0.45, MINT);
|
|
521
|
+
centred("点按小跳 · 按住高跳", top - 3, 0.4, MINT);
|
|
522
|
+
}
|
|
523
|
+
if (phase === Phase.Over) {
|
|
524
|
+
centred("GAME OVER", top - 2, 1.1, CORAL);
|
|
525
|
+
centred(`Score ${run.score[i]} Best ${run.best[i]}`, top - 2.9, 0.55, WHITE);
|
|
526
|
+
if (run.phaseTicks[i]! >= 30) centred("Tap or press Space to run again · 再来一次", top - 3.7, 0.42, MINT);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
const WHITE = { r: 1, g: 1, b: 1 };
|
|
533
|
+
const MINT = { r: 0.3, g: 0.85, b: 0.75 };
|
|
534
|
+
const CORAL = { r: 1, g: 0.25, b: 0.18 };
|
|
535
|
+
|
|
536
|
+
/** The best score from the player's store: asked for on the first tick, taken when the answer arrives. */
|
|
537
|
+
export const Records = defineSystem("Records", { reads: [Actor], writes: [Run] }, (q, time, world) => {
|
|
538
|
+
const r = runnerRow(q);
|
|
539
|
+
if (r === null) return;
|
|
540
|
+
const run = r.a.column(Run);
|
|
541
|
+
if (time.tick === 0) {
|
|
542
|
+
const asked = og.platform.storeGet(world, "best");
|
|
543
|
+
run.bestRequest[r.i] = asked.ok ? asked.value : 0;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const request = run.bestRequest[r.i]!;
|
|
547
|
+
if (request === 0) return;
|
|
548
|
+
const answer = og.platform.result(world, request) as { ok: boolean; data: { value: unknown } } | null;
|
|
549
|
+
if (answer === null) return;
|
|
550
|
+
run.bestRequest[r.i] = 0;
|
|
551
|
+
const saved = answer.ok && typeof answer.data.value === "number" ? answer.data.value : 0;
|
|
552
|
+
run.best[r.i] = Math.max(run.best[r.i]!, saved);
|
|
553
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Coins: one placed at the runner's height is collected and raises the score
|
|
2
|
+
// by Tuning.coinValue; one hung above the reach of a standing runner is not.
|
|
3
|
+
import { playtest } from "@origingame/engine/playtest";
|
|
4
|
+
import { RUNNING, runOf, tap } from "./lib.ts";
|
|
5
|
+
|
|
6
|
+
export default playtest({
|
|
7
|
+
name: "a coin in reach is collected and scores; one out of reach is not",
|
|
8
|
+
commands: [
|
|
9
|
+
"0.1:set:runner.Tuning.spawnInterval=60",
|
|
10
|
+
tap(0.5),
|
|
11
|
+
"1.0:dump_entity:runner:before.json",
|
|
12
|
+
"1.0:place:coin:-2",
|
|
13
|
+
"2.0:dump_entity:runner:collected.json",
|
|
14
|
+
"2.0:place:coin:-2:3.2",
|
|
15
|
+
"3.0:dump_entity:runner:missed.json",
|
|
16
|
+
"3.0:quit",
|
|
17
|
+
].join(";"),
|
|
18
|
+
check(run, t) {
|
|
19
|
+
t.equal(run.warnings.join("; "), "", "the script ran without warnings");
|
|
20
|
+
const before = runOf(run, "before.json");
|
|
21
|
+
const collected = runOf(run, "collected.json");
|
|
22
|
+
const missed = runOf(run, "missed.json");
|
|
23
|
+
t.equal(before.coins, 0, "no coin before the first one arrives");
|
|
24
|
+
t.equal(collected.phase, RUNNING, "still running");
|
|
25
|
+
t.equal(collected.coins, 1, "the coin at the runner's height was collected");
|
|
26
|
+
t.equal(collected.score, Math.floor(collected.distance) + 10, "the score is the distance plus one coin's value");
|
|
27
|
+
t.ok(collected.score > before.score + 10, `collecting raised the score (${before.score} -> ${collected.score})`);
|
|
28
|
+
t.equal(missed.coins, 1, "the coin hung at 3.2 m passed over the standing runner");
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// The round's whole loop through real input: the title waits for a press,
|
|
2
|
+
// the run starts and speeds up, an untouched crate ends it on the game-over
|
|
3
|
+
// card, and a press after the card's hold starts round two from zero.
|
|
4
|
+
// Every system sees the rows it needs.
|
|
5
|
+
import { playtest } from "@origingame/engine/playtest";
|
|
6
|
+
import { OVER, RUNNING, TITLE, runOf, tap } from "./lib.ts";
|
|
7
|
+
|
|
8
|
+
export default playtest({
|
|
9
|
+
name: "title, run, crash on a crate, game over, run again",
|
|
10
|
+
commands: [
|
|
11
|
+
"0.3:screenshot:title.png",
|
|
12
|
+
"0.4:dump_entity:runner:title.json",
|
|
13
|
+
tap(0.5),
|
|
14
|
+
"0.6:set:runner.Tuning.spawnInterval=60",
|
|
15
|
+
"2.0:screenshot:play.png",
|
|
16
|
+
"2.1:dump_entity:runner:running.json",
|
|
17
|
+
"2.2:place:crate:3",
|
|
18
|
+
"3.6:dump_entity:runner:over.json",
|
|
19
|
+
tap(3.65),
|
|
20
|
+
"3.69:dump_entity:sfx-16:held-sound.json",
|
|
21
|
+
"3.75:dump_entity:runner:held.json",
|
|
22
|
+
"4.0:screenshot:over.png",
|
|
23
|
+
tap(4.6),
|
|
24
|
+
"4.9:dump_entity:runner:again.json",
|
|
25
|
+
"5.0:dump_world:world.json",
|
|
26
|
+
"5.1:quit",
|
|
27
|
+
].join(";"),
|
|
28
|
+
check(run, t) {
|
|
29
|
+
t.equal(run.warnings.join("; "), "", "the script ran without warnings");
|
|
30
|
+
t.ok(run.quit, "the script reached quit");
|
|
31
|
+
t.frame(["title.png", "play.png", "over.png"], () => {
|
|
32
|
+
for (const f of ["title.png", "play.png", "over.png"]) {
|
|
33
|
+
const png = run.png(f);
|
|
34
|
+
t.ok(png.width === 1280 && png.height === 720, `${f} is a 1280x720 frame`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const title = runOf(run, "title.json");
|
|
38
|
+
t.equal(title.phase, TITLE, "the game opens on the title");
|
|
39
|
+
t.equal(title.rounds, 0, "no round has started on the title");
|
|
40
|
+
|
|
41
|
+
const running = runOf(run, "running.json");
|
|
42
|
+
t.equal(running.phase, RUNNING, "a press starts the run");
|
|
43
|
+
t.ok(running.distance > 5 && running.distance < 12, `the runner covered ${running.distance.toFixed(2)} m in about 1.5 s`);
|
|
44
|
+
t.ok(running.speed > 6, `the speed ramps above the start speed (${running.speed.toFixed(3)} m/s)`);
|
|
45
|
+
|
|
46
|
+
const over = runOf(run, "over.json");
|
|
47
|
+
t.equal(over.phase, OVER, "running into the crate ends the round");
|
|
48
|
+
t.equal(over.best, over.score, "the best score takes the round's score");
|
|
49
|
+
t.ok(over.score >= Math.floor(running.distance), `the score kept the distance (${over.score})`);
|
|
50
|
+
|
|
51
|
+
const held = runOf(run, "held.json");
|
|
52
|
+
t.equal(held.phase, OVER, "a press during the card's hold does not restart");
|
|
53
|
+
const heard = run.json<{ components: Record<string, unknown> }>("held-sound.json");
|
|
54
|
+
t.ok("og.AudioPlayback" in heard.components, `but it is answered by the hold tick (sfx-16: ${Object.keys(heard.components).join(", ")})`);
|
|
55
|
+
|
|
56
|
+
const again = runOf(run, "again.json");
|
|
57
|
+
t.equal(again.phase, RUNNING, "a press after the hold starts round two");
|
|
58
|
+
t.equal(again.rounds, 2, "two rounds were started");
|
|
59
|
+
t.ok(again.distance < 3 && again.coins === 0, "round two starts from zero");
|
|
60
|
+
t.equal(again.best, over.best, "the best score survives the restart");
|
|
61
|
+
|
|
62
|
+
const world = run.json<{ systems: Array<{ name: string; matched: number | null }> }>("world.json");
|
|
63
|
+
// 36 actor rows: sky, moon, 3 clouds, 3 city maps, 27 pooled rows and the runner; 33 of them draw an og.Sprite.
|
|
64
|
+
for (const [s, rows] of [["Director", 33], ["Scroll", 36], ["Spawn", 33], ["Collide", 36], ["Dust", 36]] as const) {
|
|
65
|
+
const sys = world.systems.find((x) => x.name === s);
|
|
66
|
+
t.equal(sys?.matched, rows, `${s} sees its ${rows} actor rows`);
|
|
67
|
+
}
|
|
68
|
+
t.equal(world.systems.find((x) => x.name === "Animate")?.matched, 1, "Animate sees the runner row only");
|
|
69
|
+
t.equal(world.systems.find((x) => x.name === "Jump")?.matched, 1, "Jump sees the runner row only (the one with og.PlatformerController)");
|
|
70
|
+
t.equal(world.systems.find((x) => x.name === "Hud")?.matched, 1, "Hud sees the runner row only");
|
|
71
|
+
},
|
|
72
|
+
});
|