@playcanvas/web-components 0.1.4 → 0.1.5

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.
@@ -37,9 +37,7 @@ class ElementComponentElement extends ComponentElement {
37
37
  super('element');
38
38
  }
39
39
 
40
- async connectedCallback() {
41
- await super.connectedCallback();
42
-
40
+ initComponent() {
43
41
  this.component!._text._material.useFog = true;
44
42
  }
45
43
 
@@ -198,7 +196,20 @@ class ElementComponentElement extends ComponentElement {
198
196
  }
199
197
 
200
198
  static get observedAttributes() {
201
- return [...super.observedAttributes, 'anchor', 'asset', 'auto-width', 'color', 'font-size', 'line-height', 'pivot', 'text', 'type', 'width', 'wrap-lines'];
199
+ return [
200
+ ...super.observedAttributes,
201
+ 'anchor',
202
+ 'asset',
203
+ 'auto-width',
204
+ 'color',
205
+ 'font-size',
206
+ 'line-height',
207
+ 'pivot',
208
+ 'text',
209
+ 'type',
210
+ 'width',
211
+ 'wrap-lines'
212
+ ];
202
213
  }
203
214
 
204
215
  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
@@ -21,16 +21,11 @@ class RenderComponentElement extends ComponentElement {
21
21
  super('render');
22
22
  }
23
23
 
24
- async connectedCallback() {
25
- await super.connectedCallback();
26
-
27
- this.material = this._material;
28
- }
29
-
30
24
  getInitialComponentData() {
31
25
  return {
32
26
  type: this._type,
33
27
  castShadows: this._castShadows,
28
+ material: MaterialElement.get(this._material),
34
29
  receiveShadows: this._receiveShadows
35
30
  };
36
31
  }
@@ -42,9 +42,7 @@ class ScriptComponentElement extends ComponentElement {
42
42
  this.addEventListener('scriptenablechange', this.handleScriptEnableChange.bind(this));
43
43
  }
44
44
 
45
- async connectedCallback() {
46
- await super.connectedCallback();
47
-
45
+ initComponent() {
48
46
  // Handle initial script elements
49
47
  this.querySelectorAll<ScriptElement>(':scope > pc-script').forEach((scriptElement) => {
50
48
  const scriptName = scriptElement.getAttribute('name');
@@ -1,13 +1,13 @@
1
1
  import { SoundSlot } from 'playcanvas';
2
2
 
3
- import { AppElement } from '../app';
4
- import { SoundComponentElement } from './sound-component';
5
3
  import { AssetElement } from '../asset';
4
+ import { AsyncElement } from '../async-element';
5
+ import { SoundComponentElement } from './sound-component';
6
6
 
7
7
  /**
8
8
  * Represents a sound slot in the PlayCanvas engine.
9
9
  */
10
- class SoundSlotElement extends HTMLElement {
10
+ class SoundSlotElement extends AsyncElement {
11
11
  private _asset: string = '';
12
12
 
13
13
  private _autoPlay: boolean = false;
@@ -32,13 +32,7 @@ class SoundSlotElement extends HTMLElement {
32
32
  soundSlot: SoundSlot | null = null;
33
33
 
34
34
  async connectedCallback() {
35
- const appElement = this.closest('pc-app') as AppElement | null;
36
- if (!appElement) {
37
- console.error(`${this.tagName.toLowerCase()} should be a descendant of pc-app`);
38
- return;
39
- }
40
-
41
- await appElement.getApplication();
35
+ await this.soundElement?.ready();
42
36
 
43
37
  const options = {
44
38
  autoPlay: this._autoPlay,
@@ -51,9 +45,12 @@ class SoundSlotElement extends HTMLElement {
51
45
  if (this._duration) {
52
46
  options.duration = this._duration;
53
47
  }
48
+
54
49
  this.soundSlot = this.soundElement!.component!.addSlot(this._name, options);
55
50
  this.asset = this._asset;
56
51
  this.soundSlot!.play();
52
+
53
+ this._onReady();
57
54
  }
58
55
 
59
56
  disconnectedCallback() {
package/src/entity.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { Entity, Vec3 } from 'playcanvas';
2
2
 
3
- import { AppElement } from './app';
3
+ import { AsyncElement } from './async-element';
4
4
  import { parseVec3 } from './utils';
5
5
 
6
6
  /**
7
7
  * Represents an entity in the PlayCanvas engine.
8
8
  */
9
- class EntityElement extends HTMLElement {
9
+ class EntityElement extends AsyncElement {
10
10
  /**
11
11
  * Whether the entity is enabled.
12
12
  */
@@ -37,38 +37,22 @@ class EntityElement extends HTMLElement {
37
37
  */
38
38
  private _tags: string[] = [];
39
39
 
40
- private _resolveEntity!: (entity: Entity) => void;
41
-
42
- private _entityReady = new Promise<Entity>((resolve) => {
43
- this._resolveEntity = resolve;
44
- });
45
-
46
40
  /**
47
41
  * The PlayCanvas entity instance.
48
42
  */
49
43
  entity: Entity | null = null;
50
44
 
51
45
  async connectedCallback() {
52
- // Get the application
53
- const appElement = this.closest('pc-app') as AppElement;
54
- if (!appElement) {
55
- console.warn(`${this.tagName} must be a child of pc-app`);
56
- return;
57
- }
46
+ const closestApp = this.closestApp;
47
+ if (!closestApp) return;
48
+
49
+ // Wait for the app to complete initialization
50
+ await closestApp.ready();
58
51
 
59
- const app = await appElement.getApplication();
52
+ const app = closestApp.app!;
60
53
 
61
54
  // Create a new entity
62
55
  this.entity = new Entity(this._name, app);
63
- this._resolveEntity(this.entity);
64
-
65
- if (this.parentElement &&
66
- this.parentElement.tagName.toLowerCase() === 'pc-entity') {
67
- const parentEntity = await (this.parentElement as EntityElement)._entityReady;
68
- parentEntity.addChild(this.entity);
69
- } else {
70
- app.root.addChild(this.entity);
71
- }
72
56
 
73
57
  // Initialize from attributes
74
58
  const nameAttr = this.getAttribute('name');
@@ -82,6 +66,17 @@ class EntityElement extends HTMLElement {
82
66
  if (rotationAttr) this.rotation = parseVec3(rotationAttr);
83
67
  if (scaleAttr) this.scale = parseVec3(scaleAttr);
84
68
  if (tagsAttr) this.tags = tagsAttr.split(',').map(tag => tag.trim());
69
+
70
+ const closestEntity = this.closestEntity;
71
+ if (closestEntity) {
72
+ closestEntity.ready().then(() => {
73
+ closestEntity.entity!.addChild(this.entity!);
74
+ this._onReady();
75
+ });
76
+ } else {
77
+ app.root.addChild(this.entity);
78
+ this._onReady();
79
+ }
85
80
  }
86
81
 
87
82
  disconnectedCallback() {
@@ -95,11 +90,6 @@ class EntityElement extends HTMLElement {
95
90
  this.entity.destroy();
96
91
  this.entity = null;
97
92
  }
98
-
99
- // Reset the promise for potential reconnection
100
- this._entityReady = new Promise<Entity>((resolve) => {
101
- this._resolveEntity = resolve;
102
- });
103
93
  }
104
94
 
105
95
  /**
package/src/index.ts CHANGED
@@ -1,6 +1,16 @@
1
+ /**
2
+ * The Engine Web Components module provides a set of Web Components for the PlayCanvas Engine.
3
+ * While these components are normally instantiated in a declarative fashion using HTML, this
4
+ * reference covers the TypeScript/JavaScript API that allows these components to be created
5
+ * programmatically.
6
+ *
7
+ * @module EngineWebComponents
8
+ */
9
+
1
10
  /* eslint-disable import/order */
2
11
 
3
12
  // Note that order matters here (e.g. pc-entity must be defined before components)
13
+ import { AsyncElement } from './async-element';
4
14
  import { ModuleElement } from './module';
5
15
  import { AppElement } from './app';
6
16
  import { EntityElement } from './entity';
@@ -25,6 +35,7 @@ import { SceneElement } from './scene';
25
35
  import { SkyElement } from './sky';
26
36
 
27
37
  export {
38
+ AsyncElement,
28
39
  ModuleElement,
29
40
  AppElement,
30
41
  EntityElement,
package/src/model.ts CHANGED
@@ -1,27 +1,21 @@
1
- import { AppElement } from './app';
2
1
  import { AssetElement } from './asset';
3
- import { EntityElement } from './entity';
2
+ import { AsyncElement } from './async-element';
4
3
 
5
4
  /**
6
5
  * Represents a model in the PlayCanvas engine.
7
6
  */
8
- class ModelElement extends HTMLElement {
7
+ class ModelElement extends AsyncElement {
9
8
  private _asset: string = '';
10
9
 
11
10
  async connectedCallback() {
12
- // Get the application
13
- const appElement = this.closest('pc-app') as AppElement;
14
- if (!appElement) {
15
- console.warn(`${this.tagName} must be a child of pc-app`);
16
- return;
17
- }
18
-
19
- await appElement.getApplication();
11
+ await this.closestApp?.ready();
20
12
 
21
13
  const asset = this.getAttribute('asset');
22
14
  if (asset) {
23
15
  this.asset = asset;
24
16
  }
17
+
18
+ this._onReady();
25
19
  }
26
20
 
27
21
  _loadModel() {
@@ -31,12 +25,23 @@ class ModelElement extends HTMLElement {
31
25
  }
32
26
  const entity = asset.resource.instantiateRenderEntity();
33
27
 
34
- const parentEntityElement = this.closest('pc-entity') as EntityElement | null;
28
+ if (asset.resource.animations.length > 0) {
29
+ entity.addComponent('anim');
30
+ entity.anim.assignAnimation('animation', asset.resource.animations[0].resource);
31
+ }
32
+
33
+ const parentEntityElement = this.closestEntity;
35
34
  if (parentEntityElement) {
36
- parentEntityElement.entity!.addChild(entity);
35
+ parentEntityElement.ready().then(() => {
36
+ parentEntityElement.entity!.addChild(entity);
37
+ });
37
38
  } else {
38
- const appElement = this.closest('pc-app') as AppElement;
39
- appElement.app!.root.addChild(entity);
39
+ const appElement = this.closestApp;
40
+ if (appElement) {
41
+ appElement.ready().then(() => {
42
+ appElement.app!.root.addChild(entity);
43
+ });
44
+ }
40
45
  }
41
46
  }
42
47
 
package/src/scene.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { Color, Scene } from 'playcanvas';
2
2
 
3
- import { AppElement } from './app';
3
+ import { AsyncElement } from './async-element';
4
4
  import { parseColor } from './utils';
5
5
 
6
6
  /**
7
7
  * Represents a scene in the PlayCanvas engine.
8
8
  */
9
- class SceneElement extends HTMLElement {
9
+ class SceneElement extends AsyncElement {
10
10
  /**
11
11
  * The fog type of the scene.
12
12
  */
@@ -38,17 +38,12 @@ class SceneElement extends HTMLElement {
38
38
  scene: Scene | null = null;
39
39
 
40
40
  async connectedCallback() {
41
- // Get the application
42
- const appElement = this.closest('pc-app') as AppElement;
43
- if (!appElement) {
44
- console.warn(`${this.tagName} must be a child of pc-app`);
45
- return;
46
- }
47
-
48
- const app = await appElement.getApplication();
41
+ await this.closestApp?.ready();
49
42
 
50
- this.scene = app.scene;
43
+ this.scene = this.closestApp!.app!.scene;
51
44
  this.updateSceneSettings();
45
+
46
+ this._onReady();
52
47
  }
53
48
 
54
49
  updateSceneSettings() {
package/src/sky.ts CHANGED
@@ -1,61 +1,72 @@
1
- import { LAYERID_SKYBOX, Quat } from 'playcanvas';
1
+ import { EnvLighting, LAYERID_SKYBOX, Quat, Texture, Vec3 } from 'playcanvas';
2
2
 
3
- import { AppElement } from './app';
4
3
  import { AssetElement } from './asset';
4
+ import { AsyncElement } from './async-element';
5
+ import { parseVec3 } from './utils';
5
6
 
6
7
  /**
7
8
  * Represents a sky in the PlayCanvas engine.
8
9
  */
9
- class SkyElement extends HTMLElement {
10
+ class SkyElement extends AsyncElement {
10
11
  private _asset = '';
11
12
 
13
+ private _center = new Vec3(0, 0.01, 0);
14
+
12
15
  private _intensity = 1;
13
16
 
14
- private _rotation = [0, 0, 0];
17
+ private _rotation = new Vec3();
15
18
 
16
19
  private _level = 0;
17
20
 
18
- private _solidColor = false;
21
+ private _scale = new Vec3(100, 100, 100);
19
22
 
20
- async connectedCallback() {
21
- // Get the application
22
- const appElement = this.closest('pc-app') as AppElement;
23
- if (!appElement) {
24
- console.warn(`${this.tagName} must be a child of pc-app`);
25
- return;
26
- }
23
+ private _type: 'box' | 'dome' | 'infinite' | 'none' = 'infinite';
27
24
 
28
- await appElement.getApplication();
25
+ async connectedCallback() {
26
+ await this.closestApp?.ready();
29
27
 
30
28
  this.asset = this.getAttribute('asset') || '';
31
- this.solidColor = this.hasAttribute('solid-color');
29
+
30
+ this._onReady();
32
31
  }
33
32
 
34
33
  getScene() {
35
- const appElement = this.closest('pc-app') as AppElement | null;
36
- if (!appElement) {
37
- return;
38
- }
39
- const app = appElement.app;
34
+ const app = this.closestApp!.app;
40
35
  if (!app) {
41
36
  return;
42
37
  }
43
38
  return app.scene;
44
39
  }
45
40
 
41
+ private initSkybox(source: Texture) {
42
+ source.anisotropy = 4;
43
+
44
+ const skybox = EnvLighting.generateSkyboxCubemap(source);
45
+ const lighting = EnvLighting.generateLightingSource(source);
46
+ const envAtlas = EnvLighting.generateAtlas(lighting);
47
+ const app = this.closestApp!.app;
48
+ if (app) {
49
+ app.scene.envAtlas = envAtlas;
50
+ app.scene.skybox = skybox;
51
+
52
+ const layer = app.scene.layers.getLayerById(LAYERID_SKYBOX);
53
+ if (layer) {
54
+ layer.enabled = this._type !== 'none';
55
+ }
56
+
57
+ app.scene.sky.type = this._type;
58
+ app.scene.sky.node.setLocalScale(this._scale);
59
+ app.scene.sky.center = this._center;
60
+ }
61
+ }
62
+
46
63
  set asset(value: string) {
47
64
  this._asset = value;
48
65
  const scene = this.getScene();
49
66
  if (scene) {
50
67
  const asset = AssetElement.get(value);
51
68
  if (asset) {
52
- if (asset.resource) {
53
- scene.envAtlas = asset.resource;
54
- } else {
55
- asset.once('load', () => {
56
- scene.envAtlas = asset.resource;
57
- });
58
- }
69
+ this.initSkybox(asset.resource);
59
70
  }
60
71
  }
61
72
  }
@@ -64,6 +75,18 @@ class SkyElement extends HTMLElement {
64
75
  return this._asset;
65
76
  }
66
77
 
78
+ set center(value: Vec3) {
79
+ this._center = value;
80
+ const scene = this.getScene();
81
+ if (scene) {
82
+ scene.sky.center = this._center;
83
+ }
84
+ }
85
+
86
+ get center() {
87
+ return this._center;
88
+ }
89
+
67
90
  set intensity(value: number) {
68
91
  this._intensity = value;
69
92
  const scene = this.getScene();
@@ -76,11 +99,11 @@ class SkyElement extends HTMLElement {
76
99
  return this._intensity;
77
100
  }
78
101
 
79
- set rotation(value: number[]) {
102
+ set rotation(value: Vec3) {
80
103
  this._rotation = value;
81
104
  const scene = this.getScene();
82
105
  if (scene) {
83
- scene.skyboxRotation = new Quat().setFromEulerAngles(this._rotation[0], this._rotation[1], this._rotation[2]);
106
+ scene.skyboxRotation = new Quat().setFromEulerAngles(value);
84
107
  }
85
108
  }
86
109
 
@@ -88,19 +111,16 @@ class SkyElement extends HTMLElement {
88
111
  return this._rotation;
89
112
  }
90
113
 
91
- set solidColor(value: boolean) {
92
- this._solidColor = value;
114
+ set scale(value: Vec3) {
115
+ this._scale = value;
93
116
  const scene = this.getScene();
94
117
  if (scene) {
95
- const layer = scene.layers.getLayerById(LAYERID_SKYBOX);
96
- if (layer) {
97
- layer.enabled = !this._solidColor;
98
- }
118
+ scene.sky.node.setLocalScale(this._scale);
99
119
  }
100
120
  }
101
121
 
102
- get solidColor() {
103
- return this._solidColor;
122
+ get scale() {
123
+ return this._scale;
104
124
  }
105
125
 
106
126
  set level(value: number) {
@@ -115,8 +135,24 @@ class SkyElement extends HTMLElement {
115
135
  return this._level;
116
136
  }
117
137
 
138
+ set type(value: 'box' | 'dome' | 'infinite' | 'none') {
139
+ this._type = value;
140
+ const scene = this.getScene();
141
+ if (scene) {
142
+ scene.sky.type = this._type;
143
+ const layer = scene.layers.getLayerById(LAYERID_SKYBOX);
144
+ if (layer) {
145
+ layer.enabled = this._type !== 'none';
146
+ }
147
+ }
148
+ }
149
+
150
+ get type() {
151
+ return this._type;
152
+ }
153
+
118
154
  static get observedAttributes() {
119
- return ['asset', 'intensity', 'level', 'rotation', 'solid-color'];
155
+ return ['asset', 'center', 'intensity', 'level', 'rotation', 'scale', 'type'];
120
156
  }
121
157
 
122
158
  attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
@@ -124,17 +160,23 @@ class SkyElement extends HTMLElement {
124
160
  case 'asset':
125
161
  this.asset = newValue;
126
162
  break;
163
+ case 'center':
164
+ this.center = parseVec3(newValue);
165
+ break;
127
166
  case 'intensity':
128
167
  this.intensity = parseFloat(newValue);
129
168
  break;
130
169
  case 'rotation':
131
- this.rotation = newValue.split(',').map(Number);
170
+ this.rotation = parseVec3(newValue);
132
171
  break;
133
172
  case 'level':
134
173
  this.level = parseInt(newValue, 10);
135
174
  break;
136
- case 'solid-color':
137
- this.solidColor = this.hasAttribute('solid-color');
175
+ case 'scale':
176
+ this.scale = parseVec3(newValue);
177
+ break;
178
+ case 'type':
179
+ this.type = newValue as 'box' | 'dome' | 'infinite' | 'none';
138
180
  break;
139
181
  }
140
182
  }