@v12-engine/create 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/base/_gitignore +5 -0
- package/base/package.json +3 -3
- package/base/src/main.ts +36 -10
- package/base/src/navigation.worker.ts +7 -0
- package/base/vite.config.ts +24 -2
- package/cli.mjs +21 -6
- package/index.mjs +27 -7
- package/layers/desktop/electron/app.mjs +237 -0
- package/layers/desktop/electron/config.mjs +16 -0
- package/layers/desktop/electron/dev.mjs +63 -0
- package/layers/desktop/electron/files.mjs +86 -0
- package/layers/desktop/electron/icons/icon.icns +0 -0
- package/layers/desktop/electron/icons/icon.png +0 -0
- package/layers/desktop/electron/main.mjs +12 -0
- package/layers/desktop/electron/package.mjs +75 -0
- package/layers/desktop/electron/preload.cjs +41 -0
- package/layers/desktop/electron/presentation.m +87 -0
- package/layers/desktop/electron/serve.mjs +76 -0
- package/layers/desktop/electron/tools.mjs +63 -0
- package/layers/desktop/electron/updates.mjs +71 -0
- package/layers/desktop/package.json +13 -0
- package/layers/desktop/src/desktop.ts +175 -0
- package/layers/desktop/src/shell.ts +279 -0
- package/layers/desktop/tests/main.test.ts +189 -0
- package/layers/desktop/tests/shell.test.ts +152 -0
- package/layers/rooms/server.ts +13 -10
- package/layers/server/server.ts +4 -5
- package/layers/source/vite.config.ts +21 -1
- package/package.json +1 -1
- package/starters/campaign/README.md +9 -2
- package/starters/campaign/layers/rooms/server.ts +3 -3
- package/starters/campaign/layers/server/server.ts +3 -5
- package/starters/campaign/layers/tests/tests/rules.test.ts +40 -21
- package/starters/campaign/src/campaign/game.ts +26 -5
- package/starters/campaign/src/campaign/index.ts +104 -68
- package/starters/campaign/src/campaign/kills.ts +1 -1
- package/starters/campaign/src/campaign/missions/homecoming.ts +7 -7
- package/starters/campaign/src/campaign/missions/siege.ts +6 -6
- package/starters/campaign/src/campaign/missions/warlord.ts +13 -14
- package/starters/campaign/src/campaign/scenes.ts +8 -7
- package/starters/campaign/src/campaign/units.ts +59 -20
- package/starters/campaign/src/campaign/view/attacks.ts +19 -0
- package/starters/campaign/src/campaign/view/fire.ts +2 -2
- package/starters/campaign/src/campaign/view/index.ts +54 -43
- package/starters/campaign/src/campaign/waves.ts +18 -17
- package/starters/campaign/src/game.ts +8 -1
- package/starters/campaign/src/main.ts +38 -19
- package/starters/campaign/src/maps/fort.names.ts +2 -2
- package/starters/campaign/src/maps/fort.vgmap.json +102 -2
- package/starters/campaign/src/maps/homestead.names.ts +2 -2
- package/starters/campaign/src/maps/homestead.vgmap.json +85 -1
- package/starters/campaign/src/maps/pass.names.ts +2 -2
- package/starters/campaign/src/maps/pass.vgmap.json +69 -1
- package/starters/campaign/src/waves-panel.ts +2 -1
- package/starters/empty/README.md +18 -10
- package/starters/empty/layers/tests/tests/rules.test.ts +16 -17
- package/starters/empty/src/game.ts +11 -75
- package/starters/empty/src/generate.worker.ts +5 -5
- package/starters/empty/src/rules.ts +83 -25
- package/starters/empty/src/view.ts +82 -0
- package/starters/examples/README.md +83 -0
- package/starters/examples/layers/tests/tests/examples.test.ts +30 -0
- package/starters/examples/package.json +5 -0
- package/starters/examples/src/examples/abilities/areas.ts +165 -0
- package/starters/examples/src/examples/abilities/areasLooks.ts +30 -0
- package/starters/examples/src/examples/abilities/buffs.ts +287 -0
- package/starters/examples/src/examples/abilities/buffsLooks.ts +46 -0
- package/starters/examples/src/examples/abilities/changing.ts +228 -0
- package/starters/examples/src/examples/abilities/changingLooks.ts +61 -0
- package/starters/examples/src/examples/abilities/control.ts +158 -0
- package/starters/examples/src/examples/abilities/controlLooks.ts +78 -0
- package/starters/examples/src/examples/abilities/damage.ts +156 -0
- package/starters/examples/src/examples/abilities/damageLooks.ts +37 -0
- package/starters/examples/src/examples/abilities/family.ts +29 -0
- package/starters/examples/src/examples/abilities/index.ts +32 -0
- package/starters/examples/src/examples/abilities/kit.ts +179 -0
- package/starters/examples/src/examples/abilities/lookKit.ts +71 -0
- package/starters/examples/src/examples/abilities/looks.ts +47 -0
- package/starters/examples/src/examples/abilities/missiles.ts +203 -0
- package/starters/examples/src/examples/abilities/missilesLooks.ts +38 -0
- package/starters/examples/src/examples/abilities/moves.ts +206 -0
- package/starters/examples/src/examples/abilities/movesLooks.ts +52 -0
- package/starters/examples/src/examples/abilities/recasts.ts +181 -0
- package/starters/examples/src/examples/abilities/recastsLooks.ts +41 -0
- package/starters/examples/src/examples/abilities/resources.ts +166 -0
- package/starters/examples/src/examples/abilities/resourcesLooks.ts +42 -0
- package/starters/examples/src/examples/abilities/summons.ts +296 -0
- package/starters/examples/src/examples/abilities/summonsLooks.ts +115 -0
- package/starters/examples/src/examples/abilities/triggers.ts +204 -0
- package/starters/examples/src/examples/abilities/triggersLooks.ts +53 -0
- package/starters/examples/src/examples/abilities/vision.ts +137 -0
- package/starters/examples/src/examples/abilities/visionLooks.ts +50 -0
- package/starters/examples/src/examples/animation/index.ts +235 -0
- package/starters/examples/src/examples/animation/looks.ts +444 -0
- package/starters/examples/src/examples/catalog.ts +52 -0
- package/starters/examples/src/examples/cinematics/index.ts +217 -0
- package/starters/examples/src/examples/cinematics/looks.ts +476 -0
- package/starters/examples/src/examples/cinematics/tour.json +43 -0
- package/starters/examples/src/examples/cinematics/village.ts +88 -0
- package/starters/examples/src/examples/common/looks.ts +65 -0
- package/starters/examples/src/examples/common/units.ts +70 -0
- package/starters/examples/src/examples/construction/index.ts +329 -0
- package/starters/examples/src/examples/construction/looks.ts +275 -0
- package/starters/examples/src/examples/custom-animation/bones.ts +92 -0
- package/starters/examples/src/examples/custom-animation/index.ts +190 -0
- package/starters/examples/src/examples/custom-animation/looks.ts +523 -0
- package/starters/examples/src/examples/custom-animation/runes.ts +82 -0
- package/starters/examples/src/examples/custom-fx/glaive.look.ts +110 -0
- package/starters/examples/src/examples/custom-fx/glaive.ts +40 -0
- package/starters/examples/src/examples/custom-fx/gpu.look.ts +145 -0
- package/starters/examples/src/examples/custom-fx/gpu.ts +22 -0
- package/starters/examples/src/examples/custom-fx/index.ts +25 -0
- package/starters/examples/src/examples/custom-fx/lights.look.ts +144 -0
- package/starters/examples/src/examples/custom-fx/lights.ts +25 -0
- package/starters/examples/src/examples/custom-fx/looks.ts +36 -0
- package/starters/examples/src/examples/custom-fx/meteor.look.ts +133 -0
- package/starters/examples/src/examples/custom-fx/meteor.ts +26 -0
- package/starters/examples/src/examples/custom-fx/parts.look.ts +193 -0
- package/starters/examples/src/examples/custom-fx/parts.ts +21 -0
- package/starters/examples/src/examples/custom-fx/portal.look.ts +193 -0
- package/starters/examples/src/examples/custom-fx/portal.ts +48 -0
- package/starters/examples/src/examples/custom-fx/renderer.look.ts +139 -0
- package/starters/examples/src/examples/custom-fx/renderer.ts +18 -0
- package/starters/examples/src/examples/custom-fx/ribbons.look.ts +151 -0
- package/starters/examples/src/examples/custom-fx/ribbons.ts +22 -0
- package/starters/examples/src/examples/custom-fx/shader.ts +42 -0
- package/starters/examples/src/examples/custom-fx/shield.look.ts +226 -0
- package/starters/examples/src/examples/custom-fx/shield.ts +43 -0
- package/starters/examples/src/examples/define.ts +86 -0
- package/starters/examples/src/examples/destruction/index.ts +570 -0
- package/starters/examples/src/examples/destruction/looks.ts +447 -0
- package/starters/examples/src/examples/dialogs/index.ts +383 -0
- package/starters/examples/src/examples/dialogs/looks.ts +546 -0
- package/starters/examples/src/examples/dynamic-map/index.ts +603 -0
- package/starters/examples/src/examples/dynamic-map/looks.ts +301 -0
- package/starters/examples/src/examples/fx/blood.look.ts +62 -0
- package/starters/examples/src/examples/fx/blood.ts +59 -0
- package/starters/examples/src/examples/fx/feel.look.ts +86 -0
- package/starters/examples/src/examples/fx/feel.ts +57 -0
- package/starters/examples/src/examples/fx/fight.ts +28 -0
- package/starters/examples/src/examples/fx/floats.look.ts +108 -0
- package/starters/examples/src/examples/fx/floats.ts +24 -0
- package/starters/examples/src/examples/fx/gallery.look.ts +168 -0
- package/starters/examples/src/examples/fx/gallery.ts +23 -0
- package/starters/examples/src/examples/fx/index.ts +30 -0
- package/starters/examples/src/examples/fx/links.look.ts +83 -0
- package/starters/examples/src/examples/fx/links.ts +49 -0
- package/starters/examples/src/examples/fx/looks.ts +55 -0
- package/starters/examples/src/examples/fx/map.look.ts +93 -0
- package/starters/examples/src/examples/fx/map.ts +33 -0
- package/starters/examples/src/examples/fx/marks.look.ts +100 -0
- package/starters/examples/src/examples/fx/marks.ts +36 -0
- package/starters/examples/src/examples/fx/shots.look.ts +92 -0
- package/starters/examples/src/examples/fx/shots.ts +71 -0
- package/starters/examples/src/examples/fx/spells.look.ts +87 -0
- package/starters/examples/src/examples/fx/spells.ts +122 -0
- package/starters/examples/src/examples/fx/stage.ts +31 -0
- package/starters/examples/src/examples/gui/babylonScreen.ts +167 -0
- package/starters/examples/src/examples/gui/babylonWorld.ts +191 -0
- package/starters/examples/src/examples/gui/htmlPart.ts +95 -0
- package/starters/examples/src/examples/gui/index.ts +143 -0
- package/starters/examples/src/examples/gui/keys.ts +91 -0
- package/starters/examples/src/examples/gui/layout.ts +42 -0
- package/starters/examples/src/examples/gui/looks.ts +270 -0
- package/starters/examples/src/examples/gui/minimap.ts +127 -0
- package/starters/examples/src/examples/gui/ownInterface.ts +129 -0
- package/starters/examples/src/examples/gui/plates.ts +87 -0
- package/starters/examples/src/examples/gui/radial.ts +104 -0
- package/starters/examples/src/examples/gui/solidPart.ts +107 -0
- package/starters/examples/src/examples/looks.ts +45 -0
- package/starters/examples/src/examples/mechanics/battle.ts +51 -0
- package/starters/examples/src/examples/mechanics/damage.ts +80 -0
- package/starters/examples/src/examples/mechanics/flying.ts +93 -0
- package/starters/examples/src/examples/mechanics/formations.ts +58 -0
- package/starters/examples/src/examples/mechanics/highGround.ts +82 -0
- package/starters/examples/src/examples/mechanics/index.ts +44 -0
- package/starters/examples/src/examples/mechanics/looks.ts +289 -0
- package/starters/examples/src/examples/mechanics/maze.ts +119 -0
- package/starters/examples/src/examples/mechanics/orders.ts +68 -0
- package/starters/examples/src/examples/mechanics/selection.ts +30 -0
- package/starters/examples/src/examples/moba/index.ts +377 -0
- package/starters/examples/src/examples/moba/looks.ts +349 -0
- package/starters/examples/src/examples/moba/maps.ts +69 -0
- package/starters/examples/src/examples/moba/spells.ts +34 -0
- package/starters/examples/src/examples/moba/units.ts +196 -0
- package/starters/examples/src/examples/nature/camera.ts +15 -0
- package/starters/examples/src/examples/nature/grass.ts +64 -0
- package/starters/examples/src/examples/nature/index.ts +327 -0
- package/starters/examples/src/examples/nature/land.ts +173 -0
- package/starters/examples/src/examples/nature/looks.ts +577 -0
- package/starters/examples/src/examples/nature/maps.ts +370 -0
- package/starters/examples/src/examples/nature/skies.ts +324 -0
- package/starters/examples/src/examples/nature/weather.ts +80 -0
- package/starters/examples/src/examples/performance/index.ts +263 -0
- package/starters/examples/src/examples/performance/looks.ts +372 -0
- package/starters/examples/src/examples/performance/meter.ts +158 -0
- package/starters/examples/src/examples/post/grades.ts +86 -0
- package/starters/examples/src/examples/post/index.ts +202 -0
- package/starters/examples/src/examples/post/looks.ts +884 -0
- package/starters/examples/src/examples/post/passes.ts +213 -0
- package/starters/examples/src/examples/rts/ai.ts +49 -0
- package/starters/examples/src/examples/rts/airlift.ts +43 -0
- package/starters/examples/src/examples/rts/base.ts +129 -0
- package/starters/examples/src/examples/rts/build.ts +65 -0
- package/starters/examples/src/examples/rts/camps.ts +36 -0
- package/starters/examples/src/examples/rts/carry.ts +103 -0
- package/starters/examples/src/examples/rts/creeps.ts +61 -0
- package/starters/examples/src/examples/rts/data.ts +406 -0
- package/starters/examples/src/examples/rts/ferry.ts +41 -0
- package/starters/examples/src/examples/rts/garrison.ts +50 -0
- package/starters/examples/src/examples/rts/harvest.ts +29 -0
- package/starters/examples/src/examples/rts/hero.ts +67 -0
- package/starters/examples/src/examples/rts/index.ts +77 -0
- package/starters/examples/src/examples/rts/looks.ts +721 -0
- package/starters/examples/src/examples/rts/shop.ts +46 -0
- package/starters/examples/src/examples/rts/tech.ts +39 -0
- package/starters/examples/src/examples/rts/train.ts +48 -0
- package/starters/examples/src/examples/rts/waterside.ts +185 -0
- package/starters/examples/src/examples/showcase/index.ts +714 -0
- package/starters/examples/src/examples/showcase/looks.ts +238 -0
- package/starters/examples/src/game.ts +17 -0
- package/starters/examples/src/main.ts +82 -0
- package/starters/examples/src/rules.ts +31 -0
- package/starters/examples/src/shell/look.ts +79 -0
- package/starters/examples/src/shell/registry.ts +7 -0
- package/starters/examples/src/shell/scenes.ts +178 -0
- package/starters/examples/src/shell/shell.ts +290 -0
- package/starters/examples/src/shell/style.ts +202 -0
- package/starters/examples/src/view.ts +50 -0
- package/starters/melee/README.md +12 -3
- package/starters/melee/layers/tests/tests/heroes.test.ts +402 -0
- package/starters/melee/layers/tests/tests/rules.test.ts +10 -8
- package/starters/melee/src/game.ts +32 -2
- package/starters/melee/src/maps/bay.names.ts +2 -2
- package/starters/melee/src/maps/bay.vgmap.json +198 -1
- package/starters/melee/src/maps.ts +11 -9
- package/starters/melee/src/melee/abilities.ts +1 -194
- package/starters/melee/src/melee/blight.ts +7 -7
- package/starters/melee/src/melee/cellLayers.json +3 -3
- package/starters/melee/src/melee/data.json +1 -18
- package/starters/melee/src/melee/game.ts +6 -4
- package/starters/melee/src/melee/heroes.ts +1025 -0
- package/starters/melee/src/melee/index.ts +64 -61
- package/starters/melee/src/melee/items.ts +15 -362
- package/starters/melee/src/melee/languages/he.json +14 -2
- package/starters/melee/src/melee/pings.ts +5 -6
- package/starters/melee/src/melee/saves.ts +1 -1
- package/starters/melee/src/melee/undead.ts +15 -230
- package/starters/melee/src/melee/units.ts +37 -171
- package/starters/melee/src/melee/view/attacks.ts +19 -0
- package/starters/melee/src/melee/view/heroSpellLooks.ts +39 -8
- package/starters/melee/src/melee/view/index.ts +56 -52
- package/starters/melee/src/melee/view/looks.ts +86 -63
- package/starters/melee/src/melee/view/undeadLooks.ts +70 -31
- package/starters/melee/src/melee/view/walls.ts +1 -1
- package/starters.mjs +6 -0
- package/v12.mjs +14 -15
- package/starters/campaign/src/rules.ts +0 -20
- package/starters/empty/src/maps.ts +0 -44
- package/starters/empty/src/tiles.ts +0 -12
- package/starters/melee/src/melee/heroSpells.ts +0 -38
- package/starters/melee/src/rules.ts +0 -24
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The examples of moves made in code, on the client's side. Two ways in: a look's `pose` (`UnitLook.pose`), called
|
|
3
|
+
* every frame after the client has placed the unit, to move, turn and squash its whole body (the hops, the waddle, the
|
|
4
|
+
* springs, the banking); and the model's bones, turned after the clips have posed them (`./bones.ts`: the look, the
|
|
5
|
+
* reach). And a renderer of the game's own for what is not a unit at all (`./runes.ts`)
|
|
6
|
+
*/
|
|
7
|
+
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder.js'
|
|
8
|
+
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial.js'
|
|
9
|
+
import { Color3 } from '@babylonjs/core/Maths/math.color.js'
|
|
10
|
+
import { Vector3 } from '@babylonjs/core/Maths/math.vector.js'
|
|
11
|
+
import type { EntityView, GameClientApi, GameView, ShapeDef, UnitLook } from '@v12-engine/client'
|
|
12
|
+
import { presetFx } from '@v12-engine/client'
|
|
13
|
+
import type { TransformNode } from '@babylonjs/core/Meshes/transformNode.js'
|
|
14
|
+
import { button, type CategoryLooks, readout, slider, toggle } from '../../shell/look'
|
|
15
|
+
import { character, hold, person, TWO_HANDED } from '../common/looks'
|
|
16
|
+
import { afterClips, aimBone, approach, boneOf, groundUnder, trackPointer, turnBone, worldOf } from './bones'
|
|
17
|
+
import { MOVE, summon } from './index'
|
|
18
|
+
import { runesRenderer } from './runes'
|
|
19
|
+
|
|
20
|
+
// ---- a pose of one's own: what each unit keeps from frame to frame, by its id
|
|
21
|
+
|
|
22
|
+
/** What a pose keeps of a unit between frames: where it was, how fast it went, where it is in its step */
|
|
23
|
+
interface Kept {
|
|
24
|
+
x: number
|
|
25
|
+
z: number
|
|
26
|
+
facing: number
|
|
27
|
+
speed: number
|
|
28
|
+
turn: number
|
|
29
|
+
phase: number
|
|
30
|
+
/** A spring's lean, and how fast it leans, across and along (the springs) */
|
|
31
|
+
lean: { x: number; z: number; vx: number; vz: number }
|
|
32
|
+
health: number
|
|
33
|
+
}
|
|
34
|
+
const kept = new Map<number, Kept>()
|
|
35
|
+
/** What the poses kept, forgotten as an example is left: the next one's units start afresh */
|
|
36
|
+
const forget = () => kept.clear()
|
|
37
|
+
|
|
38
|
+
/** What a pose keeps of this unit, its speed and its turning measured from where it is drawn now and was last frame */
|
|
39
|
+
function measure(entity: EntityView, root: TransformNode, dt: number): Kept {
|
|
40
|
+
let own = kept.get(entity.id)
|
|
41
|
+
if (!own) {
|
|
42
|
+
own = {
|
|
43
|
+
x: root.position.x,
|
|
44
|
+
z: root.position.z,
|
|
45
|
+
facing: root.rotation.y,
|
|
46
|
+
speed: 0,
|
|
47
|
+
turn: 0,
|
|
48
|
+
phase: 0,
|
|
49
|
+
lean: { x: 0, z: 0, vx: 0, vz: 0 },
|
|
50
|
+
health: entity.health
|
|
51
|
+
}
|
|
52
|
+
kept.set(entity.id, own)
|
|
53
|
+
}
|
|
54
|
+
if (dt > 0) {
|
|
55
|
+
const speed = Math.hypot(root.position.x - own.x, root.position.z - own.z) / dt
|
|
56
|
+
let turned = root.rotation.y - own.facing
|
|
57
|
+
turned = Math.atan2(Math.sin(turned), Math.cos(turned))
|
|
58
|
+
// smoothed: the snapshots come a few times a second, a frame's own step is uneven
|
|
59
|
+
own.speed = approach(own.speed, speed, 10, dt)
|
|
60
|
+
own.turn = approach(own.turn, turned / dt, 6, dt)
|
|
61
|
+
}
|
|
62
|
+
own.x = root.position.x
|
|
63
|
+
own.z = root.position.z
|
|
64
|
+
own.facing = root.rotation.y
|
|
65
|
+
return own
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---- the slimes: hops
|
|
69
|
+
|
|
70
|
+
/** How squashy the slimes are, from the card: 0 not at all */
|
|
71
|
+
const slimes = { squash: 0.6 }
|
|
72
|
+
|
|
73
|
+
/** A slime hops while it goes: up in an arc each hop, stretched tall as it leaves and comes down, squashed flat as it lands */
|
|
74
|
+
function hop(entity: EntityView, root: TransformNode, frame: { dt: number; time: number }): void {
|
|
75
|
+
const own = measure(entity, root, frame.dt)
|
|
76
|
+
const going = Math.min(own.speed / 2, 1)
|
|
77
|
+
// it finishes the hop it is in before it stops
|
|
78
|
+
const inHop = own.phase % 1
|
|
79
|
+
if (going > 0.1 || inHop > 0.02) own.phase += frame.dt * 2.4
|
|
80
|
+
else own.phase = 0
|
|
81
|
+
const t = own.phase % 1
|
|
82
|
+
const height = Math.sin(t * Math.PI)
|
|
83
|
+
const rising = Math.cos(t * Math.PI)
|
|
84
|
+
let tall = 1
|
|
85
|
+
if (own.phase > 0) {
|
|
86
|
+
// stretched by how fast it goes up or down; squashed in the moment it touches the ground
|
|
87
|
+
tall += slimes.squash * 0.35 * Math.abs(rising) * height
|
|
88
|
+
if (t < 0.12) tall -= slimes.squash * 0.45 * (1 - t / 0.12)
|
|
89
|
+
if (t > 0.92) tall -= slimes.squash * 0.3 * ((t - 0.92) / 0.08)
|
|
90
|
+
} else tall += Math.sin(frame.time * 3 + entity.id) * 0.04 * (0.5 + slimes.squash)
|
|
91
|
+
root.position.y += height * 0.7 * (own.phase > 0 ? 1 : 0)
|
|
92
|
+
// as much of it as before: what it gains in height it loses across
|
|
93
|
+
root.scaling.y *= tall
|
|
94
|
+
root.scaling.x /= Math.sqrt(tall)
|
|
95
|
+
root.scaling.z /= Math.sqrt(tall)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---- the waddlers: a walk of rocks and bobs
|
|
99
|
+
|
|
100
|
+
/** A waddler rocks from foot to foot as it walks and bobs on each step, leans forward as fast as it goes, and into its turns */
|
|
101
|
+
function waddle(entity: EntityView, root: TransformNode, frame: { dt: number; time: number }): void {
|
|
102
|
+
const own = measure(entity, root, frame.dt)
|
|
103
|
+
const going = Math.min(own.speed / 2.2, 1)
|
|
104
|
+
own.phase += frame.dt * (1 + own.speed * 2.2)
|
|
105
|
+
root.rotation.z += Math.sin(own.phase * Math.PI) * 0.22 * going - Math.max(-0.35, Math.min(0.35, own.turn * 0.18))
|
|
106
|
+
root.rotation.x += 0.12 * going
|
|
107
|
+
root.position.y += Math.abs(Math.cos(own.phase * Math.PI)) * 0.12 * going
|
|
108
|
+
// standing, it breathes
|
|
109
|
+
root.scaling.y *= 1 + Math.sin(frame.time * 2.5 + entity.id) * 0.02 * (1 - going)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---- the jellies: springs
|
|
113
|
+
|
|
114
|
+
/** The springs of the jellies, from the card: how stiff (how fast they sway back), how damped (how soon they settle) */
|
|
115
|
+
const springs = { stiffness: 90, damping: 5 }
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A jelly sways away from each blow and back on a spring until it settles: a hit (its health down since last frame)
|
|
119
|
+
* pushes the spring away from the brute, which stands to its west. Each frame the spring pulls it back by how far it
|
|
120
|
+
* leans (`stiffness`) and slows it by how fast it moves (`damping`)
|
|
121
|
+
*/
|
|
122
|
+
function wobble(entity: EntityView, root: TransformNode, frame: { dt: number; time: number }): void {
|
|
123
|
+
const own = measure(entity, root, frame.dt)
|
|
124
|
+
const { lean } = own
|
|
125
|
+
if (entity.health < own.health - 3) {
|
|
126
|
+
// away from the brute, which stands to its west, and a little to one side or the other
|
|
127
|
+
lean.vx += 2.6
|
|
128
|
+
lean.vz += Math.sin(frame.time * 7 + entity.id) * 1.2
|
|
129
|
+
}
|
|
130
|
+
own.health = entity.health
|
|
131
|
+
// a few small steps a frame, so a stiff spring does not fly apart on a long frame
|
|
132
|
+
const steps = 4
|
|
133
|
+
const dt = frame.dt / steps
|
|
134
|
+
for (let i = 0; i < steps; i++) {
|
|
135
|
+
lean.vx += (-springs.stiffness * lean.x - springs.damping * lean.vx) * dt
|
|
136
|
+
lean.vz += (-springs.stiffness * lean.z - springs.damping * lean.vz) * dt
|
|
137
|
+
lean.x += lean.vx * dt
|
|
138
|
+
lean.z += lean.vz * dt
|
|
139
|
+
}
|
|
140
|
+
root.rotation.x += lean.z
|
|
141
|
+
root.rotation.z -= lean.x
|
|
142
|
+
// squashed by how hard it is moving: a jelly
|
|
143
|
+
const squash = 1 - Math.min(0.3, Math.hypot(lean.vx, lean.vz) * 0.05)
|
|
144
|
+
root.scaling.y *= squash
|
|
145
|
+
root.scaling.x /= Math.sqrt(squash)
|
|
146
|
+
root.scaling.z /= Math.sqrt(squash)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---- the glider: banking
|
|
150
|
+
|
|
151
|
+
/** The glider banks as hard as it turns, rises and falls gently along its way, and pitches with it */
|
|
152
|
+
function bank(entity: EntityView, root: TransformNode, frame: { dt: number; time: number }): void {
|
|
153
|
+
const own = measure(entity, root, frame.dt)
|
|
154
|
+
own.phase = approach(own.phase, Math.max(-1.1, Math.min(1.1, own.turn * 0.75)), 4, frame.dt)
|
|
155
|
+
root.rotation.z -= own.phase
|
|
156
|
+
const climb = Math.cos(frame.time * 0.8) * 0.8
|
|
157
|
+
root.position.y += Math.sin(frame.time * 0.8) * 1.0
|
|
158
|
+
root.rotation.x -= climb * 0.25
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---- the looks of the category's units
|
|
162
|
+
|
|
163
|
+
const shape = (
|
|
164
|
+
shape: ShapeDef['shape'],
|
|
165
|
+
size: ShapeDef['size'],
|
|
166
|
+
offset: ShapeDef['offset'],
|
|
167
|
+
color: ShapeDef['color'],
|
|
168
|
+
own: Partial<ShapeDef> = {}
|
|
169
|
+
): ShapeDef => ({
|
|
170
|
+
shape,
|
|
171
|
+
size,
|
|
172
|
+
offset,
|
|
173
|
+
color,
|
|
174
|
+
...own
|
|
175
|
+
})
|
|
176
|
+
const WHITE: [number, number, number] = [0.97, 0.97, 0.95]
|
|
177
|
+
const BLACK: [number, number, number] = [0.05, 0.05, 0.08]
|
|
178
|
+
|
|
179
|
+
const looks: Record<string, UnitLook> = {
|
|
180
|
+
[MOVE.customAnimationKnight]: {
|
|
181
|
+
height: 1.8,
|
|
182
|
+
model: character('knight', 0.7, { hold: [hold('sword_1handed'), hold('shield_round', 'l')] }),
|
|
183
|
+
shapes: person(1.8)
|
|
184
|
+
},
|
|
185
|
+
// the slimes, waddlers, jellies and the glider are a few solids on purpose: no model and no clips, every move theirs
|
|
186
|
+
// is the look's `pose`, in code
|
|
187
|
+
[MOVE.customAnimationSlime]: {
|
|
188
|
+
height: 0.9,
|
|
189
|
+
shapes: [
|
|
190
|
+
shape('sphere', [0.95, 0.75, 0.95], [0, 0.36, 0], [0.35, 0.82, 0.4]),
|
|
191
|
+
shape('sphere', [0.22, 0.26, 0.12], [-0.17, 0.5, 0.38], WHITE),
|
|
192
|
+
shape('sphere', [0.22, 0.26, 0.12], [0.17, 0.5, 0.38], WHITE),
|
|
193
|
+
shape('sphere', [0.1, 0.13, 0.06], [-0.17, 0.5, 0.44], BLACK),
|
|
194
|
+
shape('sphere', [0.1, 0.13, 0.06], [0.17, 0.5, 0.44], BLACK)
|
|
195
|
+
],
|
|
196
|
+
pose: hop
|
|
197
|
+
},
|
|
198
|
+
[MOVE.customAnimationWaddler]: {
|
|
199
|
+
height: 1.1,
|
|
200
|
+
shapes: [
|
|
201
|
+
shape('sphere', [0.7, 0.85, 0.65], [0, 0.5, 0], [0.18, 0.22, 0.3]),
|
|
202
|
+
shape('sphere', [0.5, 0.6, 0.2], [0, 0.48, 0.24], WHITE),
|
|
203
|
+
shape('sphere', [0.46, 0.46, 0.46], [0, 1.05, 0.02], [0.18, 0.22, 0.3]),
|
|
204
|
+
shape('cone', [0.14, 0.22, 0.14], [0, 1.0, 0.3], [1, 0.6, 0.15], { tilt: Math.PI / 2 }),
|
|
205
|
+
shape('sphere', [0.08, 0.08, 0.05], [-0.1, 1.12, 0.22], WHITE),
|
|
206
|
+
shape('sphere', [0.08, 0.08, 0.05], [0.1, 1.12, 0.22], WHITE),
|
|
207
|
+
shape('box', [0.18, 0.06, 0.26], [-0.16, 0.03, 0.08], [1, 0.6, 0.15]),
|
|
208
|
+
shape('box', [0.18, 0.06, 0.26], [0.16, 0.03, 0.08], [1, 0.6, 0.15])
|
|
209
|
+
],
|
|
210
|
+
pose: waddle
|
|
211
|
+
},
|
|
212
|
+
[MOVE.customAnimationJelly]: {
|
|
213
|
+
height: 1.5,
|
|
214
|
+
shapes: [
|
|
215
|
+
shape('sphere', [1.3, 1.3, 1.3], [0, 0.62, 0], [0.85, 0.3, 0.6]),
|
|
216
|
+
shape('sphere', [0.24, 0.3, 0.12], [-0.22, 0.85, -0.6], WHITE),
|
|
217
|
+
shape('sphere', [0.24, 0.3, 0.12], [0.22, 0.85, -0.6], WHITE),
|
|
218
|
+
shape('sphere', [0.11, 0.14, 0.06], [-0.22, 0.85, -0.66], BLACK),
|
|
219
|
+
shape('sphere', [0.11, 0.14, 0.06], [0.22, 0.85, -0.66], BLACK)
|
|
220
|
+
],
|
|
221
|
+
pose: wobble
|
|
222
|
+
},
|
|
223
|
+
[MOVE.customAnimationBrute]: {
|
|
224
|
+
height: 2,
|
|
225
|
+
model: character('barbarian', 0.8, { hold: [hold('axe_2handed')] }),
|
|
226
|
+
attacks: [TWO_HANDED],
|
|
227
|
+
shapes: person(2)
|
|
228
|
+
},
|
|
229
|
+
[MOVE.customAnimationGlider]: {
|
|
230
|
+
height: 1,
|
|
231
|
+
altitude: 4,
|
|
232
|
+
motion: { bob: 0 },
|
|
233
|
+
shapes: [
|
|
234
|
+
shape('cone', [0.7, 2.9, 0.7], [0, 0, 0.15], [0.92, 0.9, 0.85], { tilt: Math.PI / 2 }),
|
|
235
|
+
shape('box', [5.2, 0.1, 1.3], [0, 0.03, -0.1], [0.85, 0.25, 0.2]),
|
|
236
|
+
shape('box', [1.8, 0.08, 0.65], [0, 0.08, -1.4], [0.85, 0.25, 0.2]),
|
|
237
|
+
shape('box', [0.08, 0.75, 0.55], [0, 0.36, -1.4], [0.92, 0.9, 0.85])
|
|
238
|
+
],
|
|
239
|
+
pose: bank
|
|
240
|
+
},
|
|
241
|
+
[MOVE.customAnimationAdept]: { height: 1.8, model: character('mage', 0.7, { hold: [hold('staff')] }), shapes: person(1.8) }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** The one unit of a type the client knows, if it does */
|
|
245
|
+
const one = (client: GameClientApi, type: string) => [...client.state.entities.values()].find((each) => each.def.id === type && each.health > 0)
|
|
246
|
+
|
|
247
|
+
/** Puts the camera at a point, `distance` back and as steep as `pitch` */
|
|
248
|
+
const stage = (client: GameClientApi, x: number, z: number, distance: number, pitch: number, yaw = 0) =>
|
|
249
|
+
client.camera.setPose({ x, z, distance, yaw, pitch })
|
|
250
|
+
|
|
251
|
+
// ---- the look: the head and the chest turned to the point under the pointer
|
|
252
|
+
|
|
253
|
+
const looking = { on: true, most: 1.2 }
|
|
254
|
+
|
|
255
|
+
/** Every frame once the clip has posed the knight: its chest turned a third of the way to the point, its head the rest, eased */
|
|
256
|
+
function lookAtPointer(client: GameClientApi): () => void {
|
|
257
|
+
const { scene } = client
|
|
258
|
+
const now = { yaw: 0, pitch: 0 }
|
|
259
|
+
const pointer = trackPointer(scene)
|
|
260
|
+
const stop = afterClips(scene, (dt) => {
|
|
261
|
+
const knight = one(client, MOVE.customAnimationKnight)
|
|
262
|
+
const root = knight && client.renderer.kit.model(knight.id)?.root
|
|
263
|
+
const head = knight && boneOf(client, knight.id, 'head')
|
|
264
|
+
const chest = knight && boneOf(client, knight.id, 'chest')
|
|
265
|
+
if (!knight || !root || !head || !chest) return
|
|
266
|
+
const facing = root.rotation.y
|
|
267
|
+
const at = worldOf(head)
|
|
268
|
+
const target = looking.on && pointer.at ? groundUnder(scene, pointer.at.x, pointer.at.y) : undefined
|
|
269
|
+
let yaw = 0
|
|
270
|
+
let pitch = 0
|
|
271
|
+
if (target) {
|
|
272
|
+
const dx = target.x - at.x
|
|
273
|
+
const dz = target.z - at.z
|
|
274
|
+
const away = Math.atan2(dx, dz) - facing
|
|
275
|
+
// how far round from where it faces, the short way, no farther than a neck and a waist turn
|
|
276
|
+
yaw = Math.max(-looking.most, Math.min(looking.most, Math.atan2(Math.sin(away), Math.cos(away))))
|
|
277
|
+
pitch = Math.min(0.7, Math.atan2(at.y, Math.hypot(dx, dz)) * 0.8)
|
|
278
|
+
}
|
|
279
|
+
now.yaw = approach(now.yaw, yaw, 8, dt)
|
|
280
|
+
now.pitch = approach(now.pitch, pitch, 8, dt)
|
|
281
|
+
turnBone(chest, now.yaw * 0.35, 0, facing)
|
|
282
|
+
turnBone(head, now.yaw * 0.65, now.pitch, facing + now.yaw * 0.35)
|
|
283
|
+
})
|
|
284
|
+
return () => {
|
|
285
|
+
stop()
|
|
286
|
+
pointer.stop()
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ---- the reach: the sword arm aimed at an orb, the lower arm then the upper, twice over
|
|
291
|
+
|
|
292
|
+
const reaching = { pointer: false, on: true }
|
|
293
|
+
|
|
294
|
+
function reachForOrb(client: GameClientApi): () => void {
|
|
295
|
+
const { scene } = client
|
|
296
|
+
const orb = MeshBuilder.CreateSphere('examples.orb', { diameter: 0.22, segments: 12 }, scene)
|
|
297
|
+
const material = new StandardMaterial('examples.orb', scene)
|
|
298
|
+
material.emissiveColor = new Color3(0.4, 0.85, 1)
|
|
299
|
+
material.disableLighting = true
|
|
300
|
+
orb.material = material
|
|
301
|
+
const pointer = trackPointer(scene)
|
|
302
|
+
let time = 0
|
|
303
|
+
const stop = afterClips(scene, (dt) => {
|
|
304
|
+
time += dt
|
|
305
|
+
const knight = one(client, MOVE.customAnimationKnight)
|
|
306
|
+
const root = knight && client.renderer.kit.model(knight.id)?.root
|
|
307
|
+
if (!knight || !root) return
|
|
308
|
+
const shoulder = boneOf(client, knight.id, 'upperarm.r')
|
|
309
|
+
const elbow = boneOf(client, knight.id, 'lowerarm.r')
|
|
310
|
+
const hand = boneOf(client, knight.id, 'hand.r')
|
|
311
|
+
if (!shoulder || !elbow || !hand) return
|
|
312
|
+
const base = root.position
|
|
313
|
+
// the orb: where the pointer is (no farther than a step from the knight), or wandering before it on its own
|
|
314
|
+
let target = new Vector3(
|
|
315
|
+
base.x + Math.sin(time * 0.9) * 0.9,
|
|
316
|
+
base.y + 1.05 + Math.sin(time * 1.7) * 0.45,
|
|
317
|
+
base.z - 0.75 + Math.cos(time * 0.6) * 0.35
|
|
318
|
+
)
|
|
319
|
+
const under = reaching.pointer && pointer.at ? groundUnder(scene, pointer.at.x, pointer.at.y) : undefined
|
|
320
|
+
if (under) {
|
|
321
|
+
const dx = under.x - base.x
|
|
322
|
+
const dz = under.z - base.z
|
|
323
|
+
const far = Math.hypot(dx, dz)
|
|
324
|
+
const k = far > 1.6 ? 1.6 / far : 1
|
|
325
|
+
target = new Vector3(base.x + dx * k, base.y + 1.1, base.z + dz * k)
|
|
326
|
+
}
|
|
327
|
+
orb.position.copyFrom(target)
|
|
328
|
+
if (!reaching.on) return
|
|
329
|
+
// a reach in two joints: the elbow aims the hand at the orb, then the shoulder does, twice over (CCD)
|
|
330
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
331
|
+
aimBone(elbow, worldOf(hand), target, 0.8)
|
|
332
|
+
aimBone(shoulder, worldOf(hand), target, 0.8)
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
return () => {
|
|
336
|
+
stop()
|
|
337
|
+
pointer.stop()
|
|
338
|
+
orb.dispose()
|
|
339
|
+
material.dispose()
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export const category: CategoryLooks = {
|
|
344
|
+
view: (view: GameView<any>) => ({
|
|
345
|
+
...view,
|
|
346
|
+
units: { ...view.units, looks: { ...view.units.looks, ...looks } },
|
|
347
|
+
fx: { ...view.fx, defs: { ...presetFx(['smoke-trail']), ...view.fx?.defs } }
|
|
348
|
+
}),
|
|
349
|
+
looks: {
|
|
350
|
+
'custom-animation-look-at': {
|
|
351
|
+
controls: [
|
|
352
|
+
toggle(
|
|
353
|
+
'Look at the pointer',
|
|
354
|
+
true,
|
|
355
|
+
(_client, on) => (looking.on = on),
|
|
356
|
+
'On, the knight’s head and chest turn to the ground under the pointer (move it over the field); off, they ease back to the clip'
|
|
357
|
+
),
|
|
358
|
+
slider(
|
|
359
|
+
'How far it turns',
|
|
360
|
+
{ min: 0.2, max: 1.8, step: 0.05, value: 1.2 },
|
|
361
|
+
(_client, value) => (looking.most = value),
|
|
362
|
+
'How far the knight may turn to follow the pointer, in radians either way: the waist and the neck together'
|
|
363
|
+
),
|
|
364
|
+
button(
|
|
365
|
+
'Play a swing',
|
|
366
|
+
(client) => {
|
|
367
|
+
const knight = one(client, MOVE.customAnimationKnight)
|
|
368
|
+
if (knight) client.playClip(knight.id, 'Melee_1H_Attack_Chop')
|
|
369
|
+
},
|
|
370
|
+
'The knight swings once: its head keeps looking while the clip moves the rest'
|
|
371
|
+
),
|
|
372
|
+
readout(
|
|
373
|
+
'Turned by',
|
|
374
|
+
() => 'scene.onAfterAnimationsObservable, after the clip',
|
|
375
|
+
'Where the code turns the bones: each frame, once the clip has posed them'
|
|
376
|
+
)
|
|
377
|
+
],
|
|
378
|
+
ready: (client) => {
|
|
379
|
+
Object.assign(looking, { on: true, most: 1.2 })
|
|
380
|
+
stage(client, 24, 23, 9, 0.75)
|
|
381
|
+
return lookAtPointer(client)
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
'custom-animation-reach': {
|
|
385
|
+
controls: [
|
|
386
|
+
toggle(
|
|
387
|
+
'Reach',
|
|
388
|
+
true,
|
|
389
|
+
(_client, on) => (reaching.on = on),
|
|
390
|
+
'On, the knight’s sword arm is turned toward the orb each frame; off, the arm is the clip’s alone'
|
|
391
|
+
),
|
|
392
|
+
toggle(
|
|
393
|
+
'The orb follows the pointer',
|
|
394
|
+
false,
|
|
395
|
+
(_client, on) => (reaching.pointer = on),
|
|
396
|
+
'On, the orb goes where the pointer is over the ground, no farther than a step from the knight; off, it wanders by itself'
|
|
397
|
+
),
|
|
398
|
+
button(
|
|
399
|
+
'Play a swing',
|
|
400
|
+
(client) => {
|
|
401
|
+
const knight = one(client, MOVE.customAnimationKnight)
|
|
402
|
+
if (knight) client.playClip(knight.id, 'Melee_1H_Attack_Stab')
|
|
403
|
+
},
|
|
404
|
+
'The knight stabs once: the reach is laid over the clip'
|
|
405
|
+
)
|
|
406
|
+
],
|
|
407
|
+
ready: (client) => {
|
|
408
|
+
Object.assign(reaching, { on: true, pointer: false })
|
|
409
|
+
stage(client, 24, 23.6, 8.5, 0.5)
|
|
410
|
+
return reachForOrb(client)
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
'custom-animation-hop': {
|
|
414
|
+
controls: [
|
|
415
|
+
slider(
|
|
416
|
+
'Squash and stretch',
|
|
417
|
+
{ min: 0, max: 1.5, step: 0.05, value: 0.6 },
|
|
418
|
+
(_client, value) => (slimes.squash = value),
|
|
419
|
+
'How much the slimes stretch as they leave the ground and squash as they land: 0 hops stiff'
|
|
420
|
+
)
|
|
421
|
+
],
|
|
422
|
+
ready: (client) => {
|
|
423
|
+
slimes.squash = 0.6
|
|
424
|
+
stage(client, 24, 22, 20, 0.7)
|
|
425
|
+
return forget
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
'custom-animation-waddle': {
|
|
429
|
+
controls: [
|
|
430
|
+
readout(
|
|
431
|
+
'Each frame',
|
|
432
|
+
() => 'rock, bob, lean: from how fast it goes and turns',
|
|
433
|
+
'What the pose works out for each waddler every frame, from its speed and its turning'
|
|
434
|
+
)
|
|
435
|
+
],
|
|
436
|
+
ready: (client) => {
|
|
437
|
+
stage(client, 24, 22, 16, 0.65)
|
|
438
|
+
return forget
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
'custom-animation-springs': {
|
|
442
|
+
controls: [
|
|
443
|
+
slider(
|
|
444
|
+
'Stiffness',
|
|
445
|
+
{ min: 10, max: 300, step: 5, value: 90 },
|
|
446
|
+
(_client, value) => (springs.stiffness = value),
|
|
447
|
+
'How hard the spring pulls it back upright: higher sways faster'
|
|
448
|
+
),
|
|
449
|
+
slider(
|
|
450
|
+
'Damping',
|
|
451
|
+
{ min: 0.5, max: 25, step: 0.5, value: 5 },
|
|
452
|
+
(_client, value) => (springs.damping = value),
|
|
453
|
+
'How soon a sway dies down: low wobbles on and on'
|
|
454
|
+
),
|
|
455
|
+
readout(
|
|
456
|
+
'Spring',
|
|
457
|
+
() => `stiffness ${springs.stiffness} · damping ${springs.damping}`,
|
|
458
|
+
'The two sliders’ values, which every jelly’s spring uses'
|
|
459
|
+
)
|
|
460
|
+
],
|
|
461
|
+
ready: (client) => {
|
|
462
|
+
springs.stiffness = 90
|
|
463
|
+
springs.damping = 5
|
|
464
|
+
stage(client, 25, 24, 21, 0.55)
|
|
465
|
+
return forget
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
'custom-animation-flight': {
|
|
469
|
+
controls: [
|
|
470
|
+
button(
|
|
471
|
+
'Follow it',
|
|
472
|
+
(client) => {
|
|
473
|
+
const glider = one(client, MOVE.customAnimationGlider)
|
|
474
|
+
if (glider) client.follow(glider.id)
|
|
475
|
+
},
|
|
476
|
+
'The camera follows the glider round its figure of eight: watch it bank into each turn'
|
|
477
|
+
),
|
|
478
|
+
button(
|
|
479
|
+
'Watch from above',
|
|
480
|
+
(client) => {
|
|
481
|
+
client.follow(null)
|
|
482
|
+
stage(client, 24, 23, 30, 0.8)
|
|
483
|
+
},
|
|
484
|
+
'The camera stops following and looks down on the whole figure of eight'
|
|
485
|
+
)
|
|
486
|
+
],
|
|
487
|
+
ready: (client) => {
|
|
488
|
+
stage(client, 24, 23, 30, 0.8)
|
|
489
|
+
let trail: ReturnType<GameClientApi['fx']['play']> | undefined
|
|
490
|
+
// the trail once the glider is drawn: on it, following it
|
|
491
|
+
const start = setInterval(() => {
|
|
492
|
+
const glider = one(client, MOVE.customAnimationGlider)
|
|
493
|
+
if (!glider || trail) return
|
|
494
|
+
trail = client.fx.play('smoke-trail', { on: glider.id, params: { color: '#e8eef5' } })
|
|
495
|
+
clearInterval(start)
|
|
496
|
+
}, 250)
|
|
497
|
+
return () => {
|
|
498
|
+
clearInterval(start)
|
|
499
|
+
trail?.stop()
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
'custom-animation-renderer': {
|
|
504
|
+
view: (view) => ({ ...view, renderers: [...(view.renderers ?? []), runesRenderer(MOVE.customAnimationAdept, [0.55, 0.85, 1])] }),
|
|
505
|
+
controls: [
|
|
506
|
+
button(
|
|
507
|
+
'Summon three more',
|
|
508
|
+
(client) => client.session.send(summon.make({})),
|
|
509
|
+
'Three more adepts pop in: the renderer is told of each and gives it three runes'
|
|
510
|
+
),
|
|
511
|
+
readout(
|
|
512
|
+
'Adepts · runes',
|
|
513
|
+
(client) => {
|
|
514
|
+
const adepts = [...client.state.entities.values()].filter((each) => each.def.id === MOVE.customAnimationAdept && each.health > 0).length
|
|
515
|
+
return `${adepts} · ${adepts * 3}`
|
|
516
|
+
},
|
|
517
|
+
'How many adepts there are, and the runes the renderer draws for them'
|
|
518
|
+
)
|
|
519
|
+
],
|
|
520
|
+
ready: (client) => stage(client, 24, 22.5, 16, 0.6)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A renderer of the game's own (`GameView.renderers`): three runes that orbit every adept, made as one comes into view
|
|
3
|
+
* and taken away as it goes, moved every frame round where the client draws it. Made once from the client's render
|
|
4
|
+
* kit (`RenderKit`): its scene to make meshes in, `drawn` for where a unit is this frame, `timers` for a tween
|
|
5
|
+
*/
|
|
6
|
+
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder.js'
|
|
7
|
+
import type { Mesh } from '@babylonjs/core/Meshes/mesh.js'
|
|
8
|
+
import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js'
|
|
9
|
+
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial.js'
|
|
10
|
+
import { Color3 } from '@babylonjs/core/Maths/math.color.js'
|
|
11
|
+
import { ease, type FeatureRenderer, type RenderKit } from '@v12-engine/client'
|
|
12
|
+
|
|
13
|
+
/** How many runes go round each, how far out, how fast (turns a second) */
|
|
14
|
+
const RUNES = 3
|
|
15
|
+
const RADIUS = 0.95
|
|
16
|
+
const SPEED = 0.35
|
|
17
|
+
|
|
18
|
+
/** The runes round the units of a type: `type` is the unit type's id */
|
|
19
|
+
export const runesRenderer =
|
|
20
|
+
(type: string, color: [number, number, number]) =>
|
|
21
|
+
(kit: RenderKit): FeatureRenderer => {
|
|
22
|
+
const material = new StandardMaterial('examples.runes', kit.scene)
|
|
23
|
+
material.emissiveColor = new Color3(...color)
|
|
24
|
+
material.diffuseColor = Color3.Black()
|
|
25
|
+
material.disableLighting = true
|
|
26
|
+
// one rune made, the others its copies: the same mesh drawn three times a unit
|
|
27
|
+
const source = MeshBuilder.CreatePolyhedron('examples.rune', { type: 1, size: 0.13 }, kit.scene)
|
|
28
|
+
source.material = material
|
|
29
|
+
source.setEnabled(false)
|
|
30
|
+
/** Each adept's runes, by its id: the node they turn round, and the runes */
|
|
31
|
+
const orbits = new Map<number, { node: TransformNode; runes: Mesh[]; tween?: number; born: number }>()
|
|
32
|
+
let time = 0
|
|
33
|
+
|
|
34
|
+
const add = (id: number) => {
|
|
35
|
+
const node = new TransformNode(`examples.runes.${id}`, kit.scene)
|
|
36
|
+
const runes = Array.from({ length: RUNES }, (_, i) => {
|
|
37
|
+
const rune = source.clone(`examples.rune.${id}.${i}`, node)
|
|
38
|
+
rune.setEnabled(true)
|
|
39
|
+
return rune
|
|
40
|
+
})
|
|
41
|
+
node.scaling.setAll(0)
|
|
42
|
+
// popping in: from nothing to its size and a little past, and back (`ease.outBack`), over half a second
|
|
43
|
+
const tween = kit.timers.tween(0.5, (t) => node.scaling.setAll(ease.outBack(t)))
|
|
44
|
+
orbits.set(id, { node, runes, tween, born: time })
|
|
45
|
+
}
|
|
46
|
+
const remove = (id: number) => {
|
|
47
|
+
const orbit = orbits.get(id)
|
|
48
|
+
if (!orbit) return
|
|
49
|
+
if (orbit.tween !== undefined) kit.timers.cancel(orbit.tween)
|
|
50
|
+
orbit.node.dispose()
|
|
51
|
+
orbits.delete(id)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
sync: (changes) => {
|
|
56
|
+
for (const entity of changes.added) if (entity.def.id === type) add(entity.id)
|
|
57
|
+
for (const entity of changes.removed) remove(entity.id)
|
|
58
|
+
// one that died stays in the state a while, lying: its runes go at once
|
|
59
|
+
for (const entity of changes.changed) if (entity.health <= 0) remove(entity.id)
|
|
60
|
+
},
|
|
61
|
+
frame: (_alpha, dt) => {
|
|
62
|
+
time += dt
|
|
63
|
+
for (const [id, orbit] of orbits) {
|
|
64
|
+
const drawn = kit.drawn(id)
|
|
65
|
+
orbit.node.setEnabled(!!drawn?.visible)
|
|
66
|
+
if (!drawn?.visible) continue
|
|
67
|
+
orbit.node.position.set(drawn.position.x, drawn.position.y + 1.1, drawn.position.z)
|
|
68
|
+
orbit.runes.forEach((rune, i) => {
|
|
69
|
+
const angle = (time * SPEED + i / RUNES) * Math.PI * 2 + id
|
|
70
|
+
// round in a ring that tilts as it turns, each rune bobbing on a beat of its own, spinning
|
|
71
|
+
rune.position.set(Math.cos(angle) * RADIUS, Math.sin(angle * 2 + i) * 0.18, Math.sin(angle) * RADIUS)
|
|
72
|
+
rune.rotation.set(time * 2 + i, time * 3, 0)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
dispose: () => {
|
|
77
|
+
for (const id of [...orbits.keys()]) remove(id)
|
|
78
|
+
source.dispose()
|
|
79
|
+
material.dispose()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|