littlejsengine 1.14.11 → 1.14.19
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/littlejs.d.ts +303 -137
- package/dist/littlejs.esm.js +936 -463
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +934 -462
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +859 -417
- package/examples/box2d/game.js +3 -2
- package/examples/box2d/gameObjects.js +2 -2
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +5 -5
- package/examples/breakout/gameObjects.js +2 -2
- package/examples/breakoutTutorial/README.md +32 -32
- package/examples/breakoutTutorial/game.js +1 -1
- package/examples/electron/game.js +4 -4
- package/examples/electron/index.html +2 -2
- package/examples/electron/package.json +1 -8
- package/examples/index.html +67 -56
- package/examples/module/game.js +4 -4
- package/examples/platformer/gameEffects.js +5 -5
- package/examples/platformer/gameLevel.js +1 -1
- package/examples/platformer/gameObjects.js +4 -4
- package/examples/puzzle/game.js +1 -1
- package/examples/shorts/animation.js +1 -1
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/blending.js +8 -14
- package/examples/shorts/box2d.js +7 -3
- package/examples/shorts/box2dCar.js +2 -1
- package/examples/shorts/empty.js +30 -0
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/hillGlideGame.js +10 -4
- package/examples/shorts/landerGame.js +11 -8
- package/examples/shorts/medals.js +4 -4
- package/examples/shorts/music.js +29 -56
- package/examples/shorts/musicPlayer.js +135 -0
- package/examples/shorts/nineSlice.js +34 -15
- package/examples/shorts/parallax.js +5 -4
- package/examples/shorts/particles.js +15 -15
- package/examples/shorts/piano.js +15 -21
- package/examples/shorts/pongGame.js +6 -4
- package/examples/shorts/raycasting.js +9 -4
- package/examples/shorts/sequencer.js +122 -0
- package/examples/shorts/shader.js +29 -0
- package/examples/shorts/shapes.js +7 -4
- package/examples/shorts/slidingPuzzle.js +4 -2
- package/examples/shorts/sound.js +18 -9
- package/examples/shorts/spaceGame.js +12 -8
- package/examples/shorts/spriteAtlas.js +7 -7
- package/examples/shorts/starfield.js +1 -1
- package/examples/shorts/systemFont.js +2 -2
- package/examples/shorts/texture.js +2 -2
- package/examples/shorts/tileLayer.js +10 -10
- package/examples/shorts/tiltedView.js +24 -5
- package/examples/shorts/timers.js +10 -0
- package/examples/shorts/topDown.js +4 -1
- package/examples/shorts/uiSystem.js +8 -5
- package/examples/starter/game.js +4 -4
- package/examples/starter/index.html +2 -2
- package/examples/style.css +1 -0
- package/examples/typescript/game.js +4 -4
- package/examples/typescript/game.ts +4 -4
- package/examples/uiSystem/game.js +4 -3
- package/package.json +4 -2
- package/plugins/box2d.js +17 -2
- package/plugins/newgrounds.js +7 -5
- package/plugins/postProcess.js +76 -53
- package/plugins/uiSystem.js +192 -53
- package/plugins/zzfxm.js +5 -1
- package/reference.md +1 -1
- package/src/engine.js +60 -23
- package/src/engineAudio.js +44 -17
- package/src/engineDebug.js +75 -45
- package/src/engineDraw.js +94 -59
- package/src/engineExport.js +2 -1
- package/src/engineMedals.js +10 -2
- package/src/engineObject.js +14 -10
- package/src/engineParticles.js +60 -47
- package/src/engineSettings.js +7 -7
- package/src/engineTileLayer.js +73 -56
- package/src/engineUtilities.js +67 -20
- package/src/engineWebGL.js +134 -63
package/dist/littlejs.esm.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.14.
|
|
36
|
+
const engineVersion = '1.14.19';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -94,29 +94,60 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
|
94
94
|
///////////////////////////////////////////////////////////////////////////////
|
|
95
95
|
// plugin hooks
|
|
96
96
|
|
|
97
|
-
const
|
|
97
|
+
const pluginList = [];
|
|
98
|
+
class EnginePlugin
|
|
99
|
+
{
|
|
100
|
+
constructor(update, render, glContextLost, glContextRestored)
|
|
101
|
+
{
|
|
102
|
+
this.update = update;
|
|
103
|
+
this.render = render;
|
|
104
|
+
this.glContextLost = glContextLost;
|
|
105
|
+
this.glContextRestored = glContextRestored;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @callback PluginCallback - Update or render function for a plugin
|
|
111
|
+
* @memberof Engine
|
|
112
|
+
*/
|
|
98
113
|
|
|
99
114
|
/** Add a new update function for a plugin
|
|
100
|
-
* @param {
|
|
101
|
-
* @param {
|
|
115
|
+
* @param {PluginCallback} [update]
|
|
116
|
+
* @param {PluginCallback} [render]
|
|
117
|
+
* @param {PluginCallback} [glContextLost]
|
|
118
|
+
* @param {PluginCallback} [glContextRestored]
|
|
102
119
|
* @memberof Engine */
|
|
103
|
-
function engineAddPlugin(
|
|
120
|
+
function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
104
121
|
{
|
|
105
|
-
|
|
106
|
-
ASSERT(!
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
// make sure plugin functions are unique
|
|
123
|
+
ASSERT(!pluginList.find(p=>
|
|
124
|
+
p.update === update && p.render === render &&
|
|
125
|
+
p.glContextLost === glContextLost &&
|
|
126
|
+
p.glContextRestored === glContextRestored));
|
|
127
|
+
|
|
128
|
+
const plugin = new EnginePlugin(update, render, glContextLost, glContextRestored);
|
|
129
|
+
pluginList.push(plugin);
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
///////////////////////////////////////////////////////////////////////////////
|
|
112
133
|
// Main Engine Functions
|
|
113
134
|
|
|
135
|
+
/**
|
|
136
|
+
* @callback GameInitCallback - Called after the engine starts, can be async
|
|
137
|
+
* @returns {void|Promise<void>}
|
|
138
|
+
* @memberof Engine
|
|
139
|
+
*/
|
|
140
|
+
/**
|
|
141
|
+
* @callback GameCallback - Update or render function for the game
|
|
142
|
+
* @memberof Engine
|
|
143
|
+
*/
|
|
144
|
+
|
|
114
145
|
/** Startup LittleJS engine with your callback functions
|
|
115
|
-
* @param {
|
|
116
|
-
* @param {
|
|
117
|
-
* @param {
|
|
118
|
-
* @param {
|
|
119
|
-
* @param {
|
|
146
|
+
* @param {GameInitCallback} gameInit - Called once after the engine starts up, can be async for loading
|
|
147
|
+
* @param {GameCallback} gameUpdate - Called every frame before objects are updated (60fps), use for game logic
|
|
148
|
+
* @param {GameCallback} gameUpdatePost - Called after physics and objects are updated, even when paused, use for UI updates
|
|
149
|
+
* @param {GameCallback} gameRender - Called before objects are rendered, use for drawing backgrounds/world elements
|
|
150
|
+
* @param {GameCallback} gameRenderPost - Called after objects are rendered, use for drawing UI/overlays
|
|
120
151
|
* @param {Array<string>} [imageSources=[]] - List of image file paths to preload (e.g., ['player.png', 'tiles.png'])
|
|
121
152
|
* @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body
|
|
122
153
|
* @example
|
|
@@ -180,7 +211,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
180
211
|
wasUpdated = true;
|
|
181
212
|
updateCanvas();
|
|
182
213
|
inputUpdate();
|
|
183
|
-
|
|
214
|
+
pluginList.forEach(plugin=>plugin.update?.());
|
|
184
215
|
|
|
185
216
|
// update object transforms even when paused
|
|
186
217
|
for (const o of engineObjects)
|
|
@@ -213,7 +244,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
213
244
|
updateCanvas();
|
|
214
245
|
inputUpdate();
|
|
215
246
|
gameUpdate();
|
|
216
|
-
|
|
247
|
+
pluginList.forEach(plugin=>plugin.update?.());
|
|
217
248
|
engineObjectsUpdate();
|
|
218
249
|
|
|
219
250
|
// do post update
|
|
@@ -247,7 +278,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
247
278
|
for (const o of engineObjects)
|
|
248
279
|
o.destroyed || o.render();
|
|
249
280
|
gameRenderPost();
|
|
250
|
-
|
|
281
|
+
pluginList.forEach(plugin=>plugin.render?.());
|
|
251
282
|
touchGamepadRender();
|
|
252
283
|
debugRender();
|
|
253
284
|
glFlush();
|
|
@@ -368,11 +399,12 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
368
399
|
const promises = imageSources.map((src, textureIndex)=>
|
|
369
400
|
new Promise(resolve =>
|
|
370
401
|
{
|
|
402
|
+
ASSERT(isString(src), 'imageSources must be an array of strings');
|
|
403
|
+
|
|
371
404
|
const image = new Image;
|
|
372
405
|
image.onerror = image.onload = ()=>
|
|
373
406
|
{
|
|
374
407
|
const textureInfo = new TextureInfo(image);
|
|
375
|
-
textureInfo.createWebGLTexture();
|
|
376
408
|
textureInfos[textureIndex] = textureInfo;
|
|
377
409
|
resolve();
|
|
378
410
|
}
|
|
@@ -388,7 +420,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
388
420
|
{
|
|
389
421
|
const textureInfo = new TextureInfo(new Image);
|
|
390
422
|
textureInfos[0] = textureInfo;
|
|
391
|
-
textureInfo.createWebGLTexture();
|
|
392
423
|
resolve();
|
|
393
424
|
}));
|
|
394
425
|
}
|
|
@@ -399,7 +430,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
399
430
|
promises.push(new Promise(resolve =>
|
|
400
431
|
{
|
|
401
432
|
let t = 0;
|
|
402
|
-
|
|
433
|
+
console.log(`${engineName} Engine v${engineVersion}`);
|
|
403
434
|
updateSplash();
|
|
404
435
|
function updateSplash()
|
|
405
436
|
{
|
|
@@ -483,10 +514,16 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
483
514
|
return collectedObjects;
|
|
484
515
|
}
|
|
485
516
|
|
|
517
|
+
/**
|
|
518
|
+
* @callback ObjectCallbackFunction - Function that processes an object
|
|
519
|
+
* @param {EngineObject} uiObjects
|
|
520
|
+
* @memberof Engine
|
|
521
|
+
*/
|
|
522
|
+
|
|
486
523
|
/** Triggers a callback for each object within a given area
|
|
487
|
-
* @param {Vector2} [pos]
|
|
488
|
-
* @param {Vector2|number} [size]
|
|
489
|
-
* @param {
|
|
524
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
525
|
+
* @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
526
|
+
* @param {ObjectCallbackFunction} [callbackFunction] - Calls this function on every object that passes the test
|
|
490
527
|
* @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
|
|
491
528
|
* @memberof Engine */
|
|
492
529
|
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
@@ -727,10 +764,16 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
|
|
|
727
764
|
// Debug helper functions
|
|
728
765
|
|
|
729
766
|
/** Asserts if the expression is false, does nothing in release builds
|
|
767
|
+
* Halts execution if the assert fails and throws an error
|
|
730
768
|
* @param {boolean} assert
|
|
731
769
|
* @param {...Object} [output] - error message output
|
|
732
770
|
* @memberof Debug */
|
|
733
|
-
function ASSERT(assert, ...output)
|
|
771
|
+
function ASSERT(assert, ...output)
|
|
772
|
+
{
|
|
773
|
+
if (assert) return;
|
|
774
|
+
console.assert(assert, ...output)
|
|
775
|
+
throw new Error('Assert failed!'); // halt execution
|
|
776
|
+
}
|
|
734
777
|
|
|
735
778
|
/** Log to console if debug is enabled, does nothing in release builds
|
|
736
779
|
* @param {...Object} [output] - message output
|
|
@@ -740,64 +783,72 @@ function LOG(...output) { console.log(...output); }
|
|
|
740
783
|
/** Draw a debug rectangle in world space
|
|
741
784
|
* @param {Vector2} pos
|
|
742
785
|
* @param {Vector2} [size=Vector2()]
|
|
743
|
-
* @param {string}
|
|
744
|
-
* @param {number}
|
|
745
|
-
* @param {number}
|
|
786
|
+
* @param {Color|string} [color]
|
|
787
|
+
* @param {number} [time]
|
|
788
|
+
* @param {number} [angle]
|
|
746
789
|
* @param {boolean} [fill]
|
|
747
790
|
* @memberof Debug */
|
|
748
|
-
function debugRect(pos, size=vec2(), color=
|
|
791
|
+
function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
749
792
|
{
|
|
750
793
|
if (typeof size === 'number')
|
|
751
794
|
size = vec2(size); // allow passing in floats
|
|
752
|
-
|
|
753
|
-
|
|
795
|
+
if (isColor(color))
|
|
796
|
+
color = color.toString();
|
|
797
|
+
pos = pos.copy();
|
|
798
|
+
size = size.copy();
|
|
799
|
+
const timer = new Timer(time);
|
|
800
|
+
debugPrimitives.push({pos:pos.copy(), size:size.copy(), color, timer, angle, fill});
|
|
754
801
|
}
|
|
755
802
|
|
|
756
803
|
/** Draw a debug poly in world space
|
|
757
804
|
* @param {Vector2} pos
|
|
758
805
|
* @param {Array<Vector2>} points
|
|
759
|
-
* @param {string}
|
|
760
|
-
* @param {number}
|
|
761
|
-
* @param {number}
|
|
806
|
+
* @param {Color|string} [color]
|
|
807
|
+
* @param {number} [time]
|
|
808
|
+
* @param {number} [angle]
|
|
762
809
|
* @param {boolean} [fill]
|
|
763
810
|
* @memberof Debug */
|
|
764
|
-
function debugPoly(pos, points, color=
|
|
811
|
+
function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
765
812
|
{
|
|
766
|
-
|
|
767
|
-
|
|
813
|
+
if (isColor(color))
|
|
814
|
+
color = color.toString();
|
|
815
|
+
pos = pos.copy();
|
|
816
|
+
points = points.map(p=>p.copy());
|
|
817
|
+
const timer = new Timer(time);
|
|
818
|
+
debugPrimitives.push({pos, points, color, timer, angle, fill});
|
|
768
819
|
}
|
|
769
820
|
|
|
770
821
|
/** Draw a debug circle in world space
|
|
771
822
|
* @param {Vector2} pos
|
|
772
|
-
* @param {number}
|
|
773
|
-
* @param {string}
|
|
774
|
-
* @param {number}
|
|
823
|
+
* @param {number} [size] - diameter
|
|
824
|
+
* @param {Color|string} [color]
|
|
825
|
+
* @param {number} [time]
|
|
775
826
|
* @param {boolean} [fill]
|
|
776
827
|
* @memberof Debug */
|
|
777
|
-
function debugCircle(pos, size=0, color=
|
|
828
|
+
function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
|
|
778
829
|
{
|
|
779
|
-
|
|
780
|
-
|
|
830
|
+
if (isColor(color))
|
|
831
|
+
color = color.toString();
|
|
832
|
+
pos = pos.copy();
|
|
833
|
+
const timer = new Timer(time);
|
|
834
|
+
debugPrimitives.push({pos, size, color, timer, angle:0, fill});
|
|
781
835
|
}
|
|
782
836
|
|
|
783
837
|
/** Draw a debug point in world space
|
|
784
838
|
* @param {Vector2} pos
|
|
785
|
-
* @param {string}
|
|
786
|
-
* @param {number}
|
|
787
|
-
* @param {number}
|
|
839
|
+
* @param {Color|string} [color]
|
|
840
|
+
* @param {number} [time]
|
|
841
|
+
* @param {number} [angle]
|
|
788
842
|
* @memberof Debug */
|
|
789
843
|
function debugPoint(pos, color, time, angle)
|
|
790
|
-
{
|
|
791
|
-
ASSERT(typeof color === 'string', 'pass in css color strings');
|
|
792
|
-
debugRect(pos, undefined, color, time, angle);
|
|
793
|
-
}
|
|
844
|
+
{ debugRect(pos, undefined, color, time, angle); }
|
|
794
845
|
|
|
795
846
|
/** Draw a debug line in world space
|
|
796
847
|
* @param {Vector2} posA
|
|
797
848
|
* @param {Vector2} posB
|
|
798
|
-
* @param {string}
|
|
799
|
-
* @param {number}
|
|
800
|
-
* @param {number}
|
|
849
|
+
* @param {Color|string} [color]
|
|
850
|
+
* @param {number} [width]
|
|
851
|
+
* @param {number} [time]
|
|
801
852
|
* @memberof Debug */
|
|
802
853
|
function debugLine(posA, posB, color, width=.1, time)
|
|
803
854
|
{
|
|
@@ -811,7 +862,7 @@ function debugLine(posA, posB, color, width=.1, time)
|
|
|
811
862
|
* @param {Vector2} sizeA
|
|
812
863
|
* @param {Vector2} posB
|
|
813
864
|
* @param {Vector2} sizeB
|
|
814
|
-
* @param {string}
|
|
865
|
+
* @param {Color|string} [color]
|
|
815
866
|
* @memberof Debug */
|
|
816
867
|
function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
817
868
|
{
|
|
@@ -827,18 +878,21 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
|
827
878
|
}
|
|
828
879
|
|
|
829
880
|
/** Draw a debug axis aligned bounding box in world space
|
|
830
|
-
* @param {string}
|
|
881
|
+
* @param {string} text
|
|
831
882
|
* @param {Vector2} pos
|
|
832
|
-
* @param {number}
|
|
833
|
-
* @param {string}
|
|
834
|
-
* @param {number}
|
|
835
|
-
* @param {number}
|
|
836
|
-
* @param {string}
|
|
883
|
+
* @param {number} [size]
|
|
884
|
+
* @param {Color|string} [color]
|
|
885
|
+
* @param {number} [time]
|
|
886
|
+
* @param {number} [angle]
|
|
887
|
+
* @param {string} [font]
|
|
837
888
|
* @memberof Debug */
|
|
838
|
-
function debugText(text, pos, size=1, color=
|
|
889
|
+
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace')
|
|
839
890
|
{
|
|
840
|
-
|
|
841
|
-
|
|
891
|
+
if (isColor(color))
|
|
892
|
+
color = color.toString();
|
|
893
|
+
pos = pos.copy();
|
|
894
|
+
const timer = new Timer(time);
|
|
895
|
+
debugPrimitives.push({text, pos, size, color, timer, angle, font});
|
|
842
896
|
}
|
|
843
897
|
|
|
844
898
|
/** Clear all debug primitives in the list
|
|
@@ -889,17 +943,30 @@ function debugSaveDataURL(dataURL, filename)
|
|
|
889
943
|
downloadLink.click();
|
|
890
944
|
}
|
|
891
945
|
|
|
892
|
-
/**
|
|
946
|
+
/** Breaks on all asserts/errors, hides the canvas, and shows message in plain text
|
|
947
|
+
* This is a good function to call at the start of your game to catch all errors
|
|
948
|
+
* In release builds this function has no effect
|
|
893
949
|
* @memberof Debug */
|
|
894
950
|
function debugShowErrors()
|
|
895
951
|
{
|
|
896
952
|
const showError = (message)=>
|
|
897
953
|
{
|
|
898
954
|
// replace entire page with error message
|
|
899
|
-
document.body.style
|
|
900
|
-
document.body.
|
|
901
|
-
document.body.innerHTML = `<pre style=color:#f00;font-size:50px;white-space:pre-wrap>` + message;
|
|
955
|
+
document.body.style = 'background-color:#111;margin:8px';
|
|
956
|
+
document.body.innerHTML = `<pre style=color:#f00;font-size:28px;white-space:pre-wrap>` + message;
|
|
902
957
|
}
|
|
958
|
+
|
|
959
|
+
const originalAssert = console.assert;
|
|
960
|
+
console.assert = (assertion, ...output)=>
|
|
961
|
+
{
|
|
962
|
+
originalAssert(assertion, ...output);
|
|
963
|
+
if (!assertion)
|
|
964
|
+
{
|
|
965
|
+
const message = output.join(' ');
|
|
966
|
+
const stack = new Error().stack;
|
|
967
|
+
throw 'Assertion failed!\n' + message + '\n' + stack;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
903
970
|
onunhandledrejection = (event)=>
|
|
904
971
|
showError(event.reason.stack || event.reason);
|
|
905
972
|
onerror = (message, source, lineno, colno)=>
|
|
@@ -1067,7 +1134,7 @@ function debugRender()
|
|
|
1067
1134
|
p.fill && overlayContext.fill();
|
|
1068
1135
|
overlayContext.stroke();
|
|
1069
1136
|
}
|
|
1070
|
-
else if (p.size === 0 || p.size.x === 0 && p.size.y === 0)
|
|
1137
|
+
else if (p.size === 0 || (p.size.x === 0 && p.size.y === 0))
|
|
1071
1138
|
{
|
|
1072
1139
|
// point
|
|
1073
1140
|
overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
|
|
@@ -1094,7 +1161,7 @@ function debugRender()
|
|
|
1094
1161
|
});
|
|
1095
1162
|
|
|
1096
1163
|
// remove expired primitives
|
|
1097
|
-
debugPrimitives = debugPrimitives.filter(r=>r.
|
|
1164
|
+
debugPrimitives = debugPrimitives.filter(r=>r.timer<0);
|
|
1098
1165
|
}
|
|
1099
1166
|
|
|
1100
1167
|
if (debugObject)
|
|
@@ -1504,6 +1571,8 @@ function formatTime(t)
|
|
|
1504
1571
|
async function fetchJSON(url)
|
|
1505
1572
|
{
|
|
1506
1573
|
const response = await fetch(url);
|
|
1574
|
+
if (!response.ok)
|
|
1575
|
+
throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`);
|
|
1507
1576
|
return response.json();
|
|
1508
1577
|
}
|
|
1509
1578
|
|
|
@@ -1514,6 +1583,13 @@ async function fetchJSON(url)
|
|
|
1514
1583
|
* @memberof Utilities */
|
|
1515
1584
|
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
1516
1585
|
|
|
1586
|
+
/**
|
|
1587
|
+
* Check if object is a valid string or can be converted to one
|
|
1588
|
+
* @param {any} s
|
|
1589
|
+
* @return {boolean}
|
|
1590
|
+
* @memberof Utilities */
|
|
1591
|
+
function isString(s) { return s !== undefined && s !== null && typeof s.toString() === 'string'; }
|
|
1592
|
+
|
|
1517
1593
|
///////////////////////////////////////////////////////////////////////////////
|
|
1518
1594
|
|
|
1519
1595
|
/** Random global functions
|
|
@@ -1576,6 +1652,7 @@ function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
|
1576
1652
|
/**
|
|
1577
1653
|
* Seeded random number generator
|
|
1578
1654
|
* - Can be used to create a deterministic random number sequence
|
|
1655
|
+
* @memberof Engine
|
|
1579
1656
|
* @example
|
|
1580
1657
|
* let r = new RandomGenerator(123); // random number generator with seed 123
|
|
1581
1658
|
* let a = r.float(); // random value between 0 and 1
|
|
@@ -1672,6 +1749,7 @@ function ASSERT_VECTOR2_NORMAL(v)
|
|
|
1672
1749
|
/**
|
|
1673
1750
|
* 2D Vector object with vector math library
|
|
1674
1751
|
* - Functions do not change this so they can be chained together
|
|
1752
|
+
* @memberof Engine
|
|
1675
1753
|
* @example
|
|
1676
1754
|
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
1677
1755
|
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
@@ -1812,9 +1890,10 @@ class Vector2
|
|
|
1812
1890
|
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
1813
1891
|
}
|
|
1814
1892
|
|
|
1815
|
-
/**
|
|
1893
|
+
/** Sets this this vector to point in the specified integer direction (0-3), corresponding to multiples of 90 degree rotation
|
|
1816
1894
|
* @param {number} [direction]
|
|
1817
|
-
* @param {number} [length]
|
|
1895
|
+
* @param {number} [length]
|
|
1896
|
+
* @return {Vector2} */
|
|
1818
1897
|
setDirection(direction, length=1)
|
|
1819
1898
|
{
|
|
1820
1899
|
ASSERT_NUMBER_VALID(direction);
|
|
@@ -1822,8 +1901,10 @@ class Vector2
|
|
|
1822
1901
|
direction = mod(direction, 4);
|
|
1823
1902
|
ASSERT(direction===0 || direction===1 || direction===2 || direction===3,
|
|
1824
1903
|
'Vector2.setDirection() direction must be an integer between 0 and 3.');
|
|
1825
|
-
|
|
1826
|
-
|
|
1904
|
+
|
|
1905
|
+
this.x = direction%2 ? direction-1 ? -length : length : 0;
|
|
1906
|
+
this.y = direction%2 ? 0 : direction ? -length : length;
|
|
1907
|
+
return this;
|
|
1827
1908
|
}
|
|
1828
1909
|
|
|
1829
1910
|
/** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
|
|
@@ -1919,6 +2000,7 @@ function ASSERT_COLOR_VALID(c) { ASSERT(isColor(c), 'Color is invalid.', c); }
|
|
|
1919
2000
|
|
|
1920
2001
|
/**
|
|
1921
2002
|
* Color object (red, green, blue, alpha) with some helpful functions
|
|
2003
|
+
* @memberof Engine
|
|
1922
2004
|
* @example
|
|
1923
2005
|
* let a = new Color; // white
|
|
1924
2006
|
* let b = new Color(1, 0, 0); // red
|
|
@@ -2096,7 +2178,8 @@ class Color
|
|
|
2096
2178
|
* @return {Color} */
|
|
2097
2179
|
setHex(hex)
|
|
2098
2180
|
{
|
|
2099
|
-
ASSERT(
|
|
2181
|
+
ASSERT(isString(hex), 'Color hex code must be a string');
|
|
2182
|
+
ASSERT(hex[0] === '#', 'Color hex code must start with #');
|
|
2100
2183
|
ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
|
|
2101
2184
|
|
|
2102
2185
|
if (hex.length < 6)
|
|
@@ -2138,77 +2221,78 @@ class Color
|
|
|
2138
2221
|
}
|
|
2139
2222
|
|
|
2140
2223
|
///////////////////////////////////////////////////////////////////////////////
|
|
2141
|
-
//
|
|
2224
|
+
// Default Colors
|
|
2142
2225
|
|
|
2143
2226
|
/** Color - White #ffffff
|
|
2144
2227
|
* @type {Color}
|
|
2145
2228
|
* @memberof Utilities */
|
|
2146
|
-
const WHITE = rgb();
|
|
2229
|
+
const WHITE = protectEngineConstant(rgb());
|
|
2147
2230
|
|
|
2148
|
-
/** Color - Clear White #
|
|
2231
|
+
/** Color - Clear White #757474ff with 0 alpha
|
|
2149
2232
|
* @type {Color}
|
|
2150
2233
|
* @memberof Utilities */
|
|
2151
|
-
const CLEAR_WHITE = rgb(1,1,1,0);
|
|
2234
|
+
const CLEAR_WHITE = protectEngineConstant(rgb(1,1,1,0));
|
|
2152
2235
|
|
|
2153
2236
|
/** Color - Black #000000
|
|
2154
2237
|
* @type {Color}
|
|
2155
2238
|
* @memberof Utilities */
|
|
2156
|
-
const BLACK = rgb(0,0,0);
|
|
2239
|
+
const BLACK = protectEngineConstant(rgb(0,0,0));
|
|
2157
2240
|
|
|
2158
2241
|
/** Color - Clear Black #000000 with 0 alpha
|
|
2159
2242
|
* @type {Color}
|
|
2160
2243
|
* @memberof Utilities */
|
|
2161
|
-
const CLEAR_BLACK = rgb(0,0,0,0);
|
|
2244
|
+
const CLEAR_BLACK = protectEngineConstant(rgb(0,0,0,0));
|
|
2162
2245
|
|
|
2163
2246
|
/** Color - Gray #808080
|
|
2164
2247
|
* @type {Color}
|
|
2165
2248
|
* @memberof Utilities */
|
|
2166
|
-
const GRAY = rgb(.5,.5,.5);
|
|
2249
|
+
const GRAY = protectEngineConstant(rgb(.5,.5,.5));
|
|
2167
2250
|
|
|
2168
2251
|
/** Color - Red #ff0000
|
|
2169
2252
|
* @type {Color}
|
|
2170
2253
|
* @memberof Utilities */
|
|
2171
|
-
const RED = rgb(1,0,0);
|
|
2254
|
+
const RED = protectEngineConstant(rgb(1,0,0));
|
|
2172
2255
|
|
|
2173
2256
|
/** Color - Orange #ff8000
|
|
2174
2257
|
* @type {Color}
|
|
2175
2258
|
* @memberof Utilities */
|
|
2176
|
-
const ORANGE = rgb(1,.5,0);
|
|
2259
|
+
const ORANGE = protectEngineConstant(rgb(1,.5,0));
|
|
2177
2260
|
|
|
2178
2261
|
/** Color - Yellow #ffff00
|
|
2179
2262
|
* @type {Color}
|
|
2180
2263
|
* @memberof Utilities */
|
|
2181
|
-
const YELLOW = rgb(1,1,0);
|
|
2264
|
+
const YELLOW = protectEngineConstant(rgb(1,1,0));
|
|
2182
2265
|
|
|
2183
2266
|
/** Color - Green #00ff00
|
|
2184
2267
|
* @type {Color}
|
|
2185
2268
|
* @memberof Utilities */
|
|
2186
|
-
const GREEN = rgb(0,1,0);
|
|
2269
|
+
const GREEN = protectEngineConstant(rgb(0,1,0));
|
|
2187
2270
|
|
|
2188
2271
|
/** Color - Cyan #00ffff
|
|
2189
2272
|
* @type {Color}
|
|
2190
2273
|
* @memberof Utilities */
|
|
2191
|
-
const CYAN = rgb(0,1,1);
|
|
2274
|
+
const CYAN = protectEngineConstant(rgb(0,1,1));
|
|
2192
2275
|
|
|
2193
2276
|
/** Color - Blue #0000ff
|
|
2194
2277
|
* @type {Color}
|
|
2195
2278
|
* @memberof Utilities */
|
|
2196
|
-
const BLUE = rgb(0,0,1);
|
|
2279
|
+
const BLUE = protectEngineConstant(rgb(0,0,1));
|
|
2197
2280
|
|
|
2198
2281
|
/** Color - Purple #8000ff
|
|
2199
2282
|
* @type {Color}
|
|
2200
2283
|
* @memberof Utilities */
|
|
2201
|
-
const PURPLE = rgb(.5,0,1);
|
|
2284
|
+
const PURPLE = protectEngineConstant(rgb(.5,0,1));
|
|
2202
2285
|
|
|
2203
2286
|
/** Color - Magenta #ff00ff
|
|
2204
2287
|
* @type {Color}
|
|
2205
2288
|
* @memberof Utilities */
|
|
2206
|
-
const MAGENTA = rgb(1,0,1);
|
|
2289
|
+
const MAGENTA = protectEngineConstant(rgb(1,0,1));
|
|
2207
2290
|
|
|
2208
2291
|
///////////////////////////////////////////////////////////////////////////////
|
|
2209
2292
|
|
|
2210
2293
|
/**
|
|
2211
2294
|
* Timer object tracks how long has passed since it was set
|
|
2295
|
+
* @memberof Engine
|
|
2212
2296
|
* @example
|
|
2213
2297
|
* let a = new Timer; // creates a timer that is not set
|
|
2214
2298
|
* a.set(3); // sets the timer to 3 seconds
|
|
@@ -2270,6 +2354,36 @@ class Timer
|
|
|
2270
2354
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
2271
2355
|
* @return {number} */
|
|
2272
2356
|
valueOf() { return this.get(); }
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2360
|
+
// Helper functions used by the engine
|
|
2361
|
+
|
|
2362
|
+
// make color constants immutable with debug assertions
|
|
2363
|
+
function protectEngineConstant(obj)
|
|
2364
|
+
{
|
|
2365
|
+
if (debug)
|
|
2366
|
+
{
|
|
2367
|
+
// get properties and store original values
|
|
2368
|
+
const props = Object.keys(obj), values = {};
|
|
2369
|
+
props.forEach(prop => values[prop] = obj[prop]);
|
|
2370
|
+
|
|
2371
|
+
// replace with getters/setters that assert
|
|
2372
|
+
props.forEach(prop =>
|
|
2373
|
+
{
|
|
2374
|
+
Object.defineProperty(obj, prop, {
|
|
2375
|
+
get: () => values[prop],
|
|
2376
|
+
set: (value) =>
|
|
2377
|
+
{
|
|
2378
|
+
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
2379
|
+
},
|
|
2380
|
+
enumerable: true
|
|
2381
|
+
});
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
// freeze the object to prevent adding new properties
|
|
2386
|
+
return Object.freeze(obj);
|
|
2273
2387
|
}
|
|
2274
2388
|
/**
|
|
2275
2389
|
* LittleJS Engine Settings
|
|
@@ -2577,7 +2691,7 @@ let medalsPreventUnlock = false;
|
|
|
2577
2691
|
/** Set position of camera in world space
|
|
2578
2692
|
* @param {Vector2} pos
|
|
2579
2693
|
* @memberof Settings */
|
|
2580
|
-
function setCameraPos(pos) { cameraPos = pos; }
|
|
2694
|
+
function setCameraPos(pos) { cameraPos = pos.copy(); }
|
|
2581
2695
|
|
|
2582
2696
|
/** Set angle of camera in world space
|
|
2583
2697
|
* @param {number} angle
|
|
@@ -2600,17 +2714,17 @@ function setCanvasColorTiles(colorTiles) { canvasColorTiles = colorTiles; }
|
|
|
2600
2714
|
/** Set color to clear the canvas to before render
|
|
2601
2715
|
* @param {Color} color
|
|
2602
2716
|
* @memberof Settings */
|
|
2603
|
-
function setCanvasClearColor(color) { canvasClearColor = color; }
|
|
2717
|
+
function setCanvasClearColor(color) { canvasClearColor = color.copy(); }
|
|
2604
2718
|
|
|
2605
2719
|
/** Set max size of the canvas
|
|
2606
2720
|
* @param {Vector2} size
|
|
2607
2721
|
* @memberof Settings */
|
|
2608
|
-
function setCanvasMaxSize(size) { canvasMaxSize = size; }
|
|
2722
|
+
function setCanvasMaxSize(size) { canvasMaxSize = size.copy(); }
|
|
2609
2723
|
|
|
2610
2724
|
/** Set fixed size of the canvas
|
|
2611
2725
|
* @param {Vector2} size
|
|
2612
2726
|
* @memberof Settings */
|
|
2613
|
-
function setCanvasFixedSize(size) { canvasFixedSize = size; }
|
|
2727
|
+
function setCanvasFixedSize(size) { canvasFixedSize = size.copy(); }
|
|
2614
2728
|
|
|
2615
2729
|
/** Use nearest scaling algorithm for canvas for more pixelated look
|
|
2616
2730
|
* - If enabled sets css image-rendering:pixelated
|
|
@@ -2675,7 +2789,7 @@ function setGLCircleSides(sides) { glCircleSides = sides; }
|
|
|
2675
2789
|
/** Set default size of tiles in pixels
|
|
2676
2790
|
* @param {Vector2} size
|
|
2677
2791
|
* @memberof Settings */
|
|
2678
|
-
function setTileSizeDefault(size) { tileSizeDefault = size; }
|
|
2792
|
+
function setTileSizeDefault(size) { tileSizeDefault = size.copy(); }
|
|
2679
2793
|
|
|
2680
2794
|
/** Set to prevent tile bleeding from neighbors in pixels
|
|
2681
2795
|
* @param {number} scale
|
|
@@ -2720,7 +2834,7 @@ function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
|
|
|
2720
2834
|
/** Set how much gravity to apply to objects
|
|
2721
2835
|
* @param {Vector2} newGravity
|
|
2722
2836
|
* @memberof Settings */
|
|
2723
|
-
function setGravity(newGravity) { gravity = newGravity; }
|
|
2837
|
+
function setGravity(newGravity) { gravity = newGravity.copy(); }
|
|
2724
2838
|
|
|
2725
2839
|
/** Set to scales emit rate of particles
|
|
2726
2840
|
* @param {number} scale
|
|
@@ -2810,7 +2924,7 @@ function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
|
|
|
2810
2924
|
/** Set size of medal display
|
|
2811
2925
|
* @param {Vector2} size
|
|
2812
2926
|
* @memberof Settings */
|
|
2813
|
-
function setMedalDisplaySize(size) { medalDisplaySize = size; }
|
|
2927
|
+
function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
|
|
2814
2928
|
|
|
2815
2929
|
/** Set to stop medals from being unlockable
|
|
2816
2930
|
* @param {boolean} preventUnlock
|
|
@@ -2850,6 +2964,7 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
2850
2964
|
* - Collision for objects can be set to be solid to block other objects
|
|
2851
2965
|
* - Objects may get pushed into overlapping other solid objects, if so they will push away
|
|
2852
2966
|
* - Solid objects are more performance intensive and should be used sparingly
|
|
2967
|
+
* @memberof Engine
|
|
2853
2968
|
* @example
|
|
2854
2969
|
* // create an engine object, normally you would first extend the class with your own
|
|
2855
2970
|
* const pos = vec2(2,3);
|
|
@@ -2858,18 +2973,18 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
2858
2973
|
class EngineObject
|
|
2859
2974
|
{
|
|
2860
2975
|
/** Create an engine object and adds it to the list of objects
|
|
2861
|
-
* @param {Vector2} [pos=(0,0)]
|
|
2862
|
-
* @param {Vector2} [size=(1,1)]
|
|
2863
|
-
* @param {TileInfo} [tileInfo]
|
|
2864
|
-
* @param {number} [angle]
|
|
2865
|
-
* @param {Color} [color=
|
|
2866
|
-
* @param {number} [renderOrder]
|
|
2976
|
+
* @param {Vector2} [pos=(0,0)] - World space position of the object
|
|
2977
|
+
* @param {Vector2} [size=(1,1)] - World space size of the object
|
|
2978
|
+
* @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
|
|
2979
|
+
* @param {number} [angle] - Angle the object is rotated by
|
|
2980
|
+
* @param {Color} [color=WHITE] - Color to apply to tile when rendered
|
|
2981
|
+
* @param {number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
2867
2982
|
*/
|
|
2868
|
-
constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=
|
|
2983
|
+
constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=WHITE, renderOrder=0)
|
|
2869
2984
|
{
|
|
2870
2985
|
// check passed in params
|
|
2871
|
-
ASSERT(isVector2(pos), 'object pos
|
|
2872
|
-
ASSERT(isVector2(size), 'object size
|
|
2986
|
+
ASSERT(isVector2(pos), 'object pos must be a vec2');
|
|
2987
|
+
ASSERT(isVector2(size), 'object size must be a vec2');
|
|
2873
2988
|
ASSERT(!tileInfo || tileInfo instanceof TileInfo, 'object tileInfo should be a TileInfo or undefined');
|
|
2874
2989
|
ASSERT(typeof angle === 'number' && isFinite(angle), 'object angle should be a number');
|
|
2875
2990
|
ASSERT(isColor(color), 'object color should be a valid rgba color');
|
|
@@ -3154,7 +3269,7 @@ class EngineObject
|
|
|
3154
3269
|
drawTile(this.pos, this.drawSize || this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
3155
3270
|
}
|
|
3156
3271
|
|
|
3157
|
-
/** Destroy this object, destroy its children, detach
|
|
3272
|
+
/** Destroy this object, destroy its children, detach its parent, and mark it for removal */
|
|
3158
3273
|
destroy()
|
|
3159
3274
|
{
|
|
3160
3275
|
if (this.destroyed)
|
|
@@ -3228,6 +3343,8 @@ class EngineObject
|
|
|
3228
3343
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
3229
3344
|
{
|
|
3230
3345
|
ASSERT(!child.parent && !this.children.includes(child));
|
|
3346
|
+
ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
|
|
3347
|
+
ASSERT(child !== this, 'cannot add self as child');
|
|
3231
3348
|
this.children.push(child);
|
|
3232
3349
|
child.parent = this;
|
|
3233
3350
|
child.localPos = localPos.copy();
|
|
@@ -3239,6 +3356,7 @@ class EngineObject
|
|
|
3239
3356
|
removeChild(child)
|
|
3240
3357
|
{
|
|
3241
3358
|
ASSERT(child.parent === this && this.children.includes(child));
|
|
3359
|
+
ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
|
|
3242
3360
|
this.children.splice(this.children.indexOf(child), 1);
|
|
3243
3361
|
child.parent = 0;
|
|
3244
3362
|
}
|
|
@@ -3377,7 +3495,7 @@ let drawCount;
|
|
|
3377
3495
|
* Create a tile info object using a grid based system
|
|
3378
3496
|
* - This can take vecs or floats for easier use and conversion
|
|
3379
3497
|
* - If an index is passed in, the tile size and index will determine the position
|
|
3380
|
-
* @param {Vector2|number} [pos=0] -
|
|
3498
|
+
* @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
|
|
3381
3499
|
* @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
|
|
3382
3500
|
* @param {number} [textureIndex] - Texture index to use
|
|
3383
3501
|
* @param {number} [padding] - How many pixels padding around tiles
|
|
@@ -3422,6 +3540,7 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
|
3422
3540
|
|
|
3423
3541
|
/**
|
|
3424
3542
|
* Tile Info - Stores info about how to draw a tile
|
|
3543
|
+
* @memberof Draw
|
|
3425
3544
|
*/
|
|
3426
3545
|
class TileInfo
|
|
3427
3546
|
{
|
|
@@ -3466,48 +3585,54 @@ class TileInfo
|
|
|
3466
3585
|
}
|
|
3467
3586
|
|
|
3468
3587
|
/**
|
|
3469
|
-
* Set this tile to use a full image
|
|
3470
|
-
* @param {
|
|
3471
|
-
* @param {WebGLTexture} [glTexture] - WebGL texture
|
|
3588
|
+
* Set this tile to use a full image in a texture info
|
|
3589
|
+
* @param {TextureInfo} textureInfo
|
|
3472
3590
|
* @return {TileInfo}
|
|
3473
3591
|
*/
|
|
3474
|
-
setFullImage(
|
|
3592
|
+
setFullImage(textureInfo)
|
|
3475
3593
|
{
|
|
3476
3594
|
this.pos = new Vector2;
|
|
3477
|
-
this.size =
|
|
3478
|
-
this.textureInfo =
|
|
3595
|
+
this.size = textureInfo.size.copy();
|
|
3596
|
+
this.textureInfo = textureInfo;
|
|
3479
3597
|
// do not use padding or bleed
|
|
3480
3598
|
this.bleedScale = this.padding = 0;
|
|
3481
3599
|
return this;
|
|
3482
3600
|
}
|
|
3483
3601
|
}
|
|
3484
3602
|
|
|
3485
|
-
/**
|
|
3603
|
+
/**
|
|
3604
|
+
* Tile Info - Stores info about each texture
|
|
3605
|
+
* @memberof Draw
|
|
3606
|
+
*/
|
|
3486
3607
|
class TextureInfo
|
|
3487
3608
|
{
|
|
3488
3609
|
/**
|
|
3489
3610
|
* Create a TextureInfo, called automatically by the engine
|
|
3490
3611
|
* @param {HTMLImageElement|OffscreenCanvas} image
|
|
3491
|
-
* @param {
|
|
3612
|
+
* @param {boolean} [useWebGL] - Should use WebGL if available?
|
|
3492
3613
|
*/
|
|
3493
|
-
constructor(image,
|
|
3614
|
+
constructor(image, useWebGL=true)
|
|
3494
3615
|
{
|
|
3495
|
-
/** @property {HTMLImageElement} - image source */
|
|
3616
|
+
/** @property {HTMLImageElement|OffscreenCanvas} - image source */
|
|
3496
3617
|
this.image = image;
|
|
3497
3618
|
/** @property {Vector2} - size of the image */
|
|
3498
|
-
this.size = vec2(image.width, image.height);
|
|
3619
|
+
this.size = image ? vec2(image.width, image.height) : vec2();
|
|
3499
3620
|
/** @property {Vector2} - inverse of the size, cached for rendering */
|
|
3500
|
-
this.sizeInverse = vec2(1/image.width, 1/image.height);
|
|
3621
|
+
this.sizeInverse = image ? vec2(1/image.width, 1/image.height) : vec2();
|
|
3501
3622
|
/** @property {WebGLTexture} - WebGL texture */
|
|
3502
|
-
this.glTexture =
|
|
3623
|
+
this.glTexture = undefined;
|
|
3624
|
+
useWebGL && this.createWebGLTexture();
|
|
3503
3625
|
}
|
|
3504
3626
|
|
|
3505
|
-
|
|
3506
|
-
{
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3627
|
+
/** Creates the WebGL texture, updates if already created */
|
|
3628
|
+
createWebGLTexture() { glRegisterTextureInfo(this); }
|
|
3629
|
+
|
|
3630
|
+
/** Destroys the WebGL texture */
|
|
3631
|
+
destroyWebGLTexture() { glUnregisterTextureInfo(this); }
|
|
3632
|
+
|
|
3633
|
+
/** Check if the texture is webgl enabled
|
|
3634
|
+
* @return {boolean} */
|
|
3635
|
+
hasWebGL() { return !!this.glTexture; }
|
|
3511
3636
|
}
|
|
3512
3637
|
|
|
3513
3638
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3528,10 +3653,11 @@ class TextureInfo
|
|
|
3528
3653
|
function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
3529
3654
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
3530
3655
|
{
|
|
3531
|
-
ASSERT(isVector2(pos), '
|
|
3532
|
-
ASSERT(isVector2(size), '
|
|
3533
|
-
ASSERT(isColor(color)
|
|
3534
|
-
ASSERT(isNumber(angle), '
|
|
3656
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3657
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
3658
|
+
ASSERT(isColor(color), 'color is invalid');
|
|
3659
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3660
|
+
ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
|
|
3535
3661
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3536
3662
|
|
|
3537
3663
|
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
@@ -3626,10 +3752,10 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
3626
3752
|
* @memberof Draw */
|
|
3627
3753
|
function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
3628
3754
|
{
|
|
3629
|
-
ASSERT(isVector2(pos), '
|
|
3630
|
-
ASSERT(isVector2(size), '
|
|
3631
|
-
ASSERT(isColor(colorTop) && isColor(colorBottom), '
|
|
3632
|
-
ASSERT(isNumber(angle), '
|
|
3755
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3756
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
3757
|
+
ASSERT(isColor(colorTop) && isColor(colorBottom), 'color is invalid');
|
|
3758
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3633
3759
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3634
3760
|
if (useWebGL)
|
|
3635
3761
|
{
|
|
@@ -3687,11 +3813,11 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
3687
3813
|
* @memberof Draw */
|
|
3688
3814
|
function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
|
|
3689
3815
|
{
|
|
3690
|
-
ASSERT(Array.isArray(points), '
|
|
3691
|
-
ASSERT(isNumber(width), '
|
|
3692
|
-
ASSERT(isColor(color), '
|
|
3693
|
-
ASSERT(isVector2(pos), '
|
|
3694
|
-
ASSERT(isNumber(angle), '
|
|
3816
|
+
ASSERT(Array.isArray(points), 'points must be an array');
|
|
3817
|
+
ASSERT(isNumber(width), 'width must be a number');
|
|
3818
|
+
ASSERT(isColor(color), 'color is invalid');
|
|
3819
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3820
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3695
3821
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3696
3822
|
if (useWebGL)
|
|
3697
3823
|
{
|
|
@@ -3762,8 +3888,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
3762
3888
|
* @memberof Draw */
|
|
3763
3889
|
function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
3764
3890
|
{
|
|
3765
|
-
ASSERT(isVector2(size), '
|
|
3766
|
-
ASSERT(isNumber(sides), '
|
|
3891
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
3892
|
+
ASSERT(isNumber(sides), 'sides must be a number');
|
|
3767
3893
|
|
|
3768
3894
|
// build regular polygon points
|
|
3769
3895
|
const points = [];
|
|
@@ -3789,12 +3915,13 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
3789
3915
|
* @memberof Draw */
|
|
3790
3916
|
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
3791
3917
|
{
|
|
3792
|
-
ASSERT(isVector2(pos), '
|
|
3793
|
-
ASSERT(Array.isArray(points), '
|
|
3794
|
-
ASSERT(isColor(color) && isColor(lineColor), '
|
|
3795
|
-
ASSERT(isNumber(lineWidth), '
|
|
3796
|
-
ASSERT(isNumber(angle), '
|
|
3918
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3919
|
+
ASSERT(Array.isArray(points), 'points must be an array');
|
|
3920
|
+
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
3921
|
+
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
3922
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3797
3923
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3924
|
+
|
|
3798
3925
|
if (useWebGL)
|
|
3799
3926
|
{
|
|
3800
3927
|
let scale = 1;
|
|
@@ -3841,13 +3968,14 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
3841
3968
|
* @memberof Draw */
|
|
3842
3969
|
function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
3843
3970
|
{
|
|
3844
|
-
ASSERT(isVector2(pos), '
|
|
3845
|
-
ASSERT(isVector2(size), '
|
|
3846
|
-
ASSERT(isColor(color) && isColor(lineColor), '
|
|
3847
|
-
ASSERT(isNumber(angle), '
|
|
3848
|
-
ASSERT(isNumber(lineWidth), '
|
|
3849
|
-
ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, '
|
|
3971
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3972
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
3973
|
+
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
3974
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3975
|
+
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
3976
|
+
ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'invalid lineWidth');
|
|
3850
3977
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
3978
|
+
|
|
3851
3979
|
if (useWebGL)
|
|
3852
3980
|
{
|
|
3853
3981
|
// draw as a regular polygon
|
|
@@ -3884,21 +4012,32 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
3884
4012
|
* @memberof Draw */
|
|
3885
4013
|
function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
3886
4014
|
{
|
|
3887
|
-
ASSERT(isNumber(size), '
|
|
4015
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
3888
4016
|
drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
|
|
3889
4017
|
}
|
|
3890
4018
|
|
|
4019
|
+
/**
|
|
4020
|
+
* @callback Canvas2DDrawFunction - A function that draws to a 2D canvas context
|
|
4021
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
4022
|
+
* @memberof Draw
|
|
4023
|
+
*/
|
|
4024
|
+
|
|
3891
4025
|
/** Draw directly to a 2d canvas context in world space
|
|
3892
4026
|
* @param {Vector2} pos
|
|
3893
4027
|
* @param {Vector2} size
|
|
3894
4028
|
* @param {number} angle
|
|
3895
4029
|
* @param {boolean} [mirror]
|
|
3896
|
-
* @param {
|
|
4030
|
+
* @param {Canvas2DDrawFunction} [drawFunction]
|
|
3897
4031
|
* @param {boolean} [screenSpace=false]
|
|
3898
4032
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3899
4033
|
* @memberof Draw */
|
|
3900
4034
|
function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
|
|
3901
4035
|
{
|
|
4036
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4037
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
4038
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
4039
|
+
ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
|
|
4040
|
+
|
|
3902
4041
|
if (!screenSpace)
|
|
3903
4042
|
{
|
|
3904
4043
|
// transform from world space to screen space
|
|
@@ -3926,12 +4065,13 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
3926
4065
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3927
4066
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
3928
4067
|
* @param {string} [font=fontDefault]
|
|
4068
|
+
* @param {string} [fontStyle]
|
|
3929
4069
|
* @param {number} [maxWidth]
|
|
3930
4070
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
3931
4071
|
* @memberof Draw */
|
|
3932
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth, context=drawContext)
|
|
4072
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
|
|
3933
4073
|
{
|
|
3934
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, maxWidth, context);
|
|
4074
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
|
|
3935
4075
|
}
|
|
3936
4076
|
|
|
3937
4077
|
/** Draw text on overlay canvas in world space
|
|
@@ -3944,11 +4084,12 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3944
4084
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3945
4085
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
3946
4086
|
* @param {string} [font=fontDefault]
|
|
4087
|
+
* @param {string} [fontStyle]
|
|
3947
4088
|
* @param {number} [maxWidth]
|
|
3948
4089
|
* @memberof Draw */
|
|
3949
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, maxWidth)
|
|
4090
|
+
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
|
|
3950
4091
|
{
|
|
3951
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, maxWidth, overlayContext);
|
|
4092
|
+
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
|
|
3952
4093
|
}
|
|
3953
4094
|
|
|
3954
4095
|
/** Draw text on overlay canvas in screen space
|
|
@@ -3961,16 +4102,27 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
3961
4102
|
* @param {Color} [lineColor=(0,0,0,1)]
|
|
3962
4103
|
* @param {CanvasTextAlign} [textAlign]
|
|
3963
4104
|
* @param {string} [font=fontDefault]
|
|
4105
|
+
* @param {string} [fontStyle]
|
|
3964
4106
|
* @param {number} [maxWidth]
|
|
3965
4107
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
3966
4108
|
* @memberof Draw */
|
|
3967
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
|
|
3968
|
-
{
|
|
4109
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
|
|
4110
|
+
{
|
|
4111
|
+
ASSERT(isString(text), 'text must be a string');
|
|
4112
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4113
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
4114
|
+
ASSERT(isColor(color), 'color must be a color');
|
|
4115
|
+
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
4116
|
+
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
4117
|
+
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
4118
|
+
ASSERT(isString(font), 'font must be a string');
|
|
4119
|
+
ASSERT(isString(fontStyle), 'fontStyle must be a string');
|
|
4120
|
+
|
|
3969
4121
|
context.fillStyle = color.toString();
|
|
3970
4122
|
context.strokeStyle = lineColor.toString();
|
|
3971
4123
|
context.lineWidth = lineWidth;
|
|
3972
4124
|
context.textAlign = textAlign;
|
|
3973
|
-
context.font = size + 'px '+ font;
|
|
4125
|
+
context.font = fontStyle + ' ' + size + 'px '+ font;
|
|
3974
4126
|
context.textBaseline = 'middle';
|
|
3975
4127
|
|
|
3976
4128
|
const lines = (text+'').split('\n');
|
|
@@ -4167,6 +4319,7 @@ let engineFontImage;
|
|
|
4167
4319
|
* - 96 characters (from space to tilde) are stored in an image
|
|
4168
4320
|
* - Uses a default 8x8 font if none is supplied
|
|
4169
4321
|
* - You can also use fonts from the main tile sheet
|
|
4322
|
+
* @memberof Draw
|
|
4170
4323
|
* @example
|
|
4171
4324
|
* // use built in font
|
|
4172
4325
|
* const font = new FontImage;
|
|
@@ -4177,11 +4330,11 @@ let engineFontImage;
|
|
|
4177
4330
|
class FontImage
|
|
4178
4331
|
{
|
|
4179
4332
|
/** Create an image font
|
|
4180
|
-
* @param {HTMLImageElement} [image]
|
|
4181
|
-
* @param {Vector2} [tileSize=(8,8)]
|
|
4182
|
-
* @param {Vector2} [paddingSize=(0,1)] - How much
|
|
4333
|
+
* @param {HTMLImageElement} [image] - Image for the font, default if undefined
|
|
4334
|
+
* @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
|
|
4335
|
+
* @param {Vector2} [paddingSize=(0,1)] - How much space between characters
|
|
4183
4336
|
*/
|
|
4184
|
-
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1)
|
|
4337
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1))
|
|
4185
4338
|
{
|
|
4186
4339
|
// load default font image
|
|
4187
4340
|
if (!engineFontImage)
|
|
@@ -4223,7 +4376,7 @@ class FontImage
|
|
|
4223
4376
|
* @param {boolean} [center]
|
|
4224
4377
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
4225
4378
|
*/
|
|
4226
|
-
drawTextScreen(text, pos, scale=4, center, context=overlayContext)
|
|
4379
|
+
drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
|
|
4227
4380
|
{
|
|
4228
4381
|
context.save();
|
|
4229
4382
|
const size = this.tileSize;
|
|
@@ -4917,6 +5070,7 @@ function audioInit()
|
|
|
4917
5070
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
4918
5071
|
*
|
|
4919
5072
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
5073
|
+
* @memberof Audio
|
|
4920
5074
|
* @example
|
|
4921
5075
|
* // create a sound
|
|
4922
5076
|
* const sound_example = new Sound([.5,.5]);
|
|
@@ -5011,12 +5165,12 @@ class Sound
|
|
|
5011
5165
|
|
|
5012
5166
|
/** Play the sound as a musical note with a semitone offset
|
|
5013
5167
|
* This can be used to play music with chromatic scales
|
|
5014
|
-
* @param {number} semitoneOffset - How many semitones to offset pitch
|
|
5168
|
+
* @param {number} [semitoneOffset=0] - How many semitones to offset pitch
|
|
5015
5169
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
5016
5170
|
* @param {number} [volume=1] - How much to scale volume by
|
|
5017
5171
|
* @return {SoundInstance} - The audio source node
|
|
5018
5172
|
*/
|
|
5019
|
-
playNote(semitoneOffset, pos, volume)
|
|
5173
|
+
playNote(semitoneOffset=0, pos, volume)
|
|
5020
5174
|
{
|
|
5021
5175
|
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
5022
5176
|
return this.play(pos, volume, pitch, 0);
|
|
@@ -5039,6 +5193,8 @@ class Sound
|
|
|
5039
5193
|
/**
|
|
5040
5194
|
* Sound Wave Object - Stores a wave sound for later use and can be played positionally
|
|
5041
5195
|
* - this can be used to play wave, mp3, and ogg files
|
|
5196
|
+
* @extends Sound
|
|
5197
|
+
* @memberof Audio
|
|
5042
5198
|
* @example
|
|
5043
5199
|
* // create a sound
|
|
5044
5200
|
* const sound_example = new SoundWave('sound.mp3');
|
|
@@ -5048,22 +5204,29 @@ class Sound
|
|
|
5048
5204
|
*/
|
|
5049
5205
|
class SoundWave extends Sound
|
|
5050
5206
|
{
|
|
5207
|
+
/**
|
|
5208
|
+
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
5209
|
+
* @param {SoundWave} sound
|
|
5210
|
+
* @memberof Audio
|
|
5211
|
+
*/
|
|
5212
|
+
|
|
5051
5213
|
/** Create a sound object and cache the wave file for later use
|
|
5052
5214
|
* @param {string} filename - Filename of audio file to load
|
|
5053
5215
|
* @param {number} [randomness] - How much to randomize frequency each time sound plays
|
|
5054
5216
|
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
5055
5217
|
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
5056
|
-
* @param {
|
|
5218
|
+
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
5057
5219
|
*/
|
|
5058
5220
|
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
5059
5221
|
{
|
|
5060
5222
|
super(undefined, range, taper);
|
|
5061
5223
|
if (!soundEnable || headlessMode) return;
|
|
5224
|
+
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
5062
5225
|
|
|
5063
|
-
/** @property {
|
|
5226
|
+
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
5064
5227
|
this.onloadCallback = onloadCallback;
|
|
5065
5228
|
this.randomness = randomness;
|
|
5066
|
-
this.loadSound(filename);
|
|
5229
|
+
filename && this.loadSound(filename);
|
|
5067
5230
|
}
|
|
5068
5231
|
|
|
5069
5232
|
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
@@ -5072,6 +5235,8 @@ class SoundWave extends Sound
|
|
|
5072
5235
|
async loadSound(filename)
|
|
5073
5236
|
{
|
|
5074
5237
|
const response = await fetch(filename);
|
|
5238
|
+
if (!response.ok)
|
|
5239
|
+
throw new Error(`Failed to load sound from ${filename}: ${response.status} ${response.statusText}`);
|
|
5075
5240
|
const arrayBuffer = await response.arrayBuffer();
|
|
5076
5241
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
5077
5242
|
|
|
@@ -5107,7 +5272,7 @@ class SoundWave extends Sound
|
|
|
5107
5272
|
this.sampleChannels = sampleChannels;
|
|
5108
5273
|
this.loadedPercent = 1;
|
|
5109
5274
|
if (this.onloadCallback)
|
|
5110
|
-
this.onloadCallback();
|
|
5275
|
+
this.onloadCallback(this);
|
|
5111
5276
|
}
|
|
5112
5277
|
}
|
|
5113
5278
|
|
|
@@ -5116,6 +5281,7 @@ class SoundWave extends Sound
|
|
|
5116
5281
|
/**
|
|
5117
5282
|
* Sound Instance - Wraps an AudioBufferSourceNode for individual sound control
|
|
5118
5283
|
* Represents a single playing instance of a sound with pause/resume capabilities
|
|
5284
|
+
* @memberof Audio
|
|
5119
5285
|
* @example
|
|
5120
5286
|
* // Play a sound and get an instance for control
|
|
5121
5287
|
* const jumpSound = new Sound([.5,.5,220]);
|
|
@@ -5181,8 +5347,16 @@ class SoundInstance
|
|
|
5181
5347
|
this.stop();
|
|
5182
5348
|
this.gainNode = audioContext.createGain();
|
|
5183
5349
|
this.source = playSamples(this.sound.sampleChannels, this.volume, this.rate, this.pan, this.loop, this.sound.sampleRate, this.gainNode, offset, this.onendedCallback);
|
|
5184
|
-
this.
|
|
5185
|
-
|
|
5350
|
+
if (this.source)
|
|
5351
|
+
{
|
|
5352
|
+
this.startTime = audioContext.currentTime - offset;
|
|
5353
|
+
this.pausedTime = undefined;
|
|
5354
|
+
}
|
|
5355
|
+
else
|
|
5356
|
+
{
|
|
5357
|
+
this.startTime = undefined;
|
|
5358
|
+
this.pausedTime = 0;
|
|
5359
|
+
}
|
|
5186
5360
|
}
|
|
5187
5361
|
|
|
5188
5362
|
/** Set the volume of this sound instance
|
|
@@ -5315,6 +5489,12 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
5315
5489
|
|
|
5316
5490
|
///////////////////////////////////////////////////////////////////////////////
|
|
5317
5491
|
|
|
5492
|
+
/**
|
|
5493
|
+
* @callback AudioEndedCallback - Function called when a sound ends
|
|
5494
|
+
* @param {AudioBufferSourceNode} source
|
|
5495
|
+
* @memberof Audio
|
|
5496
|
+
*/
|
|
5497
|
+
|
|
5318
5498
|
/** Play cached audio samples with given settings
|
|
5319
5499
|
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
5320
5500
|
* @param {number} [volume] - How much to scale volume by
|
|
@@ -5324,13 +5504,20 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
5324
5504
|
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
5325
5505
|
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
5326
5506
|
* @param {number} [offset] - Offset in seconds to start playback from
|
|
5327
|
-
* @param {
|
|
5328
|
-
* @return {AudioBufferSourceNode} - The
|
|
5507
|
+
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
5508
|
+
* @return {AudioBufferSourceNode} - The source node of the sound played, may be undefined if play fails
|
|
5329
5509
|
* @memberof Audio */
|
|
5330
5510
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
|
|
5331
5511
|
{
|
|
5332
5512
|
if (!soundEnable || headlessMode) return;
|
|
5333
5513
|
|
|
5514
|
+
if (!audioIsRunning())
|
|
5515
|
+
{
|
|
5516
|
+
// fix stalled audio, this sound won't be able to play
|
|
5517
|
+
audioContext.resume();
|
|
5518
|
+
return;
|
|
5519
|
+
}
|
|
5520
|
+
|
|
5334
5521
|
// create buffer and source
|
|
5335
5522
|
const channelCount = sampleChannels.length;
|
|
5336
5523
|
const sampleLength = sampleChannels[0].length;
|
|
@@ -5356,13 +5543,6 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
5356
5543
|
if (onended)
|
|
5357
5544
|
source.addEventListener('ended', ()=> onended(source));
|
|
5358
5545
|
|
|
5359
|
-
if (!audioIsRunning())
|
|
5360
|
-
{
|
|
5361
|
-
// fix stalled audio, this sound won't be able to play
|
|
5362
|
-
audioContext.resume();
|
|
5363
|
-
return;
|
|
5364
|
-
}
|
|
5365
|
-
|
|
5366
5546
|
// play and return sound
|
|
5367
5547
|
const startOffset = offset * rate;
|
|
5368
5548
|
source.start(0, startOffset);
|
|
@@ -5530,7 +5710,7 @@ function zzfxG
|
|
|
5530
5710
|
* - Unlimited numbers of layers, allocates canvases as needed
|
|
5531
5711
|
* - Tile layers can be drawn to using their context with canvas2d
|
|
5532
5712
|
* - Tile layers can also have collision with EngineObjects
|
|
5533
|
-
* @namespace
|
|
5713
|
+
* @namespace TileLayers
|
|
5534
5714
|
*/
|
|
5535
5715
|
|
|
5536
5716
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -5538,13 +5718,13 @@ function zzfxG
|
|
|
5538
5718
|
|
|
5539
5719
|
/** Keep track of all tile layers with collision
|
|
5540
5720
|
* @type {Array<TileCollisionLayer>}
|
|
5541
|
-
* @memberof
|
|
5721
|
+
* @memberof TileLayers */
|
|
5542
5722
|
const tileCollisionLayers = [];
|
|
5543
5723
|
|
|
5544
5724
|
/** Get tile collision data for a given cell in the grid
|
|
5545
5725
|
* @param {Vector2} pos
|
|
5546
5726
|
* @return {number}
|
|
5547
|
-
* @memberof
|
|
5727
|
+
* @memberof TileLayers */
|
|
5548
5728
|
function tileCollisionGetData(pos)
|
|
5549
5729
|
{
|
|
5550
5730
|
// check all tile collision layers
|
|
@@ -5560,7 +5740,7 @@ function tileCollisionGetData(pos)
|
|
|
5560
5740
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
5561
5741
|
* @param {boolean} [solidOnly] - Only check solid layers if true
|
|
5562
5742
|
* @return {TileCollisionLayer}
|
|
5563
|
-
* @memberof
|
|
5743
|
+
* @memberof TileLayers */
|
|
5564
5744
|
function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
5565
5745
|
{
|
|
5566
5746
|
for (const layer of tileCollisionLayers)
|
|
@@ -5578,7 +5758,7 @@ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
|
|
|
5578
5758
|
* @param {EngineObject} [object] - An object or undefined for generic test
|
|
5579
5759
|
* @param {boolean} [solidOnly=true] - Only check solid layers if true
|
|
5580
5760
|
* @return {Vector2}
|
|
5581
|
-
* @memberof
|
|
5761
|
+
* @memberof TileLayers */
|
|
5582
5762
|
function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
5583
5763
|
{
|
|
5584
5764
|
for (const layer of tileCollisionLayers)
|
|
@@ -5601,8 +5781,8 @@ function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
|
|
|
5601
5781
|
* @param {number} [collisionLayer] - Layer to use for collision if any
|
|
5602
5782
|
* @param {boolean} [draw] - Should the layer be drawn automatically
|
|
5603
5783
|
* @return {Array<TileCollisionLayer>}
|
|
5604
|
-
* @memberof
|
|
5605
|
-
function
|
|
5784
|
+
* @memberof TileLayers */
|
|
5785
|
+
function tileLayersLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true)
|
|
5606
5786
|
{
|
|
5607
5787
|
if (!tileMapData)
|
|
5608
5788
|
{
|
|
@@ -5633,9 +5813,9 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
|
|
|
5633
5813
|
tileLayers[layerIndex] = tileLayer;
|
|
5634
5814
|
|
|
5635
5815
|
// apply layer color
|
|
5636
|
-
const layerColor = dataLayer.
|
|
5637
|
-
|
|
5638
|
-
|
|
5816
|
+
const layerColor = dataLayer.tintcolor ?
|
|
5817
|
+
new Color().setHex(dataLayer.tintcolor) :
|
|
5818
|
+
dataLayer.color || WHITE;
|
|
5639
5819
|
ASSERT(isColor(layerColor), 'layer color is not a color');
|
|
5640
5820
|
|
|
5641
5821
|
for (let x=levelSize.x; x--;)
|
|
@@ -5662,6 +5842,7 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
|
|
|
5662
5842
|
///////////////////////////////////////////////////////////////////////////////
|
|
5663
5843
|
/**
|
|
5664
5844
|
* Tile layer data object stores info about how to draw a tile
|
|
5845
|
+
* @memberof TileLayers
|
|
5665
5846
|
* @example
|
|
5666
5847
|
* // create tile layer data with tile index 0 and random orientation and color
|
|
5667
5848
|
* const tileIndex = 0;
|
|
@@ -5699,6 +5880,7 @@ class TileLayerData
|
|
|
5699
5880
|
* - Contains an offscreen canvas that can be rendered to
|
|
5700
5881
|
* - WebGL rendering is optional, call useWebGL to enable
|
|
5701
5882
|
* @extends EngineObject
|
|
5883
|
+
* @memberof TileLayers
|
|
5702
5884
|
* @example
|
|
5703
5885
|
* const canvasLayer = new CanvasLayer(vec2(), vec2(200,100));
|
|
5704
5886
|
*/
|
|
@@ -5713,15 +5895,19 @@ class CanvasLayer extends EngineObject
|
|
|
5713
5895
|
*/
|
|
5714
5896
|
constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
|
|
5715
5897
|
{
|
|
5898
|
+
ASSERT(isVector2(canvasSize), 'canvasSize must be a Vector2');
|
|
5716
5899
|
super(position, size, undefined, angle, WHITE, renderOrder);
|
|
5717
5900
|
|
|
5718
5901
|
/** @property {HTMLCanvasElement} - The canvas used by this layer */
|
|
5719
5902
|
this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
|
|
5720
5903
|
/** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
|
|
5721
|
-
this.context =
|
|
5722
|
-
/** @property {
|
|
5723
|
-
|
|
5724
|
-
this.
|
|
5904
|
+
this.context = this.canvas?.getContext('2d');
|
|
5905
|
+
/** @property {TextureInfo} - Texture info to use for this object rendering */
|
|
5906
|
+
const useWebGL = false; // do not use webgl by default
|
|
5907
|
+
this.textureInfo = new TextureInfo(this.canvas, useWebGL);
|
|
5908
|
+
|
|
5909
|
+
// disable physics by default
|
|
5910
|
+
this.mass = this.gravityScale = this.friction = this.restitution = 0;
|
|
5725
5911
|
}
|
|
5726
5912
|
|
|
5727
5913
|
/** Destroy this canvas layer */
|
|
@@ -5730,9 +5916,7 @@ class CanvasLayer extends EngineObject
|
|
|
5730
5916
|
if (this.destroyed)
|
|
5731
5917
|
return;
|
|
5732
5918
|
|
|
5733
|
-
|
|
5734
|
-
if (this.glTexture)
|
|
5735
|
-
glDeleteTexture(this.glTexture);
|
|
5919
|
+
this.textureInfo.destroyWebGLTexture();
|
|
5736
5920
|
super.destroy();
|
|
5737
5921
|
}
|
|
5738
5922
|
|
|
@@ -5755,19 +5939,27 @@ class CanvasLayer extends EngineObject
|
|
|
5755
5939
|
draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
|
|
5756
5940
|
{
|
|
5757
5941
|
// draw the canvas layer as a single tile that uses the whole texture
|
|
5758
|
-
const useWebGL = glEnable && this.
|
|
5759
|
-
const tileInfo = new TileInfo().setFullImage(this.
|
|
5942
|
+
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
5943
|
+
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
5760
5944
|
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebGL, screenSpace, context);
|
|
5761
5945
|
}
|
|
5762
5946
|
|
|
5947
|
+
/**
|
|
5948
|
+
* @callback Canvas2DDrawCallback - Function that draws to a canvas 2D context
|
|
5949
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
5950
|
+
* @memberof TileLayers
|
|
5951
|
+
*/
|
|
5952
|
+
|
|
5763
5953
|
/** Draw onto the layer canvas in world space (bypass WebGL)
|
|
5764
5954
|
* @param {Vector2} pos
|
|
5765
5955
|
* @param {Vector2} size
|
|
5766
5956
|
* @param {number} angle
|
|
5767
5957
|
* @param {boolean} mirror
|
|
5768
|
-
* @param {
|
|
5958
|
+
* @param {Canvas2DDrawCallback} drawFunction */
|
|
5769
5959
|
drawCanvas2D(pos, size, angle, mirror, drawFunction)
|
|
5770
5960
|
{
|
|
5961
|
+
if (!this.context) return;
|
|
5962
|
+
|
|
5771
5963
|
const context = this.context;
|
|
5772
5964
|
context.save();
|
|
5773
5965
|
pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
|
|
@@ -5802,7 +5994,7 @@ class CanvasLayer extends EngineObject
|
|
|
5802
5994
|
else
|
|
5803
5995
|
{
|
|
5804
5996
|
// untextured
|
|
5805
|
-
context.fillStyle = color;
|
|
5997
|
+
context.fillStyle = color.toString();
|
|
5806
5998
|
context.fillRect(-.5, -.5, 1, 1);
|
|
5807
5999
|
}
|
|
5808
6000
|
});
|
|
@@ -5820,15 +6012,10 @@ class CanvasLayer extends EngineObject
|
|
|
5820
6012
|
* @param {boolean} [enable] - enable WebGL rendering and update the texture */
|
|
5821
6013
|
useWebGL(enable=true)
|
|
5822
6014
|
{
|
|
5823
|
-
if (
|
|
5824
|
-
|
|
5825
|
-
if (this.glTexture)
|
|
5826
|
-
glSetTextureData(this.glTexture, this.canvas);
|
|
5827
|
-
else
|
|
5828
|
-
this.glTexture = glCreateTexture(this.canvas);
|
|
5829
|
-
}
|
|
6015
|
+
if (enable)
|
|
6016
|
+
this.textureInfo.createWebGLTexture();
|
|
5830
6017
|
else
|
|
5831
|
-
this.
|
|
6018
|
+
this.textureInfo.destroyWebGLTexture();
|
|
5832
6019
|
}
|
|
5833
6020
|
}
|
|
5834
6021
|
|
|
@@ -5839,7 +6026,9 @@ class CanvasLayer extends EngineObject
|
|
|
5839
6026
|
* - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
5840
6027
|
* - Some devices like mobile phones are limited to 4k texture resolution
|
|
5841
6028
|
* - For with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
6029
|
+
* - Tile layers are centered on their corner, so normal levels are at (0,0)
|
|
5842
6030
|
* @extends CanvasLayer
|
|
6031
|
+
* @memberof TileLayers
|
|
5843
6032
|
* @example
|
|
5844
6033
|
* const tileLayer = new TileLayer(vec2(), vec2(200,100));
|
|
5845
6034
|
*/
|
|
@@ -5849,26 +6038,15 @@ class TileLayer extends CanvasLayer
|
|
|
5849
6038
|
* @param {Vector2} position - World space position
|
|
5850
6039
|
* @param {Vector2} size - World space size
|
|
5851
6040
|
* @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
|
|
5852
|
-
* @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
|
|
5853
6041
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
5854
|
-
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
5855
6042
|
*/
|
|
5856
|
-
constructor(position, size, tileInfo=tile(),
|
|
6043
|
+
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
5857
6044
|
{
|
|
5858
|
-
super(position, size, 0, renderOrder, size);
|
|
5859
|
-
this.tileInfo = tileInfo;
|
|
5860
|
-
|
|
5861
6045
|
const canvasSize = size.multiply(tileInfo.size);
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
this.
|
|
5866
|
-
/** @property {WebGLTexture} - Texture if using WebGL for this layer */
|
|
5867
|
-
this.glTexture = useWebGL ? glCreateTexture(this.canvas) : undefined;
|
|
5868
|
-
// set no friction by default, applied friction is max of both objects
|
|
5869
|
-
this.friction = 0;
|
|
5870
|
-
// set no restitution by default, applied restitution is max of both objects
|
|
5871
|
-
this.restitution = 0;
|
|
6046
|
+
super(position, size, 0, renderOrder, canvasSize);
|
|
6047
|
+
|
|
6048
|
+
// set tile info
|
|
6049
|
+
this.tileInfo = tileInfo;
|
|
5872
6050
|
|
|
5873
6051
|
// init tile data
|
|
5874
6052
|
this.data = [];
|
|
@@ -5894,6 +6072,8 @@ class TileLayer extends CanvasLayer
|
|
|
5894
6072
|
* @param {boolean} [redraw] - Force the tile to redraw if true */
|
|
5895
6073
|
setData(layerPos, data, redraw=false)
|
|
5896
6074
|
{
|
|
6075
|
+
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6076
|
+
ASSERT(data instanceof TileLayerData, 'data must be a TileLayerData');
|
|
5897
6077
|
if (layerPos.arrayCheck(this.size))
|
|
5898
6078
|
{
|
|
5899
6079
|
this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
|
|
@@ -5905,7 +6085,10 @@ class TileLayer extends CanvasLayer
|
|
|
5905
6085
|
* @param {Vector2} layerPos - Local position in array
|
|
5906
6086
|
* @return {TileLayerData} */
|
|
5907
6087
|
getData(layerPos)
|
|
5908
|
-
{
|
|
6088
|
+
{
|
|
6089
|
+
ASSERT(isVector2(layerPos), 'layerPos must be a Vector2');
|
|
6090
|
+
return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0];
|
|
6091
|
+
}
|
|
5909
6092
|
|
|
5910
6093
|
// Render the tile layer, called automatically by the engine
|
|
5911
6094
|
render()
|
|
@@ -5913,10 +6096,11 @@ class TileLayer extends CanvasLayer
|
|
|
5913
6096
|
ASSERT(drawContext !== this.context, 'must call redrawEnd() after drawing tiles!');
|
|
5914
6097
|
|
|
5915
6098
|
// draw the tile layer as a single tile
|
|
5916
|
-
const tileInfo = new TileInfo().setFullImage(this.
|
|
5917
|
-
const
|
|
5918
|
-
const
|
|
5919
|
-
|
|
6099
|
+
const tileInfo = new TileInfo().setFullImage(this.textureInfo);
|
|
6100
|
+
const size = this.drawSize || this.size;
|
|
6101
|
+
const pos = this.pos.add(size.scale(.5));
|
|
6102
|
+
const useWebGL = glEnable && this.textureInfo.hasWebGL();
|
|
6103
|
+
drawTile(pos, size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebGL);
|
|
5920
6104
|
}
|
|
5921
6105
|
|
|
5922
6106
|
/** Draw all the tile data to an offscreen canvas
|
|
@@ -5928,8 +6112,7 @@ class TileLayer extends CanvasLayer
|
|
|
5928
6112
|
for (let y = this.size.y; y--;)
|
|
5929
6113
|
this.drawTileData(vec2(x,y), false);
|
|
5930
6114
|
this.redrawEnd();
|
|
5931
|
-
|
|
5932
|
-
this.useWebGL(); // update WebGL texture
|
|
6115
|
+
this.useWebGL();
|
|
5933
6116
|
}
|
|
5934
6117
|
|
|
5935
6118
|
/** Call to start the redraw process
|
|
@@ -5937,6 +6120,8 @@ class TileLayer extends CanvasLayer
|
|
|
5937
6120
|
* @param {boolean} [clear] - Should it clear the canvas before drawing */
|
|
5938
6121
|
redrawStart(clear=false)
|
|
5939
6122
|
{
|
|
6123
|
+
if (!this.context) return;
|
|
6124
|
+
|
|
5940
6125
|
// save current render settings
|
|
5941
6126
|
/** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number]} */
|
|
5942
6127
|
this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale];
|
|
@@ -5965,6 +6150,8 @@ class TileLayer extends CanvasLayer
|
|
|
5965
6150
|
/** Call to end the redraw process */
|
|
5966
6151
|
redrawEnd()
|
|
5967
6152
|
{
|
|
6153
|
+
if (!this.context) return;
|
|
6154
|
+
|
|
5968
6155
|
ASSERT(drawContext === this.context, 'must call redrawStart() before drawing tiles');
|
|
5969
6156
|
glCopyToContext(drawContext);
|
|
5970
6157
|
//debugSaveCanvas(this.canvas);
|
|
@@ -5981,6 +6168,8 @@ class TileLayer extends CanvasLayer
|
|
|
5981
6168
|
*/
|
|
5982
6169
|
drawTileData(layerPos, clear=true)
|
|
5983
6170
|
{
|
|
6171
|
+
if (!this.context) return;
|
|
6172
|
+
|
|
5984
6173
|
// clear out where the tile was, for full opaque tiles this can be skipped
|
|
5985
6174
|
const s = this.tileInfo.size;
|
|
5986
6175
|
if (clear)
|
|
@@ -6008,6 +6197,7 @@ class TileLayer extends CanvasLayer
|
|
|
6008
6197
|
* - there can be multiple tile collision layers
|
|
6009
6198
|
* - tile collision layers should not overlap each other
|
|
6010
6199
|
* @extends TileLayer
|
|
6200
|
+
* @memberof TileLayers
|
|
6011
6201
|
*/
|
|
6012
6202
|
class TileCollisionLayer extends TileLayer
|
|
6013
6203
|
{
|
|
@@ -6016,12 +6206,10 @@ class TileCollisionLayer extends TileLayer
|
|
|
6016
6206
|
* @param {Vector2} size - World space size
|
|
6017
6207
|
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
6018
6208
|
* @param {number} [renderOrder] - Objects are sorted by renderOrder
|
|
6019
|
-
* @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
6020
6209
|
*/
|
|
6021
|
-
constructor(position, size, tileInfo=tile(), renderOrder=0
|
|
6210
|
+
constructor(position, size, tileInfo=tile(), renderOrder=0)
|
|
6022
6211
|
{
|
|
6023
|
-
|
|
6024
|
-
super(position, size.floor(), tileInfo, scale, renderOrder, useWebGL);
|
|
6212
|
+
super(position, size.floor(), tileInfo, renderOrder);
|
|
6025
6213
|
|
|
6026
6214
|
/** @property {Array<number>} - The tile collision grid */
|
|
6027
6215
|
this.collisionData = [];
|
|
@@ -6051,6 +6239,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6051
6239
|
* @param {Vector2} size - width and height of tile collision 2d grid */
|
|
6052
6240
|
initCollision(size)
|
|
6053
6241
|
{
|
|
6242
|
+
ASSERT(isVector2(size), 'size must be a Vector2');
|
|
6054
6243
|
this.size = size.floor();
|
|
6055
6244
|
this.collisionData = [];
|
|
6056
6245
|
this.collisionData.length = size.area();
|
|
@@ -6062,6 +6251,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6062
6251
|
* @param {number} [data] */
|
|
6063
6252
|
setCollisionData(gridPos, data=1)
|
|
6064
6253
|
{
|
|
6254
|
+
ASSERT(isVector2(gridPos), 'gridPos must be a Vector2');
|
|
6065
6255
|
const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
|
|
6066
6256
|
gridPos.arrayCheck(this.size) && (this.collisionData[i] = data);
|
|
6067
6257
|
}
|
|
@@ -6071,6 +6261,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6071
6261
|
* @return {number} */
|
|
6072
6262
|
getCollisionData(gridPos)
|
|
6073
6263
|
{
|
|
6264
|
+
ASSERT(isVector2(gridPos), 'gridPos must be a Vector2');
|
|
6074
6265
|
const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
|
|
6075
6266
|
return gridPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
|
|
6076
6267
|
}
|
|
@@ -6082,6 +6273,9 @@ class TileCollisionLayer extends TileLayer
|
|
|
6082
6273
|
* @return {boolean} */
|
|
6083
6274
|
collisionTest(pos, size=new Vector2, object)
|
|
6084
6275
|
{
|
|
6276
|
+
ASSERT(isVector2(pos) && isVector2(size), 'pos and size must be Vector2s');
|
|
6277
|
+
ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
|
|
6278
|
+
|
|
6085
6279
|
// transform to local layer space
|
|
6086
6280
|
const posX = pos.x - this.pos.x;
|
|
6087
6281
|
const posY = pos.y - this.pos.y;
|
|
@@ -6112,6 +6306,9 @@ class TileCollisionLayer extends TileLayer
|
|
|
6112
6306
|
* @return {Vector2} */
|
|
6113
6307
|
collisionRaycast(posStart, posEnd, object)
|
|
6114
6308
|
{
|
|
6309
|
+
ASSERT(isVector2(posStart) && isVector2(posEnd), 'positions must be Vector2s');
|
|
6310
|
+
ASSERT(!object || object instanceof EngineObject, 'object must be an EngineObject');
|
|
6311
|
+
|
|
6115
6312
|
// transform to local layer space
|
|
6116
6313
|
const posStartX = posStart.x - this.pos.x;
|
|
6117
6314
|
const posStartY = posStart.y - this.pos.y;
|
|
@@ -6161,9 +6358,16 @@ class TileCollisionLayer extends TileLayer
|
|
|
6161
6358
|
* LittleJS Particle System
|
|
6162
6359
|
*/
|
|
6163
6360
|
|
|
6361
|
+
/**
|
|
6362
|
+
* @callback ParticleCallbackFunction - Function that processes a particle
|
|
6363
|
+
* @param {Particle} particle
|
|
6364
|
+
* @memberof Engine
|
|
6365
|
+
*/
|
|
6366
|
+
|
|
6164
6367
|
/**
|
|
6165
6368
|
* Particle Emitter - Spawns particles with the given settings
|
|
6166
6369
|
* @extends EngineObject
|
|
6370
|
+
* @memberof Engine
|
|
6167
6371
|
* @example
|
|
6168
6372
|
* // create a particle emitter
|
|
6169
6373
|
* let pos = vec2(2,3);
|
|
@@ -6173,7 +6377,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
6173
6377
|
* tile(0, 16), // tileInfo
|
|
6174
6378
|
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
6175
6379
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
6176
|
-
*
|
|
6380
|
+
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
6177
6381
|
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
6178
6382
|
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
6179
6383
|
* );
|
|
@@ -6188,10 +6392,10 @@ class ParticleEmitter extends EngineObject
|
|
|
6188
6392
|
* @param {number} [emitRate] - How many particles per second to spawn, does not emit if 0
|
|
6189
6393
|
* @param {number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
6190
6394
|
* @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
|
|
6191
|
-
* @param {Color} [colorStartA=
|
|
6192
|
-
* @param {Color} [colorStartB=
|
|
6193
|
-
* @param {Color} [colorEndA=
|
|
6194
|
-
* @param {Color} [colorEndB=
|
|
6395
|
+
* @param {Color} [colorStartA=WHITE] - Color at start of life 1, randomized between start colors
|
|
6396
|
+
* @param {Color} [colorStartB=WHITE] - Color at start of life 2, randomized between start colors
|
|
6397
|
+
* @param {Color} [colorEndA=CLEAR_WHITE] - Color at end of life 1, randomized between end colors
|
|
6398
|
+
* @param {Color} [colorEndB=CLEAR_WHITE] - Color at end of life 2, randomized between end colors
|
|
6195
6399
|
* @param {number} [particleTime] - How long particles live
|
|
6196
6400
|
* @param {number} [sizeStart] - How big are particles at start
|
|
6197
6401
|
* @param {number} [sizeEnd] - How big are particles at end
|
|
@@ -6218,10 +6422,10 @@ class ParticleEmitter extends EngineObject
|
|
|
6218
6422
|
emitRate = 100,
|
|
6219
6423
|
emitConeAngle = PI,
|
|
6220
6424
|
tileInfo,
|
|
6221
|
-
colorStartA =
|
|
6222
|
-
colorStartB =
|
|
6223
|
-
colorEndA =
|
|
6224
|
-
colorEndB =
|
|
6425
|
+
colorStartA = WHITE,
|
|
6426
|
+
colorStartB = WHITE,
|
|
6427
|
+
colorEndA = CLEAR_WHITE,
|
|
6428
|
+
colorEndB = CLEAR_WHITE,
|
|
6225
6429
|
particleTime = .5,
|
|
6226
6430
|
sizeStart = .1,
|
|
6227
6431
|
sizeEnd = 1,
|
|
@@ -6244,7 +6448,8 @@ class ParticleEmitter extends EngineObject
|
|
|
6244
6448
|
|
|
6245
6449
|
// emitter settings
|
|
6246
6450
|
/** @property {number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
6247
|
-
this.emitSize = emitSize
|
|
6451
|
+
this.emitSize = emitSize instanceof Vector2 ?
|
|
6452
|
+
emitSize.copy() : emitSize;
|
|
6248
6453
|
/** @property {number} - How long to stay alive (0 is forever) */
|
|
6249
6454
|
this.emitTime = emitTime;
|
|
6250
6455
|
/** @property {number} - How many particles per second to spawn, does not emit if 0 */
|
|
@@ -6254,13 +6459,13 @@ class ParticleEmitter extends EngineObject
|
|
|
6254
6459
|
|
|
6255
6460
|
// color settings
|
|
6256
6461
|
/** @property {Color} - Color at start of life 1, randomized between start colors */
|
|
6257
|
-
this.colorStartA = colorStartA;
|
|
6462
|
+
this.colorStartA = colorStartA.copy();
|
|
6258
6463
|
/** @property {Color} - Color at start of life 2, randomized between start colors */
|
|
6259
|
-
this.colorStartB = colorStartB;
|
|
6464
|
+
this.colorStartB = colorStartB.copy();
|
|
6260
6465
|
/** @property {Color} - Color at end of life 1, randomized between end colors */
|
|
6261
|
-
this.colorEndA = colorEndA;
|
|
6466
|
+
this.colorEndA = colorEndA.copy();
|
|
6262
6467
|
/** @property {Color} - Color at end of life 2, randomized between end colors */
|
|
6263
|
-
this.colorEndB = colorEndB;
|
|
6468
|
+
this.colorEndB = colorEndB.copy();
|
|
6264
6469
|
/** @property {boolean} - Should color be randomized linearly or across each component */
|
|
6265
6470
|
this.randomColorLinear = randomColorLinear;
|
|
6266
6471
|
|
|
@@ -6295,9 +6500,9 @@ class ParticleEmitter extends EngineObject
|
|
|
6295
6500
|
this.localSpace = localSpace;
|
|
6296
6501
|
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
6297
6502
|
this.trailScale = 0;
|
|
6298
|
-
/** @property {
|
|
6503
|
+
/** @property {ParticleCallbackFunction} - Callback when particle is destroyed */
|
|
6299
6504
|
this.particleDestroyCallback = undefined;
|
|
6300
|
-
/** @property {
|
|
6505
|
+
/** @property {ParticleCallbackFunction} - Callback when particle is created */
|
|
6301
6506
|
this.particleCreateCallback = undefined;
|
|
6302
6507
|
/** @property {number} - Track particle emit time */
|
|
6303
6508
|
this.emitTimeBuffer = 0;
|
|
@@ -6398,6 +6603,7 @@ class ParticleEmitter extends EngineObject
|
|
|
6398
6603
|
/**
|
|
6399
6604
|
* Particle Object - Created automatically by Particle Emitters
|
|
6400
6605
|
* @extends EngineObject
|
|
6606
|
+
* @memberof Engine
|
|
6401
6607
|
*/
|
|
6402
6608
|
class Particle extends EngineObject
|
|
6403
6609
|
{
|
|
@@ -6416,7 +6622,7 @@ class Particle extends EngineObject
|
|
|
6416
6622
|
* @param {boolean} additive - Does it use additive blend mode
|
|
6417
6623
|
* @param {number} trailScale - If a trail, how long to make it
|
|
6418
6624
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
6419
|
-
* @param {
|
|
6625
|
+
* @param {ParticleCallbackFunction} [destroyCallback] - Callback when particle dies
|
|
6420
6626
|
*/
|
|
6421
6627
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
6422
6628
|
)
|
|
@@ -6425,14 +6631,14 @@ class Particle extends EngineObject
|
|
|
6425
6631
|
|
|
6426
6632
|
/** @property {Color} - Color at start of life */
|
|
6427
6633
|
this.colorStart = colorStart;
|
|
6428
|
-
/** @property {Color} -
|
|
6429
|
-
this.
|
|
6634
|
+
/** @property {Color} - Color at end of life */
|
|
6635
|
+
this.colorEnd = colorEnd;
|
|
6430
6636
|
/** @property {number} - How long to live for */
|
|
6431
6637
|
this.lifeTime = lifeTime;
|
|
6432
6638
|
/** @property {number} - Size at start of life */
|
|
6433
6639
|
this.sizeStart = sizeStart;
|
|
6434
|
-
/** @property {number} -
|
|
6435
|
-
this.
|
|
6640
|
+
/** @property {number} - Size at end of life */
|
|
6641
|
+
this.sizeEnd = sizeEnd;
|
|
6436
6642
|
/** @property {number} - How quick to fade in/out */
|
|
6437
6643
|
this.fadeRate = fadeRate;
|
|
6438
6644
|
/** @property {boolean} - Is it additive */
|
|
@@ -6441,7 +6647,7 @@ class Particle extends EngineObject
|
|
|
6441
6647
|
this.trailScale = trailScale;
|
|
6442
6648
|
/** @property {ParticleEmitter} - Parent emitter if local space */
|
|
6443
6649
|
this.localSpaceEmitter = localSpaceEmitter;
|
|
6444
|
-
/** @property {
|
|
6650
|
+
/** @property {ParticleCallbackFunction} - Called when particle dies */
|
|
6445
6651
|
this.destroyCallback = destroyCallback;
|
|
6446
6652
|
// particles do not clamp speed by default
|
|
6447
6653
|
this.clampSpeed = false;
|
|
@@ -6468,54 +6674,58 @@ class Particle extends EngineObject
|
|
|
6468
6674
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
6469
6675
|
render()
|
|
6470
6676
|
{
|
|
6471
|
-
//
|
|
6472
|
-
const
|
|
6473
|
-
const radius = this.sizeStart +
|
|
6474
|
-
|
|
6677
|
+
// lerp color and size
|
|
6678
|
+
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
6679
|
+
const radius = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
6680
|
+
this.size.x = this.size.y = radius;
|
|
6681
|
+
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
6682
|
+
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
6683
|
+
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
6684
|
+
this.color.a = p2 * this.colorStart.a + p1 * this.colorEnd.a;
|
|
6685
|
+
|
|
6686
|
+
// fade alpha
|
|
6475
6687
|
const fadeRate = this.fadeRate/2;
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
this.colorStart.g + p * this.colorEndDelta.g,
|
|
6479
|
-
this.colorStart.b + p * this.colorEndDelta.b,
|
|
6480
|
-
(this.colorStart.a + p * this.colorEndDelta.a) *
|
|
6481
|
-
(p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
|
|
6688
|
+
this.color.a *= p1 < fadeRate ? p1/fadeRate :
|
|
6689
|
+
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
6482
6690
|
|
|
6483
6691
|
// draw the particle
|
|
6484
6692
|
this.additive && setBlendMode(true);
|
|
6485
6693
|
|
|
6694
|
+
// update the position and angle for drawing
|
|
6486
6695
|
let pos = this.pos, angle = this.angle;
|
|
6487
6696
|
if (this.localSpaceEmitter)
|
|
6488
6697
|
{
|
|
6489
6698
|
// in local space of emitter
|
|
6490
|
-
|
|
6699
|
+
const a = this.localSpaceEmitter.angle;
|
|
6700
|
+
const c = Math.cos(a), s = Math.sin(a);
|
|
6701
|
+
pos = this.localSpaceEmitter.pos.add(
|
|
6702
|
+
new Vector2(pos.x*c - pos.y*s, pos.x*s + pos.y*c));
|
|
6491
6703
|
angle += this.localSpaceEmitter.angle;
|
|
6492
6704
|
}
|
|
6493
6705
|
if (this.trailScale)
|
|
6494
6706
|
{
|
|
6495
6707
|
// trail style particles
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
const speed =
|
|
6708
|
+
const direction = this.localSpaceEmitter ?
|
|
6709
|
+
this.velocity.rotate(-this.localSpaceEmitter.angle) :
|
|
6710
|
+
this.velocity;
|
|
6711
|
+
const speed = direction.length();
|
|
6500
6712
|
if (speed)
|
|
6501
6713
|
{
|
|
6502
|
-
|
|
6714
|
+
// stretch in direction of motion
|
|
6503
6715
|
const trailLength = speed * this.trailScale;
|
|
6504
|
-
size.y = max(size.x, trailLength);
|
|
6505
|
-
angle = direction.
|
|
6506
|
-
drawTile(pos
|
|
6716
|
+
this.size.y = max(this.size.x, trailLength);
|
|
6717
|
+
angle = Math.atan2(direction.x, direction.y);
|
|
6718
|
+
drawTile(pos, this.size, this.tileInfo, this.color, angle, this.mirror);
|
|
6507
6719
|
}
|
|
6508
6720
|
}
|
|
6509
6721
|
else
|
|
6510
|
-
drawTile(pos, size, this.tileInfo, color, angle, this.mirror);
|
|
6722
|
+
drawTile(pos, this.size, this.tileInfo, this.color, angle, this.mirror);
|
|
6511
6723
|
this.additive && setBlendMode();
|
|
6512
|
-
debugParticles && debugRect(pos, size, '#f005', 0, angle);
|
|
6724
|
+
debugParticles && debugRect(pos, this.size, '#f005', 0, angle);
|
|
6513
6725
|
|
|
6514
|
-
if (
|
|
6726
|
+
if (p1 === 1)
|
|
6515
6727
|
{
|
|
6516
|
-
// destroy particle when
|
|
6517
|
-
this.color = color;
|
|
6518
|
-
this.size = size;
|
|
6728
|
+
// destroy particle when its time runs out
|
|
6519
6729
|
this.destroyCallback && this.destroyCallback(this);
|
|
6520
6730
|
this.destroyed = 1;
|
|
6521
6731
|
}
|
|
@@ -6580,8 +6790,14 @@ function medalsInit(saveName)
|
|
|
6580
6790
|
}
|
|
6581
6791
|
}
|
|
6582
6792
|
|
|
6793
|
+
/**
|
|
6794
|
+
* @callback MedalCallbackFunction - Function that processes a medal
|
|
6795
|
+
* @param {Medal} medal
|
|
6796
|
+
* @memberof Medals
|
|
6797
|
+
*/
|
|
6798
|
+
|
|
6583
6799
|
/** Calls a function for each medal
|
|
6584
|
-
* @param {
|
|
6800
|
+
* @param {MedalCallbackFunction} callback
|
|
6585
6801
|
* @memberof Medals */
|
|
6586
6802
|
function medalsForEach(callback)
|
|
6587
6803
|
{ Object.values(medals).forEach(medal=>callback(medal)); }
|
|
@@ -6590,6 +6806,7 @@ function medalsForEach(callback)
|
|
|
6590
6806
|
|
|
6591
6807
|
/**
|
|
6592
6808
|
* Medal - Tracks an unlockable medal
|
|
6809
|
+
* @memberof Medals
|
|
6593
6810
|
* @example
|
|
6594
6811
|
* // create a medal
|
|
6595
6812
|
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
|
|
@@ -6658,11 +6875,12 @@ class Medal
|
|
|
6658
6875
|
const height = medalDisplaySize.y;
|
|
6659
6876
|
const x = overlayCanvas.width - width;
|
|
6660
6877
|
const y = -height*hidePercent;
|
|
6878
|
+
const backgroundColor = hsl(0,0,.9);
|
|
6661
6879
|
|
|
6662
6880
|
// draw containing rect and clip to that region
|
|
6663
6881
|
context.save();
|
|
6664
6882
|
context.beginPath();
|
|
6665
|
-
context.fillStyle =
|
|
6883
|
+
context.fillStyle = backgroundColor.toString();
|
|
6666
6884
|
context.strokeStyle = BLACK.toString();
|
|
6667
6885
|
context.lineWidth = 3;
|
|
6668
6886
|
context.rect(x, y, width, height);
|
|
@@ -6733,7 +6951,7 @@ let glContext;
|
|
|
6733
6951
|
let glAntialias = true;
|
|
6734
6952
|
|
|
6735
6953
|
// WebGL internal variables not exposed to documentation
|
|
6736
|
-
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount;
|
|
6954
|
+
let glShader, glPolyShader, glPolyMode, glAdditive, glBatchAdditive, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glBatchCount, glTextureInfos;
|
|
6737
6955
|
|
|
6738
6956
|
// WebGL internal constants
|
|
6739
6957
|
const gl_ARRAY_BUFFER_SIZE = 5e5;
|
|
@@ -6749,6 +6967,9 @@ const gl_MAX_POLY_VERTEXES = gl_ARRAY_BUFFER_SIZE / gl_POLY_VERTEX_BYTE_STRIDE |
|
|
|
6749
6967
|
// Initialize WebGL, called automatically by the engine
|
|
6750
6968
|
function glInit()
|
|
6751
6969
|
{
|
|
6970
|
+
// keep set of texture infos so they can be restored if context is lost
|
|
6971
|
+
glTextureInfos = new Set;
|
|
6972
|
+
|
|
6752
6973
|
if (!glEnable || headlessMode) return;
|
|
6753
6974
|
|
|
6754
6975
|
// create the canvas and textures
|
|
@@ -6766,68 +6987,101 @@ function glInit()
|
|
|
6766
6987
|
// create the WebGL canvas
|
|
6767
6988
|
const rootElement = mainCanvas.parentElement;
|
|
6768
6989
|
rootElement.appendChild(glCanvas);
|
|
6990
|
+
|
|
6991
|
+
// startup webgl
|
|
6992
|
+
initWebGL();
|
|
6993
|
+
|
|
6994
|
+
// setup context lost and restore handlers
|
|
6995
|
+
glCanvas.addEventListener('webglcontextlost', (e)=>
|
|
6996
|
+
{
|
|
6997
|
+
glEnable = false; // disable WebGL rendering
|
|
6998
|
+
glCanvas.style.display = 'none'; // hide the gl canvas
|
|
6999
|
+
e.preventDefault(); // prevent default to allow restoration
|
|
7000
|
+
LOG('WebGL context lost! Switching to Canvas2d rendering.');
|
|
7001
|
+
|
|
7002
|
+
// remove WebGL textures
|
|
7003
|
+
for (const info of glTextureInfos)
|
|
7004
|
+
info.glTexture = undefined;
|
|
7005
|
+
glActiveTexture = undefined;
|
|
7006
|
+
pluginList.forEach(plugin=>plugin.glContextLost?.());
|
|
7007
|
+
});
|
|
7008
|
+
glCanvas.addEventListener('webglcontextrestored', ()=>
|
|
7009
|
+
{
|
|
7010
|
+
glEnable = true; // disable WebGL rendering
|
|
7011
|
+
glCanvas.style.display = ''; // show the gl canvas
|
|
7012
|
+
LOG('WebGL context restored, reinitializing...');
|
|
6769
7013
|
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
6777
|
-
'in float r;'+ // in: rotation
|
|
6778
|
-
'out vec2 v;'+ // out: uv
|
|
6779
|
-
'out vec4 d,e;'+ // out: color, additiveColor
|
|
6780
|
-
'void main(){'+ // shader entry point
|
|
6781
|
-
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
6782
|
-
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
6783
|
-
'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
|
|
6784
|
-
'd=c;e=a;'+ // pass colors to fragment shader
|
|
6785
|
-
'}' // end of shader
|
|
6786
|
-
,
|
|
6787
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
6788
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
6789
|
-
'uniform sampler2D s;'+ // texture
|
|
6790
|
-
'in vec2 v;'+ // in: uv
|
|
6791
|
-
'in vec4 d,e;'+ // in: color, additiveColor
|
|
6792
|
-
'out vec4 c;'+ // out: color
|
|
6793
|
-
'void main(){'+ // shader entry point
|
|
6794
|
-
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
6795
|
-
'}' // end of shader
|
|
6796
|
-
);
|
|
7014
|
+
// reinit WebGL and restore textures
|
|
7015
|
+
initWebGL();
|
|
7016
|
+
for (const info of glTextureInfos)
|
|
7017
|
+
info.glTexture = glCreateTexture(info.image);
|
|
7018
|
+
pluginList.forEach(plugin=>plugin.glContextRestored?.());
|
|
7019
|
+
});
|
|
6797
7020
|
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
7021
|
+
function initWebGL()
|
|
7022
|
+
{
|
|
7023
|
+
// setup instanced rendering shader program
|
|
7024
|
+
glShader = glCreateProgram(
|
|
7025
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7026
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7027
|
+
'uniform mat4 m;'+ // transform matrix
|
|
7028
|
+
'in vec2 g;'+ // in: geometry
|
|
7029
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
7030
|
+
'in float r;'+ // in: rotation
|
|
7031
|
+
'out vec2 v;'+ // out: uv
|
|
7032
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
7033
|
+
'void main(){'+ // shader entry point
|
|
7034
|
+
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
7035
|
+
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
7036
|
+
'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
|
|
7037
|
+
'd=c;e=a;'+ // pass colors to fragment shader
|
|
7038
|
+
'}' // end of shader
|
|
7039
|
+
,
|
|
7040
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7041
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7042
|
+
'uniform sampler2D s;'+ // texture
|
|
7043
|
+
'in vec2 v;'+ // in: uv
|
|
7044
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
7045
|
+
'out vec4 c;'+ // out: color
|
|
7046
|
+
'void main(){'+ // shader entry point
|
|
7047
|
+
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
7048
|
+
'}' // end of shader
|
|
7049
|
+
);
|
|
6819
7050
|
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
7051
|
+
// setup poly rendering shaders
|
|
7052
|
+
glPolyShader = glCreateProgram(
|
|
7053
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7054
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7055
|
+
'uniform mat4 m;'+ // transform matrix
|
|
7056
|
+
'in vec2 p;'+ // in: position
|
|
7057
|
+
'in vec4 c;'+ // in: color
|
|
7058
|
+
'out vec4 d;'+ // out: color
|
|
7059
|
+
'void main(){'+ // shader entry point
|
|
7060
|
+
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
7061
|
+
'd=c;'+ // pass color to fragment shader
|
|
7062
|
+
'}' // end of shader
|
|
7063
|
+
,
|
|
7064
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7065
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7066
|
+
'in vec4 d;'+ // in: color
|
|
7067
|
+
'out vec4 c;'+ // out: color
|
|
7068
|
+
'void main(){'+ // shader entry point
|
|
7069
|
+
'c=d;'+ // set color
|
|
7070
|
+
'}' // end of shader
|
|
7071
|
+
);
|
|
6826
7072
|
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
7073
|
+
// init buffers
|
|
7074
|
+
const glInstanceData = new ArrayBuffer(gl_ARRAY_BUFFER_SIZE);
|
|
7075
|
+
glPositionData = new Float32Array(glInstanceData);
|
|
7076
|
+
glColorData = new Uint32Array(glInstanceData);
|
|
7077
|
+
glArrayBuffer = glContext.createBuffer();
|
|
7078
|
+
glGeometryBuffer = glContext.createBuffer();
|
|
7079
|
+
|
|
7080
|
+
// create the geometry buffer, triangle strip square
|
|
7081
|
+
const geometry = new Float32Array([glBatchCount=0,0,1,0,0,1,1,1]);
|
|
7082
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7083
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
7084
|
+
}
|
|
6831
7085
|
}
|
|
6832
7086
|
|
|
6833
7087
|
function glSetInstancedMode()
|
|
@@ -6934,7 +7188,7 @@ function glPreRender()
|
|
|
6934
7188
|
// start with additive blending off
|
|
6935
7189
|
glAdditive = glBatchAdditive = false;
|
|
6936
7190
|
|
|
6937
|
-
// force it to
|
|
7191
|
+
// force it to set instanced mode by first setting poly mode true
|
|
6938
7192
|
glPolyMode = true;
|
|
6939
7193
|
glSetInstancedMode();
|
|
6940
7194
|
}
|
|
@@ -7075,6 +7329,41 @@ function glSetTextureData(texture, image)
|
|
|
7075
7329
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture); // rebind active texture
|
|
7076
7330
|
}
|
|
7077
7331
|
|
|
7332
|
+
/** Tells WebGL to create or update the glTexture and start tracking it
|
|
7333
|
+
* @param {TextureInfo} textureInfo
|
|
7334
|
+
* @memberof WebGL */
|
|
7335
|
+
function glRegisterTextureInfo(textureInfo)
|
|
7336
|
+
{
|
|
7337
|
+
if (headlessMode) return;
|
|
7338
|
+
|
|
7339
|
+
// add texture info to tracking list even if gl is not enabled
|
|
7340
|
+
glTextureInfos.add(textureInfo);
|
|
7341
|
+
|
|
7342
|
+
if (!glContext) return;
|
|
7343
|
+
|
|
7344
|
+
// create or set the texture data
|
|
7345
|
+
if (textureInfo.glTexture)
|
|
7346
|
+
glSetTextureData(textureInfo.glTexture, textureInfo.image);
|
|
7347
|
+
else
|
|
7348
|
+
textureInfo.glTexture = glCreateTexture(textureInfo.image);
|
|
7349
|
+
}
|
|
7350
|
+
|
|
7351
|
+
/** Tells WebGL to destroy the glTexture and stop tracking it
|
|
7352
|
+
* @param {TextureInfo} textureInfo
|
|
7353
|
+
* @memberof WebGL */
|
|
7354
|
+
function glUnregisterTextureInfo(textureInfo)
|
|
7355
|
+
{
|
|
7356
|
+
if (headlessMode) return;
|
|
7357
|
+
|
|
7358
|
+
// delete texture info from tracking list even if gl is not enabled
|
|
7359
|
+
glTextureInfos.delete(textureInfo);
|
|
7360
|
+
|
|
7361
|
+
// unset and destroy the texture
|
|
7362
|
+
const glTexture = textureInfo.glTexture;
|
|
7363
|
+
textureInfo.glTexture = undefined;
|
|
7364
|
+
glDeleteTexture(glTexture);
|
|
7365
|
+
}
|
|
7366
|
+
|
|
7078
7367
|
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
7079
7368
|
* @memberof WebGL */
|
|
7080
7369
|
function glFlush()
|
|
@@ -7464,24 +7753,25 @@ function glPolyStrip(points)
|
|
|
7464
7753
|
return strip;
|
|
7465
7754
|
}
|
|
7466
7755
|
/**
|
|
7467
|
-
* LittleJS Newgrounds
|
|
7756
|
+
* LittleJS Newgrounds Plugin
|
|
7468
7757
|
* - NewgroundsMedal extends Medal with Newgrounds API functionality
|
|
7469
|
-
* - Call new NewgroundsPlugin() to setup Newgrounds
|
|
7758
|
+
* - Call new NewgroundsPlugin(app_id) to setup Newgrounds
|
|
7470
7759
|
* - Uses CryptoJS for encryption if optional cipher is provided
|
|
7760
|
+
* - provides functions to interact with medals scoreboards
|
|
7471
7761
|
* - Keeps connection alive and logs views
|
|
7472
|
-
*
|
|
7473
|
-
* - Functions to unlock medals
|
|
7762
|
+
* @namespace Newgrounds
|
|
7474
7763
|
*/
|
|
7475
7764
|
|
|
7476
7765
|
/** Global Newgrounds object
|
|
7477
7766
|
* @type {NewgroundsPlugin}
|
|
7478
|
-
* @memberof
|
|
7767
|
+
* @memberof Newgrounds */
|
|
7479
7768
|
let newgrounds;
|
|
7480
7769
|
|
|
7481
7770
|
///////////////////////////////////////////////////////////////////////////////
|
|
7482
7771
|
/**
|
|
7483
7772
|
* Newgrounds medal auto unlocks in newgrounds API
|
|
7484
7773
|
* @extends Medal
|
|
7774
|
+
* @memberof Newgrounds
|
|
7485
7775
|
*/
|
|
7486
7776
|
class NewgroundsMedal extends Medal
|
|
7487
7777
|
{
|
|
@@ -7506,6 +7796,7 @@ class NewgroundsMedal extends Medal
|
|
|
7506
7796
|
///////////////////////////////////////////////////////////////////////////////
|
|
7507
7797
|
/**
|
|
7508
7798
|
* Newgrounds API object
|
|
7799
|
+
* @memberof Newgrounds
|
|
7509
7800
|
*/
|
|
7510
7801
|
class NewgroundsPlugin
|
|
7511
7802
|
{
|
|
@@ -7641,99 +7932,119 @@ class NewgroundsPlugin
|
|
|
7641
7932
|
/**
|
|
7642
7933
|
* LittleJS Post Processing Plugin
|
|
7643
7934
|
* - Supports shadertoy style post processing shaders
|
|
7644
|
-
* - call new
|
|
7935
|
+
* - call new PostProcessPlugin() to setup post processing
|
|
7645
7936
|
* - can be enabled to pass other canvases through a final shader
|
|
7937
|
+
* @namespace PostProcess
|
|
7646
7938
|
*/
|
|
7647
7939
|
|
|
7648
7940
|
///////////////////////////////////////////////////////////////////////////////
|
|
7649
7941
|
|
|
7650
7942
|
/** Global Post Process plugin object
|
|
7651
|
-
* @type {PostProcessPlugin}
|
|
7943
|
+
* @type {PostProcessPlugin}
|
|
7944
|
+
* @memberof PostProcess */
|
|
7652
7945
|
let postProcess;
|
|
7653
7946
|
|
|
7654
7947
|
/////////////////////////////////////////////////////////////////////////
|
|
7655
7948
|
/**
|
|
7656
7949
|
* UI System Global Object
|
|
7950
|
+
* @memberof PostProcess
|
|
7657
7951
|
*/
|
|
7658
7952
|
class PostProcessPlugin
|
|
7659
7953
|
{
|
|
7660
7954
|
/** Create global post processing shader
|
|
7661
7955
|
* @param {string} shaderCode
|
|
7662
7956
|
* @param {boolean} [includeOverlay]
|
|
7957
|
+
* @param {boolean} [includeMainCanvas]
|
|
7663
7958
|
* @example
|
|
7664
7959
|
* // create the post process plugin object
|
|
7665
7960
|
* new PostProcessPlugin(shaderCode);
|
|
7666
7961
|
*/
|
|
7667
|
-
constructor(shaderCode, includeOverlay=false)
|
|
7962
|
+
constructor(shaderCode, includeOverlay=false, includeMainCanvas=true)
|
|
7668
7963
|
{
|
|
7669
7964
|
ASSERT(!postProcess, 'Post process already initialized');
|
|
7670
7965
|
postProcess = this;
|
|
7671
7966
|
|
|
7672
|
-
if (headlessMode) return;
|
|
7673
|
-
|
|
7674
|
-
if (!glEnable)
|
|
7675
|
-
{
|
|
7676
|
-
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
7677
|
-
return;
|
|
7678
|
-
}
|
|
7679
|
-
|
|
7680
7967
|
if (!shaderCode) // default shader pass through
|
|
7681
7968
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
7682
7969
|
|
|
7683
7970
|
/** @property {WebGLProgram} - Shader for post processing */
|
|
7684
|
-
this.shader =
|
|
7685
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
7686
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
7687
|
-
'in vec2 p;'+ // position
|
|
7688
|
-
'void main(){'+ // shader entry point
|
|
7689
|
-
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
7690
|
-
'}' // end of shader
|
|
7691
|
-
,
|
|
7692
|
-
'#version 300 es\n' + // specify GLSL ES version
|
|
7693
|
-
'precision highp float;'+ // use highp for better accuracy
|
|
7694
|
-
'uniform sampler2D iChannel0;'+ // input texture
|
|
7695
|
-
'uniform vec3 iResolution;'+ // size of output texture
|
|
7696
|
-
'uniform float iTime;'+ // time
|
|
7697
|
-
'out vec4 c;'+ // out color
|
|
7698
|
-
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
7699
|
-
'void main(){'+ // shader entry point
|
|
7700
|
-
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
7701
|
-
'c.a=1.;'+ // always use full alpha
|
|
7702
|
-
'}' // end of shader
|
|
7703
|
-
);
|
|
7971
|
+
this.shader = undefined;
|
|
7704
7972
|
|
|
7705
7973
|
/** @property {WebGLTexture} - Texture for post processing */
|
|
7706
|
-
this.texture =
|
|
7974
|
+
this.texture = undefined;
|
|
7707
7975
|
|
|
7708
|
-
|
|
7709
|
-
|
|
7976
|
+
// setup the post processing plugin
|
|
7977
|
+
initPostProcess();
|
|
7978
|
+
engineAddPlugin(undefined, postProcessRender, postProcessContextLost, postProcessContextRestored);
|
|
7710
7979
|
|
|
7711
|
-
|
|
7712
|
-
engineAddPlugin(undefined, postProcessRender);
|
|
7713
|
-
function postProcessRender()
|
|
7980
|
+
function initPostProcess()
|
|
7714
7981
|
{
|
|
7715
7982
|
if (headlessMode) return;
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
if (glEnable)
|
|
7719
|
-
{
|
|
7720
|
-
glFlush(); // clear out the buffer
|
|
7721
|
-
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
7722
|
-
}
|
|
7723
|
-
else
|
|
7983
|
+
|
|
7984
|
+
if (!glEnable)
|
|
7724
7985
|
{
|
|
7725
|
-
|
|
7726
|
-
|
|
7986
|
+
console.warn('PostProcessPlugin: WebGL not enabled!');
|
|
7987
|
+
return;
|
|
7727
7988
|
}
|
|
7728
7989
|
|
|
7729
|
-
|
|
7990
|
+
// create resources
|
|
7991
|
+
postProcess.texture = glCreateTexture();
|
|
7992
|
+
postProcess.shader = glCreateProgram(
|
|
7993
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
7994
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
7995
|
+
'in vec2 p;'+ // position
|
|
7996
|
+
'void main(){'+ // shader entry point
|
|
7997
|
+
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
7998
|
+
'}' // end of shader
|
|
7999
|
+
,
|
|
8000
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
8001
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
8002
|
+
'uniform sampler2D iChannel0;'+ // input texture
|
|
8003
|
+
'uniform vec3 iResolution;'+ // size of output texture
|
|
8004
|
+
'uniform float iTime;'+ // time
|
|
8005
|
+
'out vec4 c;'+ // out color
|
|
8006
|
+
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
8007
|
+
'void main(){'+ // shader entry point
|
|
8008
|
+
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
8009
|
+
'c.a=1.;'+ // always use full alpha
|
|
8010
|
+
'}' // end of shader
|
|
8011
|
+
);
|
|
8012
|
+
}
|
|
8013
|
+
function postProcessContextLost()
|
|
8014
|
+
{
|
|
8015
|
+
postProcess.shader = undefined;
|
|
8016
|
+
postProcess.texture = undefined;
|
|
8017
|
+
LOG('PostProcessPlugin: WebGL context lost');
|
|
8018
|
+
}
|
|
8019
|
+
function postProcessContextRestored()
|
|
8020
|
+
{
|
|
8021
|
+
initPostProcess();
|
|
8022
|
+
LOG('PostProcessPlugin: WebGL context restored');
|
|
8023
|
+
}
|
|
8024
|
+
function postProcessRender()
|
|
8025
|
+
{
|
|
8026
|
+
if (headlessMode) return;
|
|
8027
|
+
|
|
8028
|
+
if (!glEnable)
|
|
8029
|
+
return;
|
|
8030
|
+
|
|
8031
|
+
// clear out the buffer
|
|
8032
|
+
glFlush();
|
|
8033
|
+
|
|
8034
|
+
if (includeMainCanvas || includeOverlay)
|
|
7730
8035
|
{
|
|
7731
|
-
// copy
|
|
7732
|
-
mainContext.drawImage(
|
|
7733
|
-
|
|
8036
|
+
// copy WebGL to the main canvas
|
|
8037
|
+
mainContext.drawImage(glCanvas, 0, 0);
|
|
8038
|
+
|
|
8039
|
+
if (includeOverlay)
|
|
8040
|
+
{
|
|
8041
|
+
// copy overlay canvas so it will be included in post processing
|
|
8042
|
+
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
8043
|
+
overlayCanvas.width |= 0; // clear overlay canvas
|
|
8044
|
+
}
|
|
7734
8045
|
}
|
|
7735
8046
|
|
|
7736
|
-
// setup shader program to draw
|
|
8047
|
+
// setup shader program to draw a quad
|
|
7737
8048
|
glContext.useProgram(postProcess.shader);
|
|
7738
8049
|
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
7739
8050
|
glContext.pixelStorei(glContext.UNPACK_FLIP_Y_WEBGL, 1);
|
|
@@ -7742,7 +8053,10 @@ class PostProcessPlugin
|
|
|
7742
8053
|
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
7743
8054
|
glContext.activeTexture(glContext.TEXTURE0);
|
|
7744
8055
|
glContext.bindTexture(glContext.TEXTURE_2D, postProcess.texture);
|
|
7745
|
-
|
|
8056
|
+
if (includeMainCanvas || includeOverlay)
|
|
8057
|
+
{
|
|
8058
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, mainCanvas);
|
|
8059
|
+
}
|
|
7746
8060
|
|
|
7747
8061
|
// set vertex position attribute
|
|
7748
8062
|
const vertexByteStride = 8;
|
|
@@ -7761,12 +8075,15 @@ class PostProcessPlugin
|
|
|
7761
8075
|
}
|
|
7762
8076
|
/**
|
|
7763
8077
|
* LittleJS ZzFXM Plugin
|
|
8078
|
+
* @namespace ZzFXM
|
|
7764
8079
|
*/
|
|
7765
8080
|
|
|
7766
8081
|
/**
|
|
7767
8082
|
* Music Object - Stores a zzfx music track for later use
|
|
7768
8083
|
*
|
|
7769
8084
|
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
8085
|
+
* @extends Sound
|
|
8086
|
+
* @memberof ZzFXM
|
|
7770
8087
|
* @example
|
|
7771
8088
|
* // create some music
|
|
7772
8089
|
* const music_example = new Music(
|
|
@@ -7825,7 +8142,8 @@ class ZzFXMusic extends Sound
|
|
|
7825
8142
|
* @param {Array} patterns - Array of pattern data
|
|
7826
8143
|
* @param {Array} sequence - Array of pattern indexes
|
|
7827
8144
|
* @param {number} [BPM] - Playback speed of the song in BPM
|
|
7828
|
-
* @return {Array} - Left and right channel sample data
|
|
8145
|
+
* @return {Array} - Left and right channel sample data
|
|
8146
|
+
* @memberof ZzFXM */
|
|
7829
8147
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
7830
8148
|
{
|
|
7831
8149
|
let i, j, k;
|
|
@@ -7928,17 +8246,20 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
7928
8246
|
* - Buttons
|
|
7929
8247
|
* - Checkboxes
|
|
7930
8248
|
* - Images
|
|
8249
|
+
* @namespace UISystem
|
|
7931
8250
|
*/
|
|
7932
8251
|
|
|
7933
8252
|
///////////////////////////////////////////////////////////////////////////////
|
|
7934
8253
|
|
|
7935
8254
|
/** Global UI system plugin object
|
|
7936
|
-
* @type {UISystemPlugin}
|
|
8255
|
+
* @type {UISystemPlugin}
|
|
8256
|
+
* @memberof UISystem */
|
|
7937
8257
|
let uiSystem;
|
|
7938
8258
|
|
|
7939
8259
|
///////////////////////////////////////////////////////////////////////////////
|
|
7940
8260
|
/**
|
|
7941
8261
|
* UI System Global Object
|
|
8262
|
+
* @memberof UISystem
|
|
7942
8263
|
*/
|
|
7943
8264
|
class UISystemPlugin
|
|
7944
8265
|
{
|
|
@@ -7965,6 +8286,8 @@ class UISystemPlugin
|
|
|
7965
8286
|
this.defaultHoverColor = hsl(0,0,.9);
|
|
7966
8287
|
/** @property {Color} - Default color for disabled UI elements */
|
|
7967
8288
|
this.defaultDisabledColor = hsl(0,0,.3);
|
|
8289
|
+
/** @property {Color} - Uses a gradient fill combined with color */
|
|
8290
|
+
this.defaultGradientColor = undefined;
|
|
7968
8291
|
/** @property {number} - Default line width for UI elements */
|
|
7969
8292
|
this.defaultLineWidth = 4;
|
|
7970
8293
|
/** @property {number} - Default rounded rect corner radius for UI elements */
|
|
@@ -7972,7 +8295,7 @@ class UISystemPlugin
|
|
|
7972
8295
|
/** @property {number} - Default scale to use for fitting text to object */
|
|
7973
8296
|
this.defaultTextScale = .8;
|
|
7974
8297
|
/** @property {string} - Default font for UI elements */
|
|
7975
|
-
this.defaultFont =
|
|
8298
|
+
this.defaultFont = fontDefault;
|
|
7976
8299
|
/** @property {Sound} - Default sound when interactive UI element is pressed */
|
|
7977
8300
|
this.defaultSoundPress = undefined;
|
|
7978
8301
|
/** @property {Sound} - Default sound when interactive UI element is released */
|
|
@@ -7983,11 +8306,15 @@ class UISystemPlugin
|
|
|
7983
8306
|
this.uiObjects = [];
|
|
7984
8307
|
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
|
|
7985
8308
|
this.uiContext = context;
|
|
7986
|
-
/** @property {UIObject} - Top most object user is over */
|
|
7987
|
-
this.hoverObject = undefined;
|
|
7988
8309
|
/** @property {UIObject} - Object user is currently interacting with */
|
|
7989
8310
|
this.activeObject = undefined;
|
|
7990
|
-
|
|
8311
|
+
/** @property {UIObject} - Top most object user is over */
|
|
8312
|
+
this.hoverObject = undefined;
|
|
8313
|
+
/** @property {UIObject} - Hover object at start of update */
|
|
8314
|
+
this.lastHoverObject = undefined;
|
|
8315
|
+
/** @property {number} - If set ui coords will be renormalized to this canvas height */
|
|
8316
|
+
this.nativeHeight = 0;
|
|
8317
|
+
|
|
7991
8318
|
engineAddPlugin(uiUpdate, uiRender);
|
|
7992
8319
|
|
|
7993
8320
|
// setup recursive update and render
|
|
@@ -8017,6 +8344,7 @@ class UISystemPlugin
|
|
|
8017
8344
|
updateInvisibleObject(o);
|
|
8018
8345
|
}
|
|
8019
8346
|
// reset hover object at start of update
|
|
8347
|
+
uiSystem.lastHoverObject = uiSystem.hoverObject;
|
|
8020
8348
|
uiSystem.hoverObject = undefined;
|
|
8021
8349
|
for (let i = uiSystem.uiObjects.length; i--;)
|
|
8022
8350
|
{
|
|
@@ -8026,6 +8354,17 @@ class UISystemPlugin
|
|
|
8026
8354
|
}
|
|
8027
8355
|
function uiRender()
|
|
8028
8356
|
{
|
|
8357
|
+
const context = uiSystem.uiContext;
|
|
8358
|
+
context.save();
|
|
8359
|
+
if (uiSystem.nativeHeight)
|
|
8360
|
+
{
|
|
8361
|
+
// convert to native height
|
|
8362
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8363
|
+
context.translate(-s*mainCanvasSize.x/2,0);
|
|
8364
|
+
context.scale(s,s);
|
|
8365
|
+
context.translate(mainCanvasSize.x/2/s,0);
|
|
8366
|
+
}
|
|
8367
|
+
|
|
8029
8368
|
function renderObject(o)
|
|
8030
8369
|
{
|
|
8031
8370
|
if (!o.visible)
|
|
@@ -8037,6 +8376,7 @@ class UISystemPlugin
|
|
|
8037
8376
|
renderObject(c);
|
|
8038
8377
|
}
|
|
8039
8378
|
uiSystem.uiObjects.forEach(o=> o.parent || renderObject(o));
|
|
8379
|
+
context.restore();
|
|
8040
8380
|
}
|
|
8041
8381
|
}
|
|
8042
8382
|
|
|
@@ -8046,11 +8386,30 @@ class UISystemPlugin
|
|
|
8046
8386
|
* @param {Color} [color=uiSystem.defaultColor]
|
|
8047
8387
|
* @param {number} [lineWidth=uiSystem.defaultLineWidth]
|
|
8048
8388
|
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
8049
|
-
* @param {number} [cornerRadius=uiSystem.defaultCornerRadius]
|
|
8050
|
-
|
|
8051
|
-
|
|
8389
|
+
* @param {number} [cornerRadius=uiSystem.defaultCornerRadius]
|
|
8390
|
+
* @param {Color} [gradientColor=uiSystem.defaultGradientColor] */
|
|
8391
|
+
drawRect(pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, cornerRadius=uiSystem.defaultCornerRadius, gradientColor=uiSystem.defaultGradientColor)
|
|
8392
|
+
{
|
|
8393
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
8394
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
8395
|
+
ASSERT(isColor(color), 'color must be a color');
|
|
8396
|
+
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
8397
|
+
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
8398
|
+
ASSERT(isNumber(cornerRadius), 'cornerRadius must be a number');
|
|
8399
|
+
|
|
8052
8400
|
const context = uiSystem.uiContext;
|
|
8053
|
-
|
|
8401
|
+
if (gradientColor)
|
|
8402
|
+
{
|
|
8403
|
+
const g = context.createLinearGradient(
|
|
8404
|
+
pos.x, pos.y-size.y/2, pos.x, pos.y+size.y/2);
|
|
8405
|
+
const c = color.toString();
|
|
8406
|
+
g.addColorStop(0, c);
|
|
8407
|
+
g.addColorStop(.5, gradientColor.toString());
|
|
8408
|
+
g.addColorStop(1, c);
|
|
8409
|
+
context.fillStyle = g;
|
|
8410
|
+
}
|
|
8411
|
+
else
|
|
8412
|
+
context.fillStyle = color.toString();
|
|
8054
8413
|
context.beginPath();
|
|
8055
8414
|
if (cornerRadius && context['roundRect'])
|
|
8056
8415
|
context['roundRect'](pos.x-size.x/2, pos.y-size.y/2, size.x, size.y, cornerRadius);
|
|
@@ -8072,6 +8431,11 @@ class UISystemPlugin
|
|
|
8072
8431
|
* @param {Color} [lineColor=uiSystem.defaultLineColor] */
|
|
8073
8432
|
drawLine(posA, posB, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor)
|
|
8074
8433
|
{
|
|
8434
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
8435
|
+
ASSERT(isVector2(posB), 'posB must be a vec2');
|
|
8436
|
+
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
8437
|
+
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
8438
|
+
|
|
8075
8439
|
const context = uiSystem.uiContext;
|
|
8076
8440
|
context.strokeStyle = lineColor.toString();
|
|
8077
8441
|
context.lineWidth = lineWidth;
|
|
@@ -8102,17 +8466,43 @@ class UISystemPlugin
|
|
|
8102
8466
|
* @param {Color} [lineColor=uiSystem.defaultLineColor]
|
|
8103
8467
|
* @param {string} [align]
|
|
8104
8468
|
* @param {string} [font=uiSystem.defaultFont]
|
|
8469
|
+
* @param {string} [fontStyle]
|
|
8105
8470
|
* @param {boolean} [applyMaxWidth=true] */
|
|
8106
|
-
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, applyMaxWidth=true)
|
|
8471
|
+
drawText(text, pos, size, color=uiSystem.defaultColor, lineWidth=uiSystem.defaultLineWidth, lineColor=uiSystem.defaultLineColor, align='center', font=uiSystem.defaultFont, fontStyle='', applyMaxWidth=true)
|
|
8107
8472
|
{
|
|
8108
|
-
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, applyMaxWidth ? size.x : undefined, uiSystem.uiContext);
|
|
8473
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, fontStyle, applyMaxWidth ? size.x : undefined, uiSystem.uiContext);
|
|
8474
|
+
}
|
|
8475
|
+
|
|
8476
|
+
/**
|
|
8477
|
+
* @callback DragAndDropCallback - Callback for drag and drop events
|
|
8478
|
+
* @param {DragEvent} event - The drag event
|
|
8479
|
+
* @memberof UISystem
|
|
8480
|
+
*/
|
|
8481
|
+
|
|
8482
|
+
/** Setup drag and drop event handlers
|
|
8483
|
+
* Automatically prevents defaults and calls the given functions
|
|
8484
|
+
* @param {DragAndDropCallback} [onDrop] - when a file is dropped
|
|
8485
|
+
* @param {DragAndDropCallback} [onDragEnter] - when a file is dragged onto the window
|
|
8486
|
+
* @param {DragAndDropCallback} [onDragLeave] - when a file is dragged off the window
|
|
8487
|
+
* @param {DragAndDropCallback} [onDragOver] - continously when dragging over */
|
|
8488
|
+
setupDragAndDrop(onDrop, onDragEnter, onDragLeave, onDragOver)
|
|
8489
|
+
{
|
|
8490
|
+
function setCallback(callback, listenerType)
|
|
8491
|
+
{
|
|
8492
|
+
function listener(e) { e.preventDefault(); callback && callback(e); }
|
|
8493
|
+
document.addEventListener(listenerType, listener);
|
|
8494
|
+
}
|
|
8495
|
+
setCallback(onDrop, 'drop');
|
|
8496
|
+
setCallback(onDragEnter, 'dragenter');
|
|
8497
|
+
setCallback(onDragLeave, 'dragleave');
|
|
8498
|
+
setCallback(onDragOver, 'dragover');
|
|
8109
8499
|
}
|
|
8110
8500
|
}
|
|
8111
8501
|
|
|
8112
8502
|
///////////////////////////////////////////////////////////////////////////////
|
|
8113
8503
|
/**
|
|
8114
8504
|
* UI Object - Base level object for all UI elements
|
|
8115
|
-
*/
|
|
8505
|
+
* @memberof UISystem */
|
|
8116
8506
|
class UIObject
|
|
8117
8507
|
{
|
|
8118
8508
|
/** Create a UIObject
|
|
@@ -8121,6 +8511,9 @@ class UIObject
|
|
|
8121
8511
|
*/
|
|
8122
8512
|
constructor(pos=vec2(), size=vec2())
|
|
8123
8513
|
{
|
|
8514
|
+
ASSERT(isVector2(pos), 'ui object pos must be a vec2');
|
|
8515
|
+
ASSERT(isVector2(size), 'ui object size must be a vec2');
|
|
8516
|
+
|
|
8124
8517
|
/** @property {Vector2} - Local position of the object */
|
|
8125
8518
|
this.localPos = pos.copy();
|
|
8126
8519
|
/** @property {Vector2} - Screen space position of the object */
|
|
@@ -8128,27 +8521,31 @@ class UIObject
|
|
|
8128
8521
|
/** @property {Vector2} - Screen space size of the object */
|
|
8129
8522
|
this.size = size.copy();
|
|
8130
8523
|
/** @property {Color} - Color of the object */
|
|
8131
|
-
this.color = uiSystem.defaultColor;
|
|
8524
|
+
this.color = uiSystem.defaultColor.copy();
|
|
8132
8525
|
/** @property {Color} - Color of the object when active, uses color if undefined */
|
|
8133
8526
|
this.activeColor = undefined;
|
|
8134
8527
|
/** @property {string} - Text for this ui object */
|
|
8135
8528
|
this.text = undefined;
|
|
8136
8529
|
/** @property {Color} - Color when disabled */
|
|
8137
|
-
this.disabledColor = uiSystem.defaultDisabledColor;
|
|
8530
|
+
this.disabledColor = uiSystem.defaultDisabledColor.copy();
|
|
8138
8531
|
/** @property {boolean} - Is this object disabled? */
|
|
8139
8532
|
this.disabled = false;
|
|
8140
8533
|
/** @property {Color} - Color for text */
|
|
8141
|
-
this.textColor = uiSystem.defaultTextColor;
|
|
8534
|
+
this.textColor = uiSystem.defaultTextColor.copy();
|
|
8142
8535
|
/** @property {Color} - Color used when hovering over the object */
|
|
8143
|
-
this.hoverColor = uiSystem.defaultHoverColor;
|
|
8536
|
+
this.hoverColor = uiSystem.defaultHoverColor.copy();
|
|
8144
8537
|
/** @property {Color} - Color for line drawing */
|
|
8145
|
-
this.lineColor = uiSystem.defaultLineColor;
|
|
8538
|
+
this.lineColor = uiSystem.defaultLineColor.copy();
|
|
8539
|
+
/** @property {Color} - Uses a gradient fill combined with color */
|
|
8540
|
+
this.gradientColor = uiSystem.defaultGradientColor ? uiSystem.defaultGradientColor.copy() : undefined;
|
|
8146
8541
|
/** @property {number} - Width for line drawing */
|
|
8147
8542
|
this.lineWidth = uiSystem.defaultLineWidth;
|
|
8148
8543
|
/** @property {number} - Corner radius for rounded rects */
|
|
8149
8544
|
this.cornerRadius = uiSystem.defaultCornerRadius;
|
|
8150
8545
|
/** @property {string} - Font for this objecct */
|
|
8151
8546
|
this.font = uiSystem.defaultFont;
|
|
8547
|
+
/** @property {string} - Font style for this object or undefined */
|
|
8548
|
+
this.fontStyle = undefined;
|
|
8152
8549
|
/** @property {number} - Override for text width */
|
|
8153
8550
|
this.textWidth = undefined;
|
|
8154
8551
|
/** @property {number} - Override for text height */
|
|
@@ -8173,6 +8570,8 @@ class UIObject
|
|
|
8173
8570
|
this.interactive = false;
|
|
8174
8571
|
/** @property {boolean} - Activate when dragged over with mouse held down */
|
|
8175
8572
|
this.dragActivate = false;
|
|
8573
|
+
/** @property {boolean} - True if this can be a hover object */
|
|
8574
|
+
this.canBeHover = true;
|
|
8176
8575
|
uiSystem.uiObjects.push(this);
|
|
8177
8576
|
}
|
|
8178
8577
|
|
|
@@ -8196,31 +8595,47 @@ class UIObject
|
|
|
8196
8595
|
child.parent = undefined;
|
|
8197
8596
|
}
|
|
8198
8597
|
|
|
8598
|
+
/** Check if the mouse is overlapping a box in screen space
|
|
8599
|
+
* @return {boolean} - True if overlapping
|
|
8600
|
+
*/
|
|
8601
|
+
isMouseOverlapping()
|
|
8602
|
+
{
|
|
8603
|
+
const size = !isTouchDevice ? this.size :
|
|
8604
|
+
this.size.add(vec2(this.extraTouchSize || 0));
|
|
8605
|
+
if (!uiSystem.nativeHeight)
|
|
8606
|
+
return isOverlapping(this.pos, size, mousePosScreen);
|
|
8607
|
+
|
|
8608
|
+
const s = mainCanvasSize.y / uiSystem.nativeHeight;
|
|
8609
|
+
const sInv = 1/s;
|
|
8610
|
+
let pos = mousePosScreen.copy();
|
|
8611
|
+
pos.x += s*mainCanvasSize.x/2;
|
|
8612
|
+
pos.x *= sInv;
|
|
8613
|
+
pos.y *= sInv;
|
|
8614
|
+
pos.x -= sInv*mainCanvasSize.x/2;
|
|
8615
|
+
return isOverlapping(this.pos, size, pos);
|
|
8616
|
+
}
|
|
8617
|
+
|
|
8199
8618
|
/** Update the object, called automatically by plugin once each frame */
|
|
8200
8619
|
update()
|
|
8201
8620
|
{
|
|
8202
|
-
const wasHover = this
|
|
8621
|
+
const wasHover = uiSystem.lastHoverObject === this;
|
|
8203
8622
|
const isActive = this.isActiveObject();
|
|
8204
8623
|
const mouseDown = mouseIsDown(0);
|
|
8205
8624
|
const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
|
|
8206
|
-
if (
|
|
8625
|
+
if (this.canBeHover)
|
|
8207
8626
|
if (mousePress || isActive || (!mouseDown && !isTouchDevice))
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
if (isOverlapping(this.pos, size, mousePosScreen))
|
|
8211
|
-
uiSystem.hoverObject = this;
|
|
8212
|
-
}
|
|
8627
|
+
if (!uiSystem.hoverObject && this.isMouseOverlapping())
|
|
8628
|
+
uiSystem.hoverObject = this;
|
|
8213
8629
|
if (this.isHoverObject())
|
|
8214
8630
|
{
|
|
8215
|
-
if (mousePress)
|
|
8216
|
-
inputClearKey(0,0,0,1,0); // clear mouse was pressed state
|
|
8217
8631
|
if (!this.disabled)
|
|
8218
8632
|
{
|
|
8219
8633
|
if (mousePress)
|
|
8220
8634
|
{
|
|
8221
8635
|
if (this.interactive)
|
|
8222
8636
|
{
|
|
8223
|
-
this.
|
|
8637
|
+
if (!this.dragActivate || (!wasHover || mouseWasPressed(0)))
|
|
8638
|
+
this.onPress();
|
|
8224
8639
|
if (this.soundPress)
|
|
8225
8640
|
this.soundPress.play();
|
|
8226
8641
|
if (uiSystem.activeObject && !isActive)
|
|
@@ -8228,13 +8643,15 @@ class UIObject
|
|
|
8228
8643
|
uiSystem.activeObject = this;
|
|
8229
8644
|
}
|
|
8230
8645
|
}
|
|
8231
|
-
if (!mouseDown &&
|
|
8646
|
+
if (!mouseDown && this.isActiveObject() && this.interactive)
|
|
8232
8647
|
{
|
|
8233
8648
|
this.onClick();
|
|
8234
8649
|
if (this.soundClick)
|
|
8235
8650
|
this.soundClick.play();
|
|
8236
8651
|
}
|
|
8237
8652
|
}
|
|
8653
|
+
// clear mouse was pressed state even when disabled
|
|
8654
|
+
mousePress && inputClearKey(0,0,0,1,0);
|
|
8238
8655
|
}
|
|
8239
8656
|
if (isActive)
|
|
8240
8657
|
if (!mouseDown || (this.dragActivate && !this.isHoverObject()))
|
|
@@ -8245,6 +8662,7 @@ class UIObject
|
|
|
8245
8662
|
uiSystem.activeObject = undefined;
|
|
8246
8663
|
}
|
|
8247
8664
|
|
|
8665
|
+
// call enter/leave events
|
|
8248
8666
|
if (this.isHoverObject() !== wasHover)
|
|
8249
8667
|
this.isHoverObject() ? this.onEnter() : this.onLeave();
|
|
8250
8668
|
}
|
|
@@ -8255,7 +8673,7 @@ class UIObject
|
|
|
8255
8673
|
if (!this.size.x || !this.size.y) return;
|
|
8256
8674
|
|
|
8257
8675
|
const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
|
|
8258
|
-
const color = this.
|
|
8676
|
+
const color = this.disabled ? this.disabledColor : this.interactive ? this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
|
|
8259
8677
|
uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius);
|
|
8260
8678
|
}
|
|
8261
8679
|
|
|
@@ -8306,6 +8724,7 @@ class UIObject
|
|
|
8306
8724
|
/**
|
|
8307
8725
|
* UIText - A UI object that displays text
|
|
8308
8726
|
* @extends UIObject
|
|
8727
|
+
* @memberof UISystem
|
|
8309
8728
|
*/
|
|
8310
8729
|
class UIText extends UIObject
|
|
8311
8730
|
{
|
|
@@ -8320,6 +8739,10 @@ class UIText extends UIObject
|
|
|
8320
8739
|
{
|
|
8321
8740
|
super(pos, size);
|
|
8322
8741
|
|
|
8742
|
+
ASSERT(isString(text), 'ui text must be a string');
|
|
8743
|
+
ASSERT(['left','center','right'].includes(align), 'ui text align must be left, center, or right');
|
|
8744
|
+
ASSERT(isString(font), 'ui text font must be a string');
|
|
8745
|
+
|
|
8323
8746
|
// set properties
|
|
8324
8747
|
this.text = text;
|
|
8325
8748
|
this.align = align;
|
|
@@ -8327,11 +8750,13 @@ class UIText extends UIObject
|
|
|
8327
8750
|
|
|
8328
8751
|
// make text not outlined by default
|
|
8329
8752
|
this.lineWidth = 0;
|
|
8753
|
+
// text can not be a hover object by default
|
|
8754
|
+
this.canBeHover = false;
|
|
8330
8755
|
}
|
|
8331
8756
|
render()
|
|
8332
8757
|
{
|
|
8333
8758
|
const textSize = this.getTextSize();
|
|
8334
|
-
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
|
|
8759
|
+
uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font, this.fontStyle);
|
|
8335
8760
|
}
|
|
8336
8761
|
}
|
|
8337
8762
|
|
|
@@ -8339,6 +8764,7 @@ class UIText extends UIObject
|
|
|
8339
8764
|
/**
|
|
8340
8765
|
* UITile - A UI object that displays a tile image
|
|
8341
8766
|
* @extends UIObject
|
|
8767
|
+
* @memberof UISystem
|
|
8342
8768
|
*/
|
|
8343
8769
|
class UITile extends UIObject
|
|
8344
8770
|
{
|
|
@@ -8353,15 +8779,19 @@ class UITile extends UIObject
|
|
|
8353
8779
|
constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false)
|
|
8354
8780
|
{
|
|
8355
8781
|
super(pos, size);
|
|
8782
|
+
|
|
8783
|
+
ASSERT(tileInfo instanceof TileInfo, 'ui tile tileInfo must be a TileInfo');
|
|
8784
|
+
ASSERT(isColor(color), 'ui tile color must be a color');
|
|
8785
|
+
ASSERT(isNumber(angle), 'ui tile angle must be a number');
|
|
8786
|
+
|
|
8356
8787
|
/** @property {TileInfo} - Tile image to use */
|
|
8357
8788
|
this.tileInfo = tileInfo;
|
|
8358
8789
|
/** @property {number} - Angle to rotate in radians */
|
|
8359
8790
|
this.angle = angle;
|
|
8360
8791
|
/** @property {boolean} - Should it be mirrored? */
|
|
8361
8792
|
this.mirror = mirror;
|
|
8362
|
-
|
|
8363
8793
|
// set properties
|
|
8364
|
-
this.color = color;
|
|
8794
|
+
this.color = color.copy();
|
|
8365
8795
|
}
|
|
8366
8796
|
render()
|
|
8367
8797
|
{
|
|
@@ -8373,6 +8803,7 @@ class UITile extends UIObject
|
|
|
8373
8803
|
/**
|
|
8374
8804
|
* UIButton - A UI object that acts as a button
|
|
8375
8805
|
* @extends UIObject
|
|
8806
|
+
* @memberof UISystem
|
|
8376
8807
|
*/
|
|
8377
8808
|
class UIButton extends UIObject
|
|
8378
8809
|
{
|
|
@@ -8386,9 +8817,12 @@ class UIButton extends UIObject
|
|
|
8386
8817
|
{
|
|
8387
8818
|
super(pos, size);
|
|
8388
8819
|
|
|
8820
|
+
ASSERT(isString(text), 'ui button must be a string');
|
|
8821
|
+
ASSERT(isColor(color), 'ui button color must be a color');
|
|
8822
|
+
|
|
8389
8823
|
// set properties
|
|
8390
8824
|
this.text = text;
|
|
8391
|
-
this.color = color;
|
|
8825
|
+
this.color = color.copy();
|
|
8392
8826
|
this.interactive = true;
|
|
8393
8827
|
}
|
|
8394
8828
|
render()
|
|
@@ -8398,7 +8832,7 @@ class UIButton extends UIObject
|
|
|
8398
8832
|
// draw the text scaled to fit
|
|
8399
8833
|
const textSize = this.getTextSize();
|
|
8400
8834
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8401
|
-
this.textColor, 0, undefined, this.align, this.font);
|
|
8835
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
8402
8836
|
}
|
|
8403
8837
|
}
|
|
8404
8838
|
|
|
@@ -8406,6 +8840,7 @@ class UIButton extends UIObject
|
|
|
8406
8840
|
/**
|
|
8407
8841
|
* UICheckbox - A UI object that acts as a checkbox
|
|
8408
8842
|
* @extends UIObject
|
|
8843
|
+
* @memberof UISystem
|
|
8409
8844
|
*/
|
|
8410
8845
|
class UICheckbox extends UIObject
|
|
8411
8846
|
{
|
|
@@ -8419,12 +8854,15 @@ class UICheckbox extends UIObject
|
|
|
8419
8854
|
constructor(pos, size, checked=false, text='', color=uiSystem.defaultButtonColor)
|
|
8420
8855
|
{
|
|
8421
8856
|
super(pos, size);
|
|
8857
|
+
|
|
8858
|
+
ASSERT(isString(text), 'ui checkbox must be a string');
|
|
8859
|
+
ASSERT(isColor(color), 'ui checkbox color must be a color');
|
|
8860
|
+
|
|
8422
8861
|
/** @property {boolean} - Current percentage value of this scrollbar 0-1 */
|
|
8423
8862
|
this.checked = checked;
|
|
8424
|
-
|
|
8425
8863
|
// set properties
|
|
8426
8864
|
this.text = text;
|
|
8427
|
-
this.color = color;
|
|
8865
|
+
this.color = color.copy();
|
|
8428
8866
|
this.interactive = true;
|
|
8429
8867
|
}
|
|
8430
8868
|
onClick()
|
|
@@ -8448,7 +8886,7 @@ class UICheckbox extends UIObject
|
|
|
8448
8886
|
const textSize = this.getTextSize();
|
|
8449
8887
|
const pos = this.pos.add(vec2(this.size.x,0));
|
|
8450
8888
|
uiSystem.drawText(this.text, pos, textSize,
|
|
8451
|
-
this.textColor, 0, undefined, 'left', this.font, false);
|
|
8889
|
+
this.textColor, 0, undefined, 'left', this.font, this.fontStyle, false);
|
|
8452
8890
|
}
|
|
8453
8891
|
}
|
|
8454
8892
|
|
|
@@ -8456,6 +8894,7 @@ class UICheckbox extends UIObject
|
|
|
8456
8894
|
/**
|
|
8457
8895
|
* UIScrollbar - A UI object that acts as a scrollbar
|
|
8458
8896
|
* @extends UIObject
|
|
8897
|
+
* @memberof UISystem
|
|
8459
8898
|
*/
|
|
8460
8899
|
class UIScrollbar extends UIObject
|
|
8461
8900
|
{
|
|
@@ -8471,14 +8910,19 @@ class UIScrollbar extends UIObject
|
|
|
8471
8910
|
{
|
|
8472
8911
|
super(pos, size);
|
|
8473
8912
|
|
|
8913
|
+
ASSERT(isNumber(value), 'ui scrollbar value must be a number');
|
|
8914
|
+
ASSERT(isString(text), 'ui scrollbar must be a string');
|
|
8915
|
+
ASSERT(isColor(color), 'ui scrollbar color must be a color');
|
|
8916
|
+
ASSERT(isColor(handleColor), 'ui scrollbar handleColor must be a color');
|
|
8917
|
+
|
|
8474
8918
|
/** @property {number} - Current percentage value of this scrollbar 0-1 */
|
|
8475
8919
|
this.value = value;
|
|
8476
8920
|
/** @property {Color} - Color for the handle part of the scrollbar */
|
|
8477
|
-
this.handleColor = handleColor;
|
|
8921
|
+
this.handleColor = handleColor.copy();
|
|
8478
8922
|
|
|
8479
8923
|
// set properties
|
|
8480
8924
|
this.text = text;
|
|
8481
|
-
this.color = color;
|
|
8925
|
+
this.color = color.copy();
|
|
8482
8926
|
this.interactive = true;
|
|
8483
8927
|
}
|
|
8484
8928
|
update()
|
|
@@ -8486,34 +8930,47 @@ class UIScrollbar extends UIObject
|
|
|
8486
8930
|
super.update();
|
|
8487
8931
|
if (this.isActiveObject() && this.interactive)
|
|
8488
8932
|
{
|
|
8933
|
+
// handle horizontal or vertical scrollbar
|
|
8934
|
+
const isHorizontal = this.size.x > this.size.y;
|
|
8935
|
+
const handleSize = isHorizontal ? this.size.y : this.size.x;
|
|
8936
|
+
const barSize = isHorizontal ? this.size.x : this.size.y;
|
|
8937
|
+
const centerPos = isHorizontal ? this.pos.x : this.pos.y;
|
|
8938
|
+
|
|
8489
8939
|
// check if value changed
|
|
8490
|
-
const
|
|
8491
|
-
const
|
|
8492
|
-
const
|
|
8493
|
-
const p2 = this.pos.x + handleWidth/2;
|
|
8940
|
+
const handleWidth = barSize - handleSize;
|
|
8941
|
+
const p1 = centerPos - handleWidth/2;
|
|
8942
|
+
const p2 = centerPos + handleWidth/2;
|
|
8494
8943
|
const oldValue = this.value;
|
|
8495
|
-
this.value =
|
|
8944
|
+
this.value = isHorizontal ?
|
|
8945
|
+
percent(mousePosScreen.x, p1, p2) :
|
|
8946
|
+
percent(mousePosScreen.y, p2, p1);
|
|
8496
8947
|
this.value === oldValue || this.onChange();
|
|
8497
8948
|
}
|
|
8498
8949
|
}
|
|
8499
8950
|
render()
|
|
8500
8951
|
{
|
|
8501
8952
|
super.render();
|
|
8502
|
-
|
|
8953
|
+
|
|
8954
|
+
// handle horizontal or vertical scrollbar
|
|
8955
|
+
const isHorizontal = this.size.x > this.size.y;
|
|
8956
|
+
const handleSize = isHorizontal ? this.size.y : this.size.x;
|
|
8957
|
+
const barSize = isHorizontal ? this.size.x : this.size.y;
|
|
8958
|
+
const centerPos = isHorizontal ? this.pos.x : this.pos.y;
|
|
8959
|
+
|
|
8503
8960
|
// draw the scrollbar handle
|
|
8504
|
-
const
|
|
8505
|
-
const
|
|
8506
|
-
const
|
|
8507
|
-
const
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
uiSystem.drawRect(handlePos, handleSize, handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
8961
|
+
const handleWidth = barSize - handleSize;
|
|
8962
|
+
const p1 = centerPos - handleWidth/2;
|
|
8963
|
+
const p2 = centerPos + handleWidth/2;
|
|
8964
|
+
const handlePos = isHorizontal ?
|
|
8965
|
+
vec2(lerp(p1, p2, this.value), this.pos.y) :
|
|
8966
|
+
vec2(this.pos.x, lerp(p2, p1, this.value))
|
|
8967
|
+
const handleColor = this.disabled ? this.disabledColor : this.handleColor;
|
|
8968
|
+
uiSystem.drawRect(handlePos, vec2(handleSize), handleColor, this.lineWidth, this.lineColor, this.cornerRadius);
|
|
8512
8969
|
|
|
8513
8970
|
// draw the text scaled to fit on the scrollbar
|
|
8514
8971
|
const textSize = this.getTextSize();
|
|
8515
8972
|
uiSystem.drawText(this.text, this.pos, textSize,
|
|
8516
|
-
this.textColor, 0, undefined, this.align, this.font);
|
|
8973
|
+
this.textColor, 0, undefined, this.align, this.font, this.fontStyle);
|
|
8517
8974
|
}
|
|
8518
8975
|
}
|
|
8519
8976
|
/**
|
|
@@ -8555,6 +9012,7 @@ function box2dSetDebug(enable) { box2dDebug = enable; }
|
|
|
8555
9012
|
* - Each object has a Box2D body which can have multiple fixtures and joints
|
|
8556
9013
|
* - Provides interface for Box2D body and fixture functions
|
|
8557
9014
|
* @extends EngineObject
|
|
9015
|
+
* @memberof Box2D
|
|
8558
9016
|
*/
|
|
8559
9017
|
class Box2dObject extends EngineObject
|
|
8560
9018
|
{
|
|
@@ -8566,7 +9024,7 @@ class Box2dObject extends EngineObject
|
|
|
8566
9024
|
* @param {Color} [color]
|
|
8567
9025
|
* @param {number} [bodyType]
|
|
8568
9026
|
* @param {number} [renderOrder] */
|
|
8569
|
-
constructor(pos
|
|
9027
|
+
constructor(pos, size, tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
|
|
8570
9028
|
{
|
|
8571
9029
|
super(pos, size, tileInfo, angle, color, renderOrder);
|
|
8572
9030
|
|
|
@@ -8580,7 +9038,7 @@ class Box2dObject extends EngineObject
|
|
|
8580
9038
|
this.lineColor = BLACK;
|
|
8581
9039
|
}
|
|
8582
9040
|
|
|
8583
|
-
/** Destroy this object and
|
|
9041
|
+
/** Destroy this object and its physics body */
|
|
8584
9042
|
destroy()
|
|
8585
9043
|
{
|
|
8586
9044
|
// destroy physics body, fixtures, and joints
|
|
@@ -9064,6 +9522,7 @@ class Box2dRaycastResult
|
|
|
9064
9522
|
* Box2D Joint
|
|
9065
9523
|
* - Base class for Box2D joints
|
|
9066
9524
|
* - A joint is used to connect objects together
|
|
9525
|
+
* @memberof Box2D
|
|
9067
9526
|
*/
|
|
9068
9527
|
class Box2dJoint
|
|
9069
9528
|
{
|
|
@@ -9119,6 +9578,7 @@ class Box2dJoint
|
|
|
9119
9578
|
* - This a soft constraint with a max force
|
|
9120
9579
|
* - This allows the constraint to stretch and without applying huge forces
|
|
9121
9580
|
* @extends Box2dJoint
|
|
9581
|
+
* @memberof Box2D
|
|
9122
9582
|
*/
|
|
9123
9583
|
class Box2dTargetJoint extends Box2dJoint
|
|
9124
9584
|
{
|
|
@@ -9168,6 +9628,7 @@ class Box2dTargetJoint extends Box2dJoint
|
|
|
9168
9628
|
* - Constrains two points on two objects to remain at a fixed distance
|
|
9169
9629
|
* - You can view this as a massless, rigid rod
|
|
9170
9630
|
* @extends Box2dJoint
|
|
9631
|
+
* @memberof Box2D
|
|
9171
9632
|
*/
|
|
9172
9633
|
class Box2dDistanceJoint extends Box2dJoint
|
|
9173
9634
|
{
|
|
@@ -9231,6 +9692,7 @@ class Box2dDistanceJoint extends Box2dJoint
|
|
|
9231
9692
|
* Box2D Pin Joint
|
|
9232
9693
|
* - Pins two objects together at a point
|
|
9233
9694
|
* @extends Box2dDistanceJoint
|
|
9695
|
+
* @memberof Box2D
|
|
9234
9696
|
*/
|
|
9235
9697
|
class Box2dPinJoint extends Box2dDistanceJoint
|
|
9236
9698
|
{
|
|
@@ -9250,6 +9712,7 @@ class Box2dPinJoint extends Box2dDistanceJoint
|
|
|
9250
9712
|
* Box2D Rope Joint
|
|
9251
9713
|
* - Enforces a maximum distance between two points on two objects
|
|
9252
9714
|
* @extends Box2dJoint
|
|
9715
|
+
* @memberof Box2D
|
|
9253
9716
|
*/
|
|
9254
9717
|
class Box2dRopeJoint extends Box2dJoint
|
|
9255
9718
|
{
|
|
@@ -9302,6 +9765,7 @@ class Box2dRopeJoint extends Box2dJoint
|
|
|
9302
9765
|
* - You can use a motor to drive the relative rotation about the shared point
|
|
9303
9766
|
* - A maximum motor torque is provided so that infinite forces are not generated
|
|
9304
9767
|
* @extends Box2dJoint
|
|
9768
|
+
* @memberof Box2D
|
|
9305
9769
|
*/
|
|
9306
9770
|
class Box2dRevoluteJoint extends Box2dJoint
|
|
9307
9771
|
{
|
|
@@ -9403,6 +9867,7 @@ class Box2dRevoluteJoint extends Box2dJoint
|
|
|
9403
9867
|
* - Either joint can be a revolute or prismatic joint
|
|
9404
9868
|
* - You specify a gear ratio to bind the motions together
|
|
9405
9869
|
* @extends Box2dJoint
|
|
9870
|
+
* @memberof Box2D
|
|
9406
9871
|
*/
|
|
9407
9872
|
class Box2dGearJoint extends Box2dJoint
|
|
9408
9873
|
{
|
|
@@ -9451,6 +9916,7 @@ class Box2dGearJoint extends Box2dJoint
|
|
|
9451
9916
|
* - You can use a joint limit to restrict the range of motion
|
|
9452
9917
|
* - You can use a joint motor to drive the motion or to model joint friction
|
|
9453
9918
|
* @extends Box2dJoint
|
|
9919
|
+
* @memberof Box2D
|
|
9454
9920
|
*/
|
|
9455
9921
|
class Box2dPrismaticJoint extends Box2dJoint
|
|
9456
9922
|
{
|
|
@@ -9560,6 +10026,7 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
9560
10026
|
* - You can use a joint motor to drive the motion or to model joint friction
|
|
9561
10027
|
* - This joint is designed for vehicle suspensions
|
|
9562
10028
|
* @extends Box2dJoint
|
|
10029
|
+
* @memberof Box2D
|
|
9563
10030
|
*/
|
|
9564
10031
|
class Box2dWheelJoint extends Box2dJoint
|
|
9565
10032
|
{
|
|
@@ -9655,6 +10122,7 @@ class Box2dWheelJoint extends Box2dJoint
|
|
|
9655
10122
|
* Box2D Weld Joint
|
|
9656
10123
|
* - Glues two objects together
|
|
9657
10124
|
* @extends Box2dJoint
|
|
10125
|
+
* @memberof Box2D
|
|
9658
10126
|
*/
|
|
9659
10127
|
class Box2dWeldJoint extends Box2dJoint
|
|
9660
10128
|
{
|
|
@@ -9713,6 +10181,7 @@ class Box2dWeldJoint extends Box2dJoint
|
|
|
9713
10181
|
* - Used to apply top-down friction
|
|
9714
10182
|
* - Provides 2D translational friction and angular friction
|
|
9715
10183
|
* @extends Box2dJoint
|
|
10184
|
+
* @memberof Box2D
|
|
9716
10185
|
*/
|
|
9717
10186
|
class Box2dFrictionJoint extends Box2dJoint
|
|
9718
10187
|
{
|
|
@@ -9767,6 +10236,7 @@ class Box2dFrictionJoint extends Box2dJoint
|
|
|
9767
10236
|
* - The pulley supports a ratio such that: length1 + ratio * length2 <= constant
|
|
9768
10237
|
* - The force transmitted is scaled by the ratio
|
|
9769
10238
|
* @extends Box2dJoint
|
|
10239
|
+
* @memberof Box2D
|
|
9770
10240
|
*/
|
|
9771
10241
|
class Box2dPulleyJoint extends Box2dJoint
|
|
9772
10242
|
{
|
|
@@ -9834,6 +10304,7 @@ class Box2dPulleyJoint extends Box2dJoint
|
|
|
9834
10304
|
* - Controls the relative motion between two objects
|
|
9835
10305
|
* - Typical usage is to control the movement of a object with respect to the ground
|
|
9836
10306
|
* @extends Box2dJoint
|
|
10307
|
+
* @memberof Box2D
|
|
9837
10308
|
*/
|
|
9838
10309
|
class Box2dMotorJoint extends Box2dJoint
|
|
9839
10310
|
{
|
|
@@ -9897,6 +10368,7 @@ class Box2dMotorJoint extends Box2dJoint
|
|
|
9897
10368
|
/**
|
|
9898
10369
|
* Box2D Global Object
|
|
9899
10370
|
* - Wraps Box2d world and provides global functions
|
|
10371
|
+
* @memberof Box2D
|
|
9900
10372
|
*/
|
|
9901
10373
|
class Box2dPlugin
|
|
9902
10374
|
{
|
|
@@ -10615,6 +11087,7 @@ export
|
|
|
10615
11087
|
isColor,
|
|
10616
11088
|
isVector2,
|
|
10617
11089
|
isNumber,
|
|
11090
|
+
isString,
|
|
10618
11091
|
|
|
10619
11092
|
// Default Colors
|
|
10620
11093
|
WHITE,
|
|
@@ -10750,7 +11223,7 @@ export
|
|
|
10750
11223
|
tileCollisionGetData,
|
|
10751
11224
|
tileCollisionTest,
|
|
10752
11225
|
tileCollisionRaycast,
|
|
10753
|
-
|
|
11226
|
+
tileLayersLoad,
|
|
10754
11227
|
TileLayerData,
|
|
10755
11228
|
CanvasLayer,
|
|
10756
11229
|
TileLayer,
|