littlejsengine 1.18.19 → 1.18.22
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/README.md +15 -1
- package/dist/littlejs.d.ts +215 -20
- package/dist/littlejs.esm.js +709 -58
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +690 -57
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +687 -57
- package/package.json +1 -1
- package/plugins/box2d.js +12 -11
- package/plugins/newgrounds.js +2 -1
- package/plugins/pluginExport.js +18 -1
- package/plugins/postProcess.js +5 -4
- package/plugins/textureSheet.js +439 -0
- package/plugins/threejs.js +155 -0
- package/plugins/tweenSystem.js +5 -1
- package/plugins/uiSystem.js +4 -3
- package/src/engine.js +6 -2
- package/src/engineAudio.js +2 -2
- package/src/engineBuild.mjs +4 -2
- package/src/engineDebug.js +3 -0
- package/src/engineDraw.js +32 -14
- package/src/engineInput.js +8 -4
- package/src/engineMath.js +2 -2
- package/src/engineParticles.js +5 -6
- package/src/engineSettings.js +1 -0
- package/src/engineTileLayer.js +10 -6
- package/src/engineUtilities.js +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Three.js Plugin
|
|
3
|
+
* - Renders a three.js scene on a canvas behind the LittleJS canvases
|
|
4
|
+
* - The three.js module is passed in by the user, nothing is bundled
|
|
5
|
+
* - Keep canvasClearColor transparent so the 3D scene shows through
|
|
6
|
+
* - Aligned camera mode locks the 3D camera to the LittleJS 2D camera
|
|
7
|
+
* - ThreeJSObject lets LittleJS physics drive a three.js mesh
|
|
8
|
+
* - Call new ThreeJSPlugin(THREE) in gameInit to set up
|
|
9
|
+
* @namespace ThreeJS
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
15
|
+
|
|
16
|
+
/** Global ThreeJS plugin object
|
|
17
|
+
* @type {ThreeJSPlugin}
|
|
18
|
+
* @memberof ThreeJS */
|
|
19
|
+
let threeJS;
|
|
20
|
+
|
|
21
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
22
|
+
/**
|
|
23
|
+
* ThreeJS Plugin - Renders a three.js scene behind the LittleJS canvas
|
|
24
|
+
* @example
|
|
25
|
+
* // in gameInit, with three.js loaded by the user
|
|
26
|
+
* new ThreeJSPlugin(THREE);
|
|
27
|
+
* threeJS.scene.add(new THREE.AmbientLight);
|
|
28
|
+
* @memberof ThreeJS
|
|
29
|
+
*/
|
|
30
|
+
class ThreeJSPlugin
|
|
31
|
+
{
|
|
32
|
+
/** Set up the three.js rendering layer, call in gameInit
|
|
33
|
+
* @param {Object} THREE - The three.js module, supplied by the user
|
|
34
|
+
* @param {number} [cameraFOV] - Vertical field of view in degrees */
|
|
35
|
+
constructor(THREE, cameraFOV=60)
|
|
36
|
+
{
|
|
37
|
+
ASSERT(!threeJS, 'ThreeJS plugin already initialized');
|
|
38
|
+
threeJS = this;
|
|
39
|
+
if (headlessMode) return;
|
|
40
|
+
ASSERT(mainCanvas, 'ThreeJS plugin must be created after engineInit, call in gameInit');
|
|
41
|
+
ASSERT(THREE && THREE.WebGLRenderer, 'three.js module must be passed in');
|
|
42
|
+
|
|
43
|
+
/** @property {Object} - The three.js module passed into the constructor */
|
|
44
|
+
this.THREE = THREE;
|
|
45
|
+
/** @property {Object} - The three.js renderer */
|
|
46
|
+
this.renderer = new THREE.WebGLRenderer({antialias: true});
|
|
47
|
+
/** @property {Object} - The three.js scene, add lights and meshes here */
|
|
48
|
+
this.scene = new THREE.Scene();
|
|
49
|
+
/** @property {Object} - The three.js perspective camera */
|
|
50
|
+
this.camera = new THREE.PerspectiveCamera(cameraFOV, 1, .1, 1e3);
|
|
51
|
+
/** @property {boolean} - Lock the camera to the LittleJS 2D camera so the z=0 plane matches world space */
|
|
52
|
+
this.cameraAlign2D = true;
|
|
53
|
+
|
|
54
|
+
// insert the canvas below the engine canvases and match the layout
|
|
55
|
+
const threeCanvas = this.renderer.domElement;
|
|
56
|
+
const rootElement = mainCanvas.parentElement;
|
|
57
|
+
rootElement.insertBefore(threeCanvas, rootElement.firstChild);
|
|
58
|
+
threeCanvas.style.cssText = mainCanvas.style.cssText;
|
|
59
|
+
|
|
60
|
+
// render automatically each frame after the engine renders
|
|
61
|
+
engineAddPlugin(undefined, ()=> this.render());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Position the camera so the z=0 plane exactly matches LittleJS world space,
|
|
65
|
+
* called automatically when cameraAlign2D is set */
|
|
66
|
+
alignCamera2D()
|
|
67
|
+
{
|
|
68
|
+
const halfHeight = mainCanvasSize.y / 2 / cameraScale; // half visible height in world units
|
|
69
|
+
const distance = halfHeight / tan(this.camera.fov/2 * PI/180);
|
|
70
|
+
this.camera.position.set(cameraPos.x, cameraPos.y, distance);
|
|
71
|
+
// reset all axes in case a free camera was used, littlejs angles are clockwise
|
|
72
|
+
this.camera.rotation.set(0, 0, -cameraAngle);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Sync the canvas layout and render the scene, called automatically each frame */
|
|
76
|
+
render()
|
|
77
|
+
{
|
|
78
|
+
if (!this.renderer) return; // headless mode
|
|
79
|
+
|
|
80
|
+
// keep renderer size and css in sync with the LittleJS canvas
|
|
81
|
+
const threeCanvas = this.renderer.domElement;
|
|
82
|
+
if (threeCanvas.width != mainCanvasSize.x || threeCanvas.height != mainCanvasSize.y)
|
|
83
|
+
{
|
|
84
|
+
this.renderer.setSize(mainCanvasSize.x, mainCanvasSize.y, false);
|
|
85
|
+
this.camera.aspect = mainCanvasSize.x / mainCanvasSize.y;
|
|
86
|
+
this.camera.updateProjectionMatrix();
|
|
87
|
+
}
|
|
88
|
+
if (threeCanvas.style.cssText != mainCanvas.style.cssText)
|
|
89
|
+
threeCanvas.style.cssText = mainCanvas.style.cssText;
|
|
90
|
+
|
|
91
|
+
if (this.cameraAlign2D)
|
|
92
|
+
this.alignCamera2D();
|
|
93
|
+
this.renderer.render(this.scene, this.camera);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
98
|
+
/**
|
|
99
|
+
* ThreeJS Object - EngineObject that drives a three.js mesh
|
|
100
|
+
* - LittleJS physics moves the object and the mesh follows automatically
|
|
101
|
+
* - Destroying the object removes the mesh from the scene
|
|
102
|
+
* @extends EngineObject
|
|
103
|
+
* @memberof ThreeJS
|
|
104
|
+
*/
|
|
105
|
+
class ThreeJSObject extends EngineObject
|
|
106
|
+
{
|
|
107
|
+
/** Create an engine object that drives a three.js mesh
|
|
108
|
+
* @param {Vector2} [pos] - World space position
|
|
109
|
+
* @param {Vector2} [size] - World space size
|
|
110
|
+
* @param {Object} [mesh] - The three.js object3d to drive
|
|
111
|
+
* @param {number} [z] - Mesh height above the 2D plane */
|
|
112
|
+
constructor(pos, size, mesh, z=0)
|
|
113
|
+
{
|
|
114
|
+
super(pos, size);
|
|
115
|
+
ASSERT(threeJS, 'ThreeJS plugin must be initialized first');
|
|
116
|
+
|
|
117
|
+
/** @property {Object} - The three.js object3d this object drives */
|
|
118
|
+
this.mesh = mesh;
|
|
119
|
+
/** @property {number} - Mesh height above the 2D plane */
|
|
120
|
+
this.z = z;
|
|
121
|
+
if (mesh)
|
|
122
|
+
{
|
|
123
|
+
threeJS.scene.add(mesh);
|
|
124
|
+
this.syncMesh();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Update the object and sync the mesh to its transform */
|
|
129
|
+
update()
|
|
130
|
+
{
|
|
131
|
+
super.update();
|
|
132
|
+
this.syncMesh();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Copy this object's transform to the mesh */
|
|
136
|
+
syncMesh()
|
|
137
|
+
{
|
|
138
|
+
if (!this.mesh) return;
|
|
139
|
+
this.mesh.position.set(this.pos.x, this.pos.y, this.z);
|
|
140
|
+
this.mesh.rotation.z = -this.angle; // littlejs angles are clockwise
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The mesh is this object's visual, the default 2D rendering is skipped */
|
|
144
|
+
render() {}
|
|
145
|
+
|
|
146
|
+
/** Destroy this object and remove its mesh from the scene
|
|
147
|
+
* @param {boolean} [immediate] */
|
|
148
|
+
destroy(immediate)
|
|
149
|
+
{
|
|
150
|
+
if (this.destroyed) return;
|
|
151
|
+
// note: frequently destroyed objects should also dispose geometry and material
|
|
152
|
+
this.mesh && threeJS.scene.remove(this.mesh);
|
|
153
|
+
super.destroy(immediate);
|
|
154
|
+
}
|
|
155
|
+
}
|
package/plugins/tweenSystem.js
CHANGED
|
@@ -426,7 +426,11 @@ function tweenProperty(target, propertyPath, start, end, duration = 1, options =
|
|
|
426
426
|
const callback = (value) =>
|
|
427
427
|
{
|
|
428
428
|
let obj = target;
|
|
429
|
-
for (const k of parts)
|
|
429
|
+
for (const k of parts)
|
|
430
|
+
{
|
|
431
|
+
obj = obj[k];
|
|
432
|
+
ASSERT(obj != null, 'tweenProperty path does not resolve: ' + propertyPath);
|
|
433
|
+
}
|
|
430
434
|
obj[lastKey] = value;
|
|
431
435
|
};
|
|
432
436
|
return new Tween(callback, start, end, duration, options);
|
package/plugins/uiSystem.js
CHANGED
|
@@ -580,7 +580,7 @@ class UISystemPlugin
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
/** Get other axis navigation direction from gamepad or keyboard
|
|
583
|
-
* @return {
|
|
583
|
+
* @return {number} */
|
|
584
584
|
getNavigationOtherDirection()
|
|
585
585
|
{
|
|
586
586
|
if (uiSystem.navigationDirection === 2)
|
|
@@ -669,6 +669,7 @@ class UISystemPlugin
|
|
|
669
669
|
uiSystem.navigationDirection = savedNavigationDirection;
|
|
670
670
|
inputClear();
|
|
671
671
|
}
|
|
672
|
+
return confirmMenu;
|
|
672
673
|
}
|
|
673
674
|
}
|
|
674
675
|
|
|
@@ -1102,7 +1103,7 @@ class UITextInput extends UIObject
|
|
|
1102
1103
|
this.onClick();
|
|
1103
1104
|
}
|
|
1104
1105
|
|
|
1105
|
-
/** Stop editing the text
|
|
1106
|
+
/** Stop editing the text */
|
|
1106
1107
|
stopEditing()
|
|
1107
1108
|
{
|
|
1108
1109
|
if (!this.isKeyInputObject())
|
|
@@ -1265,7 +1266,7 @@ class UICheckbox extends UIObject
|
|
|
1265
1266
|
ASSERT(isStringLike(text), 'ui checkbox must be a string');
|
|
1266
1267
|
ASSERT(isColor(color), 'ui checkbox color must be a color');
|
|
1267
1268
|
|
|
1268
|
-
/** @property {boolean} -
|
|
1269
|
+
/** @property {boolean} - Is the checkbox currently checked? */
|
|
1269
1270
|
this.checked = checked;
|
|
1270
1271
|
// set properties
|
|
1271
1272
|
this.text = text;
|
package/src/engine.js
CHANGED
|
@@ -32,7 +32,7 @@ const engineName = 'LittleJS';
|
|
|
32
32
|
* @type {string}
|
|
33
33
|
* @default
|
|
34
34
|
* @memberof Engine */
|
|
35
|
-
const engineVersion = '1.18.
|
|
35
|
+
const engineVersion = '1.18.22';
|
|
36
36
|
|
|
37
37
|
/** Frames per second to update
|
|
38
38
|
* @type {number}
|
|
@@ -471,6 +471,11 @@ function engineObjectsUpdate()
|
|
|
471
471
|
// get list of solid objects for physics optimization
|
|
472
472
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
473
473
|
|
|
474
|
+
// update physics before object update
|
|
475
|
+
for (const o of engineObjects)
|
|
476
|
+
if (!o.parent && !o.destroyed)
|
|
477
|
+
o.updatePhysics();
|
|
478
|
+
|
|
474
479
|
// recursive object update
|
|
475
480
|
function updateChildObject(o)
|
|
476
481
|
{
|
|
@@ -486,7 +491,6 @@ function engineObjectsUpdate()
|
|
|
486
491
|
|
|
487
492
|
// update top level objects
|
|
488
493
|
o.update();
|
|
489
|
-
o.updatePhysics();
|
|
490
494
|
for (const child of o.children)
|
|
491
495
|
updateChildObject(child);
|
|
492
496
|
o.updateTransforms();
|
package/src/engineAudio.js
CHANGED
|
@@ -446,7 +446,7 @@ class SoundInstance
|
|
|
446
446
|
* @param {number} [rate] - How quickly to speak
|
|
447
447
|
* @param {number} [pitch] - How much to change the pitch by
|
|
448
448
|
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
449
|
-
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
449
|
+
* @return {SpeechSynthesisUtterance|undefined} - The utterance that was spoken, or undefined if speech is unavailable
|
|
450
450
|
* @memberof Audio */
|
|
451
451
|
function speak(text, volume=1, rate=1, pitch=1, language='')
|
|
452
452
|
{
|
|
@@ -499,7 +499,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
499
499
|
* @param {number} [pan] - How much to apply stereo panning
|
|
500
500
|
* @param {boolean} [loop] - True if the sound should loop when it reaches the end
|
|
501
501
|
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
502
|
-
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
502
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing (disconnected when the sound ends)
|
|
503
503
|
* @param {number} [offset] - Offset in seconds to start playback from
|
|
504
504
|
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
505
505
|
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
package/src/engineBuild.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
import { execSync } from 'node:child_process';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
-
import { dirname, join } from 'node:path';
|
|
15
|
+
import { basename, dirname, join } from 'node:path';
|
|
16
16
|
|
|
17
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
18
|
const __dirname = dirname(__filename);
|
|
@@ -46,8 +46,10 @@ const enginePluginFiles =
|
|
|
46
46
|
`${PLUGIN_FOLDER}/uiSystem.js`,
|
|
47
47
|
`${PLUGIN_FOLDER}/box2d.js`,
|
|
48
48
|
`${PLUGIN_FOLDER}/drawUtilities.js`,
|
|
49
|
+
`${PLUGIN_FOLDER}/textureSheet.js`,
|
|
49
50
|
`${PLUGIN_FOLDER}/tweenSystem.js`,
|
|
50
51
|
`${PLUGIN_FOLDER}/pathFinder.js`,
|
|
52
|
+
`${PLUGIN_FOLDER}/threejs.js`,
|
|
51
53
|
];
|
|
52
54
|
const engineExtraFiles =
|
|
53
55
|
[
|
|
@@ -76,7 +78,7 @@ try
|
|
|
76
78
|
|
|
77
79
|
// copy extra files to build folder
|
|
78
80
|
for (const file of engineExtraFiles)
|
|
79
|
-
fs.copyFileSync(file,
|
|
81
|
+
fs.copyFileSync(file, join(BUILD_FOLDER, basename(file)));
|
|
80
82
|
|
|
81
83
|
}
|
|
82
84
|
catch (e) { handleError(e, 'Failed to create build folder!'); }
|
package/src/engineDebug.js
CHANGED
|
@@ -659,6 +659,7 @@ function debugVideoCaptureStart()
|
|
|
659
659
|
{
|
|
660
660
|
LOG('Video capture not supported in this browser!');
|
|
661
661
|
silentAudioSource?.stop();
|
|
662
|
+
audioStreamDestination && audioMasterGain.disconnect(audioStreamDestination);
|
|
662
663
|
return;
|
|
663
664
|
}
|
|
664
665
|
|
|
@@ -687,6 +688,8 @@ function debugVideoCaptureStop()
|
|
|
687
688
|
debugVideoCapture.silentAudioSource?.stop();
|
|
688
689
|
debugVideoCapture.mediaRecorder?.stop();
|
|
689
690
|
debugVideoCapture.videoTrack?.stop();
|
|
691
|
+
if (debugVideoCapture.audioStreamDestination)
|
|
692
|
+
audioMasterGain.disconnect(debugVideoCapture.audioStreamDestination);
|
|
690
693
|
debugVideoCapture = undefined;
|
|
691
694
|
}
|
|
692
695
|
|
package/src/engineDraw.js
CHANGED
|
@@ -155,8 +155,9 @@ class TileInfo
|
|
|
155
155
|
* @param {TextureInfo} [textureInfo] - Texture info to use
|
|
156
156
|
* @param {number} [padding] - How many pixels padding around all sides of each tile (increases grid size, does not affect tile size)
|
|
157
157
|
* @param {number} [bleed] - How many pixels smaller to shrink UVS of tiles (does not affect grid size, only UVs)
|
|
158
|
+
* @param {number} [columns] - How many frames per row for frame(), 0 to keep frames on a single row
|
|
158
159
|
*/
|
|
159
|
-
constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed)
|
|
160
|
+
constructor(pos=vec2(), size=tileDefaultSize, textureInfo=textureInfos[0], padding=tileDefaultPadding, bleed=tileDefaultBleed, columns=0)
|
|
160
161
|
{
|
|
161
162
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
162
163
|
this.pos = pos.copy();
|
|
@@ -168,6 +169,8 @@ class TileInfo
|
|
|
168
169
|
this.textureInfo = textureInfo;
|
|
169
170
|
/** @property {number} - Shrinks tile by this many pixels to prevent neighbors bleeding */
|
|
170
171
|
this.bleed = bleed;
|
|
172
|
+
/** @property {number} - How many frames per row for frame(), 0 to keep frames on a single row */
|
|
173
|
+
this.columns = columns;
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
/** Returns a copy of this tile offset by a vector
|
|
@@ -175,9 +178,10 @@ class TileInfo
|
|
|
175
178
|
* @return {TileInfo}
|
|
176
179
|
*/
|
|
177
180
|
offset(offset)
|
|
178
|
-
{ return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed); }
|
|
181
|
+
{ return new TileInfo(this.pos.add(offset), this.size, this.textureInfo, this.padding, this.bleed, this.columns); }
|
|
179
182
|
|
|
180
183
|
/** Returns a copy of this tile offset by a number of animation frames
|
|
184
|
+
* Frames wrap down to the next row if columns is set
|
|
181
185
|
* @param {number} frame - Offset to apply in animation frames
|
|
182
186
|
* @return {TileInfo}
|
|
183
187
|
*/
|
|
@@ -185,11 +189,33 @@ class TileInfo
|
|
|
185
189
|
{
|
|
186
190
|
ASSERT(typeof frame === 'number');
|
|
187
191
|
const w = this.size.x + this.padding*2;
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
const h = this.size.y + this.padding*2;
|
|
193
|
+
const x = (this.columns ? frame % this.columns : frame) * w;
|
|
194
|
+
const y = (this.columns ? frame / this.columns | 0 : 0) * h;
|
|
195
|
+
ASSERT(this.pos.x + x + this.size.x <= this.textureInfo.size.x, 'frame extends beyond texture width!');
|
|
196
|
+
ASSERT(this.pos.y + y + this.size.y <= this.textureInfo.size.y, 'frame extends beyond texture height!');
|
|
197
|
+
return this.offset(new Vector2(x, y));
|
|
191
198
|
}
|
|
192
199
|
|
|
200
|
+
/** Set how many frames per row this tile uses, so frame() can wrap
|
|
201
|
+
* @param {number} [columns] - Frames per row, 0 to keep frames on a single row
|
|
202
|
+
* @return {TileInfo}
|
|
203
|
+
*/
|
|
204
|
+
setColumns(columns=0)
|
|
205
|
+
{
|
|
206
|
+
ASSERT(isNumber(columns) && columns >= 0, 'columns must be a number >= 0');
|
|
207
|
+
this.columns = columns;
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Returns a tile info for an index using this tile as reference
|
|
213
|
+
* @param {Vector2|number} [index=0]
|
|
214
|
+
* @return {TileInfo}
|
|
215
|
+
*/
|
|
216
|
+
index(index)
|
|
217
|
+
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed).setColumns(this.columns); }
|
|
218
|
+
|
|
193
219
|
/**
|
|
194
220
|
* Set this tile to use a full image in a texture info
|
|
195
221
|
* @param {TextureInfo} [textureInfo]
|
|
@@ -200,17 +226,9 @@ class TileInfo
|
|
|
200
226
|
this.textureInfo = textureInfo;
|
|
201
227
|
this.pos = new Vector2;
|
|
202
228
|
this.size = textureInfo.size.copy();
|
|
203
|
-
this.bleed = this.padding = 0;
|
|
229
|
+
this.bleed = this.padding = this.columns = 0;
|
|
204
230
|
return this;
|
|
205
231
|
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Returns a tile info for an index using this tile as reference
|
|
209
|
-
* @param {Vector2|number} [index=0]
|
|
210
|
-
* @return {TileInfo}
|
|
211
|
-
*/
|
|
212
|
-
tile(index)
|
|
213
|
-
{ return tile(index, this.size, this.textureInfo, this.padding, this.bleed); }
|
|
214
232
|
}
|
|
215
233
|
|
|
216
234
|
/**
|
package/src/engineInput.js
CHANGED
|
@@ -438,7 +438,10 @@ function inputInit()
|
|
|
438
438
|
{
|
|
439
439
|
inputData[0][e.code] = (inputData[0][e.code]&2) | 4;
|
|
440
440
|
if (inputWASDEmulateDirection)
|
|
441
|
-
|
|
441
|
+
{
|
|
442
|
+
const remap = remapKey(e.code);
|
|
443
|
+
inputData[0][remap] = (inputData[0][remap]&2) | 4;
|
|
444
|
+
}
|
|
442
445
|
}
|
|
443
446
|
function remapKey(k)
|
|
444
447
|
{
|
|
@@ -513,7 +516,7 @@ function inputInit()
|
|
|
513
516
|
document.addEventListener('touchend', (e)=> handleTouch(e), { passive: false });
|
|
514
517
|
|
|
515
518
|
// handle all touch events the same way
|
|
516
|
-
let wasTouching;
|
|
519
|
+
let wasTouching, touchIdentifier;
|
|
517
520
|
function handleTouch(e)
|
|
518
521
|
{
|
|
519
522
|
if (!touchInputEnable) return;
|
|
@@ -544,10 +547,11 @@ function inputInit()
|
|
|
544
547
|
const pos = vec2(gameTouches[0].clientX, gameTouches[0].clientY);
|
|
545
548
|
const mousePosScreenLast = mousePosScreen;
|
|
546
549
|
mousePosScreen = mouseEventToScreen(pos);
|
|
547
|
-
if (wasTouching)
|
|
550
|
+
if (wasTouching && gameTouches[0].identifier === touchIdentifier)
|
|
548
551
|
mouseDeltaScreen = mouseDeltaScreen.add(mousePosScreen.subtract(mousePosScreenLast));
|
|
549
|
-
else
|
|
552
|
+
else if (!wasTouching)
|
|
550
553
|
inputData[0][button] = 3;
|
|
554
|
+
touchIdentifier = gameTouches[0].identifier;
|
|
551
555
|
}
|
|
552
556
|
else if (wasTouching)
|
|
553
557
|
inputData[0][button] = inputData[0][button] & 2 | 4;
|
package/src/engineMath.js
CHANGED
|
@@ -194,7 +194,7 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
|
194
194
|
* @param {number} value
|
|
195
195
|
* @return {boolean}
|
|
196
196
|
* @memberof Math */
|
|
197
|
-
function isPowerOfTwo(value) { return !(value & (value - 1)); }
|
|
197
|
+
function isPowerOfTwo(value) { return value > 0 && !(value & (value - 1)); }
|
|
198
198
|
|
|
199
199
|
/** Returns the nearest power of two not less than the value
|
|
200
200
|
* @param {number} value
|
|
@@ -271,7 +271,7 @@ function isIntersecting(start, end, pos, size)
|
|
|
271
271
|
* @memberof Math */
|
|
272
272
|
function oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0)
|
|
273
273
|
{
|
|
274
|
-
const phase = (offset + t*frequency
|
|
274
|
+
const phase = mod(offset + t*frequency, 1);
|
|
275
275
|
let value;
|
|
276
276
|
|
|
277
277
|
if (type === 1) // triangle
|
package/src/engineParticles.js
CHANGED
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
42
42
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
43
43
|
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
44
|
-
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
45
|
-
* .5, 1 // randomness, collide
|
|
44
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
45
|
+
* .5, 1 // randomness, collide
|
|
46
46
|
* );
|
|
47
47
|
*/
|
|
48
48
|
class ParticleEmitter extends EngineObject
|
|
@@ -434,13 +434,12 @@ class Particle
|
|
|
434
434
|
const hitLayer = tileCollisionTest(this.pos);
|
|
435
435
|
if (!testCollision(oldPos))
|
|
436
436
|
{
|
|
437
|
-
// testCollision already invoked collideCallback with the
|
|
438
|
-
// correct (this, data, pos) args; no need to re-check here.
|
|
439
437
|
// test which side we bounced off (or both if a corner)
|
|
440
438
|
const isBlockedX = testCollision(vec2(this.pos.x, oldPos.y));
|
|
441
439
|
const isBlockedY = testCollision(vec2(oldPos.x, this.pos.y));
|
|
442
|
-
|
|
443
|
-
const
|
|
440
|
+
// collide callback may hit where the layer test does not, so hitLayer can be undefined
|
|
441
|
+
const hitRestitution = hitLayer ? max(restitution, hitLayer.restitution) : restitution;
|
|
442
|
+
const hitFriction = hitLayer ? max(friction, hitLayer.friction) : friction;
|
|
444
443
|
if (isBlockedX)
|
|
445
444
|
{
|
|
446
445
|
// move to previous X position and bounce
|
package/src/engineSettings.js
CHANGED
|
@@ -358,6 +358,7 @@ let vibrateEnable = true;
|
|
|
358
358
|
let soundEnable = true;
|
|
359
359
|
|
|
360
360
|
/** Volume scale to apply to all sound, music and speech
|
|
361
|
+
* Use setSoundVolume to also update the audio master gain immediately
|
|
361
362
|
* @type {number}
|
|
362
363
|
* @default
|
|
363
364
|
* @memberof Settings */
|
package/src/engineTileLayer.js
CHANGED
|
@@ -31,10 +31,14 @@ function tileCollisionGetData(pos, solidOnly=true)
|
|
|
31
31
|
// check all tile collision layers
|
|
32
32
|
for (const layer of tileCollisionLayers)
|
|
33
33
|
if (!solidOnly || layer.isSolid)
|
|
34
|
-
if (pos.arrayCheck(layer.size))
|
|
35
34
|
{
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
// convert world pos to layer local space
|
|
36
|
+
const layerPos = pos.subtract(layer.pos);
|
|
37
|
+
if (layerPos.arrayCheck(layer.size))
|
|
38
|
+
{
|
|
39
|
+
const data = layer.getCollisionData(layerPos);
|
|
40
|
+
if (data) return data;
|
|
41
|
+
}
|
|
38
42
|
}
|
|
39
43
|
return 0;
|
|
40
44
|
}
|
|
@@ -309,7 +313,7 @@ class TileLayer extends CanvasLayer
|
|
|
309
313
|
|
|
310
314
|
/** @property {TileInfo} - Default tile info for layer */
|
|
311
315
|
this.tileInfo = undefined;
|
|
312
|
-
/** @property {Array<TileLayerData>} -
|
|
316
|
+
/** @property {Array<TileLayerData>} - Array of tile data for the layer */
|
|
313
317
|
this.data = [];
|
|
314
318
|
/** @property {boolean} - Is this layer using a webgl texture? */
|
|
315
319
|
this.isUsingWebGL = false;
|
|
@@ -394,7 +398,7 @@ class TileLayer extends CanvasLayer
|
|
|
394
398
|
|
|
395
399
|
const size = this.drawSize || this.size;
|
|
396
400
|
const pos = this.pos.add(size.scale(.5));
|
|
397
|
-
this.draw(pos,
|
|
401
|
+
this.draw(pos, size, this.color, this.angle, this.mirror, this.additiveColor);
|
|
398
402
|
}
|
|
399
403
|
|
|
400
404
|
/** Called after this layer is redrawn, does nothing by default */
|
|
@@ -483,7 +487,7 @@ class TileLayer extends CanvasLayer
|
|
|
483
487
|
const d = this.getData(layerPos);
|
|
484
488
|
if (!d || !d.tile) return;
|
|
485
489
|
|
|
486
|
-
const tileInfo = this.tileInfo && this.tileInfo.
|
|
490
|
+
const tileInfo = this.tileInfo && this.tileInfo.index(d.tile);
|
|
487
491
|
this.drawLayerTile(drawPos, drawSize, tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
488
492
|
}
|
|
489
493
|
|
package/src/engineUtilities.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
class Timer
|
|
26
26
|
{
|
|
27
27
|
/** Create a timer object set time passed in
|
|
28
|
-
* @param {number} [timeLeft] - How much time left before the timer
|
|
28
|
+
* @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds (undefined = unset)
|
|
29
29
|
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
30
30
|
constructor(timeLeft, useRealTime=false)
|
|
31
31
|
{
|