@needle-tools/engine 4.3.0-alpha → 4.3.0-alpha.1
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/CHANGELOG.md +3 -0
- package/dist/needle-engine.bundle.js +1467 -222
- package/dist/needle-engine.bundle.light.js +1467 -222
- package/dist/needle-engine.bundle.light.min.js +32 -32
- package/dist/needle-engine.bundle.light.umd.cjs +3 -3
- package/dist/needle-engine.bundle.min.js +3 -3
- package/dist/needle-engine.bundle.umd.cjs +3 -3
- package/dist/needle-engine.light.d.ts +9 -9
- package/lib/engine/engine_types.d.ts +162 -17
- package/lib/engine-components/Animator.d.ts +129 -21
- package/lib/engine-components/Animator.js +115 -21
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.d.ts +161 -32
- package/lib/engine-components/AnimatorController.js +176 -29
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/AudioListener.d.ts +16 -5
- package/lib/engine-components/AudioListener.js +16 -5
- package/lib/engine-components/AudioListener.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +120 -28
- package/lib/engine-components/AudioSource.js +120 -37
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/AvatarLoader.d.ts +61 -0
- package/lib/engine-components/AvatarLoader.js +61 -1
- package/lib/engine-components/AvatarLoader.js.map +1 -1
- package/lib/engine-components/AxesHelper.d.ts +19 -1
- package/lib/engine-components/AxesHelper.js +19 -1
- package/lib/engine-components/AxesHelper.js.map +1 -1
- package/lib/engine-components/BoxHelperComponent.d.ts +26 -0
- package/lib/engine-components/BoxHelperComponent.js +26 -0
- package/lib/engine-components/BoxHelperComponent.js.map +1 -1
- package/lib/engine-components/Camera.d.ts +126 -37
- package/lib/engine-components/Camera.js +139 -37
- package/lib/engine-components/Camera.js.map +1 -1
- package/lib/engine-components/CameraUtils.js +20 -0
- package/lib/engine-components/CameraUtils.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +95 -21
- package/lib/engine-components/Collider.js +100 -23
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +554 -106
- package/lib/engine-components/Component.js +352 -81
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +95 -21
- package/lib/engine-components/DragControls.js +126 -32
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/DropListener.d.ts +99 -16
- package/lib/engine-components/DropListener.js +119 -14
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/Light.d.ts +102 -5
- package/lib/engine-components/Light.js +102 -44
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/NeedleMenu.d.ts +28 -11
- package/lib/engine-components/NeedleMenu.js +28 -11
- package/lib/engine-components/NeedleMenu.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +37 -5
- package/lib/engine-components/Networking.js +37 -5
- package/lib/engine-components/Networking.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.js +44 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/SpatialTrigger.d.ts +66 -1
- package/lib/engine-components/SpatialTrigger.js +74 -2
- package/lib/engine-components/SpatialTrigger.js.map +1 -1
- package/lib/engine-components/SpectatorCamera.d.ts +66 -4
- package/lib/engine-components/SpectatorCamera.js +132 -6
- package/lib/engine-components/SpectatorCamera.js.map +1 -1
- package/lib/engine-components/SyncedTransform.d.ts +45 -6
- package/lib/engine-components/SyncedTransform.js +45 -6
- package/lib/engine-components/SyncedTransform.js.map +1 -1
- package/lib/engine-components/TransformGizmo.d.ts +49 -3
- package/lib/engine-components/TransformGizmo.js +49 -3
- package/lib/engine-components/TransformGizmo.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.d.ts +131 -22
- package/lib/engine-components/webxr/WebXR.js +132 -23
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/lib/engine-components-experimental/networking/PlayerSync.d.ts +82 -9
- package/lib/engine-components-experimental/networking/PlayerSync.js +76 -11
- package/lib/engine-components-experimental/networking/PlayerSync.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/engine_types.ts +179 -18
- package/src/engine-components/Animator.ts +142 -22
- package/src/engine-components/AnimatorController.ts +184 -34
- package/src/engine-components/AudioListener.ts +16 -5
- package/src/engine-components/AudioSource.ts +126 -37
- package/src/engine-components/AvatarLoader.ts +61 -2
- package/src/engine-components/AxesHelper.ts +21 -1
- package/src/engine-components/BoxHelperComponent.ts +26 -0
- package/src/engine-components/Camera.ts +147 -41
- package/src/engine-components/CameraUtils.ts +20 -0
- package/src/engine-components/Collider.ts +102 -27
- package/src/engine-components/Component.ts +605 -129
- package/src/engine-components/DragControls.ts +134 -38
- package/src/engine-components/DropListener.ts +143 -23
- package/src/engine-components/Light.ts +105 -44
- package/src/engine-components/NeedleMenu.ts +29 -11
- package/src/engine-components/Networking.ts +37 -6
- package/src/engine-components/SceneSwitcher.ts +48 -1
- package/src/engine-components/SpatialTrigger.ts +80 -3
- package/src/engine-components/SpectatorCamera.ts +136 -18
- package/src/engine-components/SyncedTransform.ts +50 -7
- package/src/engine-components/TransformGizmo.ts +49 -4
- package/src/engine-components/webxr/WebXR.ts +144 -27
- package/src/engine-components-experimental/networking/PlayerSync.ts +85 -13
|
@@ -17,50 +17,77 @@ import { AvatarMarker } from "./webxr/WebXRAvatar.js";
|
|
|
17
17
|
import { XRStateFlag } from "./webxr/XRFlag.js";
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Defines the viewing perspective in spectator mode
|
|
22
|
+
*/
|
|
20
23
|
export enum SpectatorMode {
|
|
24
|
+
/** View from the perspective of the followed player */
|
|
21
25
|
FirstPerson = 0,
|
|
26
|
+
/** Freely view from a third-person perspective */
|
|
22
27
|
ThirdPerson = 1,
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
const debug = getParam("debugspectator");
|
|
26
31
|
|
|
27
32
|
/**
|
|
33
|
+
* Provides functionality to follow and spectate other users in a networked environment.
|
|
34
|
+
* Handles camera switching, following behavior, and network synchronization for spectator mode.
|
|
35
|
+
*
|
|
36
|
+
* Debug mode can be enabled with the URL parameter `?debugspectator`, which provides additional console output.
|
|
37
|
+
*
|
|
28
38
|
* @category Networking
|
|
29
39
|
* @group Components
|
|
30
40
|
*/
|
|
31
41
|
export class SpectatorCamera extends Behaviour {
|
|
32
42
|
|
|
43
|
+
/** Reference to the Camera component on this GameObject */
|
|
33
44
|
cam: Camera | null = null;
|
|
34
45
|
|
|
35
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* When enabled, pressing F will send a request to all connected users to follow the local player.
|
|
48
|
+
* Pressing ESC will stop spectating.
|
|
49
|
+
*/
|
|
36
50
|
@serializable()
|
|
37
51
|
useKeys: boolean = true;
|
|
38
52
|
|
|
39
53
|
private _mode: SpectatorMode = SpectatorMode.FirstPerson;
|
|
40
54
|
|
|
55
|
+
/** Gets the current spectator perspective mode */
|
|
41
56
|
get mode() { return this._mode; }
|
|
57
|
+
/** Sets the current spectator perspective mode */
|
|
42
58
|
set mode(val: SpectatorMode) {
|
|
43
59
|
this._mode = val;
|
|
44
60
|
}
|
|
45
61
|
|
|
46
|
-
/**
|
|
62
|
+
/** Returns whether this user is currently spectating another user */
|
|
47
63
|
get isSpectating(): boolean {
|
|
48
64
|
return this._handler?.currentTarget !== undefined;
|
|
49
65
|
}
|
|
50
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Checks if this instance is spectating the user with the given ID
|
|
69
|
+
* @param userId The user ID to check
|
|
70
|
+
* @returns True if spectating the specified user, false otherwise
|
|
71
|
+
*/
|
|
51
72
|
isSpectatingUser(userId: string): boolean {
|
|
52
73
|
return this.target?.userId === userId;
|
|
53
74
|
}
|
|
54
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Checks if the user with the specified ID is following this user
|
|
78
|
+
* @param userId The user ID to check
|
|
79
|
+
* @returns True if the specified user is following this user, false otherwise
|
|
80
|
+
*/
|
|
55
81
|
isFollowedBy(userId: string): boolean {
|
|
56
82
|
return this.followers?.includes(userId);
|
|
57
83
|
}
|
|
58
84
|
|
|
59
|
-
/**
|
|
85
|
+
/** List of user IDs that are currently following the user */
|
|
60
86
|
get followers(): string[] {
|
|
61
87
|
return this._networking.followers;
|
|
62
88
|
}
|
|
63
89
|
|
|
90
|
+
/** Stops the current spectating session */
|
|
64
91
|
stopSpectating() {
|
|
65
92
|
if (this.context.isInXR) {
|
|
66
93
|
this.followSelf();
|
|
@@ -69,11 +96,15 @@ export class SpectatorCamera extends Behaviour {
|
|
|
69
96
|
this.target = undefined;
|
|
70
97
|
}
|
|
71
98
|
|
|
99
|
+
/** Gets the local player's connection ID */
|
|
72
100
|
private get localId() : string {
|
|
73
101
|
return this.context.connection.connectionId ?? "local";
|
|
74
102
|
}
|
|
75
103
|
|
|
76
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* Sets the player view to follow
|
|
106
|
+
* @param target The PlayerView to follow, or undefined to stop spectating
|
|
107
|
+
*/
|
|
77
108
|
set target(target: PlayerView | undefined) {
|
|
78
109
|
if (this._handler) {
|
|
79
110
|
|
|
@@ -106,14 +137,17 @@ export class SpectatorCamera extends Behaviour {
|
|
|
106
137
|
}
|
|
107
138
|
}
|
|
108
139
|
|
|
140
|
+
/** Gets the currently followed player view */
|
|
109
141
|
get target(): PlayerView | undefined {
|
|
110
142
|
return this._handler?.currentTarget;
|
|
111
143
|
}
|
|
112
144
|
|
|
145
|
+
/** Sends a network request for all users to follow this player */
|
|
113
146
|
requestAllFollowMe() {
|
|
114
147
|
this._networking.onRequestFollowMe();
|
|
115
148
|
}
|
|
116
149
|
|
|
150
|
+
/** Determines if the camera is spectating the local player */
|
|
117
151
|
private get isSpectatingSelf() {
|
|
118
152
|
return this.isSpectating && this.target?.currentObject === this.context.players.getPlayerView(this.localId)?.currentObject;
|
|
119
153
|
}
|
|
@@ -131,7 +165,6 @@ export class SpectatorCamera extends Behaviour {
|
|
|
131
165
|
private _networking!: SpectatorCamNetworking;
|
|
132
166
|
|
|
133
167
|
awake(): void {
|
|
134
|
-
|
|
135
168
|
this._debug = new SpectatorSelectionController(this.context, this);
|
|
136
169
|
this._networking = new SpectatorCamNetworking(this.context, this);
|
|
137
170
|
this._networking.awake();
|
|
@@ -144,7 +177,6 @@ export class SpectatorCamera extends Behaviour {
|
|
|
144
177
|
return;
|
|
145
178
|
}
|
|
146
179
|
|
|
147
|
-
|
|
148
180
|
if (!this._handler && this.cam)
|
|
149
181
|
this._handler = new SpectatorHandler(this.context, this.cam, this);
|
|
150
182
|
|
|
@@ -157,6 +189,10 @@ export class SpectatorCamera extends Behaviour {
|
|
|
157
189
|
this._networking?.destroy();
|
|
158
190
|
}
|
|
159
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Checks if the current platform supports spectator mode
|
|
194
|
+
* @returns True if the platform is supported, false otherwise
|
|
195
|
+
*/
|
|
160
196
|
private isSupportedPlatform() {
|
|
161
197
|
const ua = window.navigator.userAgent;
|
|
162
198
|
const standalone = /Windows|MacOS/.test(ua);
|
|
@@ -164,12 +200,19 @@ export class SpectatorCamera extends Behaviour {
|
|
|
164
200
|
return standalone && !isHololens;
|
|
165
201
|
}
|
|
166
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Called before entering WebXR mode
|
|
205
|
+
* @param _evt The WebXR event
|
|
206
|
+
*/
|
|
167
207
|
onBeforeXR(_evt) {
|
|
168
208
|
if (!this.isSupportedPlatform()) return;
|
|
169
209
|
GameObject.setActive(this.gameObject, true);
|
|
170
210
|
}
|
|
171
211
|
|
|
172
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Called when entering WebXR mode
|
|
214
|
+
* @param _evt The WebXR event
|
|
215
|
+
*/
|
|
173
216
|
onEnterXR(_evt) {
|
|
174
217
|
if (!this.isSupportedPlatform()) return;
|
|
175
218
|
if (debug) console.log(this.context.mainCamera);
|
|
@@ -178,6 +221,10 @@ export class SpectatorCamera extends Behaviour {
|
|
|
178
221
|
}
|
|
179
222
|
}
|
|
180
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Called when exiting WebXR mode
|
|
226
|
+
* @param _evt The WebXR event
|
|
227
|
+
*/
|
|
181
228
|
onLeaveXR(_evt) {
|
|
182
229
|
this.context.removeCamera(this.cam as ICamera);
|
|
183
230
|
GameObject.setActive(this.gameObject, false);
|
|
@@ -188,7 +235,9 @@ export class SpectatorCamera extends Behaviour {
|
|
|
188
235
|
this.stopSpectating();
|
|
189
236
|
}
|
|
190
237
|
|
|
191
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Sets the target to follow the local player
|
|
240
|
+
*/
|
|
192
241
|
private followSelf() {
|
|
193
242
|
this.target = this.context.players.getPlayerView(this.context.connection.connectionId);
|
|
194
243
|
if (!this.target) {
|
|
@@ -201,6 +250,9 @@ export class SpectatorCamera extends Behaviour {
|
|
|
201
250
|
// TODO: only show Spectator cam for DesktopVR;
|
|
202
251
|
// don't show for AR, don't show on Quest
|
|
203
252
|
// TODO: properly align cameras on enter/exit VR, seems currently spectator cam breaks alignment
|
|
253
|
+
/**
|
|
254
|
+
* Called after the main rendering pass to render the spectator view
|
|
255
|
+
*/
|
|
204
256
|
onAfterRender(): void {
|
|
205
257
|
if (!this.cam) return;
|
|
206
258
|
|
|
@@ -278,6 +330,9 @@ export class SpectatorCamera extends Behaviour {
|
|
|
278
330
|
this.resetAvatarFlags();
|
|
279
331
|
}
|
|
280
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Updates avatar visibility flags for rendering in spectator mode
|
|
335
|
+
*/
|
|
281
336
|
private setAvatarFlagsBeforeRender() {
|
|
282
337
|
const isFirstPersonMode = this._mode === SpectatorMode.FirstPerson;
|
|
283
338
|
|
|
@@ -295,6 +350,9 @@ export class SpectatorCamera extends Behaviour {
|
|
|
295
350
|
}
|
|
296
351
|
}
|
|
297
352
|
|
|
353
|
+
/**
|
|
354
|
+
* Restores avatar visibility flags after spectator rendering
|
|
355
|
+
*/
|
|
298
356
|
private resetAvatarFlags() {
|
|
299
357
|
for (const av of AvatarMarker.instances) {
|
|
300
358
|
if (av.avatar && "flags" in av.avatar) {
|
|
@@ -313,6 +371,9 @@ export class SpectatorCamera extends Behaviour {
|
|
|
313
371
|
}
|
|
314
372
|
}
|
|
315
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Interface for handling spectator camera behavior
|
|
376
|
+
*/
|
|
316
377
|
interface ISpectatorHandler {
|
|
317
378
|
context: Context;
|
|
318
379
|
get currentTarget(): PlayerView | undefined;
|
|
@@ -322,6 +383,9 @@ interface ISpectatorHandler {
|
|
|
322
383
|
destroy();
|
|
323
384
|
}
|
|
324
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Handles the smooth following behavior for the spectator camera
|
|
388
|
+
*/
|
|
325
389
|
class SpectatorHandler implements ISpectatorHandler {
|
|
326
390
|
|
|
327
391
|
readonly context: Context;
|
|
@@ -333,6 +397,7 @@ class SpectatorHandler implements ISpectatorHandler {
|
|
|
333
397
|
private view?: PlayerView;
|
|
334
398
|
private currentObject: Object3D | undefined;
|
|
335
399
|
|
|
400
|
+
/** Gets the currently targeted player view */
|
|
336
401
|
get currentTarget(): PlayerView | undefined {
|
|
337
402
|
return this.view;
|
|
338
403
|
}
|
|
@@ -343,6 +408,10 @@ class SpectatorHandler implements ISpectatorHandler {
|
|
|
343
408
|
this.spectator = spectator;
|
|
344
409
|
}
|
|
345
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Sets the target player view to follow
|
|
413
|
+
* @param view The PlayerView to follow
|
|
414
|
+
*/
|
|
346
415
|
set(view?: PlayerView): void {
|
|
347
416
|
const followObject = view?.currentObject;
|
|
348
417
|
if (!followObject) {
|
|
@@ -368,6 +437,7 @@ class SpectatorHandler implements ISpectatorHandler {
|
|
|
368
437
|
else this.context.removeCamera(this.cam as ICamera);
|
|
369
438
|
}
|
|
370
439
|
|
|
440
|
+
/** Disables the spectator following behavior */
|
|
371
441
|
disable() {
|
|
372
442
|
if (debug) console.log("STOP FOLLOW", this.currentObject);
|
|
373
443
|
this.view = undefined;
|
|
@@ -377,12 +447,17 @@ class SpectatorHandler implements ISpectatorHandler {
|
|
|
377
447
|
this.follow.enabled = false;
|
|
378
448
|
}
|
|
379
449
|
|
|
450
|
+
/** Cleans up resources used by the handler */
|
|
380
451
|
destroy() {
|
|
381
452
|
this.target?.removeFromParent();
|
|
382
453
|
if (this.follow)
|
|
383
454
|
GameObject.destroy(this.follow);
|
|
384
455
|
}
|
|
385
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Updates the camera position and orientation based on the spectator mode
|
|
459
|
+
* @param mode The current spectator mode (first or third person)
|
|
460
|
+
*/
|
|
386
461
|
update(mode: SpectatorMode) {
|
|
387
462
|
if (this.currentTarget?.isConnected === false || this.currentTarget?.removed === true) {
|
|
388
463
|
if (debug) console.log("Target disconnected or timeout", this.currentTarget);
|
|
@@ -431,13 +506,13 @@ class SpectatorHandler implements ISpectatorHandler {
|
|
|
431
506
|
target.quaternion.copy(_inverseYQuat);
|
|
432
507
|
else target.quaternion.identity();
|
|
433
508
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
509
|
}
|
|
437
510
|
|
|
438
511
|
const _inverseYQuat = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
|
439
512
|
|
|
440
|
-
|
|
513
|
+
/**
|
|
514
|
+
* Handles user input for selecting targets to spectate
|
|
515
|
+
*/
|
|
441
516
|
class SpectatorSelectionController {
|
|
442
517
|
|
|
443
518
|
private readonly context: Context;
|
|
@@ -468,6 +543,9 @@ class SpectatorSelectionController {
|
|
|
468
543
|
});
|
|
469
544
|
}
|
|
470
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Attempts to select an avatar to spectate through raycasting
|
|
548
|
+
*/
|
|
471
549
|
private trySelectObject() {
|
|
472
550
|
const opts = new RaycastOptions();
|
|
473
551
|
opts.setMask(0xffffff);
|
|
@@ -491,17 +569,17 @@ class SpectatorSelectionController {
|
|
|
491
569
|
}
|
|
492
570
|
}
|
|
493
571
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
572
|
+
/**
|
|
573
|
+
* Network model for communicating follower changes
|
|
574
|
+
*/
|
|
498
575
|
class SpectatorFollowerChangedEventModel implements IModel {
|
|
499
|
-
/**
|
|
576
|
+
/** The user ID that is following */
|
|
500
577
|
guid: string;
|
|
501
578
|
readonly dontSave: boolean = true;
|
|
502
579
|
|
|
503
|
-
/**
|
|
580
|
+
/** The user ID being followed */
|
|
504
581
|
targetUserId: string | undefined;
|
|
582
|
+
/** Indicates if the user stopped following */
|
|
505
583
|
stoppedFollowing: boolean;
|
|
506
584
|
|
|
507
585
|
constructor(connectionId: string, userId: string | undefined, stoppedFollowing: boolean) {
|
|
@@ -511,6 +589,9 @@ class SpectatorFollowerChangedEventModel implements IModel {
|
|
|
511
589
|
}
|
|
512
590
|
}
|
|
513
591
|
|
|
592
|
+
/**
|
|
593
|
+
* Network model for requesting users to follow a specific player
|
|
594
|
+
*/
|
|
514
595
|
class SpectatorFollowEventModel implements IModel {
|
|
515
596
|
guid: string;
|
|
516
597
|
userId: string | undefined;
|
|
@@ -521,11 +602,14 @@ class SpectatorFollowEventModel implements IModel {
|
|
|
521
602
|
}
|
|
522
603
|
}
|
|
523
604
|
|
|
605
|
+
/**
|
|
606
|
+
* Handles network communication for spectator functionality
|
|
607
|
+
*/
|
|
524
608
|
class SpectatorCamNetworking {
|
|
525
609
|
|
|
610
|
+
/** List of user IDs currently following this player */
|
|
526
611
|
readonly followers: string[] = [];
|
|
527
612
|
|
|
528
|
-
|
|
529
613
|
private readonly context: Context;
|
|
530
614
|
private readonly spectator: SpectatorCamera;
|
|
531
615
|
private _followerEventMethod: Function;
|
|
@@ -540,6 +624,9 @@ class SpectatorCamNetworking {
|
|
|
540
624
|
this._joinedRoomMethod = this.onUserJoinedRoom.bind(this);
|
|
541
625
|
}
|
|
542
626
|
|
|
627
|
+
/**
|
|
628
|
+
* Initializes network event listeners
|
|
629
|
+
*/
|
|
543
630
|
awake() {
|
|
544
631
|
this.context.connection.beginListen("spectator-follower-changed", this._followerEventMethod);
|
|
545
632
|
this.context.connection.beginListen("spectator-request-follow", this._requestFollowMethod);
|
|
@@ -555,12 +642,20 @@ class SpectatorCamNetworking {
|
|
|
555
642
|
});
|
|
556
643
|
}
|
|
557
644
|
|
|
645
|
+
/**
|
|
646
|
+
* Removes network event listeners
|
|
647
|
+
*/
|
|
558
648
|
destroy() {
|
|
559
649
|
this.context.connection.stopListen("spectator-follower-changed", this._followerEventMethod);
|
|
560
650
|
this.context.connection.stopListen("spectator-request-follow", this._requestFollowMethod);
|
|
561
651
|
this.context.connection.stopListen(RoomEvents.JoinedRoom, this._joinedRoomMethod);
|
|
562
652
|
}
|
|
563
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Notifies other users about spectating target changes
|
|
656
|
+
* @param target The new target being spectated
|
|
657
|
+
* @param _prevId The previous target's user ID
|
|
658
|
+
*/
|
|
564
659
|
onSpectatedObjectChanged(target: PlayerView | undefined, _prevId?: string) {
|
|
565
660
|
if (debug)
|
|
566
661
|
console.log(this.context.connection.connectionId, "onSpectatedObjectChanged", target, _prevId);
|
|
@@ -572,6 +667,10 @@ class SpectatorCamNetworking {
|
|
|
572
667
|
}
|
|
573
668
|
}
|
|
574
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Requests other users to follow this player or stop following
|
|
672
|
+
* @param stop Whether to request users to stop following
|
|
673
|
+
*/
|
|
575
674
|
onRequestFollowMe(stop: boolean = false) {
|
|
576
675
|
if (debug)
|
|
577
676
|
console.log("Request follow", this.context.connection.connectionId);
|
|
@@ -583,12 +682,19 @@ class SpectatorCamNetworking {
|
|
|
583
682
|
}
|
|
584
683
|
}
|
|
585
684
|
|
|
685
|
+
/**
|
|
686
|
+
* Handles room join events
|
|
687
|
+
*/
|
|
586
688
|
private onUserJoinedRoom() {
|
|
587
689
|
if (getParam("followme")) {
|
|
588
690
|
this.onRequestFollowMe();
|
|
589
691
|
}
|
|
590
692
|
}
|
|
591
693
|
|
|
694
|
+
/**
|
|
695
|
+
* Processes follower status change events from the network
|
|
696
|
+
* @param evt The follower change event data
|
|
697
|
+
*/
|
|
592
698
|
private onFollowerEvent(evt: SpectatorFollowerChangedEventModel) {
|
|
593
699
|
const userBeingFollowed = evt.targetUserId;
|
|
594
700
|
const userThatIsFollowing = evt.guid;
|
|
@@ -615,6 +721,9 @@ class SpectatorCamNetworking {
|
|
|
615
721
|
}
|
|
616
722
|
}
|
|
617
723
|
|
|
724
|
+
/**
|
|
725
|
+
* Removes followers that are no longer connected to the room
|
|
726
|
+
*/
|
|
618
727
|
private removeDisconnectedFollowers() {
|
|
619
728
|
for (let i = this.followers.length - 1; i >= 0; i--) {
|
|
620
729
|
const id = this.followers[i];
|
|
@@ -626,6 +735,11 @@ class SpectatorCamNetworking {
|
|
|
626
735
|
|
|
627
736
|
private _lastRequestFollowUser: SpectatorFollowEventModel | undefined;
|
|
628
737
|
|
|
738
|
+
/**
|
|
739
|
+
* Handles follow requests from other users
|
|
740
|
+
* @param evt The follow request event
|
|
741
|
+
* @returns True if the request was handled successfully
|
|
742
|
+
*/
|
|
629
743
|
private onRequestFollowEvent(evt: SpectatorFollowEventModel) {
|
|
630
744
|
this._lastRequestFollowUser = evt;
|
|
631
745
|
|
|
@@ -652,6 +766,10 @@ class SpectatorCamNetworking {
|
|
|
652
766
|
}
|
|
653
767
|
|
|
654
768
|
private _enforceFollowInterval: any;
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Periodically retries following a user if the initial attempt failed
|
|
772
|
+
*/
|
|
655
773
|
private enforceFollow() {
|
|
656
774
|
if (this._enforceFollowInterval) return;
|
|
657
775
|
this._enforceFollowInterval = setInterval(() => {
|
|
@@ -19,7 +19,12 @@ registerBinaryType(SyncedTransformIdentifier, SyncedTransformModel.getRootAsSync
|
|
|
19
19
|
|
|
20
20
|
const builder = new flatbuffers.Builder();
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Creates a flatbuffer model containing the transform data of a game object. Used by {@link SyncedTransform}
|
|
24
|
+
* @param guid The unique identifier of the object to sync
|
|
25
|
+
* @param b The behavior component containing transform data
|
|
26
|
+
* @param fast Whether to use fast mode synchronization (syncs more frequently)
|
|
27
|
+
* @returns A Uint8Array containing the serialized transform data
|
|
23
28
|
*/
|
|
24
29
|
export function createTransformModel(guid: string, b: Behaviour, fast: boolean = true): Uint8Array {
|
|
25
30
|
builder.clear();
|
|
@@ -50,18 +55,28 @@ onUpdate((ctx) => {
|
|
|
50
55
|
})
|
|
51
56
|
|
|
52
57
|
/**
|
|
53
|
-
* SyncedTransform
|
|
58
|
+
* SyncedTransform synchronizes the position and rotation of a game object over the network.
|
|
59
|
+
* It handles ownership transfer, interpolation, and network state updates automatically.
|
|
54
60
|
* @category Networking
|
|
55
61
|
* @group Components
|
|
56
62
|
*/
|
|
57
63
|
export class SyncedTransform extends Behaviour {
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
|
|
60
66
|
// public autoOwnership: boolean = true;
|
|
67
|
+
/** When true, overrides physics behavior when this object is owned by the local user */
|
|
61
68
|
public overridePhysics: boolean = true
|
|
69
|
+
|
|
70
|
+
/** Whether to smoothly interpolate position changes when receiving updates */
|
|
62
71
|
public interpolatePosition: boolean = true;
|
|
72
|
+
|
|
73
|
+
/** Whether to smoothly interpolate rotation changes when receiving updates */
|
|
63
74
|
public interpolateRotation: boolean = true;
|
|
75
|
+
|
|
76
|
+
/** When true, sends updates at a higher frequency, useful for fast-moving objects */
|
|
64
77
|
public fastMode: boolean = false;
|
|
78
|
+
|
|
79
|
+
/** When true, notifies other clients when this object is destroyed */
|
|
65
80
|
public syncDestroy: boolean = false;
|
|
66
81
|
|
|
67
82
|
// private _state!: SyncedTransformModel;
|
|
@@ -77,7 +92,10 @@ export class SyncedTransform extends Behaviour {
|
|
|
77
92
|
private _receivedFastUpdate: boolean = false;
|
|
78
93
|
private _shouldRequestOwnership: boolean = false;
|
|
79
94
|
|
|
80
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Requests ownership of this object on the network.
|
|
97
|
+
* You need to be connected to a room for this to work.
|
|
98
|
+
*/
|
|
81
99
|
public requestOwnership() {
|
|
82
100
|
if (debug)
|
|
83
101
|
console.log("Request ownership");
|
|
@@ -89,10 +107,18 @@ export class SyncedTransform extends Behaviour {
|
|
|
89
107
|
this._model.requestOwnership();
|
|
90
108
|
}
|
|
91
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Checks if this client has ownership of the object
|
|
112
|
+
* @returns true if this client has ownership, false if not, undefined if ownership state is unknown
|
|
113
|
+
*/
|
|
92
114
|
public hasOwnership(): boolean | undefined {
|
|
93
115
|
return this._model?.hasOwnership ?? undefined;
|
|
94
116
|
}
|
|
95
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Checks if the object is owned by any client
|
|
120
|
+
* @returns true if the object is owned, false if not, undefined if ownership state is unknown
|
|
121
|
+
*/
|
|
96
122
|
public isOwned(): boolean | undefined {
|
|
97
123
|
return this._model?.isOwned;
|
|
98
124
|
}
|
|
@@ -140,6 +166,9 @@ export class SyncedTransform extends Behaviour {
|
|
|
140
166
|
this.context.connection.stopListenBinary(SyncedTransformIdentifier, this.receivedDataCallback);
|
|
141
167
|
}
|
|
142
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Attempts to retrieve and apply the last known network state for this transform
|
|
171
|
+
*/
|
|
143
172
|
private tryGetLastState() {
|
|
144
173
|
const model = this.context.connection.tryGetState(this.guid) as unknown as SyncedTransformModel;
|
|
145
174
|
if (model) this.onReceivedData(model);
|
|
@@ -147,6 +176,10 @@ export class SyncedTransform extends Behaviour {
|
|
|
147
176
|
|
|
148
177
|
private tempEuler: Euler = new Euler();
|
|
149
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Handles incoming network data for this transform
|
|
181
|
+
* @param data The model containing transform information
|
|
182
|
+
*/
|
|
150
183
|
private onReceivedData(data: SyncedTransformModel) {
|
|
151
184
|
if (this.destroyed) return;
|
|
152
185
|
if (typeof data.guid === "function" && data.guid() === this.guid) {
|
|
@@ -183,7 +216,10 @@ export class SyncedTransform extends Behaviour {
|
|
|
183
216
|
}
|
|
184
217
|
}
|
|
185
218
|
|
|
186
|
-
/**
|
|
219
|
+
/**
|
|
220
|
+
* @internal
|
|
221
|
+
* Initializes tracking of position and rotation when component is enabled
|
|
222
|
+
*/
|
|
187
223
|
onEnable(): void {
|
|
188
224
|
this.lastWorldPos.copy(this.worldPosition);
|
|
189
225
|
this.lastWorldRotation.copy(this.worldQuaternion);
|
|
@@ -194,7 +230,10 @@ export class SyncedTransform extends Behaviour {
|
|
|
194
230
|
}
|
|
195
231
|
}
|
|
196
232
|
|
|
197
|
-
/**
|
|
233
|
+
/**
|
|
234
|
+
* @internal
|
|
235
|
+
* Releases ownership when component is disabled
|
|
236
|
+
*/
|
|
198
237
|
onDisable(): void {
|
|
199
238
|
if (this._model)
|
|
200
239
|
this._model.freeOwnership();
|
|
@@ -205,7 +244,11 @@ export class SyncedTransform extends Behaviour {
|
|
|
205
244
|
private lastWorldPos!: Vector3;
|
|
206
245
|
private lastWorldRotation!: Quaternion;
|
|
207
246
|
|
|
208
|
-
/**
|
|
247
|
+
/**
|
|
248
|
+
* @internal
|
|
249
|
+
* Handles transform synchronization before each render frame
|
|
250
|
+
* Sends updates when owner, receives and applies updates when not owner
|
|
251
|
+
*/
|
|
209
252
|
onBeforeRender() {
|
|
210
253
|
if (!this.activeAndEnabled || !this.context.connection.isConnected) return;
|
|
211
254
|
// console.log("BEFORE RENDER", this.destroyed, this.guid, this._model?.isOwned, this.name, this.gameObject);
|
|
@@ -8,27 +8,43 @@ import { OrbitControls } from "./OrbitControls.js";
|
|
|
8
8
|
import { SyncedTransform } from "./SyncedTransform.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* TransformGizmo
|
|
11
|
+
* TransformGizmo displays manipulation controls for translating, rotating, and scaling objects in the scene.
|
|
12
|
+
* It wraps three.js {@link TransformControls} and provides keyboard shortcuts for changing modes and settings.
|
|
12
13
|
* @category Helpers
|
|
13
14
|
* @group Components
|
|
14
15
|
*/
|
|
15
16
|
export class TransformGizmo extends Behaviour {
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* When true, this is considered a helper gizmo and will only be shown if showGizmos is enabled in engine parameters.
|
|
20
|
+
*/
|
|
17
21
|
@serializable()
|
|
18
22
|
public isGizmo: boolean = false;
|
|
19
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Specifies the translation grid snap value in world units.
|
|
26
|
+
* Applied when holding Shift while translating an object.
|
|
27
|
+
*/
|
|
20
28
|
@serializable()
|
|
21
29
|
public translationSnap: number = 1;
|
|
22
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Specifies the rotation snap angle in degrees.
|
|
33
|
+
* Applied when holding Shift while rotating an object.
|
|
34
|
+
*/
|
|
23
35
|
@serializable()
|
|
24
36
|
public rotationSnapAngle: number = 15;
|
|
25
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Specifies the scale snapping value.
|
|
40
|
+
* Applied when holding Shift while scaling an object.
|
|
41
|
+
*/
|
|
26
42
|
@serializable()
|
|
27
43
|
public scaleSnap: number = .25;
|
|
28
44
|
|
|
29
45
|
/**
|
|
30
|
-
*
|
|
31
|
-
* @returns The TransformControls instance.
|
|
46
|
+
* Gets the underlying three.js {@link TransformControls} instance.
|
|
47
|
+
* @returns The TransformControls instance or undefined if not initialized.
|
|
32
48
|
*/
|
|
33
49
|
get control() {
|
|
34
50
|
return this._control;
|
|
@@ -81,6 +97,10 @@ export class TransformGizmo extends Behaviour {
|
|
|
81
97
|
window.removeEventListener('keyup', this.windowKeyUpListener);
|
|
82
98
|
}
|
|
83
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Enables grid snapping for transform operations according to set snap values.
|
|
102
|
+
* This applies the translationSnap, rotationSnapAngle, and scaleSnap properties to the controls.
|
|
103
|
+
*/
|
|
84
104
|
enableSnapping() {
|
|
85
105
|
if (this._control) {
|
|
86
106
|
this._control.setTranslationSnap(this.translationSnap);
|
|
@@ -89,6 +109,10 @@ export class TransformGizmo extends Behaviour {
|
|
|
89
109
|
}
|
|
90
110
|
}
|
|
91
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Disables grid snapping for transform operations.
|
|
114
|
+
* Removes all snapping constraints from the transform controls.
|
|
115
|
+
*/
|
|
92
116
|
disableSnapping() {
|
|
93
117
|
if (this._control) {
|
|
94
118
|
this._control.setTranslationSnap(null);
|
|
@@ -97,6 +121,11 @@ export class TransformGizmo extends Behaviour {
|
|
|
97
121
|
}
|
|
98
122
|
}
|
|
99
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Event handler for when dragging state changes.
|
|
126
|
+
* Disables orbit controls during dragging and requests ownership of the transform if it's synchronized.
|
|
127
|
+
* @param event The drag change event
|
|
128
|
+
*/
|
|
100
129
|
private onControlChangedEvent = (event) => {
|
|
101
130
|
const orbit = this.orbit;
|
|
102
131
|
if (orbit) orbit.enabled = !event.value;
|
|
@@ -109,7 +138,18 @@ export class TransformGizmo extends Behaviour {
|
|
|
109
138
|
}
|
|
110
139
|
}
|
|
111
140
|
|
|
112
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Handles keyboard shortcuts for transform operations:
|
|
143
|
+
* - Q: Toggle local/world space
|
|
144
|
+
* - W: Translation mode
|
|
145
|
+
* - E: Rotation mode
|
|
146
|
+
* - R: Scale mode
|
|
147
|
+
* - Shift: Enable snapping (while held)
|
|
148
|
+
* - +/-: Adjust gizmo size
|
|
149
|
+
* - X/Y/Z: Toggle visibility of respective axis
|
|
150
|
+
* - Spacebar: Toggle controls enabled state
|
|
151
|
+
* @param event The keyboard event
|
|
152
|
+
*/
|
|
113
153
|
private windowKeyDownListener = (event) => {
|
|
114
154
|
if (!this.enabled) return;
|
|
115
155
|
if (!this._control) return;
|
|
@@ -162,6 +202,11 @@ export class TransformGizmo extends Behaviour {
|
|
|
162
202
|
}
|
|
163
203
|
}
|
|
164
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Handles keyboard key release events.
|
|
207
|
+
* Currently only handles releasing Shift key to disable snapping.
|
|
208
|
+
* @param event The keyboard event
|
|
209
|
+
*/
|
|
165
210
|
private windowKeyUpListener = (event) => {
|
|
166
211
|
if (!this.enabled) return;
|
|
167
212
|
switch (event.keyCode) {
|