-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add xr-session.mjs script #7942
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,166 @@ | ||||||
| import { Color, Quat, Script, Vec3, LAYERID_SKYBOX } from 'playcanvas'; | ||||||
|
|
||||||
| export class XrSession extends Script { | ||||||
| static scriptName = 'xrSession'; | ||||||
|
|
||||||
| /** | ||||||
| * Event name to start the WebXR AR session. | ||||||
| * @type {string} | ||||||
| * @attribute | ||||||
| */ | ||||||
| startArEvent = 'ar:start'; | ||||||
|
|
||||||
| /** | ||||||
| * Event name to start the WebXR VR session. | ||||||
| * @type {string} | ||||||
| * @attribute | ||||||
| */ | ||||||
| startVrEvent = 'vr:start'; | ||||||
|
|
||||||
| /** | ||||||
| * Event name to end the WebXR VR session. | ||||||
| * @type {string} | ||||||
| * @attribute | ||||||
| */ | ||||||
| endEvent = 'xr:end'; | ||||||
|
|
||||||
| cameraEntity = null; | ||||||
|
|
||||||
| cameraRootEntity = null; | ||||||
|
|
||||||
| clearColor = new Color(); | ||||||
|
|
||||||
| positionRoot = new Vec3(); | ||||||
|
|
||||||
| rotationRoot = new Quat(); | ||||||
|
|
||||||
| positionCamera = new Vec3(); | ||||||
|
|
||||||
| rotationCamera = new Quat(); | ||||||
|
|
||||||
| onKeyDownHandler = null; | ||||||
|
|
||||||
| initialize() { | ||||||
| this.cameraEntity = this.entity.findComponent('camera')?.entity || null; | ||||||
| this.cameraRootEntity = this.entity || null; | ||||||
|
|
||||||
| // Listen to global XR lifecycle to mirror example.mjs behavior | ||||||
| this.app.xr?.on('start', this.onXrStart, this); | ||||||
| this.app.xr?.on('end', this.onXrEnd, this); | ||||||
|
|
||||||
| // Listen for external events to control session | ||||||
| this.app.on(this.startArEvent, this.onStartArEvent, this); | ||||||
| this.app.on(this.startVrEvent, this.onStartVrEvent, this); | ||||||
| this.app.on(this.endEvent, this.onEndEvent, this); | ||||||
|
|
||||||
| // ESC to exit | ||||||
| this.onKeyDownHandler = (event) => { | ||||||
| if (event.key === 'Escape' && this.app.xr?.active) { | ||||||
| this.endSession(); | ||||||
| } | ||||||
| }; | ||||||
| window.addEventListener('keydown', this.onKeyDownHandler); | ||||||
|
|
||||||
| this.on('destroy', () => { | ||||||
| this.onDestroy(); | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| onDestroy() { | ||||||
| this.app.xr?.off('start', this.onXrStart, this); | ||||||
| this.app.xr?.off('end', this.onXrEnd, this); | ||||||
|
|
||||||
| this.app.off(this.startVrEvent, this.onStartVrEvent, this); | ||||||
| this.app.off(this.startArEvent, this.onStartArEvent, this); | ||||||
| this.app.off(this.endEvent, this.onEndEvent, this); | ||||||
|
|
||||||
| if (this.onKeyDownHandler) { | ||||||
| window.removeEventListener('keydown', this.onKeyDownHandler); | ||||||
| this.onKeyDownHandler = null; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| onStartArEvent(space = 'local-floor') { | ||||||
| this.startSession('immersive-ar', space); | ||||||
| } | ||||||
|
|
||||||
| onStartVrEvent(space = 'local-floor') { | ||||||
| this.startSession('immersive-vr', space); | ||||||
| } | ||||||
|
|
||||||
| onEndEvent() { | ||||||
| this.endSession(); | ||||||
| } | ||||||
|
|
||||||
| startSession(type = 'immersive-vr', space = 'local-floor') { | ||||||
| if (!this.cameraEntity.camera) { | ||||||
| console.error('XrSession: No cameraEntity.camera found on the entity.'); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| // Start XR on the camera component | ||||||
| this.cameraEntity.camera.startXr(type, space, { | ||||||
| callback: (err) => { | ||||||
| if (err) console.error(`WebXR ${type} failed to start: ${err.message}`); | ||||||
| } | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| endSession() { | ||||||
| if (!this.cameraEntity.camera) return; | ||||||
|
||||||
| if (!this.cameraEntity.camera) return; | |
| if (!this.cameraEntity || !this.cameraEntity.camera) return; |
Copilot
AI
Sep 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a new Color object on every XR session start is inefficient. Consider creating a static transparent color constant or reusing an instance variable to avoid repeated allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null reference error. The code checks
this.cameraEntity.camerabutthis.cameraEntityitself could be null if no camera component is found during initialization (line 44). This should checkthis.cameraEntityfirst before accessing itscameraproperty.