lecodes-cli 0.18.2 → 0.19.2
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/dist/index.js +1866 -301
- package/package.json +14 -12
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/src/api.ts +302 -0
- package/src/browserAuth.ts +87 -0
- package/src/cmgenTool.ts +104 -0
- package/src/commands/app.ts +891 -0
- package/src/commands/appAndroid.ts +603 -0
- package/src/commands/appDesktop.ts +326 -0
- package/src/commands/appDesktopMac.ts +470 -0
- package/src/commands/appIcon.ts +187 -0
- package/src/commands/appShared.ts +448 -0
- package/src/commands/appTemplates.ts +883 -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 +122 -0
- package/src/commands/dev.ts +214 -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 +129 -0
- package/src/commands/pull.ts +109 -0
- package/src/commands/push.ts +173 -0
- package/src/commands/render.ts +414 -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 +370 -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 +532 -0
- package/src/desktopScript.ts +276 -0
- package/src/dev/clientTemplates.ts +160 -0
- package/src/dev/devServer.ts +292 -0
- package/src/dev/wsServer.ts +144 -0
- package/src/distRoot.ts +20 -0
- package/src/ignore.ts +163 -0
- package/src/index.ts +491 -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/core/Aspect.ts +0 -512
- package/runtime/sdk/core/InspectorUI.ts +0 -212
- 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 -81
- 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 -99
- package/runtime/sdk/gl/Lightmap.ts +0 -179
- package/runtime/sdk/gl/Material.ts +0 -245
- package/runtime/sdk/gl/Mesh.ts +0 -83
- package/runtime/sdk/gl/Model.ts +0 -64
- 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/Ray.ts +0 -16
- package/runtime/sdk/gl/Scene.ts +0 -479
- package/runtime/sdk/gl/Shape.ts +0 -377
- package/runtime/sdk/gl/Texture.ts +0 -46
- 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 -87
- package/runtime/sdk/gl/animation/Layer.ts +0 -29
- package/runtime/sdk/gl/animation/Loop.ts +0 -25
- package/runtime/sdk/gl/animation/Playback.ts +0 -43
- package/runtime/sdk/gl/animation/core.ts +0 -294
- 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/touch.ts +0 -68
- package/runtime/sdk/inject.ts +0 -186
- 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/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 -1227
- package/runtime/sdk/scene/editorPlugins.ts +0 -92
- 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 -826
- 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,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
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
// Sprite sheets as data: one `.sprite.ts` file per image, `export default defineSpriteSheet({...})`.
|
|
2
|
-
// ONE model: every named entry is a region of the image (a rect), optionally SLICED into an
|
|
3
|
-
// animation grid, optionally carrying named clips over that grid — so game code (and the scene
|
|
4
|
-
// editor) reference frames by NAME and never hardcode rects:
|
|
5
|
-
//
|
|
6
|
-
// import props from './props.sprite'
|
|
7
|
-
// import hero from './hero.sprite'
|
|
8
|
-
// scene.add(props.make('tree_orange', { position: [80, 144], layer: 1 }))
|
|
9
|
-
// scene.add(props.make('water', { position: [0, 0] })) // sliced, loops on its own
|
|
10
|
-
// const player = hero.make('hero', { clip: 'idle', layer: 1 })
|
|
11
|
-
// player.anim.play('walk', inputVector)
|
|
12
|
-
//
|
|
13
|
-
// A sheet describes ART only — rects, slices, clips, anchors. World semantics (colliders, bodies)
|
|
14
|
-
// are the SCENE's job: attach Shape2D / Physics2D / Trigger2D aspects to the placed node (a
|
|
15
|
-
// `.scene2d.ts` `aspects:` list, or `.aspect(...)` in code). Each tool owns its own part.
|
|
16
|
-
//
|
|
17
|
-
// The texture loads lazily on the first make()/load(); sprites created before it resolves get their
|
|
18
|
-
// texture/frame applied on arrival (the world size is known up front from the rect / slice cell, so
|
|
19
|
-
// layout doesn't wait). The sprite editor reads and writes these files — hand edits are preserved
|
|
20
|
-
// (see packages/scene-doc).
|
|
21
|
-
|
|
22
|
-
import { type Vec2Like } from "../math/vec"
|
|
23
|
-
import { Sprite } from "./Sprite"
|
|
24
|
-
import { SpriteAnimation, type Clip } from "./SpriteAnimation"
|
|
25
|
-
import { Texture2D } from "./Texture2D"
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* A named region of the image. Just a rect is a static sprite; `slice` subdivides the rect into an
|
|
29
|
-
* animation grid ([cols, rows] — the cell is rect size / counts); `clips` names frame runs over
|
|
30
|
-
* that grid (local, row-major). A sliced entry with NO clips loops all of its cells in order.
|
|
31
|
-
*/
|
|
32
|
-
export type SheetSprite = {
|
|
33
|
-
/** Pixel rect [x, y, w, h] in the image. */
|
|
34
|
-
rect?: [number, number, number, number]
|
|
35
|
-
/** Subdivide the rect into an animation grid: [cols, rows]. */
|
|
36
|
-
slice?: [number, number]
|
|
37
|
-
/** Playback fps for this sprite's animation (defaults to the sheet fps, then 12). */
|
|
38
|
-
fps?: number
|
|
39
|
-
/** Named clips over the local grid (see SpriteAnimation). Omit to loop every cell. */
|
|
40
|
-
clips?: Record<string, Clip>
|
|
41
|
-
/** Facing names in local-row order for `perDirection` clips (compass tokens: S/SE/E/…). */
|
|
42
|
-
directions?: string[]
|
|
43
|
-
/** Overrides the sheet anchor. */
|
|
44
|
-
anchor?: Vec2Like
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export type SpriteSheetDef = {
|
|
48
|
-
/** The image URL — write `asset('./sheet.png')`. */
|
|
49
|
-
image: string
|
|
50
|
-
/** Default pivot for every sprite made from this sheet ([0.5, 1] = feet, for Y-sorted worlds). */
|
|
51
|
-
anchor?: Vec2Like
|
|
52
|
-
/** Default fps for sliced sprites / clips that don't override it. */
|
|
53
|
-
fps?: number
|
|
54
|
-
/** The named sprites. */
|
|
55
|
-
sprites?: Record<string, SheetSprite>
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export type SpriteMakeOptions = {
|
|
59
|
-
position?: Vec2Like
|
|
60
|
-
layer?: number
|
|
61
|
-
/** Overrides the sheet/sprite anchor. */
|
|
62
|
-
anchor?: Vec2Like
|
|
63
|
-
/** Start this clip immediately (sliced sprites with clips). */
|
|
64
|
-
clip?: string
|
|
65
|
-
/** Initial facing for `perDirection` clips. */
|
|
66
|
-
direction?: string
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** The name a sliced-but-clipless sprite's implicit everything-loop plays under. */
|
|
70
|
-
const LOOP_ALL = "all"
|
|
71
|
-
|
|
72
|
-
export class SpriteSheet {
|
|
73
|
-
private _def: SpriteSheetDef
|
|
74
|
-
private _tex: Texture2D | null = null
|
|
75
|
-
private _loading: Promise<Texture2D> | null = null
|
|
76
|
-
|
|
77
|
-
constructor(def: SpriteSheetDef) {
|
|
78
|
-
this._def = def
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** The sheet definition (read-only by convention — the editor owns the file). */
|
|
82
|
-
get def(): SpriteSheetDef { return this._def }
|
|
83
|
-
|
|
84
|
-
/** The loaded texture, or null before load() resolves. */
|
|
85
|
-
get texture(): Texture2D | null { return this._tex }
|
|
86
|
-
|
|
87
|
-
/** Load the sheet's texture (idempotent). make() starts this automatically. */
|
|
88
|
-
load(): Promise<Texture2D> {
|
|
89
|
-
if (!this._loading) {
|
|
90
|
-
this._loading = Texture2D.load(this._def.image).then((tex) => { this._tex = tex; return tex })
|
|
91
|
-
}
|
|
92
|
-
return this._loading
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Names of the sheet's sprites. */
|
|
96
|
-
get spriteNames(): string[] { return Object.keys(this._def.sprites ?? {}) }
|
|
97
|
-
|
|
98
|
-
/** Build a named sprite; starting a clip hands back the animation handle. */
|
|
99
|
-
make(name: string, opts: SpriteMakeOptions & { clip: string }): Sprite & { anim: SpriteAnimation }
|
|
100
|
-
make(name: string, opts?: SpriteMakeOptions): Sprite
|
|
101
|
-
make(name: string, opts: SpriteMakeOptions = {}): Sprite {
|
|
102
|
-
const def = this._def
|
|
103
|
-
const named = def.sprites?.[name]
|
|
104
|
-
if (!named) {
|
|
105
|
-
throw new Error(`SpriteSheet.make(): unknown sprite "${name}" (have: ${this.spriteNames.join(", ") || "none"})`)
|
|
106
|
-
}
|
|
107
|
-
const rect = named.rect
|
|
108
|
-
const slice = named.slice
|
|
109
|
-
|
|
110
|
-
const sprite = new Sprite({
|
|
111
|
-
anchor: opts.anchor ?? named.anchor ?? def.anchor,
|
|
112
|
-
layer: opts.layer,
|
|
113
|
-
position: opts.position,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
// The world size is known without the texture (the rect, or its slice cell) — set it up front
|
|
117
|
-
// so layout, Y-sort and colliders are correct even while the image is still loading.
|
|
118
|
-
if (rect) sprite.size = slice ? [ rect[2] / slice[0], rect[3] / slice[1] ] : [ rect[2], rect[3] ]
|
|
119
|
-
|
|
120
|
-
// sliced sprite: all animation flows through SpriteAnimation, anchored at the rect's origin.
|
|
121
|
-
// No clips declared = one implicit clip looping every cell (animated tiles).
|
|
122
|
-
if (rect && slice) {
|
|
123
|
-
const [ cols, rows ] = slice
|
|
124
|
-
const clips = named.clips && Object.keys(named.clips).length > 0
|
|
125
|
-
? named.clips
|
|
126
|
-
: { [LOOP_ALL]: Array.from({ length: cols * rows }, (_, i) => i) }
|
|
127
|
-
const animOpts: Partial<SpriteAnimation> = {
|
|
128
|
-
size: [ rect[2] / cols, rect[3] / rows ],
|
|
129
|
-
cols,
|
|
130
|
-
origin: [ rect[0], rect[1] ],
|
|
131
|
-
clips,
|
|
132
|
-
}
|
|
133
|
-
const fps = named.fps ?? def.fps
|
|
134
|
-
if (fps !== undefined) animOpts.fps = fps
|
|
135
|
-
if (named.directions) animOpts.directions = named.directions
|
|
136
|
-
sprite.aspect(SpriteAnimation, animOpts)
|
|
137
|
-
const start = opts.clip ?? (named.clips ? undefined : LOOP_ALL)
|
|
138
|
-
if (start) (sprite as Sprite & { anim: SpriteAnimation }).anim.play(start, opts.direction)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// texture + frame: now if loaded, else on arrival
|
|
142
|
-
if (this._tex) this._applyTexture(sprite, named, this._tex)
|
|
143
|
-
else this.load().then((tex) => this._applyTexture(sprite, named, tex))
|
|
144
|
-
return sprite
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** The pixel rect [x, y, w, h] of a named sprite's REGION (the whole rect, sliced or not). */
|
|
148
|
-
rectOf(name: string): [number, number, number, number] | null {
|
|
149
|
-
const rect = this._def.sprites?.[name]?.rect
|
|
150
|
-
return rect ? [ ...rect ] : null
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
private _applyTexture(sprite: Sprite, named: SheetSprite, tex: Texture2D): void {
|
|
154
|
-
sprite.texture = tex // native resets UV/size; the Sprite setter re-applies our explicit size
|
|
155
|
-
const rect = named.rect
|
|
156
|
-
if (rect) {
|
|
157
|
-
const [ cw, ch ] = named.slice ? [ rect[2] / named.slice[0], rect[3] / named.slice[1] ] : [ rect[2], rect[3] ]
|
|
158
|
-
sprite.setFramePx(rect[0], rect[1], cw, ch) // cell 0 (or the whole static rect)
|
|
159
|
-
}
|
|
160
|
-
const anim = (sprite as Partial<{ anim: SpriteAnimation }>).anim
|
|
161
|
-
if (anim) anim.define()
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** Declare a sprite sheet (the default export of a `.sprite.ts` file). */
|
|
166
|
-
export const defineSpriteSheet = (def: SpriteSheetDef): SpriteSheet => new SpriteSheet(def)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// A GPU texture decoded natively by creator-2d (stb_image). Load from a URL (or an already-fetched
|
|
2
|
-
// response); the raw file bytes are handed to the engine, which decodes + uploads them.
|
|
3
|
-
|
|
4
|
-
// Internal SDK code imports injected globals explicitly (the bare-global inject is only for USER
|
|
5
|
-
// code; here `fetch` would otherwise resolve to the node/bun ambient one).
|
|
6
|
-
import { fetch, type FetchResponse, type File } from "../runtime/fetch"
|
|
7
|
-
|
|
8
|
-
export class Texture2D {
|
|
9
|
-
/** Native texture handle. */
|
|
10
|
-
readonly id: number
|
|
11
|
-
readonly width: number
|
|
12
|
-
readonly height: number
|
|
13
|
-
|
|
14
|
-
constructor(id: number, width: number, height: number) {
|
|
15
|
-
this.id = id
|
|
16
|
-
this.width = width
|
|
17
|
-
this.height = height
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Wrap a Canvas's baked surface as a texture (RGBA8, already rasterized → synchronous, no decode).
|
|
21
|
-
// Structural type avoids a Canvas import cycle (Canvas imports Texture2D).
|
|
22
|
-
static fromCanvas(canvas: { _bakeSurface(): number, width: number, height: number, pixelRatio: number }): Texture2D {
|
|
23
|
-
const surfaceId = canvas._bakeSurface()
|
|
24
|
-
const texId = _creator2d.createTextureFromCanvas(surfaceId)
|
|
25
|
-
return new Texture2D(texId, Math.round(canvas.width * canvas.pixelRatio), Math.round(canvas.height * canvas.pixelRatio))
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Accepts a URL, an already-fetched response, or a File (e.g. from Canvas.toFile() / openFilePicker) —
|
|
29
|
-
// anything backed by a host buffer. The buffer's encoded bytes are decoded + uploaded by the engine.
|
|
30
|
-
static load(source: string | FetchResponse | File): Promise<Texture2D> {
|
|
31
|
-
if (typeof source === "string") {
|
|
32
|
-
return fetch(source, { useOnce: true }).then((resp) => {
|
|
33
|
-
if (resp.status >= 400) {
|
|
34
|
-
return Promise.reject(new Error(`Failed to fetch texture from ${source}. HTTP ${resp.status}`))
|
|
35
|
-
}
|
|
36
|
-
return Texture2D.load(resp)
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
return new Promise<Texture2D>((resolve, reject) => {
|
|
40
|
-
_creator2d.createTexture((source as any)._id, (id, w, h) => resolve(new Texture2D(id, w, h)), reject)
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
destroy(): void {
|
|
45
|
-
_creator2d.destroyTexture(this.id)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// A tilemap node. The grid is uploaded once and rendered natively with per-frame view culling; the
|
|
2
|
-
// node's position is the bottom-left of the map. Edit cells with setTile.
|
|
3
|
-
|
|
4
|
-
import { Node2D } from "./Node2D"
|
|
5
|
-
import type { Texture2D } from "./Texture2D"
|
|
6
|
-
|
|
7
|
-
export type TilemapOptions = {
|
|
8
|
-
texture: Texture2D
|
|
9
|
-
/** Map grid size (in tiles). */
|
|
10
|
-
cols: number
|
|
11
|
-
rows: number
|
|
12
|
-
/** On-screen cell size in world units — a single number (square) or [w, h]. */
|
|
13
|
-
tile: number | [number, number]
|
|
14
|
-
/** Atlas grid: how the texture is sliced into tiles, [atlasCols, atlasRows]. */
|
|
15
|
-
atlas: [number, number]
|
|
16
|
-
/** Row-major tile indices into the atlas (-1 = empty), length cols*rows. Row 0 is the top row. */
|
|
17
|
-
data: number[] | Int32Array
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class Tilemap extends Node2D {
|
|
21
|
-
readonly cols: number
|
|
22
|
-
readonly rows: number
|
|
23
|
-
|
|
24
|
-
constructor(options: TilemapOptions) {
|
|
25
|
-
const data = options.data instanceof Int32Array ? options.data : Int32Array.from(options.data)
|
|
26
|
-
const tileW = typeof options.tile === "number" ? options.tile : options.tile[0]
|
|
27
|
-
const tileH = typeof options.tile === "number" ? options.tile : options.tile[1]
|
|
28
|
-
const [ atlasCols, atlasRows ] = options.atlas
|
|
29
|
-
const id = _creator2d.createTilemap(
|
|
30
|
-
options.texture.id, options.cols, options.rows, tileW, tileH, atlasCols, atlasRows, data,
|
|
31
|
-
)
|
|
32
|
-
super(id) // wrap the native tilemap entity
|
|
33
|
-
this.cols = options.cols
|
|
34
|
-
this.rows = options.rows
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
setTile(x: number, y: number, index: number): this {
|
|
38
|
-
_creator2d.setTile(this.id, x, y, index)
|
|
39
|
-
return this
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// Tilesets as data: one `.tiles.ts` file per atlas, `export default defineTileset({...})` — the
|
|
2
|
-
// material vocabulary (autotile rules) for maps painted by material id. The pure model + deriver
|
|
3
|
-
// live in ./autotile.ts; this file is the runtime handle plus the `Autotile2D` aspect that binds a
|
|
4
|
-
// tilemap node to its tileset:
|
|
5
|
-
//
|
|
6
|
-
// // world.tiles.ts
|
|
7
|
-
// export default defineTileset({
|
|
8
|
-
// image: asset('./TileSet.png'),
|
|
9
|
-
// tile: 32,
|
|
10
|
-
// materials: {
|
|
11
|
-
// grass: { id: 1, kind: 'patch', color: '#4a7c3a', fill: [261, [262, 0.1]],
|
|
12
|
-
// over: { '*': { edges: [197, 262, 325, 260], outer: [198, 326, 324, 196],
|
|
13
|
-
// inner: [199, 327, 391, 263] } } },
|
|
14
|
-
// road: { id: 2, kind: 'path', color: '#8a8578', straights: [520, 521], corners: [522, 523, 524, 525] },
|
|
15
|
-
// },
|
|
16
|
-
// })
|
|
17
|
-
//
|
|
18
|
-
// // meadow.scene2d.ts
|
|
19
|
-
// import world from './world.tiles'
|
|
20
|
-
// ground: { tilemap: { texture: asset('./TileSet.png'), tile: 32, atlas: [64, 64],
|
|
21
|
-
// cells: cells('…') }, // cells hold MATERIAL ids here
|
|
22
|
-
// aspects: [use(Autotile2D, { tileset: world, seed: 3 })] }
|
|
23
|
-
//
|
|
24
|
-
// The aspect flips the meaning of the same `cells('…')` payload from raw atlas indices to material
|
|
25
|
-
// ids; the scene2d loader runs the deriver BEFORE the Tilemap is constructed (aspects are data on
|
|
26
|
-
// the node def), so no post-construction data path exists and derived indices are never persisted.
|
|
27
|
-
|
|
28
|
-
import { Aspect } from "../core/Aspect"
|
|
29
|
-
import type { Node2D } from "./Node2D"
|
|
30
|
-
import { deriveCells, type TilesetDef, type TileMaterial } from "./autotile"
|
|
31
|
-
|
|
32
|
-
export class Tileset {
|
|
33
|
-
private _def: TilesetDef
|
|
34
|
-
|
|
35
|
-
constructor(def: TilesetDef) { this._def = def }
|
|
36
|
-
|
|
37
|
-
/** The tileset definition (read-only by convention — the editor owns the file). */
|
|
38
|
-
get def(): TilesetDef { return this._def }
|
|
39
|
-
|
|
40
|
-
/** Names of the declared materials, in file order. */
|
|
41
|
-
get materialNames(): string[] { return Object.keys(this._def.materials ?? {}) }
|
|
42
|
-
|
|
43
|
-
/** A material by name. */
|
|
44
|
-
material(name: string): TileMaterial | undefined { return this._def.materials?.[name] }
|
|
45
|
-
|
|
46
|
-
/** The cell value maps store for a named material (what you paint / pass in `data`). */
|
|
47
|
-
id(name: string): number | undefined { return this._def.materials?.[name]?.id }
|
|
48
|
-
|
|
49
|
-
/** Derive display atlas indices from a material-id grid (what Autotile2D does at load). */
|
|
50
|
-
derive(cols: number, rows: number, data: ArrayLike<number>, seed = 0): Int32Array {
|
|
51
|
-
return deriveCells(this._def, cols, rows, data, seed)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Declare a tileset (the default export of a `.tiles.ts` file). */
|
|
56
|
-
export const defineTileset = (def: TilesetDef): Tileset => new Tileset(def)
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Marks a tilemap node's `cells` as MATERIAL ids of a tileset instead of raw atlas indices:
|
|
60
|
-
* `aspects: [use(Autotile2D, { tileset: world })]`. The scene2d loader derives the display
|
|
61
|
-
* indices (neighbor-mask autotile + seeded scatter, deterministic) before building the tilemap;
|
|
62
|
-
* the aspect itself holds no behavior. Change `seed` to reshuffle scatter variants.
|
|
63
|
-
*/
|
|
64
|
-
export class Autotile2D extends Aspect<"autotile", Node2D> {
|
|
65
|
-
static readonly aspect = "autotile"
|
|
66
|
-
|
|
67
|
-
/** The imported `.tiles.ts` handle. */
|
|
68
|
-
tileset: Tileset | null = null
|
|
69
|
-
/** Scatter seed — same seed, same map, same result on every platform. */
|
|
70
|
-
seed = 0
|
|
71
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// A trigger zone, as an aspect on a Node2D. Requires a Shape2D (its geometry); creates a SENSOR body
|
|
2
|
-
// from it, so a physics body overlapping the zone fires the node's 'enter' / 'exit' events instead of
|
|
3
|
-
// colliding. A trigger does not block movement.
|
|
4
|
-
//
|
|
5
|
-
// const goal = new Node2D()
|
|
6
|
-
// .aspect(Shape2D, { box: [32, 64] })
|
|
7
|
-
// .aspect(Trigger2D)
|
|
8
|
-
// goal.addEventListener('enter', other => win(other))
|
|
9
|
-
// goal.position = [x, y] // the zone follows — it is kinematic, not static
|
|
10
|
-
// goal.trigger.enabled = false // switch it off instead of parking it off-screen
|
|
11
|
-
//
|
|
12
|
-
// (A trigger is still pointer-pickable — clicks hit sensor shapes too.)
|
|
13
|
-
//
|
|
14
|
-
// Keep triggers on ROOT nodes: a sensor parented to a moving node does not follow it (see
|
|
15
|
-
// docs/2d-physics-plan.md §6).
|
|
16
|
-
|
|
17
|
-
import { Aspect } from "../core/Aspect"
|
|
18
|
-
import { ensurePhysicsEvents } from "./loop"
|
|
19
|
-
import { Shape2D } from "./Shape2D"
|
|
20
|
-
import { PhysicsGroup2D, _watchGroupChanges, _unwatchGroupChanges, categoryOf, sensorMask } from "./groups2d"
|
|
21
|
-
import type { Node2D } from "./Node2D"
|
|
22
|
-
|
|
23
|
-
export class Trigger2D extends Aspect<"trigger", Node2D> {
|
|
24
|
-
static readonly aspect = "trigger"
|
|
25
|
-
|
|
26
|
-
private _h = 0
|
|
27
|
-
private _enabled = true
|
|
28
|
-
private _group: PhysicsGroup2D | undefined
|
|
29
|
-
private _repush = (): void => {
|
|
30
|
-
if (this._h) _creator2d.physicsSetFilter(this._h, categoryOf(this._group), sensorMask(this._group))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Which collision group this zone belongs to. Live. */
|
|
34
|
-
get group(): PhysicsGroup2D | undefined { return this._group }
|
|
35
|
-
set group(g: PhysicsGroup2D | undefined) {
|
|
36
|
-
this._group = g
|
|
37
|
-
this.node.get(Shape2D)?._setFilter(categoryOf(g), sensorMask(g))
|
|
38
|
-
this._repush()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Native physics body id (0 if no physics support). Mirrors 3D `Trigger.id`. */
|
|
42
|
-
get id(): number { return this._h }
|
|
43
|
-
|
|
44
|
-
onAttach(): void {
|
|
45
|
-
if (!_creator2d.physicsHasSupport || !_creator2d.physicsHasSupport()) return
|
|
46
|
-
const shape = this.node.get(Shape2D)
|
|
47
|
-
if (!shape) {
|
|
48
|
-
throw new Error("Trigger2D requires a Shape2D aspect — add it first: node.aspect(Shape2D, {…}).aspect(Trigger2D)")
|
|
49
|
-
}
|
|
50
|
-
// KINEMATIC, not static: a zone has to be able to ride an elevator, a patrolling enemy or a
|
|
51
|
-
// fighter's fist. It used to be static, which is why games parked spent triggers at [-9999,-9999].
|
|
52
|
-
this._h = _creator2d.physicsCreateBody(this.node.id, 1 /* kinematic */)
|
|
53
|
-
if (!this._h) return
|
|
54
|
-
// sensorMask always keeps the character bit: a kinematic mover is not a Box2D body, so its shadow
|
|
55
|
-
// body is what triggers actually see, and no game should have to know that.
|
|
56
|
-
shape._addTo(this._h, { sensor: true, friction: 0, bounce: 0, category: categoryOf(this.group), mask: sensorMask(this.group) })
|
|
57
|
-
if (this._group) _watchGroupChanges(this._repush)
|
|
58
|
-
this.node._xf = this._h // node.position moves the zone
|
|
59
|
-
this.node._xfKind = 2
|
|
60
|
-
if (!this.enabled) _creator2d.physicsSetEnabled(this._h, false) // via the accessor on purpose — see Physics2D.onAttach
|
|
61
|
-
ensurePhysicsEvents() // routes contact/sensor events → the node's 'enter' / 'exit'
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
onDetach(): void {
|
|
65
|
-
_unwatchGroupChanges(this._repush)
|
|
66
|
-
if (this._h) { _creator2d.physicsRemoveBody(this._h); this._h = 0 }
|
|
67
|
-
this.node._xf = 0
|
|
68
|
-
this.node._xfKind = 0
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Switch the zone off and on — a spent pickup, a hitbox that is only live during a swing. */
|
|
72
|
-
get enabled(): boolean { return this._enabled }
|
|
73
|
-
set enabled(v: boolean) {
|
|
74
|
-
this._enabled = v
|
|
75
|
-
if (this._h) _creator2d.physicsSetEnabled(this._h, v)
|
|
76
|
-
}
|
|
77
|
-
}
|