lecodes-sdk 2.4.10 → 2.5.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/dist/global.d.ts +35 -0
- package/dist/types/core/env.d.ts +25 -0
- package/dist/types/core/particlesConfig.d.ts +196 -0
- package/dist/types/g2/Font2D.d.ts +19 -0
- package/dist/types/g2/Material2D.d.ts +24 -0
- package/dist/types/g2/Node2D.d.ts +6 -5
- package/dist/types/g2/Particles2D.d.ts +169 -0
- package/dist/types/g2/Path2D.d.ts +95 -0
- package/dist/types/g2/Physics2D.d.ts +1 -1
- package/dist/types/g2/Scene2D.d.ts +15 -17
- package/dist/types/g2/Sprite.d.ts +12 -1
- package/dist/types/g2/SpriteSheet.d.ts +4 -1
- package/dist/types/g2/Text2D.d.ts +96 -0
- package/dist/types/g2/Tilemap.d.ts +8 -0
- package/dist/types/g2/defineScene2d.d.ts +69 -11
- package/dist/types/gl/Foliage.d.ts +0 -7
- package/dist/types/gl/Lightmap.d.ts +101 -2
- package/dist/types/gl/Material.d.ts +21 -15
- package/dist/types/gl/Model.d.ts +8 -3
- package/dist/types/gl/Particles.d.ts +3 -135
- package/dist/types/gl/Statics.d.ts +56 -0
- package/dist/types/gl/staticsFormat.d.ts +101 -0
- package/dist/types/inject.d.ts +11 -3
- package/dist/types/runtime/env.d.ts +10 -0
- package/dist/types/runtime/envValues.d.ts +8 -0
- package/dist/types/scene/defineScene.d.ts +17 -12
- package/dist/types/server/env.d.ts +14 -0
- package/dist/types/server/inject.d.ts +1 -0
- package/dist/types/ui/fonts.d.ts +4 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/src/bridges/2d.d.ts +65 -5
- package/src/bridges/gl.d.ts +34 -15
- package/src/compile/bakeMacro.ts +3 -0
- package/src/compile/compileProject.ts +25 -4
- package/src/compile/envCompile.ts +74 -0
- package/src/compile/fontMacro.ts +11 -6
- package/src/compile/index.ts +6 -0
- package/src/compile/resourceSlots.ts +1 -0
- package/src/compile/serverSplit.ts +47 -4
- package/src/core/env.ts +47 -0
- package/src/core/particlesConfig.ts +422 -0
- package/src/g2/Font2D.ts +87 -0
- package/src/g2/Material2D.ts +64 -0
- package/src/g2/Node2D.ts +10 -6
- package/src/g2/Particles2D.ts +337 -0
- package/src/g2/Path2D.ts +215 -0
- package/src/g2/Physics2D.ts +1 -1
- package/src/g2/Scene2D.ts +16 -22
- package/src/g2/Sprite.ts +23 -4
- package/src/g2/SpriteSheet.ts +9 -5
- package/src/g2/Text2D.ts +178 -0
- package/src/g2/Tilemap.ts +10 -0
- package/src/g2/defineScene2d.ts +108 -21
- package/src/g2/groups2d.ts +2 -2
- package/src/gl/Foliage.ts +1 -16
- package/src/gl/Lightmap.ts +70 -26
- package/src/gl/Material.ts +51 -21
- package/src/gl/Model.ts +19 -5
- package/src/gl/Particles.ts +22 -361
- package/src/gl/Statics.ts +301 -0
- package/src/gl/nav/NavMesh.ts +4 -1
- package/src/gl/staticsFormat.ts +240 -0
- package/src/inject.ts +18 -3
- package/src/runtime/env.ts +13 -0
- package/src/runtime/envValues.ts +8 -0
- package/src/runtime/fetch.ts +18 -0
- package/src/scene/defineScene.ts +107 -27
- package/src/server/env.ts +25 -0
- package/src/server/host.ts +6 -0
- package/src/server/inject.ts +2 -1
- package/src/ui/fonts.ts +46 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// A 2D particle system, as a Node2D: fire, sparks, smoke, dust, magic, rain — drawn in the sprite batch,
|
|
2
|
+
// so it sorts by renderPriority / z like any node.
|
|
3
|
+
//
|
|
4
|
+
// new Particles2D({ rate: 60, lifetime: { min: 0.6, max: 1 }, size: curve(24).to(4),
|
|
5
|
+
// startVelocity: { dir: [0, 1], speed: { min: 80, max: 140 }, spread: 0.3 },
|
|
6
|
+
// color: colorCurve('#ffd27a').to('#ff3a00'), opacity: curve().fade(0.1, 0.5),
|
|
7
|
+
// material: Material2D.additive(), position: [0, -120] })
|
|
8
|
+
// new Particles2D({ texture: smoke, sheet: [4, 4], rate: 12, gravity: -40, size: curve(40).to(90) })
|
|
9
|
+
// sparks.spawn(30) // a burst
|
|
10
|
+
//
|
|
11
|
+
// One simulation and one vocabulary with 3D: the engine hosts creator-particles (the same sim the 3D
|
|
12
|
+
// `Particles` node runs, one system per entity), and the config is the same [tag, payloadLen, ...]
|
|
13
|
+
// records written by the same writers (core/particlesConfig.ts) — the same `curve()` / `colorCurve()`
|
|
14
|
+
// builders, the same param names (`size`, `rotation`, `opacity`, `frame`, `color`), the same units.
|
|
15
|
+
// What differs in 2D:
|
|
16
|
+
// - the plane: the sim runs PLANAR for a 2D system (the engine sets CPART_TAG_PLANAR at creation):
|
|
17
|
+
// shapes, launch directions, the velocity jitter and the noise field stay in XY, so a `circle` is a
|
|
18
|
+
// true disc (a `thickness` 0 one a ring) and a speed with no direction is spent in the plane. Every
|
|
19
|
+
// vector here is a Vec2 and its z is written 0. Gravity pulls toward -y (the 2D world is Y-up).
|
|
20
|
+
// - no camera sort: within a system the spawn order is the draw order; `z` / `renderPriority` order
|
|
21
|
+
// systems.
|
|
22
|
+
// - the look is a sheet TEXTURE (+ its flipbook grid), not a material: omit it for solid squares of
|
|
23
|
+
// the particle colour. The blend is the node's Material2D — `Material2D.additive()` for fire and
|
|
24
|
+
// sparks. No `Particles2D.additive`-style sugar: Material2D IS the blend.
|
|
25
|
+
// - no mesh mode, no baked light (`lit`), no `order` (z does it), no `stretch`.
|
|
26
|
+
//
|
|
27
|
+
// The node's own `rotation` (degrees, Node2D) is the emitter's; the PARTICLE rotation is the `rotation`
|
|
28
|
+
// option (radians, the 3D convention) — live, through `custom[1]`.
|
|
29
|
+
|
|
30
|
+
import { Vec2, cx, cy, type Vec2Like, type Vec3Like } from "../math/vec"
|
|
31
|
+
import {
|
|
32
|
+
MODE_POINTS, MODE_QUADS, MODE_RIBBON, SLOT_FRAME, SLOT_OPACITY, SLOT_ROTATION, SLOT_SIZE,
|
|
33
|
+
TAG_ACCELERATION, TAG_DRAG, TAG_INHERIT_VELOCITY, TAG_RATE, TAG_RATE_DISTANCE, TAG_RENDER_MODE,
|
|
34
|
+
TAG_SEED, TAG_SMOOTH_PATH, TAG_SPACE,
|
|
35
|
+
pushColor, pushDefaultFrames, pushGround, pushLifetime, pushNoise, pushParam, pushShape,
|
|
36
|
+
pushVelocity, pushVelocityLife,
|
|
37
|
+
type ParticleColor, type ParticleGround, type ParticleNoise, type ParticleShape, type ParticleValue,
|
|
38
|
+
type Range, type VelocityValue,
|
|
39
|
+
} from "../core/particlesConfig"
|
|
40
|
+
import { Node2D } from "./Node2D"
|
|
41
|
+
import type { Material2D } from "./Material2D"
|
|
42
|
+
import type { Texture2D } from "./Texture2D"
|
|
43
|
+
|
|
44
|
+
export type ParticleMode2D = "quads" | "points" | "trail"
|
|
45
|
+
|
|
46
|
+
/** Where particles are born, in the node's local frame.
|
|
47
|
+
* - `point` at `v` (default the origin);
|
|
48
|
+
* - `box` between `min` and `max` (a spawn line: give it no height);
|
|
49
|
+
* - `circle` of `radius` around `center` — a disc, evenly filled; `thickness` 0 = the outline only (a
|
|
50
|
+
* ring). Gives the launch direction (outward) when `startVelocity` has a speed and no direction;
|
|
51
|
+
* - `cone` opening along +y from its base of half-width `radius` at `center`: `angle` (half-angle,
|
|
52
|
+
* radians) flares it, `length` emits that far up the axis too; launches away from the apex.
|
|
53
|
+
* `thickness` 0 = the outline only, 1 (default) = the whole area. */
|
|
54
|
+
export type ParticleShape2D =
|
|
55
|
+
| { type: "point", v?: Vec2Like }
|
|
56
|
+
| { type: "box", min: Vec2Like, max: Vec2Like }
|
|
57
|
+
| { type: "circle", center?: Vec2Like, radius: number, thickness?: number }
|
|
58
|
+
| { type: "cone", center?: Vec2Like, radius: number, angle?: number, length?: number, thickness?: number }
|
|
59
|
+
|
|
60
|
+
/** A start velocity by direction + speed. `dir` launches along a vector, `from` away from a point,
|
|
61
|
+
* `to` toward one; with none, a sphere / cone `shape` gives the direction, else a random one.
|
|
62
|
+
* `spread` jitters the direction by up to ± that angle (radians, in the plane). */
|
|
63
|
+
export type VelocityValue2D = {
|
|
64
|
+
speed?: Range<number>
|
|
65
|
+
spread?: number
|
|
66
|
+
} & ({ from: Vec2Like } | { to: Vec2Like } | { dir: Vec2Like } | {})
|
|
67
|
+
|
|
68
|
+
/** Extra velocity over each particle's life, ADDED as a displacement: `radial` along its birth
|
|
69
|
+
* direction (a burst that decays: `curve(200).to(0)`), `x` / `y` a drift (rising smoke:
|
|
70
|
+
* `y: curve(60).from(0).to(1)`). See `VelocityOverLife` (3D). */
|
|
71
|
+
export type VelocityOverLife2D = { radial?: ParticleValue, x?: ParticleValue, y?: ParticleValue }
|
|
72
|
+
|
|
73
|
+
export type Particles2DOptions = {
|
|
74
|
+
/** The particle image — one picture or a flipbook sheet (`sheet`). Omit = solid squares of `color`. */
|
|
75
|
+
texture?: Texture2D
|
|
76
|
+
/** The texture's flipbook grid: `N` = N×N, `[cols, rows]`; frames run left-to-right, top-to-bottom.
|
|
77
|
+
* Default 1. With more than one frame and no `frame`, each particle plays the sheet once over its
|
|
78
|
+
* life, from `startFrame`. */
|
|
79
|
+
sheet?: number | [cols: number, rows: number]
|
|
80
|
+
/** Blend (+ sampling) — `Material2D.additive()` for fire, sparks, glow. Default alpha. */
|
|
81
|
+
material?: Material2D
|
|
82
|
+
/** Pool capacity (default 1000). */
|
|
83
|
+
maxParticles?: number
|
|
84
|
+
/** `'quads'` (default) a rotated square each; `'points'` the same look from a cheaper stream;
|
|
85
|
+
* `'trail'` one ribbon strip through the particles in spawn order — `size` is its width (pair it
|
|
86
|
+
* with `space: 'world'` and `rateOverDistance` for a trail behind a moving node). */
|
|
87
|
+
mode?: ParticleMode2D
|
|
88
|
+
/** Particles emitted per second. */
|
|
89
|
+
rate?: number
|
|
90
|
+
/** Particles emitted per world unit the emitter MOVES, on top of `rate` (trails; meant for
|
|
91
|
+
* `space: 'world'`). */
|
|
92
|
+
rateOverDistance?: number
|
|
93
|
+
/** Lay a frame's distance spawns along a curve through the emitter's path, not the straight chord. */
|
|
94
|
+
smooth?: boolean
|
|
95
|
+
/** `'local'` (default) — particles ride the node; `'world'` — each stays where it was born as the
|
|
96
|
+
* emitter moves on. Switching at runtime resets live particles. */
|
|
97
|
+
space?: "local" | "world"
|
|
98
|
+
/** Fraction of the emitter's own velocity given to each particle at spawn (`space: 'world'`). */
|
|
99
|
+
inheritVelocity?: number
|
|
100
|
+
shape?: ParticleShape2D
|
|
101
|
+
/** A vector (its length is the speed) or a direction + speed. World units / s. */
|
|
102
|
+
startVelocity?: Vec2Like | VelocityValue2D
|
|
103
|
+
velocityOverLife?: VelocityOverLife2D
|
|
104
|
+
/** Downward acceleration, world units/s² — positive pulls toward -y (sugar for
|
|
105
|
+
* `acceleration: [0, -g]`); negative makes smoke rise. */
|
|
106
|
+
gravity?: number
|
|
107
|
+
/** Constant acceleration (gravity, wind). Wins over `gravity` when both are set. */
|
|
108
|
+
acceleration?: Vec2Like
|
|
109
|
+
drag?: number
|
|
110
|
+
/** Seconds each particle lives. */
|
|
111
|
+
lifetime?: Range<number>
|
|
112
|
+
color?: ParticleColor
|
|
113
|
+
/** Diameter in WORLD units (custom[0]). Default 1 — a 2D world is usually pixel-scaled, so set it. */
|
|
114
|
+
size?: ParticleValue
|
|
115
|
+
/** The particle's spin, radians (custom[1]) — a curve with mode `add` turns it over its life. */
|
|
116
|
+
rotation?: ParticleValue
|
|
117
|
+
/** 0..1 (custom[2]). Default 1. */
|
|
118
|
+
opacity?: ParticleValue
|
|
119
|
+
/** Flipbook frame index (custom[3]). */
|
|
120
|
+
frame?: ParticleValue
|
|
121
|
+
/** Where the default flipbook cycle begins: `'random'` (default) or a frame index. */
|
|
122
|
+
startFrame?: number | "random"
|
|
123
|
+
/** A floor at `height` on the y axis the particles bounce on and rest on; `null` disables it. */
|
|
124
|
+
ground?: ParticleGround | null
|
|
125
|
+
/** Turbulence; `null` turns it off. */
|
|
126
|
+
noise?: ParticleNoise | null
|
|
127
|
+
/** Fixed random seed for deterministic tests. */
|
|
128
|
+
seed?: number
|
|
129
|
+
position?: Vec2Like
|
|
130
|
+
/** Draw order within a renderPriority — see {@link Node2D.z}. */
|
|
131
|
+
z?: number
|
|
132
|
+
/** Coarse draw order 0..7, default 4 — see {@link Node2D.renderPriority}. */
|
|
133
|
+
renderPriority?: number
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const MODES: Record<ParticleMode2D, number> = { quads: MODE_QUADS, points: MODE_POINTS, trail: MODE_RIBBON }
|
|
137
|
+
|
|
138
|
+
const v3 = (v: Vec2Like): Vec3Like => [ cx(v), cy(v), 0 ]
|
|
139
|
+
|
|
140
|
+
const sheetColsRows = (sheet: number | [number, number] | undefined): [number, number] => {
|
|
141
|
+
const [ c, r ] = typeof sheet === "number" ? [ sheet, sheet ] : sheet ?? [ 1, 1 ]
|
|
142
|
+
return [ Math.max(1, Math.floor(c) || 1), Math.max(1, Math.floor(r) || 1) ]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// The 2D shapes as the sim's (z = 0): circle = the sim's sphere, which the planar sim samples as a disc.
|
|
146
|
+
const shape3 = (s: ParticleShape2D): ParticleShape => {
|
|
147
|
+
if (s.type === "point") return { type: "point", v: s.v ? v3(s.v) : [ 0, 0, 0 ] }
|
|
148
|
+
if (s.type === "box") return { type: "box", min: v3(s.min), max: v3(s.max) }
|
|
149
|
+
const center = s.center ? v3(s.center) : [ 0, 0, 0 ]
|
|
150
|
+
if (s.type === "circle") return { type: "sphere", center, radius: s.radius, thickness: s.thickness }
|
|
151
|
+
return { type: "cone", center, radius: s.radius, angle: s.angle, length: s.length, thickness: s.thickness }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// The 2D velocity as the sim's: z = 0; `spread` jitters only the in-plane angle (the sim's u axis is
|
|
155
|
+
// in the plane for an in-plane direction, v points out of it).
|
|
156
|
+
const velocity3 = (val: Vec2Like | VelocityValue2D): Vec3Like | VelocityValue => {
|
|
157
|
+
if (Array.isArray(val) || val instanceof Float32Array || val instanceof Vec2) return v3(val as Vec2Like)
|
|
158
|
+
const v = val as VelocityValue2D & { dir?: Vec2Like, from?: Vec2Like, to?: Vec2Like }
|
|
159
|
+
const out: VelocityValue & Record<string, unknown> = { speed: v.speed }
|
|
160
|
+
if (v.dir !== undefined) out.dir = v3(v.dir)
|
|
161
|
+
else if (v.from !== undefined) out.from = v3(v.from)
|
|
162
|
+
else if (v.to !== undefined) out.to = v3(v.to)
|
|
163
|
+
if (v.spread !== undefined) out.randomizeAngle = { min: [ -v.spread, 0, 0 ], max: [ v.spread, 0, 0 ] }
|
|
164
|
+
return out
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export class Particles2D extends Node2D {
|
|
168
|
+
private _texture?: Texture2D
|
|
169
|
+
private _sheet: [number, number]
|
|
170
|
+
private _material?: Material2D
|
|
171
|
+
private _mode: ParticleMode2D
|
|
172
|
+
private _startFrame: number | "random"
|
|
173
|
+
private _frameSet: boolean
|
|
174
|
+
readonly maxParticles: number
|
|
175
|
+
|
|
176
|
+
/** Curve-param slots 0..3, live: size / rotation / opacity / frame alias 0..3. The particle
|
|
177
|
+
* rotation is `custom[1] = …` (the node's own `rotation` is the emitter's). */
|
|
178
|
+
readonly custom: ParticleValue[]
|
|
179
|
+
|
|
180
|
+
constructor(options: Particles2DOptions = {}) {
|
|
181
|
+
super()
|
|
182
|
+
this._texture = options.texture
|
|
183
|
+
this._sheet = sheetColsRows(options.sheet)
|
|
184
|
+
this._mode = options.mode ?? "quads"
|
|
185
|
+
this._startFrame = options.startFrame ?? "random"
|
|
186
|
+
this._frameSet = options.frame !== undefined
|
|
187
|
+
this.maxParticles = options.maxParticles ?? 0
|
|
188
|
+
// setParticles first: it is what makes the entity a particle system natively (quads by default).
|
|
189
|
+
this._sendParticles()
|
|
190
|
+
|
|
191
|
+
const id = this.id
|
|
192
|
+
const self = this
|
|
193
|
+
this.custom = new Proxy([] as ParticleValue[], {
|
|
194
|
+
set(target, prop: string, value: ParticleValue) {
|
|
195
|
+
const i = parseInt(prop)
|
|
196
|
+
if (Number.isNaN(i)) { (target as any)[prop] = value; return true }
|
|
197
|
+
target[i] = value
|
|
198
|
+
if (i === SLOT_FRAME) self._frameSet = true
|
|
199
|
+
const out: number[] = []
|
|
200
|
+
pushParam(out, i, value)
|
|
201
|
+
_creator2d.particlesConfig(id, Float32Array.from(out))
|
|
202
|
+
return true
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// every option in ONE config buffer
|
|
207
|
+
const out: number[] = []
|
|
208
|
+
if (this._mode !== "quads") out.push(TAG_RENDER_MODE, 1, MODES[this._mode])
|
|
209
|
+
if (options.ground !== undefined) pushGround(out, options.ground)
|
|
210
|
+
if (options.space !== undefined) out.push(TAG_SPACE, 1, options.space === "world" ? 1 : 0)
|
|
211
|
+
if (options.inheritVelocity !== undefined) out.push(TAG_INHERIT_VELOCITY, 1, options.inheritVelocity)
|
|
212
|
+
if (options.rateOverDistance !== undefined) out.push(TAG_RATE_DISTANCE, 1, options.rateOverDistance)
|
|
213
|
+
if (options.smooth !== undefined) out.push(TAG_SMOOTH_PATH, 1, options.smooth ? 1 : 0)
|
|
214
|
+
if (options.rate !== undefined) out.push(TAG_RATE, 1, options.rate)
|
|
215
|
+
if (options.shape !== undefined) pushShape(out, shape3(options.shape))
|
|
216
|
+
if (options.velocityOverLife !== undefined) pushVelocityLife(out, options.velocityOverLife)
|
|
217
|
+
if (options.startVelocity !== undefined) pushVelocity(out, velocity3(options.startVelocity))
|
|
218
|
+
if (options.acceleration !== undefined) {
|
|
219
|
+
out.push(TAG_ACCELERATION, 3, cx(options.acceleration), cy(options.acceleration), 0)
|
|
220
|
+
} else if (options.gravity !== undefined) {
|
|
221
|
+
out.push(TAG_ACCELERATION, 3, 0, -options.gravity, 0)
|
|
222
|
+
}
|
|
223
|
+
if (options.drag !== undefined) out.push(TAG_DRAG, 1, options.drag)
|
|
224
|
+
if (options.lifetime !== undefined) pushLifetime(out, options.lifetime)
|
|
225
|
+
if (options.color !== undefined) pushColor(out, options.color)
|
|
226
|
+
if (options.size !== undefined) pushParam(out, SLOT_SIZE, options.size)
|
|
227
|
+
if (options.rotation !== undefined) pushParam(out, SLOT_ROTATION, options.rotation)
|
|
228
|
+
if (options.opacity !== undefined) pushParam(out, SLOT_OPACITY, options.opacity)
|
|
229
|
+
if (options.frame !== undefined) pushParam(out, SLOT_FRAME, options.frame)
|
|
230
|
+
else pushDefaultFrames(out, this._sheet[0], this._sheet[1], this._startFrame)
|
|
231
|
+
if (options.noise !== undefined) pushNoise(out, options.noise)
|
|
232
|
+
if (options.seed !== undefined) out.push(TAG_SEED, 1, options.seed)
|
|
233
|
+
if (out.length > 0) this._send(out)
|
|
234
|
+
|
|
235
|
+
if (options.material) this.material = options.material
|
|
236
|
+
if (options.position) this.position = options.position
|
|
237
|
+
if (options.z !== undefined) this.z = options.z
|
|
238
|
+
if (options.renderPriority !== undefined) this.renderPriority = options.renderPriority
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private _sendParticles(): void {
|
|
242
|
+
_creator2d.setParticles(this.id, this._texture?.id ?? 0, this._sheet[0], this._sheet[1], this.maxParticles)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private _send(out: number[]): void {
|
|
246
|
+
_creator2d.particlesConfig(this.id, Float32Array.from(out))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Emit `count` particles now, where the emitter is (a burst). */
|
|
250
|
+
spawn(count: number): this { _creator2d.particlesSpawn(this.id, count); return this }
|
|
251
|
+
|
|
252
|
+
/** Live particles right now. */
|
|
253
|
+
get alive(): number { return _creator2d.particlesAlive(this.id) }
|
|
254
|
+
|
|
255
|
+
/** The particle image; undefined = solid squares of the colour. */
|
|
256
|
+
get texture(): Texture2D | undefined { return this._texture }
|
|
257
|
+
set texture(t: Texture2D | undefined) { this._texture = t; this._sendParticles() }
|
|
258
|
+
|
|
259
|
+
/** The texture's flipbook grid [cols, rows]. Without an explicit `frame` the default cycle follows. */
|
|
260
|
+
get sheet(): [number, number] { return [ this._sheet[0], this._sheet[1] ] }
|
|
261
|
+
set sheet(s: number | [number, number]) {
|
|
262
|
+
this._sheet = sheetColsRows(s)
|
|
263
|
+
this._sendParticles()
|
|
264
|
+
if (this._frameSet) return
|
|
265
|
+
const out: number[] = []
|
|
266
|
+
if (this._sheet[0] * this._sheet[1] > 1) pushDefaultFrames(out, this._sheet[0], this._sheet[1], this._startFrame)
|
|
267
|
+
else pushParam(out, SLOT_FRAME, 0)
|
|
268
|
+
this._send(out)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Blend ({@link Material2D}). Undefined = the default (alpha). */
|
|
272
|
+
get material(): Material2D | undefined { return this._material }
|
|
273
|
+
set material(m: Material2D) { this._material = m; m._apply(this.id) }
|
|
274
|
+
|
|
275
|
+
/** How particles are drawn — see `Particles2DOptions.mode`. */
|
|
276
|
+
get mode(): ParticleMode2D { return this._mode }
|
|
277
|
+
set mode(m: ParticleMode2D) { this._mode = m; this._send([ TAG_RENDER_MODE, 1, MODES[m] ]) }
|
|
278
|
+
|
|
279
|
+
set rate(val: number) { this._send([ TAG_RATE, 1, val ]) }
|
|
280
|
+
/** `'world'` leaves particles behind where they were born (trails). Switching resets live ones. */
|
|
281
|
+
set space(val: "local" | "world") { this._send([ TAG_SPACE, 1, val === "world" ? 1 : 0 ]) }
|
|
282
|
+
set inheritVelocity(val: number) { this._send([ TAG_INHERIT_VELOCITY, 1, val ]) }
|
|
283
|
+
set rateOverDistance(val: number) { this._send([ TAG_RATE_DISTANCE, 1, val ]) }
|
|
284
|
+
set smooth(val: boolean) { this._send([ TAG_SMOOTH_PATH, 1, val ? 1 : 0 ]) }
|
|
285
|
+
|
|
286
|
+
set shape(shape: ParticleShape2D) {
|
|
287
|
+
const out: number[] = []
|
|
288
|
+
pushShape(out, shape3(shape))
|
|
289
|
+
this._send(out)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
set startVelocity(val: Vec2Like | VelocityValue2D) {
|
|
293
|
+
const out: number[] = []
|
|
294
|
+
pushVelocity(out, velocity3(val))
|
|
295
|
+
this._send(out)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
set velocityOverLife(v: VelocityOverLife2D) {
|
|
299
|
+
const out: number[] = []
|
|
300
|
+
pushVelocityLife(out, v)
|
|
301
|
+
this._send(out)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Positive pulls toward -y — sugar for `acceleration = [0, -g]`. */
|
|
305
|
+
set gravity(val: number) { this._send([ TAG_ACCELERATION, 3, 0, -val, 0 ]) }
|
|
306
|
+
set acceleration(a: Vec2Like) { this._send([ TAG_ACCELERATION, 3, cx(a), cy(a), 0 ]) }
|
|
307
|
+
set drag(val: number) { this._send([ TAG_DRAG, 1, val ]) }
|
|
308
|
+
set seed(val: number) { this._send([ TAG_SEED, 1, val ]) }
|
|
309
|
+
|
|
310
|
+
set lifetime(val: Range<number>) {
|
|
311
|
+
const out: number[] = []
|
|
312
|
+
pushLifetime(out, val)
|
|
313
|
+
this._send(out)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
set size(val: ParticleValue) { this.custom[SLOT_SIZE] = val }
|
|
317
|
+
set opacity(val: ParticleValue) { this.custom[SLOT_OPACITY] = val }
|
|
318
|
+
set frame(val: ParticleValue) { this.custom[SLOT_FRAME] = val }
|
|
319
|
+
|
|
320
|
+
set color(value: ParticleColor) {
|
|
321
|
+
const out: number[] = []
|
|
322
|
+
pushColor(out, value)
|
|
323
|
+
this._send(out)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
set ground(g: ParticleGround | null) {
|
|
327
|
+
const out: number[] = []
|
|
328
|
+
pushGround(out, g)
|
|
329
|
+
this._send(out)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
set noise(n: ParticleNoise | null) {
|
|
333
|
+
const out: number[] = []
|
|
334
|
+
pushNoise(out, n)
|
|
335
|
+
this._send(out)
|
|
336
|
+
}
|
|
337
|
+
}
|
package/src/g2/Path2D.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// A vector 2D node: lines and polygons drawn in the sprite batch (through the engine's white texture,
|
|
2
|
+
// so they batch with sprites and sort by renderPriority / z like any node). What it is for: debug draw,
|
|
3
|
+
// trails and outlines, map roads and areas, UI-ish shapes inside a 2D scene.
|
|
4
|
+
//
|
|
5
|
+
// Path2D.line([[0, 0], [120, 40], [200, 0]], { width: 2, units: 'px', color: '#fc4' }) // a map road
|
|
6
|
+
// Path2D.polygon(area, { fill: '#3a74', stroke: { width: 1, units: 'px' } }) // a map area
|
|
7
|
+
// Path2D.circle(20, { fill: '#e33', position: [100, 50] })
|
|
8
|
+
//
|
|
9
|
+
// The points are in the node's LOCAL frame. Stroke `units: 'px'` is a width in logical screen pixels
|
|
10
|
+
// that stays constant while the camera zooms — what a map wants; the default 'world' scales with the
|
|
11
|
+
// zoom like the rest of the scene. Fills always close the ring; `closed` is about the stroke.
|
|
12
|
+
//
|
|
13
|
+
// The geometry is tessellated natively once per `points` / `closed` / `holes` write (the width and the
|
|
14
|
+
// anti-alias fringe are applied per frame, so zooming costs no rebuild). So when a shape MOVES, animate
|
|
15
|
+
// the node's transform (position / rotation / scale), not its points.
|
|
16
|
+
//
|
|
17
|
+
// A path has no pointer picking and no collider of its own: put a Shape2D (+ Physics2D / Trigger2D) on
|
|
18
|
+
// the same node for that.
|
|
19
|
+
|
|
20
|
+
import { Color, type ColorInput } from "../core/color"
|
|
21
|
+
import { Vec2, cx, cy, type Vec2Like } from "../math/vec"
|
|
22
|
+
import { Node2D } from "./Node2D"
|
|
23
|
+
import type { Material2D } from "./Material2D"
|
|
24
|
+
|
|
25
|
+
export type StrokeJoin2D = "miter" | "round" | "bevel"
|
|
26
|
+
export type StrokeCap2D = "butt" | "round" | "square"
|
|
27
|
+
|
|
28
|
+
export type Stroke2D = {
|
|
29
|
+
/** Line width. In `units: 'world'` (default) world units; in `'px'` logical screen pixels — constant
|
|
30
|
+
* on screen while the camera zooms (map roads, debug lines). */
|
|
31
|
+
width: number
|
|
32
|
+
/** Default white. */
|
|
33
|
+
color?: ColorInput
|
|
34
|
+
units?: "world" | "px"
|
|
35
|
+
/** How segments meet; default 'miter'. */
|
|
36
|
+
join?: StrokeJoin2D
|
|
37
|
+
/** How an OPEN path's ends look; default 'butt'. */
|
|
38
|
+
cap?: StrokeCap2D
|
|
39
|
+
/** A band around the line, `width` in the stroke's units; default colour black. Drawn UNDER every
|
|
40
|
+
* node's body at the same z, so two outlined roads merge at their junction (give a class of roads
|
|
41
|
+
* one z; a different z isolates). */
|
|
42
|
+
outline?: { width: number, color?: ColorInput }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type Path2DOptions = {
|
|
46
|
+
/** The outer ring, in the node's local frame. */
|
|
47
|
+
points: readonly Vec2Like[]
|
|
48
|
+
/** Close the ring (a polygon). Fills always close; this is about the stroke. Default false for
|
|
49
|
+
* line() and a plain constructor, true for polygon() / rect() / circle(). */
|
|
50
|
+
closed?: boolean
|
|
51
|
+
/** A number = `{ width }`. Omit = no stroke. */
|
|
52
|
+
stroke?: Stroke2D | number
|
|
53
|
+
/** Omit = no fill. */
|
|
54
|
+
fill?: ColorInput
|
|
55
|
+
/** Hole rings — fills only. */
|
|
56
|
+
holes?: readonly (readonly Vec2Like[])[]
|
|
57
|
+
/** Edge anti-aliasing (default true). Off for crisp pixel-art lines. */
|
|
58
|
+
antialias?: boolean
|
|
59
|
+
position?: Vec2Like
|
|
60
|
+
/** Degrees, CCW. */
|
|
61
|
+
rotation?: number
|
|
62
|
+
scale?: Vec2Like | number
|
|
63
|
+
/** Draw order within a renderPriority — see {@link Node2D.z}. */
|
|
64
|
+
z?: number
|
|
65
|
+
/** Coarse draw order 0..7, default 4 — see {@link Node2D.renderPriority}. */
|
|
66
|
+
renderPriority?: number
|
|
67
|
+
/** Only `blend` matters for a path (no texture); see Material2D. */
|
|
68
|
+
material?: Material2D
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type ShapeOptions = Omit<Path2DOptions, "points" | "closed">
|
|
72
|
+
|
|
73
|
+
const JOINS: Record<StrokeJoin2D, number> = { miter: 0, round: 1, bevel: 2 }
|
|
74
|
+
const CAPS: Record<StrokeCap2D, number> = { butt: 0, round: 1, square: 2 }
|
|
75
|
+
|
|
76
|
+
// x,y pairs — the engine's point format.
|
|
77
|
+
const toFloats = (points: readonly Vec2Like[]): Float32Array => {
|
|
78
|
+
const out = new Float32Array(points.length * 2)
|
|
79
|
+
for (let i = 0; i < points.length; i++) { out[i * 2] = cx(points[i]!); out[i * 2 + 1] = cy(points[i]!) }
|
|
80
|
+
return out
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class Path2D extends Node2D {
|
|
84
|
+
private _pts: Float32Array = new Float32Array(0)
|
|
85
|
+
private _closed = false
|
|
86
|
+
private _stroke: Stroke2D | null = null
|
|
87
|
+
private _outlined = false
|
|
88
|
+
private _fill: ColorInput | null = null
|
|
89
|
+
private _holes: Float32Array[] = []
|
|
90
|
+
private _antialias = true
|
|
91
|
+
private _material?: Material2D
|
|
92
|
+
|
|
93
|
+
constructor(options: Path2DOptions) {
|
|
94
|
+
super()
|
|
95
|
+
// setPath first: it is what makes the entity a path natively; stroke / fill / holes come after.
|
|
96
|
+
this._closed = options.closed ?? false
|
|
97
|
+
this.points = options.points
|
|
98
|
+
if (options.holes) this.holes = options.holes
|
|
99
|
+
if (options.stroke !== undefined) this.stroke = options.stroke
|
|
100
|
+
if (options.fill !== undefined) this.fill = options.fill
|
|
101
|
+
if (options.antialias !== undefined) this.antialias = options.antialias
|
|
102
|
+
if (options.material) this.material = options.material
|
|
103
|
+
if (options.position) this.position = options.position
|
|
104
|
+
if (options.rotation !== undefined) this.rotation = options.rotation
|
|
105
|
+
if (options.scale !== undefined) this.scale = options.scale
|
|
106
|
+
if (options.z !== undefined) this.z = options.z
|
|
107
|
+
if (options.renderPriority !== undefined) this.renderPriority = options.renderPriority
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** An open polyline. */
|
|
111
|
+
static line(
|
|
112
|
+
points: readonly Vec2Like[], stroke: Stroke2D | number,
|
|
113
|
+
options: Omit<Path2DOptions, "points" | "stroke" | "fill" | "closed"> = {},
|
|
114
|
+
): Path2D {
|
|
115
|
+
return new Path2D({ ...options, points, stroke, closed: false })
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A closed polygon; give it a `fill` and / or a `stroke`. */
|
|
119
|
+
static polygon(points: readonly Vec2Like[], options: ShapeOptions = {}): Path2D {
|
|
120
|
+
return new Path2D({ ...options, points, closed: true })
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A rectangle of `size` centred on the node's origin. */
|
|
124
|
+
static rect(size: Vec2Like, options: ShapeOptions = {}): Path2D {
|
|
125
|
+
const hw = cx(size) / 2, hh = cy(size) / 2
|
|
126
|
+
return Path2D.polygon([ [ -hw, -hh ], [ hw, -hh ], [ hw, hh ], [ -hw, hh ] ], options)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A circle centred on the node's origin. `segments` defaults to clamp(round(radius / 2), 12, 96) —
|
|
130
|
+
* generous, because a fill is not re-tessellated when the camera zooms in. */
|
|
131
|
+
static circle(radius: number, options: ShapeOptions & { segments?: number } = {}): Path2D {
|
|
132
|
+
const { segments, ...rest } = options
|
|
133
|
+
const n = Math.max(3, Math.round(segments ?? Math.min(96, Math.max(12, Math.round(radius * 0.5)))))
|
|
134
|
+
const points: Vec2Like[] = []
|
|
135
|
+
for (let i = 0; i < n; i++) {
|
|
136
|
+
const a = (i / n) * Math.PI * 2
|
|
137
|
+
points.push([ Math.cos(a) * radius, Math.sin(a) * radius ])
|
|
138
|
+
}
|
|
139
|
+
return Path2D.polygon(points, rest)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The outer ring (local frame). A write re-tessellates natively — move the node instead when you can. */
|
|
143
|
+
get points(): Vec2[] {
|
|
144
|
+
const out: Vec2[] = []
|
|
145
|
+
for (let i = 0; i < this._pts.length; i += 2) out.push(new Vec2(this._pts[i]!, this._pts[i + 1]!))
|
|
146
|
+
return out
|
|
147
|
+
}
|
|
148
|
+
set points(points: readonly Vec2Like[]) {
|
|
149
|
+
this._pts = toFloats(points)
|
|
150
|
+
_creator2d.setPath(this.id, this._pts, this._pts.length / 2, this._closed)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Whether the stroke closes the ring (fills always close). */
|
|
154
|
+
get closed(): boolean { return this._closed }
|
|
155
|
+
set closed(v: boolean) {
|
|
156
|
+
this._closed = v
|
|
157
|
+
_creator2d.setPath(this.id, this._pts, this._pts.length / 2, v)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The stroke, or null = none. A number = `{ width }`. */
|
|
161
|
+
get stroke(): Stroke2D | null { return this._stroke ? { ...this._stroke } : null }
|
|
162
|
+
set stroke(s: Stroke2D | number | null) {
|
|
163
|
+
const st = s === null ? null : typeof s === "number" ? { width: s } : { ...s }
|
|
164
|
+
this._stroke = st
|
|
165
|
+
if (!st) {
|
|
166
|
+
_creator2d.setPathStroke(this.id, 0, false, 0, 0, 0, 0, 0, 0)
|
|
167
|
+
if (this._outlined) { this._outlined = false; _creator2d.setPathOutline(this.id, 0, 0, 0, 0, 0) }
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
const [ r, g, b, a ] = st.color !== undefined ? Color.toRgba01(st.color) : [ 1, 1, 1, 1 ]
|
|
171
|
+
_creator2d.setPathStroke(
|
|
172
|
+
this.id, st.width, st.units === "px", JOINS[st.join ?? "miter"], CAPS[st.cap ?? "butt"], r, g, b, a,
|
|
173
|
+
)
|
|
174
|
+
// the outline is sent when there is one, and once more to clear a previous one
|
|
175
|
+
if (st.outline && st.outline.width > 0) {
|
|
176
|
+
this._outlined = true
|
|
177
|
+
const [ orr, og, ob, oa ] = st.outline.color !== undefined ? Color.toRgba01(st.outline.color) : [ 0, 0, 0, 1 ]
|
|
178
|
+
_creator2d.setPathOutline(this.id, st.outline.width, orr, og, ob, oa)
|
|
179
|
+
} else if (this._outlined) {
|
|
180
|
+
this._outlined = false
|
|
181
|
+
_creator2d.setPathOutline(this.id, 0, 0, 0, 0, 0)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** The fill colour, or null = no fill. */
|
|
186
|
+
get fill(): ColorInput | null { return this._fill }
|
|
187
|
+
set fill(c: ColorInput | null) {
|
|
188
|
+
this._fill = c
|
|
189
|
+
if (c === null) { _creator2d.setPathFill(this.id, false, 0, 0, 0, 0); return }
|
|
190
|
+
const [ r, g, b, a ] = Color.toRgba01(c)
|
|
191
|
+
_creator2d.setPathFill(this.id, true, r, g, b, a)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Hole rings (fills only). A write replaces them all. */
|
|
195
|
+
get holes(): Vec2[][] {
|
|
196
|
+
return this._holes.map((h) => {
|
|
197
|
+
const ring: Vec2[] = []
|
|
198
|
+
for (let i = 0; i < h.length; i += 2) ring.push(new Vec2(h[i]!, h[i + 1]!))
|
|
199
|
+
return ring
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
set holes(rings: readonly (readonly Vec2Like[])[]) {
|
|
203
|
+
this._holes = rings.map(toFloats)
|
|
204
|
+
_creator2d.pathClearHoles(this.id)
|
|
205
|
+
for (const h of this._holes) _creator2d.pathAddHole(this.id, h, h.length / 2)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Edge anti-aliasing (default true); off for crisp pixel-art lines. */
|
|
209
|
+
get antialias(): boolean { return this._antialias }
|
|
210
|
+
set antialias(v: boolean) { this._antialias = v; _creator2d.setPathAntialias(this.id, v) }
|
|
211
|
+
|
|
212
|
+
/** Blend ({@link Material2D}) — only `blend` matters for a path. Undefined = the default (alpha). */
|
|
213
|
+
get material(): Material2D | undefined { return this._material }
|
|
214
|
+
set material(m: Material2D) { this._material = m; m._apply(this.id) }
|
|
215
|
+
}
|
package/src/g2/Physics2D.ts
CHANGED
|
@@ -269,7 +269,7 @@ export class Physics2D extends Aspect<"physics", Node2D> {
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
/** Topmost body (solid or sensor) whose shape contains the world point, or null. "Topmost" means
|
|
272
|
-
* by DRAW order -
|
|
272
|
+
* by DRAW order - renderPriority, then z - so picking agrees with what the player sees. */
|
|
273
273
|
static overlapPoint(p: Vec2Like, q?: Query2D): Node2D | null {
|
|
274
274
|
return node2dById(_creator2d.physicsOverlapPoint(cx(p), cy(p), maskOf(q), ignoreOf(q), ignoreCount(q)))
|
|
275
275
|
}
|
package/src/g2/Scene2D.ts
CHANGED
|
@@ -34,24 +34,15 @@ const installRenderSyncedFrames = (): void => {
|
|
|
34
34
|
export type Scene2DOptions = {
|
|
35
35
|
/** Background color — '#10131a', 0x10131a, or [r,g,b]/[r,g,b,a] in 0..1. */
|
|
36
36
|
background?: ColorInput
|
|
37
|
-
/**
|
|
38
|
-
* (smooth —
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
/** The renderer's DEFAULT texture sampling: `'nearest'` (crisp pixel art — the engine default) or
|
|
38
|
+
* `'linear'` (smooth — hi-res / non-pixel-art assets). A node's `Material2D` `filter` overrides it
|
|
39
|
+
* per sprite / tilemap; this is what a material without one samples with. A GLOBAL renderer
|
|
40
|
+
* setting, so it is reasserted when the scene is opened (switching scenes restores the active
|
|
41
|
+
* scene's choice). Omit to leave the current setting. */
|
|
42
42
|
filter?: 'nearest' | 'linear'
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class LayerHandle {
|
|
47
|
-
private readonly sceneId: number
|
|
48
|
-
private readonly layer: number
|
|
49
|
-
constructor(sceneId: number, layer: number) { this.sceneId = sceneId; this.layer = layer }
|
|
50
|
-
/** In this layer, lower-on-screen sprites overlap higher ones (top-down feet sorting). */
|
|
51
|
-
ySort(enabled = true): this {
|
|
52
|
-
_creator2d.setLayerYSort(this.sceneId, this.layer, enabled)
|
|
53
|
-
return this
|
|
54
|
-
}
|
|
43
|
+
/** Top-down depth: among nodes of one renderPriority and one z, the one lower on screen (smaller
|
|
44
|
+
* world Y) draws on top. See {@link Scene2D.ySort}. */
|
|
45
|
+
ySort?: boolean
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
export class Scene2D implements Presentable {
|
|
@@ -72,8 +63,9 @@ export class Scene2D implements Presentable {
|
|
|
72
63
|
readonly _clickListeners: Array<(ev: ClickEvent<Node2D | null>) => void> = []
|
|
73
64
|
readonly _touchStartListeners: Array<(ev: TouchStartEvent<Node2D | null>) => void> = []
|
|
74
65
|
|
|
75
|
-
/**
|
|
66
|
+
/** Default texture filter to (re)assert on open(); the setting is global, so each scene reclaims it. */
|
|
76
67
|
private readonly _filter?: 'nearest' | 'linear'
|
|
68
|
+
private _ySort = false
|
|
77
69
|
|
|
78
70
|
constructor(options: Scene2DOptions = {}) {
|
|
79
71
|
if (!Scene2D.supported) throw new Error("Scene2D: this host has no 2D engine (Web Lite runs UI and 3D apps; a 3d / core engine variant has no 2D) — check Scene2D.supported before creating one")
|
|
@@ -82,6 +74,7 @@ export class Scene2D implements Presentable {
|
|
|
82
74
|
this.camera = new Camera2D(this.id)
|
|
83
75
|
this._filter = options.filter
|
|
84
76
|
if (options.background !== undefined) this.background = options.background
|
|
77
|
+
if (options.ySort !== undefined) this.ySort = options.ySort
|
|
85
78
|
}
|
|
86
79
|
|
|
87
80
|
set background(color: ColorInput) {
|
|
@@ -89,10 +82,11 @@ export class Scene2D implements Presentable {
|
|
|
89
82
|
_creator2d.sceneSetClearColor(this.id, r, g, b, a)
|
|
90
83
|
}
|
|
91
84
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
85
|
+
/** Top-down depth sorting, one flag for the scene: among nodes of one `renderPriority` and one `z`,
|
|
86
|
+
* the one lower on screen (smaller world Y) draws on top — give characters and props a feet anchor
|
|
87
|
+
* (`[0.5, 1]`). Draw order is renderPriority, then z, then this. Default off. */
|
|
88
|
+
get ySort(): boolean { return this._ySort }
|
|
89
|
+
set ySort(v: boolean) { this._ySort = v; _creator2d.setYSort(this.id, v) }
|
|
96
90
|
|
|
97
91
|
/** Topmost visible sprite whose world bounds contain the world point, or null. Body-free
|
|
98
92
|
* hit-test for tap/click picking — pair with camera.screenToWorld(). */
|