lecodes-cli 0.19.0 → 0.20.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 +1 -0
- package/dist/index.js +1069 -396
- package/package.json +14 -13
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
- package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/runtime/web/assets/basis-C64VHDVD.js +1 -0
- package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
- package/runtime/web/assets/createViewerLite-BvHobVhp.js +852 -0
- package/runtime/web/assets/draco-BiISTFcR.js +118 -0
- package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
- package/runtime/web/assets/index-CfNCCBjN.js +2 -0
- package/runtime/web/assets/mapViewImpl-B2JcES8l.js +810 -0
- package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
- package/runtime/web/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
- package/runtime/web/assets/worker-Caf-yYEI.js +2 -0
- package/runtime/web/embed.html +32 -0
- package/runtime/web/embed.js +171 -0
- package/src/api.ts +302 -0
- package/src/browserAuth.ts +87 -0
- package/src/cmgenTool.ts +104 -0
- package/src/commands/app.ts +896 -0
- package/src/commands/appAndroid.ts +603 -0
- package/src/commands/appDesktop.ts +339 -0
- package/src/commands/appDesktopMac.ts +470 -0
- package/src/commands/appIcon.ts +187 -0
- package/src/commands/appShared.ts +542 -0
- package/src/commands/appTemplates.ts +907 -0
- package/src/commands/appTemplatesAndroid.ts +599 -0
- package/src/commands/appTemplatesGradlew.ts +9 -0
- package/src/commands/assets.ts +28 -0
- package/src/commands/clone.ts +60 -0
- package/src/commands/compile.ts +253 -0
- package/src/commands/create.ts +59 -0
- package/src/commands/design.ts +863 -0
- package/src/commands/designTemplates.ts +11 -0
- package/src/commands/desktop.ts +127 -0
- package/src/commands/dev.ts +236 -0
- package/src/commands/diff.ts +59 -0
- package/src/commands/init.ts +191 -0
- package/src/commands/install.ts +198 -0
- package/src/commands/lightmap.ts +290 -0
- package/src/commands/link.ts +147 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/navmesh.ts +225 -0
- package/src/commands/pn.ts +305 -0
- package/src/commands/projectTemplates.ts +130 -0
- package/src/commands/pull.ts +109 -0
- package/src/commands/push.ts +173 -0
- package/src/commands/render.ts +416 -0
- package/src/commands/scene.ts +148 -0
- package/src/commands/shaders.ts +157 -0
- package/src/commands/status.ts +29 -0
- package/src/commands/test.ts +371 -0
- package/src/commands/thumbs.ts +178 -0
- package/src/commands/types.ts +87 -0
- package/src/commands/update.ts +190 -0
- package/src/compile/assetIcons.ts +214 -0
- package/src/compile/collect.ts +46 -0
- package/src/compile/collectLocal.ts +37 -0
- package/src/compile/designCompile.ts +104 -0
- package/src/compile/fonts.ts +156 -0
- package/src/compile/headlessBundle.ts +129 -0
- package/src/compile/nativeStack.ts +21 -0
- package/src/compile/projectCompile.ts +109 -0
- package/src/compile/sceneCompile.ts +113 -0
- package/src/compile/screenEntry.ts +127 -0
- package/src/compile/shaders.ts +245 -0
- package/src/config.ts +42 -0
- package/src/designMeta.ts +35 -0
- package/src/desktopRenderer.ts +545 -0
- package/src/desktopScript.ts +276 -0
- package/src/dev/clientTemplates.ts +160 -0
- package/src/dev/devServer.ts +333 -0
- package/src/dev/webRunner.ts +22 -0
- package/src/dev/wsServer.ts +144 -0
- package/src/distRoot.ts +20 -0
- package/src/ignore.ts +163 -0
- package/src/index.ts +494 -0
- package/src/lecodes-3d-editor.d.ts +41 -0
- package/src/lecodes-assets.d.ts +7 -0
- package/src/lecodes-design.d.ts +191 -0
- package/src/lecodes-renderer.d.ts +131 -0
- package/src/localFiles.ts +144 -0
- package/src/manifest.ts +46 -0
- package/src/matcTool.ts +137 -0
- package/src/peers.ts +40 -0
- package/src/project.ts +61 -0
- package/src/projectEnv.ts +94 -0
- package/src/qrcode-terminal.d.ts +9 -0
- package/src/releases.ts +125 -0
- package/src/serverDiff.ts +78 -0
- package/src/textDiff.ts +103 -0
- package/src/types.ts +0 -0
- package/src/util.ts +146 -0
- package/runtime/sdk/animate/animate.ts +0 -238
- package/runtime/sdk/animate/bezier.ts +0 -138
- package/runtime/sdk/animate/easings.ts +0 -126
- package/runtime/sdk/canvas/Canvas.ts +0 -305
- package/runtime/sdk/compile/aspectMacro.ts +0 -86
- package/runtime/sdk/compile/assetIconMacro.ts +0 -384
- package/runtime/sdk/compile/assetMacro.ts +0 -146
- package/runtime/sdk/compile/assetName.ts +0 -50
- package/runtime/sdk/compile/bundler.ts +0 -287
- package/runtime/sdk/compile/compileProject.ts +0 -129
- package/runtime/sdk/compile/detectEntry.ts +0 -128
- package/runtime/sdk/compile/fontMacro.ts +0 -459
- package/runtime/sdk/compile/fontRegistry.ts +0 -78
- package/runtime/sdk/compile/header.ts +0 -67
- package/runtime/sdk/compile/index.ts +0 -100
- package/runtime/sdk/compile/libraryImports.ts +0 -52
- package/runtime/sdk/compile/liteMaterial.ts +0 -247
- package/runtime/sdk/compile/sceneEditor.ts +0 -88
- package/runtime/sdk/compile/serverSplit.ts +0 -233
- package/runtime/sdk/compile/serverTypes.ts +0 -227
- package/runtime/sdk/compile/sfnt.ts +0 -98
- package/runtime/sdk/compile/shaderSchema.ts +0 -202
- package/runtime/sdk/compile/shaderTargets.ts +0 -81
- package/runtime/sdk/compile/sourcemap.ts +0 -25
- package/runtime/sdk/core/Aspect.ts +0 -568
- package/runtime/sdk/core/InspectorUI.ts +0 -212
- package/runtime/sdk/core/StateMachine.ts +0 -308
- package/runtime/sdk/core/color.ts +0 -66
- package/runtime/sdk/core/compWrite.ts +0 -42
- package/runtime/sdk/core/events.ts +0 -38
- package/runtime/sdk/core/fields.ts +0 -120
- package/runtime/sdk/core/registry.ts +0 -23
- package/runtime/sdk/core/signals.ts +0 -277
- package/runtime/sdk/core/time.ts +0 -84
- package/runtime/sdk/g2/Camera2D.ts +0 -40
- package/runtime/sdk/g2/CharacterController2D.ts +0 -276
- package/runtime/sdk/g2/Node2D.ts +0 -267
- package/runtime/sdk/g2/OneWay2D.ts +0 -66
- package/runtime/sdk/g2/Physics2D.ts +0 -346
- package/runtime/sdk/g2/Scene2D.ts +0 -209
- package/runtime/sdk/g2/Shape2D.ts +0 -259
- package/runtime/sdk/g2/Sprite.ts +0 -89
- package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
- package/runtime/sdk/g2/SpriteSheet.ts +0 -166
- package/runtime/sdk/g2/Texture2D.ts +0 -47
- package/runtime/sdk/g2/Tilemap.ts +0 -41
- package/runtime/sdk/g2/Tileset.ts +0 -71
- package/runtime/sdk/g2/Trigger2D.ts +0 -77
- package/runtime/sdk/g2/autotile.ts +0 -433
- package/runtime/sdk/g2/cells.ts +0 -91
- package/runtime/sdk/g2/defineScene2d.ts +0 -381
- package/runtime/sdk/g2/groups2d.ts +0 -106
- package/runtime/sdk/g2/loop.ts +0 -50
- package/runtime/sdk/g2/scenarios2d.ts +0 -69
- package/runtime/sdk/g2/touch.ts +0 -83
- package/runtime/sdk/gl/Camera.ts +0 -160
- package/runtime/sdk/gl/CameraPlace.ts +0 -52
- package/runtime/sdk/gl/CharacterController.ts +0 -238
- package/runtime/sdk/gl/Gearbox.ts +0 -212
- package/runtime/sdk/gl/Geometry.ts +0 -279
- package/runtime/sdk/gl/IK.ts +0 -193
- package/runtime/sdk/gl/InstancedMesh.ts +0 -132
- package/runtime/sdk/gl/Light.ts +0 -107
- package/runtime/sdk/gl/Lightmap.ts +0 -256
- package/runtime/sdk/gl/Material.ts +0 -260
- package/runtime/sdk/gl/Mesh.ts +0 -83
- package/runtime/sdk/gl/Model.ts +0 -124
- package/runtime/sdk/gl/NavAgent.ts +0 -337
- package/runtime/sdk/gl/NavMesh.ts +0 -397
- package/runtime/sdk/gl/Node.ts +0 -350
- package/runtime/sdk/gl/Noise.ts +0 -30
- package/runtime/sdk/gl/Particles.ts +0 -676
- package/runtime/sdk/gl/Physics.ts +0 -222
- package/runtime/sdk/gl/Plane.ts +0 -53
- package/runtime/sdk/gl/Ragdoll.ts +0 -270
- package/runtime/sdk/gl/Ray.ts +0 -16
- package/runtime/sdk/gl/Scene.ts +0 -486
- package/runtime/sdk/gl/Shape.ts +0 -409
- package/runtime/sdk/gl/Terrain.ts +0 -1092
- package/runtime/sdk/gl/Texture.ts +0 -63
- package/runtime/sdk/gl/Trigger.ts +0 -45
- package/runtime/sdk/gl/Vehicle.ts +0 -473
- package/runtime/sdk/gl/Wheel.ts +0 -240
- package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
- package/runtime/sdk/gl/animation/Animator.ts +0 -120
- package/runtime/sdk/gl/animation/Layer.ts +0 -31
- package/runtime/sdk/gl/animation/Loop.ts +0 -30
- package/runtime/sdk/gl/animation/Playback.ts +0 -44
- package/runtime/sdk/gl/animation/core.ts +0 -336
- package/runtime/sdk/gl/controls.ts +0 -95
- package/runtime/sdk/gl/physicsEvents.ts +0 -20
- package/runtime/sdk/gl/scenarios.ts +0 -291
- package/runtime/sdk/gl/state.ts +0 -6
- package/runtime/sdk/gl/terrainMesh.ts +0 -219
- package/runtime/sdk/gl/touch.ts +0 -68
- package/runtime/sdk/inject.ts +0 -197
- package/runtime/sdk/math/Mathf.ts +0 -118
- package/runtime/sdk/math/mat4.ts +0 -278
- package/runtime/sdk/math/quat.ts +0 -232
- package/runtime/sdk/math/vec.ts +0 -255
- package/runtime/sdk/net/codec.ts +0 -119
- package/runtime/sdk/net/core.ts +0 -384
- package/runtime/sdk/net/index.ts +0 -181
- package/runtime/sdk/net/replication.ts +0 -622
- package/runtime/sdk/plugins/camera.ts +0 -81
- package/runtime/sdk/plugins/geolocation.ts +0 -123
- package/runtime/sdk/plugins/oauth.ts +0 -61
- package/runtime/sdk/plugins/permission.ts +0 -7
- package/runtime/sdk/plugins/push.ts +0 -132
- package/runtime/sdk/plugins/qr.ts +0 -73
- package/runtime/sdk/plugins/service.ts +0 -47
- package/runtime/sdk/runtime/app.ts +0 -101
- package/runtime/sdk/runtime/appEvents.ts +0 -54
- package/runtime/sdk/runtime/channel.ts +0 -61
- package/runtime/sdk/runtime/clipboard.ts +0 -20
- package/runtime/sdk/runtime/datetime.ts +0 -329
- package/runtime/sdk/runtime/device.ts +0 -293
- package/runtime/sdk/runtime/fetch.ts +0 -77
- package/runtime/sdk/runtime/files.ts +0 -23
- package/runtime/sdk/runtime/input.ts +0 -175
- package/runtime/sdk/runtime/media.ts +0 -111
- package/runtime/sdk/runtime/misc.ts +0 -16
- package/runtime/sdk/runtime/net.ts +0 -36
- package/runtime/sdk/runtime/rpc.ts +0 -218
- package/runtime/sdk/runtime/service.ts +0 -83
- package/runtime/sdk/runtime/share.ts +0 -9
- package/runtime/sdk/runtime/storage.ts +0 -13
- package/runtime/sdk/runtime/touch.ts +0 -76
- package/runtime/sdk/scene/defineScene.ts +0 -1353
- package/runtime/sdk/scene/editorPlugins.ts +0 -110
- package/runtime/sdk/scene/gizmos.ts +0 -148
- package/runtime/sdk/scene/grammar.ts +0 -120
- package/runtime/sdk/scene/material.ts +0 -188
- package/runtime/sdk/server/auth/appConfig.ts +0 -12
- package/runtime/sdk/server/auth/global.ts +0 -80
- package/runtime/sdk/server/auth/host.ts +0 -318
- package/runtime/sdk/server/auth/models.ts +0 -83
- package/runtime/sdk/server/auth/types.ts +0 -50
- package/runtime/sdk/server/channel.ts +0 -56
- package/runtime/sdk/server/context.ts +0 -36
- package/runtime/sdk/server/db/defineDb.ts +0 -237
- package/runtime/sdk/server/db/fields.ts +0 -132
- package/runtime/sdk/server/db/httpTransport.ts +0 -93
- package/runtime/sdk/server/db/index.ts +0 -7
- package/runtime/sdk/server/db/marci/query.ts +0 -412
- package/runtime/sdk/server/db/types.ts +0 -202
- package/runtime/sdk/server/errors.ts +0 -12
- package/runtime/sdk/server/host.ts +0 -74
- package/runtime/sdk/server/inject.ts +0 -13
- package/runtime/sdk/server/runtime.ts +0 -133
- package/runtime/sdk/server/validate.ts +0 -87
- package/runtime/sdk/ui/NativeView.ts +0 -142
- package/runtime/sdk/ui/UI.ts +0 -39
- package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
- package/runtime/sdk/ui/UIButton.ts +0 -101
- package/runtime/sdk/ui/UIContainer.ts +0 -60
- package/runtime/sdk/ui/UIImage.ts +0 -83
- package/runtime/sdk/ui/UIInput.ts +0 -185
- package/runtime/sdk/ui/UIModal.ts +0 -139
- package/runtime/sdk/ui/UINode.ts +0 -830
- package/runtime/sdk/ui/UIPager.ts +0 -362
- package/runtime/sdk/ui/UIPopover.ts +0 -100
- package/runtime/sdk/ui/UIScreen.ts +0 -123
- package/runtime/sdk/ui/UIScrollable.ts +0 -87
- package/runtime/sdk/ui/UISpacer.ts +0 -14
- package/runtime/sdk/ui/UITabs.ts +0 -236
- package/runtime/sdk/ui/UIText.ts +0 -51
- package/runtime/sdk/ui/UIVideo.ts +0 -88
- package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
- package/runtime/sdk/ui/UIWidget.ts +0 -127
- package/runtime/sdk/ui/fonts.ts +0 -13
- package/runtime/sdk/ui/presentable.ts +0 -117
- package/runtime/sdk/ui/router.ts +0 -132
- package/runtime/sdk/ui/theme.ts +0 -84
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
// A collider shape, as an aspect on a Node2D. PURE GEOMETRY — attaching a Shape2D does not by itself
|
|
2
|
-
// make the node rigid or a sensor; pair it with a Physics2D (rigid body) or a Trigger2D (sensor), both
|
|
3
|
-
// of which require a Shape2D and turn its geometry into a Box2D fixture. The shape is also what pointer
|
|
4
|
-
// picking hits, so a node is clickable once it has a Shape2D + Physics2D/Trigger2D (a body to query).
|
|
5
|
-
//
|
|
6
|
-
// wall.aspect(Shape2D, { box: [200, 16] }).aspect(Physics2D, { motion: 'static' })
|
|
7
|
-
// goal.aspect(Shape2D, { circle: 20 }).aspect(Trigger2D)
|
|
8
|
-
// hero.aspect(Shape2D, { capsule: { from: [0, 6], to: [0, 26], radius: 6 } }).aspect(Physics2D, { motion: 'dynamic' })
|
|
9
|
-
//
|
|
10
|
-
// With no geometry set, Shape2D derives a box from WHAT THE NODE DRAWS — the sprite's size scaled by
|
|
11
|
-
// the node, positioned by its anchor. So `aspect(Shape2D, {})` on a sprite anchored at its feet gives
|
|
12
|
-
// a collider that wraps the art instead of one centred on the pivot (half of it underground), which
|
|
13
|
-
// is why every character used to carry a hand-computed offset.
|
|
14
|
-
//
|
|
15
|
-
// RE-CONFIGURING REBUILDS IT IN PLACE: `node.aspect(Shape2D, { capsule: … })` on a live node swaps the
|
|
16
|
-
// fixture while the body keeps its id, its velocity and its place in the world. That is what makes a
|
|
17
|
-
// crouch (and a live editor edit) possible.
|
|
18
|
-
|
|
19
|
-
import { Aspect } from "../core/Aspect"
|
|
20
|
-
import { Vec2, cx, cy, type Vec2Like } from "../math/vec"
|
|
21
|
-
import { ALL_GROUPS } from "./groups2d"
|
|
22
|
-
import type { Node2D } from "./Node2D"
|
|
23
|
-
|
|
24
|
-
/** @internal How an owner (Physics2D / Trigger2D) wants the geometry realised. */
|
|
25
|
-
export type ShapeFixture2D = {
|
|
26
|
-
sensor: boolean
|
|
27
|
-
friction: number
|
|
28
|
-
bounce: number
|
|
29
|
-
category?: number
|
|
30
|
-
mask?: number
|
|
31
|
-
/** Chains carry no mass, so Box2D only accepts them on a static body. */
|
|
32
|
-
staticBody?: boolean
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class Shape2D extends Aspect<"shape", Node2D> {
|
|
36
|
-
static readonly aspect = "shape"
|
|
37
|
-
|
|
38
|
-
/** Box half-extents [hw, hh] (world units). */
|
|
39
|
-
box?: Vec2Like
|
|
40
|
-
/** Circle radius. */
|
|
41
|
-
circle?: number
|
|
42
|
-
/** Capsule between two local points, with a radius. */
|
|
43
|
-
capsule?: { from: Vec2Like, to: Vec2Like, radius: number }
|
|
44
|
-
/** Thin segment (an edge) between two local points. */
|
|
45
|
-
segment?: { from: Vec2Like, to: Vec2Like }
|
|
46
|
-
/** Convex polygon, up to 8 local points (a convex hull is computed — a CONCAVE outline silently
|
|
47
|
-
* becomes its hull; use `chain` for those). */
|
|
48
|
-
polygon?: Vec2Like[]
|
|
49
|
-
/**
|
|
50
|
-
* A polyline of connected segments — the one shape that expresses a long CONCAVE surface (rolling
|
|
51
|
-
* terrain, a cave wall) in one piece, seam-free. Pass only the points that should be solid; the
|
|
52
|
-
* tangent points Box2D needs at the ends are synthesised here.
|
|
53
|
-
*
|
|
54
|
-
* STATIC bodies only (a polyline has no area, so no mass), at least 2 points (4 with `loop`), and it
|
|
55
|
-
* must not self-intersect. It is ONE-SIDED: solid on the right of the point order — walk the points
|
|
56
|
-
* so that the solid material is on your right, or set `flip`.
|
|
57
|
-
*/
|
|
58
|
-
chain?: Vec2Like[]
|
|
59
|
-
/** `chain`: close the contour (an island / a room boundary). Needs at least 4 points. */
|
|
60
|
-
loop?: boolean
|
|
61
|
-
/** `chain`: put the solid side on the other side of the line. */
|
|
62
|
-
flip?: boolean
|
|
63
|
-
/** The collider's centre relative to the node, in world units. Applies to EVERY kind — it
|
|
64
|
-
* translates a capsule's / segment's / polygon's / chain's points too, unlike the old `offset`. */
|
|
65
|
-
origin?: Vec2Like
|
|
66
|
-
|
|
67
|
-
/** @internal Geometry as of the last build — an unchanged signature means nothing to rebuild. */
|
|
68
|
-
private _sig = ""
|
|
69
|
-
/** @internal The last resize the CHARACTER refused (backs `controller.resizing`). */
|
|
70
|
-
_resizeRefused = false
|
|
71
|
-
/** @internal How the owner asked for it, so a rebuild can replay the same fixture. */
|
|
72
|
-
private _fx: ShapeFixture2D | null = null
|
|
73
|
-
private _handle = 0
|
|
74
|
-
|
|
75
|
-
/** @internal — the character claimed this shape; record the geometry so a later re-configure is
|
|
76
|
-
* compared against it. */
|
|
77
|
-
_claimedByCharacter(): void { this._sig = this._signature(); this._resizeRefused = false }
|
|
78
|
-
|
|
79
|
-
/** @internal — realise this geometry as a fixture on `handle` (a body from Physics2D/Trigger2D). */
|
|
80
|
-
_addTo(handle: number, fx: ShapeFixture2D): void {
|
|
81
|
-
this._handle = handle
|
|
82
|
-
this._fx = fx
|
|
83
|
-
this._sig = this._signature()
|
|
84
|
-
this._create()
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** @internal — the owner's material changed live; keep it for the next rebuild. */
|
|
88
|
-
_setMaterial(friction: number, bounce: number): void {
|
|
89
|
-
if (this._fx) { this._fx.friction = friction; this._fx.bounce = bounce }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** @internal — the owner's filter changed live. */
|
|
93
|
-
_setFilter(category: number, mask: number): void {
|
|
94
|
-
if (this._fx) { this._fx.category = category; this._fx.mask = mask }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
onDetach(): void { this._handle = 0; this._fx = null; this._sig = ""; this._resizeRefused = false }
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Re-configuring the aspect swaps the fixture in place — same body, same id, same velocity, same
|
|
101
|
-
* position. Nothing happens when the geometry is unchanged, so re-applying the same options in a
|
|
102
|
-
* loop is free.
|
|
103
|
-
*/
|
|
104
|
-
onReconfigure(): void {
|
|
105
|
-
const sig = this._signature()
|
|
106
|
-
if (sig === this._sig) return
|
|
107
|
-
|
|
108
|
-
// A CharacterController2D owns this shape instead of a body: resizing goes to the mover, which
|
|
109
|
-
// VALIDATES it (you cannot stand up under a low pipe) and can refuse.
|
|
110
|
-
if (this.node._xfKind === 1) {
|
|
111
|
-
const cap = this._capsule()
|
|
112
|
-
const ok = _creator2d.characterSetCapsule(this.node._xf, cap.x1, cap.y1, cap.x2, cap.y2, cap.radius)
|
|
113
|
-
// On refusal NOTHING changed — so DON'T commit the signature. The next identical
|
|
114
|
-
// `aspect(Shape2D, …)` then still sees a difference and retries; committing here would swallow
|
|
115
|
-
// every retry for good, which is the whole trap in this design.
|
|
116
|
-
this._resizeRefused = !ok
|
|
117
|
-
if (ok) this._sig = sig
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!this._handle || !this._fx) return
|
|
122
|
-
this._sig = sig
|
|
123
|
-
_creator2d.physicsClearShapes(this._handle)
|
|
124
|
-
this._create()
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @internal — this geometry as a CAPSULE, for CharacterController2D (the mover takes nothing else).
|
|
129
|
-
* An explicit capsule is used as given; anything else is derived from the drawn rect, so
|
|
130
|
-
* `aspect(Shape2D, {})` on a sprite gives a capsule that wraps the art.
|
|
131
|
-
*/
|
|
132
|
-
_capsule(): { x1: number, y1: number, x2: number, y2: number, radius: number } {
|
|
133
|
-
const ox = this.origin ? cx(this.origin) : 0
|
|
134
|
-
const oy = this.origin ? cy(this.origin) : 0
|
|
135
|
-
if (this.capsule) {
|
|
136
|
-
const { from, to, radius } = this.capsule
|
|
137
|
-
return { x1: cx(from) + ox, y1: cy(from) + oy, x2: cx(to) + ox, y2: cy(to) + oy, radius }
|
|
138
|
-
}
|
|
139
|
-
if (this.circle !== undefined) return { x1: ox, y1: oy, x2: ox, y2: oy, radius: this.circle }
|
|
140
|
-
let hw = 8, hh = 8, bx = ox, by = oy
|
|
141
|
-
if (this.box) { hw = cx(this.box); hh = cy(this.box) }
|
|
142
|
-
else {
|
|
143
|
-
const a = this._autoBox()
|
|
144
|
-
hw = a.hw; hh = a.hh
|
|
145
|
-
if (!this.origin) { bx = a.x; by = a.y }
|
|
146
|
-
}
|
|
147
|
-
const radius = Math.min(hw, hh)
|
|
148
|
-
const half = Math.max(0, hh - radius)
|
|
149
|
-
return { x1: bx, y1: by - half, x2: bx, y2: by + half, radius }
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ---- internals ---------------------------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
private _create(): void {
|
|
155
|
-
const fx = this._fx!
|
|
156
|
-
const handle = this._handle
|
|
157
|
-
const category = fx.category ?? ALL_GROUPS
|
|
158
|
-
const mask = fx.mask ?? ALL_GROUPS
|
|
159
|
-
const ox = this.origin ? cx(this.origin) : 0
|
|
160
|
-
const oy = this.origin ? cy(this.origin) : 0
|
|
161
|
-
|
|
162
|
-
if (this.chain) {
|
|
163
|
-
this._createChain(handle, fx, category, mask, ox, oy)
|
|
164
|
-
} else if (this.circle !== undefined) {
|
|
165
|
-
_creator2d.physicsAddCircle(handle, this.circle, ox, oy, fx.friction, fx.bounce, fx.sensor, category, mask)
|
|
166
|
-
} else if (this.capsule) {
|
|
167
|
-
const { from, to, radius } = this.capsule
|
|
168
|
-
_creator2d.physicsAddCapsule(handle, cx(from) + ox, cy(from) + oy, cx(to) + ox, cy(to) + oy, radius,
|
|
169
|
-
fx.friction, fx.bounce, fx.sensor, category, mask)
|
|
170
|
-
} else if (this.segment) {
|
|
171
|
-
const { from, to } = this.segment
|
|
172
|
-
_creator2d.physicsAddSegment(handle, cx(from) + ox, cy(from) + oy, cx(to) + ox, cy(to) + oy,
|
|
173
|
-
fx.friction, fx.bounce, category, mask)
|
|
174
|
-
} else if (this.polygon) {
|
|
175
|
-
const pts = new Float32Array(this.polygon.length * 2)
|
|
176
|
-
this.polygon.forEach((p, i) => { pts[i * 2] = cx(p) + ox; pts[i * 2 + 1] = cy(p) + oy })
|
|
177
|
-
_creator2d.physicsAddPolygon(handle, pts, this.polygon.length, fx.friction, fx.bounce, fx.sensor, category, mask)
|
|
178
|
-
} else {
|
|
179
|
-
// box: explicit half-extents, or "auto" — the rect the node actually draws.
|
|
180
|
-
let hw = 8, hh = 8, bx = ox, by = oy
|
|
181
|
-
if (this.box) {
|
|
182
|
-
hw = cx(this.box); hh = cy(this.box)
|
|
183
|
-
} else {
|
|
184
|
-
const auto = this._autoBox()
|
|
185
|
-
hw = auto.hw; hh = auto.hh
|
|
186
|
-
if (!this.origin) { bx = auto.x; by = auto.y } // an explicit origin still wins
|
|
187
|
-
}
|
|
188
|
-
_creator2d.physicsAddBox(handle, hw, hh, bx, by, fx.friction, fx.bounce, fx.sensor, category, mask)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Box2D's chain wants the two TANGENT points as extra entries and does not collide on its first or
|
|
194
|
-
* last edge — pass it your outline verbatim and you lose both end segments (four points give ONE).
|
|
195
|
-
* So the caller passes only the solid points and the ends are extrapolated here: `p0 + (p0 - p1)`.
|
|
196
|
-
* A loop needs none of that (every point is interior) but does need 4 of them.
|
|
197
|
-
*/
|
|
198
|
-
private _createChain(handle: number, fx: ShapeFixture2D, category: number, mask: number, ox: number, oy: number): void {
|
|
199
|
-
const src = this.chain!
|
|
200
|
-
const loop = this.loop === true
|
|
201
|
-
if (src.length < (loop ? 4 : 2)) {
|
|
202
|
-
throw new Error(`Shape2D chain needs at least ${loop ? 4 : 2} points (got ${src.length})${loop ? " — a closed contour of 3 is a polygon" : ""}`)
|
|
203
|
-
}
|
|
204
|
-
if (fx.staticBody === false) {
|
|
205
|
-
throw new Error("Shape2D { chain } works on a STATIC body only — a polyline has no area, so no mass. Use polygon/box for a dynamic body.")
|
|
206
|
-
}
|
|
207
|
-
const order = this.flip === true ? [ ...src ].reverse() : src
|
|
208
|
-
const p: number[] = []
|
|
209
|
-
if (!loop) {
|
|
210
|
-
// leading ghost: mirror the first point across the second
|
|
211
|
-
p.push(2 * cx(order[0]) - cx(order[1]) + ox, 2 * cy(order[0]) - cy(order[1]) + oy)
|
|
212
|
-
}
|
|
213
|
-
for (const v of order) p.push(cx(v) + ox, cy(v) + oy)
|
|
214
|
-
if (!loop) {
|
|
215
|
-
const n = order.length
|
|
216
|
-
p.push(2 * cx(order[n - 1]) - cx(order[n - 2]) + ox, 2 * cy(order[n - 1]) - cy(order[n - 2]) + oy)
|
|
217
|
-
}
|
|
218
|
-
_creator2d.physicsAddChain(handle, new Float32Array(p), p.length / 2, loop, fx.friction, fx.bounce, category, mask)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Everything that changes the GEOMETRY, so a re-configure that only touches the material is free.
|
|
222
|
-
private _signature(): string {
|
|
223
|
-
const o = this.origin ? `${cx(this.origin)},${cy(this.origin)}` : ""
|
|
224
|
-
if (this.chain) return `chain:${this.chain.map(v => `${cx(v)},${cy(v)}`).join(";")}|${this.loop}|${this.flip}|${o}`
|
|
225
|
-
if (this.circle !== undefined) return `circle:${this.circle}|${o}`
|
|
226
|
-
if (this.capsule) {
|
|
227
|
-
const { from, to, radius } = this.capsule
|
|
228
|
-
return `cap:${cx(from)},${cy(from)},${cx(to)},${cy(to)},${radius}|${o}`
|
|
229
|
-
}
|
|
230
|
-
if (this.segment) {
|
|
231
|
-
const { from, to } = this.segment
|
|
232
|
-
return `seg:${cx(from)},${cy(from)},${cx(to)},${cy(to)}|${o}`
|
|
233
|
-
}
|
|
234
|
-
if (this.polygon) return `poly:${this.polygon.map(v => `${cx(v)},${cy(v)}`).join(";")}|${o}`
|
|
235
|
-
if (this.box) return `box:${cx(this.box)},${cy(this.box)}|${o}`
|
|
236
|
-
const a = this._autoBox()
|
|
237
|
-
return `auto:${a.hw},${a.hh},${a.x},${a.y}|${o}`
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* @internal — half-extents and centre of the rect this node DRAWS, in the node's own frame.
|
|
242
|
-
*
|
|
243
|
-
* Sprite anchor space is Y-DOWN while the world is Y-up (see docs/conventions.md), so `anchor: [.5, 1]`
|
|
244
|
-
* ("feet") puts the art ABOVE the origin: its centre sits at +h/2. A node with no sprite falls back
|
|
245
|
-
* to 8x8 at the origin. This is a SNAPSHOT taken when the fixture is built — growing the sprite later
|
|
246
|
-
* does not resize the collider; re-configure the aspect to re-measure.
|
|
247
|
-
*/
|
|
248
|
-
private _autoBox(): { hw: number, hh: number, x: number, y: number } {
|
|
249
|
-
const n = this.node as { size?: Vec2, anchor?: Vec2, scale?: Vec2 }
|
|
250
|
-
const size = n.size
|
|
251
|
-
if (!size || !(size.x > 0) || !(size.y > 0)) return { hw: 8, hh: 8, x: 0, y: 0 }
|
|
252
|
-
const sx = n.scale ? Math.abs(n.scale.x) : 1
|
|
253
|
-
const sy = n.scale ? Math.abs(n.scale.y) : 1
|
|
254
|
-
const w = size.x * sx, h = size.y * sy
|
|
255
|
-
const ax = n.anchor ? n.anchor.x : 0.5
|
|
256
|
-
const ay = n.anchor ? n.anchor.y : 0.5
|
|
257
|
-
return { hw: w * 0.5, hh: h * 0.5, x: (0.5 - ax) * w, y: (ay - 0.5) * h }
|
|
258
|
-
}
|
|
259
|
-
}
|
package/runtime/sdk/g2/Sprite.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// A textured 2D node. Cheap setters proxy straight to the native sprite; the data lives in
|
|
2
|
-
// creator-2d. Sprite-sheet animation is no longer built in — attach the SpriteAnimation aspect:
|
|
3
|
-
// new Sprite({ texture }).aspect(SpriteAnimation, { size: [32, 48], idle: [0,1,2,3] }).anim.play('idle')
|
|
4
|
-
|
|
5
|
-
import { Color, type ColorInput } from "../core/color"
|
|
6
|
-
import { Vec2, type Vec2Like } from "../math/vec"
|
|
7
|
-
import { Canvas, isCanvas } from "../canvas/Canvas"
|
|
8
|
-
import { Node2D } from "./Node2D"
|
|
9
|
-
import type { Texture2D } from "./Texture2D"
|
|
10
|
-
|
|
11
|
-
/** Anything that can back a sprite: a texture, or a Canvas (baked to a texture on assign). */
|
|
12
|
-
export type TextureSource = Texture2D | Canvas
|
|
13
|
-
|
|
14
|
-
export type SpriteOptions = {
|
|
15
|
-
texture?: TextureSource
|
|
16
|
-
/** Pivot, [0,0] = top-left .. [1,1] = bottom-right. Use [0.5, 1] (feet) for Y-sorted depth. */
|
|
17
|
-
anchor?: Vec2Like
|
|
18
|
-
/** World size; defaults to the texture's pixel dimensions. */
|
|
19
|
-
size?: Vec2Like
|
|
20
|
-
/** Normalized UV sub-rect [u0,v0,u1,v1]. */
|
|
21
|
-
frame?: [number, number, number, number]
|
|
22
|
-
/** Tint multiplied with the texture. */
|
|
23
|
-
color?: ColorInput
|
|
24
|
-
opacity?: number
|
|
25
|
-
layer?: number
|
|
26
|
-
position?: Vec2Like
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class Sprite extends Node2D {
|
|
30
|
-
private _texture?: Texture2D
|
|
31
|
-
private _anchor = new Vec2(0.5, 0.5)
|
|
32
|
-
private _color: ColorInput = 0xffffff
|
|
33
|
-
private _opacity = 1
|
|
34
|
-
private _flipX = false
|
|
35
|
-
private _flipY = false
|
|
36
|
-
private _size: Vec2 | null = null
|
|
37
|
-
|
|
38
|
-
constructor(options: SpriteOptions = {}) {
|
|
39
|
-
super()
|
|
40
|
-
if (options.texture) this.texture = options.texture
|
|
41
|
-
if (options.size) this.size = options.size
|
|
42
|
-
if (options.anchor) this.anchor = options.anchor
|
|
43
|
-
if (options.frame) this.frame = options.frame
|
|
44
|
-
if (options.color !== undefined) this.color = options.color
|
|
45
|
-
if (options.opacity !== undefined) this.opacity = options.opacity
|
|
46
|
-
if (options.layer !== undefined) this.layer = options.layer
|
|
47
|
-
if (options.position) this.position = options.position
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get texture(): Texture2D | undefined { return this._texture }
|
|
51
|
-
set texture(t: TextureSource | undefined) {
|
|
52
|
-
const tex = isCanvas(t) ? t.texture() : t // a Canvas bakes to a texture on assign
|
|
53
|
-
this._texture = tex
|
|
54
|
-
if (!tex) return
|
|
55
|
-
_creator2d.setSprite(this.id, tex.id) // native resets UV + size to the full (pixel) texture
|
|
56
|
-
// A Canvas is authored in LOGICAL units but baked at logical × pixelRatio, so default the sprite's
|
|
57
|
-
// WORLD size to the canvas's LOGICAL size — pixelRatio then only affects crispness, not on-screen
|
|
58
|
-
// size. (A plain texture keeps the native default: the texture's pixel dimensions.)
|
|
59
|
-
if (!this._size && isCanvas(t)) this._size = new Vec2(t.width, t.height)
|
|
60
|
-
if (this._size) _creator2d.setSpriteSize(this.id, this._size.x, this._size.y)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Current world size: explicit override if set, else the texture's pixel dimensions (a Canvas-backed
|
|
64
|
-
* sprite defaults to the canvas's LOGICAL size, so pixelRatio is crispness only, not size). */
|
|
65
|
-
get size(): Vec2 { return this._size ?? (this._texture ? new Vec2(this._texture.width, this._texture.height) : new Vec2(0, 0)) }
|
|
66
|
-
set size(v: Vec2Like) { this._size = new Vec2(v); _creator2d.setSpriteSize(this.id, this._size.x, this._size.y) }
|
|
67
|
-
|
|
68
|
-
get anchor(): Vec2 { return this._anchor.clone() }
|
|
69
|
-
set anchor(v: Vec2Like) { this._anchor = new Vec2(v); _creator2d.setSpriteAnchor(this.id, this._anchor.x, this._anchor.y) }
|
|
70
|
-
|
|
71
|
-
/** Set the UV sub-rect (normalized). */
|
|
72
|
-
set frame(uv: [number, number, number, number]) { _creator2d.setSpriteFrame(this.id, uv[0], uv[1], uv[2], uv[3]) }
|
|
73
|
-
/** Set the sub-rect in texture pixels. */
|
|
74
|
-
setFramePx(px: number, py: number, pw: number, ph: number): this {
|
|
75
|
-
_creator2d.setSpriteFramePx(this.id, px, py, pw, ph)
|
|
76
|
-
return this
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
get color(): ColorInput { return this._color }
|
|
80
|
-
set color(c: ColorInput) { this._color = c; const [ r, g, b ] = Color.toRgb01(c); _creator2d.setSpriteColor(this.id, r, g, b) }
|
|
81
|
-
|
|
82
|
-
get opacity(): number { return this._opacity }
|
|
83
|
-
set opacity(a: number) { this._opacity = a; _creator2d.setSpriteOpacity(this.id, a) }
|
|
84
|
-
|
|
85
|
-
get flipX(): boolean { return this._flipX }
|
|
86
|
-
set flipX(v: boolean) { this._flipX = v; _creator2d.setSpriteFlip(this.id, v, this._flipY) }
|
|
87
|
-
get flipY(): boolean { return this._flipY }
|
|
88
|
-
set flipY(v: boolean) { this._flipY = v; _creator2d.setSpriteFlip(this.id, this._flipX, v) }
|
|
89
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
// Sprite-sheet animation as an aspect on a Sprite. Configured as DATA (frame size + named clips) in
|
|
2
|
-
// the attach call, then driven by name:
|
|
3
|
-
// sprite.aspect(SpriteAnimation, { size: [32, 48], fps: 10, clips: { idle: [0,1], walk: [4,5,6,7] } })
|
|
4
|
-
// sprite.anim.play('walk')
|
|
5
|
-
// Once defined, clips advance entirely in native code; the aspect just translates clip ids back to
|
|
6
|
-
// names for the node's 'loopReached' / 'completed' events.
|
|
7
|
-
//
|
|
8
|
-
// Directional sheets (one texture row per facing): set `directions` in row order and mark clips
|
|
9
|
-
// `perDirection` — each expands to `name_DIR` with the frames offset by that row. Then
|
|
10
|
-
// `play('walk', 'SE')` or `play('walk', moveVector)` picks the row (compass tokens: E/NE/N/NW/W/SW/S/SE),
|
|
11
|
-
// and the last direction sticks, so a later `play('idle')` keeps facing.
|
|
12
|
-
//
|
|
13
|
-
// The native clips need the texture's dimensions, so when the Sprite has no texture yet (SpriteSheet's
|
|
14
|
-
// deferred load), the define is postponed: play() calls queue by name and start once define() runs.
|
|
15
|
-
|
|
16
|
-
import { Aspect } from "../core/Aspect"
|
|
17
|
-
import { cx, cy, type Vec2Like } from "../math/vec"
|
|
18
|
-
import { ensureAnimEvents } from "./loop"
|
|
19
|
-
import type { Sprite } from "./Sprite"
|
|
20
|
-
|
|
21
|
-
/** Per-clip frame list, or an object for per-clip fps/loop/direction overrides. */
|
|
22
|
-
export type Clip = number[] | { frames: number[], fps?: number, loop?: boolean, perDirection?: boolean }
|
|
23
|
-
|
|
24
|
-
// Compass angles in Y-up world degrees (E = +x, N = +y) for vector → direction resolution.
|
|
25
|
-
const COMPASS: Record<string, number> = { E: 0, NE: 45, N: 90, NW: 135, W: 180, SW: 225, S: 270, SE: 315 }
|
|
26
|
-
|
|
27
|
-
export class SpriteAnimation extends Aspect<"anim", Sprite> {
|
|
28
|
-
static readonly aspect = "anim"
|
|
29
|
-
|
|
30
|
-
/** Grid cell size in texture pixels (defaults to the full texture). */
|
|
31
|
-
size?: [number, number]
|
|
32
|
-
/** Grid columns override — for sheets with padding; defaults to floor(texWidth / cellWidth). */
|
|
33
|
-
cols?: number
|
|
34
|
-
/** Pixel origin of the grid in the texture — cells index from here (a SpriteSheet region). */
|
|
35
|
-
origin?: [number, number]
|
|
36
|
-
/** Default fps for clips that don't override it. */
|
|
37
|
-
fps = 12
|
|
38
|
-
/** Default loop for clips that don't override it. */
|
|
39
|
-
loop = true
|
|
40
|
-
/** Named clips; frame indices are row-major into the grid. Set via the attach opts. */
|
|
41
|
-
clips: Record<string, Clip> = {}
|
|
42
|
-
/** Facing names in texture-row order (row i = directions[i]) for `perDirection` clips. */
|
|
43
|
-
directions?: string[]
|
|
44
|
-
|
|
45
|
-
private _clipIds: Record<string, number> = {}
|
|
46
|
-
private _clipNames: Record<number, string> = {}
|
|
47
|
-
private _current: string | null = null
|
|
48
|
-
private _defined = false
|
|
49
|
-
private _queued: string | null = null
|
|
50
|
-
private _dir: string | null = null
|
|
51
|
-
|
|
52
|
-
onAttach(): void {
|
|
53
|
-
// No texture yet (deferred sheet load): postpone the native define — SpriteSheet (or the user)
|
|
54
|
-
// calls define() after assigning the texture; play() calls queue until then.
|
|
55
|
-
if (this.node.texture) this._define()
|
|
56
|
-
// route native animation events for this entity to named 'loopReached'/'completed' events.
|
|
57
|
-
;(this.node as { _dispatchAnimEvent: (clipId: number, type: number) => void })._dispatchAnimEvent =
|
|
58
|
-
(clipId, type) => this.node._emitAnim(type === 1 ? "completed" : "loopReached", this._clipNames[clipId] ?? "")
|
|
59
|
-
ensureAnimEvents()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** (Re)build native clips from `this.clips`. Called on attach; call again after changing clips. */
|
|
63
|
-
define(): this { this._define(); return this }
|
|
64
|
-
|
|
65
|
-
private _define(): void {
|
|
66
|
-
const node = this.node
|
|
67
|
-
const tex = node.texture
|
|
68
|
-
if (!tex) throw new Error("SpriteAnimation: set a texture on the Sprite before defining clips")
|
|
69
|
-
const [ fw, fh ] = this.size ?? [ tex.width, tex.height ]
|
|
70
|
-
const cols = this.cols ?? Math.max(1, Math.floor(tex.width / fw))
|
|
71
|
-
const [ ox, oy ] = this.origin ?? [ 0, 0 ]
|
|
72
|
-
|
|
73
|
-
const defineOne = (name: string, indices: number[], fps: number, loop: boolean): void => {
|
|
74
|
-
const frames = new Float32Array(indices.length * 4)
|
|
75
|
-
indices.forEach((idx, i) => {
|
|
76
|
-
const px = ox + (idx % cols) * fw
|
|
77
|
-
const py = oy + Math.floor(idx / cols) * fh
|
|
78
|
-
frames[i * 4 + 0] = px / tex.width
|
|
79
|
-
frames[i * 4 + 1] = py / tex.height
|
|
80
|
-
frames[i * 4 + 2] = (px + fw) / tex.width
|
|
81
|
-
frames[i * 4 + 3] = (py + fh) / tex.height
|
|
82
|
-
})
|
|
83
|
-
const clipId = _creator2d.defineAnimation(node.id, frames, fps, loop)
|
|
84
|
-
this._clipIds[name] = clipId
|
|
85
|
-
this._clipNames[clipId] = name
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
for (const name of Object.keys(this.clips)) {
|
|
89
|
-
const value = this.clips[name]
|
|
90
|
-
const indices = Array.isArray(value) ? value : value.frames
|
|
91
|
-
const fps = Array.isArray(value) ? this.fps : (value.fps ?? this.fps)
|
|
92
|
-
const loop = Array.isArray(value) ? this.loop : (value.loop ?? this.loop)
|
|
93
|
-
const dirs = !Array.isArray(value) && value.perDirection ? this.directions : undefined
|
|
94
|
-
|
|
95
|
-
if (dirs && dirs.length > 0) {
|
|
96
|
-
// one clip per facing: row i's frames are the base (row-0) frames shifted down i rows.
|
|
97
|
-
dirs.forEach((dir, row) => defineOne(`${name}_${dir}`, indices.map((f) => f + row * cols), fps, loop))
|
|
98
|
-
} else {
|
|
99
|
-
defineOne(name, indices, fps, loop)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (this.size) _creator2d.setSpriteSize(node.id, fw, fh)
|
|
103
|
-
this._defined = true
|
|
104
|
-
|
|
105
|
-
if (this._queued) {
|
|
106
|
-
const queued = this._queued
|
|
107
|
-
this._queued = null
|
|
108
|
-
const id = this._clipIds[queued]
|
|
109
|
-
if (id === undefined) throw new Error(`SpriteAnimation.play(): unknown clip "${queued}"`)
|
|
110
|
-
_creator2d.playAnimation(node.id, id)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Play a clip by name. Re-playing the active clip is a no-op (keeps it running smoothly).
|
|
116
|
-
* For a `perDirection` clip, `dir` picks the facing — a direction name or a movement vector
|
|
117
|
-
* (nearest compass row wins); omitted, the last direction (or the first row) is kept.
|
|
118
|
-
*/
|
|
119
|
-
play(name: string, dir?: string | Vec2Like): this {
|
|
120
|
-
let target = name
|
|
121
|
-
const clip = this.clips[name]
|
|
122
|
-
if (clip && !Array.isArray(clip) && clip.perDirection && this.directions?.length) {
|
|
123
|
-
const resolved = this._resolveDir(dir)
|
|
124
|
-
if (resolved) { this._dir = resolved; target = `${name}_${resolved}` }
|
|
125
|
-
}
|
|
126
|
-
if (this._current === target) return this
|
|
127
|
-
this._current = target
|
|
128
|
-
if (!this._defined) { this._queued = target; return this }
|
|
129
|
-
const id = this._clipIds[target]
|
|
130
|
-
if (id === undefined) throw new Error(`SpriteAnimation.play(): unknown clip "${target}"`)
|
|
131
|
-
_creator2d.playAnimation(this.node.id, id)
|
|
132
|
-
return this
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
stop(): this {
|
|
136
|
-
this._current = null
|
|
137
|
-
this._queued = null
|
|
138
|
-
if (this._defined) _creator2d.stopAnimation(this.node.id)
|
|
139
|
-
return this
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** Current facing (last direction resolved by play()), or null before the first directional play. */
|
|
143
|
-
get direction(): string | null { return this._dir }
|
|
144
|
-
|
|
145
|
-
set speed(value: number) { _creator2d.setAnimationSpeed(this.node.id, value) }
|
|
146
|
-
get current(): string | null { return this._current }
|
|
147
|
-
get frame(): number { return _creator2d.getAnimationFrame(this.node.id) }
|
|
148
|
-
|
|
149
|
-
// A direction name passes through; a vector resolves to the nearest compass row this sheet has.
|
|
150
|
-
// A zero vector (standing still) keeps the current facing.
|
|
151
|
-
private _resolveDir(dir?: string | Vec2Like): string | null {
|
|
152
|
-
const dirs = this.directions!
|
|
153
|
-
if (typeof dir === "string") return dir
|
|
154
|
-
if (dir !== undefined) {
|
|
155
|
-
const x = cx(dir), y = cy(dir)
|
|
156
|
-
if (x !== 0 || y !== 0) {
|
|
157
|
-
const angle = (Math.atan2(y, x) * 180 / Math.PI + 360) % 360
|
|
158
|
-
let best: string | null = null
|
|
159
|
-
let bestDist = Infinity
|
|
160
|
-
for (const d of dirs) {
|
|
161
|
-
const a = COMPASS[d]
|
|
162
|
-
if (a === undefined) continue
|
|
163
|
-
const dist = Math.min(Math.abs(a - angle), 360 - Math.abs(a - angle))
|
|
164
|
-
if (dist < bestDist) { bestDist = dist; best = d }
|
|
165
|
-
}
|
|
166
|
-
if (best) return best
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return this._dir ?? dirs[0] ?? null
|
|
170
|
-
}
|
|
171
|
-
}
|