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,209 +0,0 @@
|
|
|
1
|
-
// A 2D scene: a draw set + an orthographic Camera2D. Opening a scene asks the host to bring up the
|
|
2
|
-
// 2D engine and starts its RAF loop; only the active scene renders. Grown from the Phase 0 seed.
|
|
3
|
-
|
|
4
|
-
import { Color, type ColorInput } from "../core/color"
|
|
5
|
-
import { _installAspectFrames, _installTimeScale, _attachSystem, _detachSystem, _detachAllSystems, type Aspect, type AspectCtor, type FieldOf, type TargetOf } from "../core/Aspect"
|
|
6
|
-
import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "../ui/presentable"
|
|
7
|
-
import { cx, cy, type Vec2Like } from "../math/vec"
|
|
8
|
-
import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
|
|
9
|
-
import { Camera2D } from "./Camera2D"
|
|
10
|
-
import { node2dRegistry, type Node2D } from "./Node2D"
|
|
11
|
-
import { ensurePointerEvents } from "./touch"
|
|
12
|
-
|
|
13
|
-
// Route aspect update(dt) through the render-synced native phases (c2dRender early = before physics,
|
|
14
|
-
// late = after animations / before draw), so followers/cameras are in lockstep with the draw. Done from
|
|
15
|
-
// the 2D layer (not core) so a 3D-only bundle never pulls in the 2D bridge. Installed once, lazily on
|
|
16
|
-
// the first Scene2D. On an older wasm without the hooks the installer returns false → setLoop fallback.
|
|
17
|
-
let framesInstalled = false
|
|
18
|
-
const installRenderSyncedFrames = (): void => {
|
|
19
|
-
if (framesInstalled) return
|
|
20
|
-
framesInstalled = true
|
|
21
|
-
_installAspectFrames((early, late) => {
|
|
22
|
-
if (typeof _creator2d.setEarlyUpdate !== "function") return false
|
|
23
|
-
_creator2d.setEarlyUpdate(early)
|
|
24
|
-
_creator2d.setLateUpdate(late)
|
|
25
|
-
return true
|
|
26
|
-
})
|
|
27
|
-
// Time.scale / Time.paused reach the engine's physics + animation clocks (feature-detected).
|
|
28
|
-
_installTimeScale((scale) => { _creator2d.setTimeScale?.(scale) })
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type Scene2DOptions = {
|
|
32
|
-
/** Background color — '#10131a', 0x10131a, or [r,g,b]/[r,g,b,a] in 0..1. */
|
|
33
|
-
background?: ColorInput
|
|
34
|
-
/** Texture sampling for the 2D renderer: `'nearest'` (default — crisp pixel art) or `'linear'`
|
|
35
|
-
* (smooth — for hi-res / non-pixel-art assets). Applied when the scene is opened, so switching
|
|
36
|
-
* scenes restores the active scene's choice. NOTE: this is a GLOBAL renderer setting (one sampler
|
|
37
|
-
* for ALL textures, not per-texture), so the currently-open scene's filter wins. Omit to leave the
|
|
38
|
-
* current setting (engine default is nearest). */
|
|
39
|
-
filter?: 'nearest' | 'linear'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Returned by scene2d.layer(n); chain .ySort() to enable top-down depth sorting for that layer. */
|
|
43
|
-
class LayerHandle {
|
|
44
|
-
private readonly sceneId: number
|
|
45
|
-
private readonly layer: number
|
|
46
|
-
constructor(sceneId: number, layer: number) { this.sceneId = sceneId; this.layer = layer }
|
|
47
|
-
/** In this layer, lower-on-screen sprites overlap higher ones (top-down feet sorting). */
|
|
48
|
-
ySort(enabled = true): this {
|
|
49
|
-
_creator2d.setLayerYSort(this.sceneId, this.layer, enabled)
|
|
50
|
-
return this
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class Scene2D implements Presentable {
|
|
55
|
-
/** Native scene handle. */
|
|
56
|
-
readonly id: number
|
|
57
|
-
readonly camera: Camera2D
|
|
58
|
-
|
|
59
|
-
private static _active: Scene2D | null = null
|
|
60
|
-
static get active(): Scene2D | null { return Scene2D._active }
|
|
61
|
-
|
|
62
|
-
/** @internal — scene-level pointer listeners (the catch-all; target = hit node or null). */
|
|
63
|
-
readonly _clickListeners: Array<(ev: ClickEvent<Node2D | null>) => void> = []
|
|
64
|
-
readonly _touchStartListeners: Array<(ev: TouchStartEvent<Node2D | null>) => void> = []
|
|
65
|
-
|
|
66
|
-
/** Texture filter to (re)assert on open(); the sampler is global, so each scene reclaims it. */
|
|
67
|
-
private readonly _filter?: 'nearest' | 'linear'
|
|
68
|
-
|
|
69
|
-
constructor(options: Scene2DOptions = {}) {
|
|
70
|
-
installRenderSyncedFrames()
|
|
71
|
-
this.id = _creator2d.createScene()
|
|
72
|
-
this.camera = new Camera2D(this.id)
|
|
73
|
-
this._filter = options.filter
|
|
74
|
-
if (options.background !== undefined) this.background = options.background
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
set background(color: ColorInput) {
|
|
78
|
-
const [ r, g, b, a ] = Color.toRgba01(color)
|
|
79
|
-
_creator2d.sceneSetClearColor(this.id, r, g, b, a)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Address a draw layer to configure it, e.g. scene2d.layer(1).ySort(). */
|
|
83
|
-
layer(n: number): LayerHandle {
|
|
84
|
-
return new LayerHandle(this.id, n)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Topmost visible sprite whose world bounds contain the world point, or null. Body-free
|
|
88
|
-
* hit-test for tap/click picking — pair with camera.screenToWorld(). */
|
|
89
|
-
pick(worldPoint: Vec2Like): Node2D | null {
|
|
90
|
-
const id = _creator2d.pickSprite(this.id, cx(worldPoint), cy(worldPoint))
|
|
91
|
-
return id ? node2dRegistry.get(id) ?? null : null
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Scene-level pointer events. The handler fires for every tap/press in the scene, with
|
|
95
|
-
* `ev.target` = the hit Physics2D node (or null). On 'touchstart' you can ev.track(...) a drag. */
|
|
96
|
-
addEventListener(channel: "click", callback: (ev: ClickEvent<Node2D | null>) => void): void
|
|
97
|
-
addEventListener(channel: "touchstart", callback: (ev: TouchStartEvent<Node2D | null>) => void): void
|
|
98
|
-
addEventListener(channel: "click" | "touchstart", callback: (ev: any) => void): void {
|
|
99
|
-
const list = channel === "click" ? this._clickListeners : this._touchStartListeners
|
|
100
|
-
if (!list.includes(callback)) { list.push(callback); ensurePointerEvents() }
|
|
101
|
-
}
|
|
102
|
-
removeEventListener(channel: "click" | "touchstart", callback: (ev: any) => void): void {
|
|
103
|
-
const list = channel === "click" ? this._clickListeners : this._touchStartListeners
|
|
104
|
-
const i = list.indexOf(callback)
|
|
105
|
-
if (i >= 0) list.splice(i, 1)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
add(...nodes: Node2D[]): this {
|
|
109
|
-
for (const n of nodes) _creator2d.addEntityToScene(this.id, n.id)
|
|
110
|
-
return this
|
|
111
|
-
}
|
|
112
|
-
remove(...nodes: Node2D[]): this {
|
|
113
|
-
for (const n of nodes) _creator2d.removeEntityFromScene(this.id, n.id)
|
|
114
|
-
return this
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ---- systems: aspects of the scene (core/Aspect.ts `System<'x', Scene2D>`) ----
|
|
118
|
-
/** @internal class → instance, the authoritative store (named accessors mirror this). */
|
|
119
|
-
readonly _systems = new Map<Function, Aspect<any, any, any>>()
|
|
120
|
-
|
|
121
|
-
/** Attach (and configure) a system, or reconfigure it if already present. Returns the scene typed
|
|
122
|
-
* as now-having it (`scene.system(Hud).hud`). A 3D `System<'x'>` (target Scene) is rejected here. */
|
|
123
|
-
// `this: Self` (not `Self & Scene2D`): with the intersection TS infers Self = Scene2D and a chain
|
|
124
|
-
// `scene.system(A).system(B)` loses A's accessor from the result type.
|
|
125
|
-
system<Self extends TargetOf<A>, A extends Aspect<any, any, any>>(
|
|
126
|
-
this: Self,
|
|
127
|
-
ctor: AspectCtor<A>,
|
|
128
|
-
opts?: Partial<A>,
|
|
129
|
-
): Self & FieldOf<A> {
|
|
130
|
-
return _attachSystem(this as unknown as Scene2D, ctor, opts) as unknown as Self & FieldOf<A>
|
|
131
|
-
}
|
|
132
|
-
/** Safe access — undefined if the system isn't attached. */
|
|
133
|
-
get<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): A | undefined {
|
|
134
|
-
return this._systems.get(ctor) as A | undefined
|
|
135
|
-
}
|
|
136
|
-
/** Existence check AND type guard: inside `if (scene.has(Hud))`, `scene.hud` is present. */
|
|
137
|
-
has<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this is this & FieldOf<A> {
|
|
138
|
-
return this._systems.has(ctor)
|
|
139
|
-
}
|
|
140
|
-
/** Detach a system (runs its onDetach). Chainable. */
|
|
141
|
-
removeSystem<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this {
|
|
142
|
-
_detachSystem(this, ctor)
|
|
143
|
-
return this
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---- Presentable (docs/navigation-presentable-plan.md) ----
|
|
147
|
-
|
|
148
|
-
/** @internal presentation lifecycle — fired by the host on present/dismiss (new navigation),
|
|
149
|
-
* or synchronously by open()/close() on legacy hosts. */
|
|
150
|
-
readonly ol: (() => void)[] = []
|
|
151
|
-
readonly cl: (() => void)[] = []
|
|
152
|
-
_backButtonCallback?: () => void
|
|
153
|
-
private _vd?: object
|
|
154
|
-
|
|
155
|
-
/** Fires when the scene becomes the visible destination (incl. a pop revealing it). */
|
|
156
|
-
onOpen(callback: () => void): this {
|
|
157
|
-
this.ol.push(callback)
|
|
158
|
-
return this
|
|
159
|
-
}
|
|
160
|
-
/** Fires when the scene stops being visible (closed, replaced, or covered by a push). */
|
|
161
|
-
onClose(callback: () => void): this {
|
|
162
|
-
this.cl.push(callback)
|
|
163
|
-
return this
|
|
164
|
-
}
|
|
165
|
-
onBackPressed(callback: () => void): this {
|
|
166
|
-
this._backButtonCallback = callback
|
|
167
|
-
return this
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/** @internal stable wire descriptor; hosts hand it back via router onChange (`_p` = this). */
|
|
171
|
-
_viewDesc(): object {
|
|
172
|
-
return this._vd ??= { type: "scene2d", sceneId: this.id, _p: this }
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Make this the active scene — shows it as the current destination (replaces a screen /
|
|
176
|
-
* another scene; only the active scene renders). */
|
|
177
|
-
open(options?: PresentOptions): this {
|
|
178
|
-
_bumpNavEpoch()
|
|
179
|
-
// Reassert this scene's texture filter (the sampler is global — see Scene2DOptions.filter).
|
|
180
|
-
if (this._filter !== undefined) _creator2d.setDefaultFilter(this._filter === 'linear')
|
|
181
|
-
if (_navSupported()) {
|
|
182
|
-
// The host activates the engine (openScene) as part of presenting the destination.
|
|
183
|
-
_creatorUI.openView!(this._viewDesc(), options?.transition ?? "none")
|
|
184
|
-
} else {
|
|
185
|
-
_creator2d.openScene(this.id)
|
|
186
|
-
for (const cb of this.ol) cb()
|
|
187
|
-
}
|
|
188
|
-
Scene2D._active = this
|
|
189
|
-
_setCurrent(this)
|
|
190
|
-
return this
|
|
191
|
-
}
|
|
192
|
-
close(): this {
|
|
193
|
-
_bumpNavEpoch()
|
|
194
|
-
if (_navSupported()) {
|
|
195
|
-
_creatorUI.closeView!()
|
|
196
|
-
} else {
|
|
197
|
-
_creator2d.closeScene()
|
|
198
|
-
for (const cb of this.cl) cb()
|
|
199
|
-
}
|
|
200
|
-
if (Scene2D._active === this) Scene2D._active = null
|
|
201
|
-
if (Presentable.current === this) _setCurrent(null)
|
|
202
|
-
return this
|
|
203
|
-
}
|
|
204
|
-
destroy(): void {
|
|
205
|
-
_detachAllSystems(this) // systems go first (last-attached first), while the scene still exists
|
|
206
|
-
this.close()
|
|
207
|
-
_creator2d.destroyScene(this.id)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
@@ -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
|
-
}
|