littlejsengine 1.14.11 → 1.14.16
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 +252 -109
- package/dist/littlejs.esm.js +559 -252
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +557 -251
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +482 -206
- package/examples/box2d/gameObjects.js +2 -2
- 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 +3 -3
- package/examples/electron/index.html +2 -2
- package/examples/electron/package.json +1 -8
- package/examples/index.html +10 -7
- package/examples/module/game.js +3 -3
- package/examples/platformer/gameEffects.js +4 -4
- 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 +134 -0
- package/examples/shorts/nineSlice.js +34 -15
- package/examples/shorts/parallax.js +4 -3
- 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/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 +14 -3
- 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 +3 -3
- package/examples/starter/index.html +2 -2
- package/examples/typescript/game.js +3 -3
- package/examples/typescript/game.ts +3 -3
- package/examples/uiSystem/game.js +3 -3
- package/package.json +4 -2
- package/plugins/box2d.js +17 -2
- package/plugins/newgrounds.js +7 -5
- package/plugins/postProcess.js +5 -2
- package/plugins/uiSystem.js +120 -34
- package/plugins/zzfxm.js +5 -1
- package/reference.md +1 -1
- package/src/engine.js +35 -12
- package/src/engineAudio.js +31 -14
- package/src/engineDebug.js +75 -45
- package/src/engineDraw.js +60 -31
- package/src/engineExport.js +2 -1
- package/src/engineMedals.js +10 -2
- package/src/engineObject.js +14 -10
- package/src/engineParticles.js +59 -46
- package/src/engineSettings.js +7 -7
- package/src/engineTileLayer.js +45 -20
- package/src/engineUtilities.js +67 -20
package/src/engine.js
CHANGED
|
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
|
|
|
30
30
|
* @type {string}
|
|
31
31
|
* @default
|
|
32
32
|
* @memberof Engine */
|
|
33
|
-
const engineVersion = '1.14.
|
|
33
|
+
const engineVersion = '1.14.16';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {number}
|
|
@@ -93,9 +93,14 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
|
93
93
|
|
|
94
94
|
const pluginUpdateList = [], pluginRenderList = [];
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* @callback PluginCallback - Update or render function for a plugin
|
|
98
|
+
* @memberof Engine
|
|
99
|
+
*/
|
|
100
|
+
|
|
96
101
|
/** Add a new update function for a plugin
|
|
97
|
-
* @param {
|
|
98
|
-
* @param {
|
|
102
|
+
* @param {PluginCallback} [updateFunction]
|
|
103
|
+
* @param {PluginCallback} [renderFunction]
|
|
99
104
|
* @memberof Engine */
|
|
100
105
|
function engineAddPlugin(updateFunction, renderFunction)
|
|
101
106
|
{
|
|
@@ -108,12 +113,22 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
108
113
|
///////////////////////////////////////////////////////////////////////////////
|
|
109
114
|
// Main Engine Functions
|
|
110
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @callback GameInitCallback - Called after the engine starts, can be async
|
|
118
|
+
* @returns {void|Promise<void>}
|
|
119
|
+
* @memberof Engine
|
|
120
|
+
*/
|
|
121
|
+
/**
|
|
122
|
+
* @callback GameCallback - Update or render function for the game
|
|
123
|
+
* @memberof Engine
|
|
124
|
+
*/
|
|
125
|
+
|
|
111
126
|
/** Startup LittleJS engine with your callback functions
|
|
112
|
-
* @param {
|
|
113
|
-
* @param {
|
|
114
|
-
* @param {
|
|
115
|
-
* @param {
|
|
116
|
-
* @param {
|
|
127
|
+
* @param {GameInitCallback} gameInit - Called once after the engine starts up, can be async for loading
|
|
128
|
+
* @param {GameCallback} gameUpdate - Called every frame before objects are updated (60fps), use for game logic
|
|
129
|
+
* @param {GameCallback} gameUpdatePost - Called after physics and objects are updated, even when paused, use for UI updates
|
|
130
|
+
* @param {GameCallback} gameRender - Called before objects are rendered, use for drawing backgrounds/world elements
|
|
131
|
+
* @param {GameCallback} gameRenderPost - Called after objects are rendered, use for drawing UI/overlays
|
|
117
132
|
* @param {Array<string>} [imageSources=[]] - List of image file paths to preload (e.g., ['player.png', 'tiles.png'])
|
|
118
133
|
* @param {HTMLElement} [rootElement] - Root DOM element to attach canvas to, defaults to document.body
|
|
119
134
|
* @example
|
|
@@ -365,6 +380,8 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
365
380
|
const promises = imageSources.map((src, textureIndex)=>
|
|
366
381
|
new Promise(resolve =>
|
|
367
382
|
{
|
|
383
|
+
ASSERT(isString(src), 'imageSources must be an array of strings');
|
|
384
|
+
|
|
368
385
|
const image = new Image;
|
|
369
386
|
image.onerror = image.onload = ()=>
|
|
370
387
|
{
|
|
@@ -396,7 +413,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
396
413
|
promises.push(new Promise(resolve =>
|
|
397
414
|
{
|
|
398
415
|
let t = 0;
|
|
399
|
-
|
|
416
|
+
console.log(`${engineName} Engine v${engineVersion}`);
|
|
400
417
|
updateSplash();
|
|
401
418
|
function updateSplash()
|
|
402
419
|
{
|
|
@@ -480,10 +497,16 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
480
497
|
return collectedObjects;
|
|
481
498
|
}
|
|
482
499
|
|
|
500
|
+
/**
|
|
501
|
+
* @callback ObjectCallbackFunction - Function that processes an object
|
|
502
|
+
* @param {EngineObject} uiObjects
|
|
503
|
+
* @memberof Engine
|
|
504
|
+
*/
|
|
505
|
+
|
|
483
506
|
/** Triggers a callback for each object within a given area
|
|
484
|
-
* @param {Vector2} [pos]
|
|
485
|
-
* @param {Vector2|number} [size]
|
|
486
|
-
* @param {
|
|
507
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
508
|
+
* @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
509
|
+
* @param {ObjectCallbackFunction} [callbackFunction] - Calls this function on every object that passes the test
|
|
487
510
|
* @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
|
|
488
511
|
* @memberof Engine */
|
|
489
512
|
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
package/src/engineAudio.js
CHANGED
|
@@ -47,6 +47,7 @@ function audioInit()
|
|
|
47
47
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
48
48
|
*
|
|
49
49
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
50
|
+
* @memberof Audio
|
|
50
51
|
* @example
|
|
51
52
|
* // create a sound
|
|
52
53
|
* const sound_example = new Sound([.5,.5]);
|
|
@@ -141,12 +142,12 @@ class Sound
|
|
|
141
142
|
|
|
142
143
|
/** Play the sound as a musical note with a semitone offset
|
|
143
144
|
* This can be used to play music with chromatic scales
|
|
144
|
-
* @param {number} semitoneOffset - How many semitones to offset pitch
|
|
145
|
+
* @param {number} [semitoneOffset=0] - How many semitones to offset pitch
|
|
145
146
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
146
147
|
* @param {number} [volume=1] - How much to scale volume by
|
|
147
148
|
* @return {SoundInstance} - The audio source node
|
|
148
149
|
*/
|
|
149
|
-
playNote(semitoneOffset, pos, volume)
|
|
150
|
+
playNote(semitoneOffset=0, pos, volume)
|
|
150
151
|
{
|
|
151
152
|
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
152
153
|
return this.play(pos, volume, pitch, 0);
|
|
@@ -169,6 +170,8 @@ class Sound
|
|
|
169
170
|
/**
|
|
170
171
|
* Sound Wave Object - Stores a wave sound for later use and can be played positionally
|
|
171
172
|
* - this can be used to play wave, mp3, and ogg files
|
|
173
|
+
* @extends Sound
|
|
174
|
+
* @memberof Audio
|
|
172
175
|
* @example
|
|
173
176
|
* // create a sound
|
|
174
177
|
* const sound_example = new SoundWave('sound.mp3');
|
|
@@ -178,22 +181,29 @@ class Sound
|
|
|
178
181
|
*/
|
|
179
182
|
class SoundWave extends Sound
|
|
180
183
|
{
|
|
184
|
+
/**
|
|
185
|
+
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
186
|
+
* @param {SoundWave} sound
|
|
187
|
+
* @memberof Audio
|
|
188
|
+
*/
|
|
189
|
+
|
|
181
190
|
/** Create a sound object and cache the wave file for later use
|
|
182
191
|
* @param {string} filename - Filename of audio file to load
|
|
183
192
|
* @param {number} [randomness] - How much to randomize frequency each time sound plays
|
|
184
193
|
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
185
194
|
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
186
|
-
* @param {
|
|
195
|
+
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
187
196
|
*/
|
|
188
197
|
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
189
198
|
{
|
|
190
199
|
super(undefined, range, taper);
|
|
191
200
|
if (!soundEnable || headlessMode) return;
|
|
201
|
+
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
192
202
|
|
|
193
|
-
/** @property {
|
|
203
|
+
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
194
204
|
this.onloadCallback = onloadCallback;
|
|
195
205
|
this.randomness = randomness;
|
|
196
|
-
this.loadSound(filename);
|
|
206
|
+
filename && this.loadSound(filename);
|
|
197
207
|
}
|
|
198
208
|
|
|
199
209
|
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
@@ -202,6 +212,8 @@ class SoundWave extends Sound
|
|
|
202
212
|
async loadSound(filename)
|
|
203
213
|
{
|
|
204
214
|
const response = await fetch(filename);
|
|
215
|
+
if (!response.ok)
|
|
216
|
+
throw new Error(`Failed to load sound from ${filename}: ${response.status} ${response.statusText}`);
|
|
205
217
|
const arrayBuffer = await response.arrayBuffer();
|
|
206
218
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
207
219
|
|
|
@@ -237,7 +249,7 @@ class SoundWave extends Sound
|
|
|
237
249
|
this.sampleChannels = sampleChannels;
|
|
238
250
|
this.loadedPercent = 1;
|
|
239
251
|
if (this.onloadCallback)
|
|
240
|
-
this.onloadCallback();
|
|
252
|
+
this.onloadCallback(this);
|
|
241
253
|
}
|
|
242
254
|
}
|
|
243
255
|
|
|
@@ -246,6 +258,7 @@ class SoundWave extends Sound
|
|
|
246
258
|
/**
|
|
247
259
|
* Sound Instance - Wraps an AudioBufferSourceNode for individual sound control
|
|
248
260
|
* Represents a single playing instance of a sound with pause/resume capabilities
|
|
261
|
+
* @memberof Audio
|
|
249
262
|
* @example
|
|
250
263
|
* // Play a sound and get an instance for control
|
|
251
264
|
* const jumpSound = new Sound([.5,.5,220]);
|
|
@@ -445,6 +458,12 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
445
458
|
|
|
446
459
|
///////////////////////////////////////////////////////////////////////////////
|
|
447
460
|
|
|
461
|
+
/**
|
|
462
|
+
* @callback AudioEndedCallback - Function called when a sound ends
|
|
463
|
+
* @param {AudioBufferSourceNode} source
|
|
464
|
+
* @memberof Audio
|
|
465
|
+
*/
|
|
466
|
+
|
|
448
467
|
/** Play cached audio samples with given settings
|
|
449
468
|
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
450
469
|
* @param {number} [volume] - How much to scale volume by
|
|
@@ -454,7 +473,7 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
454
473
|
* @param {number} [sampleRate=44100] - Sample rate for the sound
|
|
455
474
|
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
456
475
|
* @param {number} [offset] - Offset in seconds to start playback from
|
|
457
|
-
* @param {
|
|
476
|
+
* @param {AudioEndedCallback} [onended] - Callback for when the sound ends
|
|
458
477
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
459
478
|
* @memberof Audio */
|
|
460
479
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=audioDefaultSampleRate, gainNode, offset=0, onended)
|
|
@@ -486,16 +505,14 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
486
505
|
if (onended)
|
|
487
506
|
source.addEventListener('ended', ()=> onended(source));
|
|
488
507
|
|
|
508
|
+
const startOffset = offset * rate;
|
|
489
509
|
if (!audioIsRunning())
|
|
490
510
|
{
|
|
491
|
-
// fix stalled audio
|
|
492
|
-
audioContext.resume();
|
|
493
|
-
return;
|
|
511
|
+
// fix stalled audio and start
|
|
512
|
+
audioContext.resume().then(()=>source.start(0, startOffset));
|
|
494
513
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const startOffset = offset * rate;
|
|
498
|
-
source.start(0, startOffset);
|
|
514
|
+
else
|
|
515
|
+
source.start(0, startOffset);
|
|
499
516
|
return source;
|
|
500
517
|
}
|
|
501
518
|
|
package/src/engineDebug.js
CHANGED
|
@@ -47,10 +47,16 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
|
|
|
47
47
|
// Debug helper functions
|
|
48
48
|
|
|
49
49
|
/** Asserts if the expression is false, does nothing in release builds
|
|
50
|
+
* Halts execution if the assert fails and throws an error
|
|
50
51
|
* @param {boolean} assert
|
|
51
52
|
* @param {...Object} [output] - error message output
|
|
52
53
|
* @memberof Debug */
|
|
53
|
-
function ASSERT(assert, ...output)
|
|
54
|
+
function ASSERT(assert, ...output)
|
|
55
|
+
{
|
|
56
|
+
if (assert) return;
|
|
57
|
+
console.assert(assert, ...output)
|
|
58
|
+
throw new Error('Assert failed!'); // halt execution
|
|
59
|
+
}
|
|
54
60
|
|
|
55
61
|
/** Log to console if debug is enabled, does nothing in release builds
|
|
56
62
|
* @param {...Object} [output] - message output
|
|
@@ -60,64 +66,72 @@ function LOG(...output) { console.log(...output); }
|
|
|
60
66
|
/** Draw a debug rectangle in world space
|
|
61
67
|
* @param {Vector2} pos
|
|
62
68
|
* @param {Vector2} [size=Vector2()]
|
|
63
|
-
* @param {string}
|
|
64
|
-
* @param {number}
|
|
65
|
-
* @param {number}
|
|
69
|
+
* @param {Color|string} [color]
|
|
70
|
+
* @param {number} [time]
|
|
71
|
+
* @param {number} [angle]
|
|
66
72
|
* @param {boolean} [fill]
|
|
67
73
|
* @memberof Debug */
|
|
68
|
-
function debugRect(pos, size=vec2(), color=
|
|
74
|
+
function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
69
75
|
{
|
|
70
76
|
if (typeof size === 'number')
|
|
71
77
|
size = vec2(size); // allow passing in floats
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
if (isColor(color))
|
|
79
|
+
color = color.toString();
|
|
80
|
+
pos = pos.copy();
|
|
81
|
+
size = size.copy();
|
|
82
|
+
const timer = new Timer(time);
|
|
83
|
+
debugPrimitives.push({pos:pos.copy(), size:size.copy(), color, timer, angle, fill});
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
/** Draw a debug poly in world space
|
|
77
87
|
* @param {Vector2} pos
|
|
78
88
|
* @param {Array<Vector2>} points
|
|
79
|
-
* @param {string}
|
|
80
|
-
* @param {number}
|
|
81
|
-
* @param {number}
|
|
89
|
+
* @param {Color|string} [color]
|
|
90
|
+
* @param {number} [time]
|
|
91
|
+
* @param {number} [angle]
|
|
82
92
|
* @param {boolean} [fill]
|
|
83
93
|
* @memberof Debug */
|
|
84
|
-
function debugPoly(pos, points, color=
|
|
94
|
+
function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
85
95
|
{
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
if (isColor(color))
|
|
97
|
+
color = color.toString();
|
|
98
|
+
pos = pos.copy();
|
|
99
|
+
points = points.map(p=>p.copy());
|
|
100
|
+
const timer = new Timer(time);
|
|
101
|
+
debugPrimitives.push({pos, points, color, timer, angle, fill});
|
|
88
102
|
}
|
|
89
103
|
|
|
90
104
|
/** Draw a debug circle in world space
|
|
91
105
|
* @param {Vector2} pos
|
|
92
|
-
* @param {number}
|
|
93
|
-
* @param {string}
|
|
94
|
-
* @param {number}
|
|
106
|
+
* @param {number} [size] - diameter
|
|
107
|
+
* @param {Color|string} [color]
|
|
108
|
+
* @param {number} [time]
|
|
95
109
|
* @param {boolean} [fill]
|
|
96
110
|
* @memberof Debug */
|
|
97
|
-
function debugCircle(pos, size=0, color=
|
|
111
|
+
function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
|
|
98
112
|
{
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
if (isColor(color))
|
|
114
|
+
color = color.toString();
|
|
115
|
+
pos = pos.copy();
|
|
116
|
+
const timer = new Timer(time);
|
|
117
|
+
debugPrimitives.push({pos, size, color, timer, angle:0, fill});
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
/** Draw a debug point in world space
|
|
104
121
|
* @param {Vector2} pos
|
|
105
|
-
* @param {string}
|
|
106
|
-
* @param {number}
|
|
107
|
-
* @param {number}
|
|
122
|
+
* @param {Color|string} [color]
|
|
123
|
+
* @param {number} [time]
|
|
124
|
+
* @param {number} [angle]
|
|
108
125
|
* @memberof Debug */
|
|
109
126
|
function debugPoint(pos, color, time, angle)
|
|
110
|
-
{
|
|
111
|
-
ASSERT(typeof color === 'string', 'pass in css color strings');
|
|
112
|
-
debugRect(pos, undefined, color, time, angle);
|
|
113
|
-
}
|
|
127
|
+
{ debugRect(pos, undefined, color, time, angle); }
|
|
114
128
|
|
|
115
129
|
/** Draw a debug line in world space
|
|
116
130
|
* @param {Vector2} posA
|
|
117
131
|
* @param {Vector2} posB
|
|
118
|
-
* @param {string}
|
|
119
|
-
* @param {number}
|
|
120
|
-
* @param {number}
|
|
132
|
+
* @param {Color|string} [color]
|
|
133
|
+
* @param {number} [width]
|
|
134
|
+
* @param {number} [time]
|
|
121
135
|
* @memberof Debug */
|
|
122
136
|
function debugLine(posA, posB, color, width=.1, time)
|
|
123
137
|
{
|
|
@@ -131,7 +145,7 @@ function debugLine(posA, posB, color, width=.1, time)
|
|
|
131
145
|
* @param {Vector2} sizeA
|
|
132
146
|
* @param {Vector2} posB
|
|
133
147
|
* @param {Vector2} sizeB
|
|
134
|
-
* @param {string}
|
|
148
|
+
* @param {Color|string} [color]
|
|
135
149
|
* @memberof Debug */
|
|
136
150
|
function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
137
151
|
{
|
|
@@ -147,18 +161,21 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
|
147
161
|
}
|
|
148
162
|
|
|
149
163
|
/** Draw a debug axis aligned bounding box in world space
|
|
150
|
-
* @param {string}
|
|
164
|
+
* @param {string} text
|
|
151
165
|
* @param {Vector2} pos
|
|
152
|
-
* @param {number}
|
|
153
|
-
* @param {string}
|
|
154
|
-
* @param {number}
|
|
155
|
-
* @param {number}
|
|
156
|
-
* @param {string}
|
|
166
|
+
* @param {number} [size]
|
|
167
|
+
* @param {Color|string} [color]
|
|
168
|
+
* @param {number} [time]
|
|
169
|
+
* @param {number} [angle]
|
|
170
|
+
* @param {string} [font]
|
|
157
171
|
* @memberof Debug */
|
|
158
|
-
function debugText(text, pos, size=1, color=
|
|
172
|
+
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace')
|
|
159
173
|
{
|
|
160
|
-
|
|
161
|
-
|
|
174
|
+
if (isColor(color))
|
|
175
|
+
color = color.toString();
|
|
176
|
+
pos = pos.copy();
|
|
177
|
+
const timer = new Timer(time);
|
|
178
|
+
debugPrimitives.push({text, pos, size, color, timer, angle, font});
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
/** Clear all debug primitives in the list
|
|
@@ -209,17 +226,30 @@ function debugSaveDataURL(dataURL, filename)
|
|
|
209
226
|
downloadLink.click();
|
|
210
227
|
}
|
|
211
228
|
|
|
212
|
-
/**
|
|
229
|
+
/** Breaks on all asserts/errors, hides the canvas, and shows message in plain text
|
|
230
|
+
* This is a good function to call at the start of your game to catch all errors
|
|
231
|
+
* In release builds this function has no effect
|
|
213
232
|
* @memberof Debug */
|
|
214
233
|
function debugShowErrors()
|
|
215
234
|
{
|
|
216
235
|
const showError = (message)=>
|
|
217
236
|
{
|
|
218
237
|
// replace entire page with error message
|
|
219
|
-
document.body.style
|
|
220
|
-
document.body.
|
|
221
|
-
document.body.innerHTML = `<pre style=color:#f00;font-size:50px;white-space:pre-wrap>` + message;
|
|
238
|
+
document.body.style = 'background-color:#111;margin:8px';
|
|
239
|
+
document.body.innerHTML = `<pre style=color:#f00;font-size:28px;white-space:pre-wrap>` + message;
|
|
222
240
|
}
|
|
241
|
+
|
|
242
|
+
const originalAssert = console.assert;
|
|
243
|
+
console.assert = (assertion, ...output)=>
|
|
244
|
+
{
|
|
245
|
+
originalAssert(assertion, ...output);
|
|
246
|
+
if (!assertion)
|
|
247
|
+
{
|
|
248
|
+
const message = output.join(' ');
|
|
249
|
+
const stack = new Error().stack;
|
|
250
|
+
throw 'Assertion failed!\n' + message + '\n' + stack;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
223
253
|
onunhandledrejection = (event)=>
|
|
224
254
|
showError(event.reason.stack || event.reason);
|
|
225
255
|
onerror = (message, source, lineno, colno)=>
|
|
@@ -387,7 +417,7 @@ function debugRender()
|
|
|
387
417
|
p.fill && overlayContext.fill();
|
|
388
418
|
overlayContext.stroke();
|
|
389
419
|
}
|
|
390
|
-
else if (p.size === 0 || p.size.x === 0 && p.size.y === 0)
|
|
420
|
+
else if (p.size === 0 || (p.size.x === 0 && p.size.y === 0))
|
|
391
421
|
{
|
|
392
422
|
// point
|
|
393
423
|
overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
|
|
@@ -414,7 +444,7 @@ function debugRender()
|
|
|
414
444
|
});
|
|
415
445
|
|
|
416
446
|
// remove expired primitives
|
|
417
|
-
debugPrimitives = debugPrimitives.filter(r=>r.
|
|
447
|
+
debugPrimitives = debugPrimitives.filter(r=>r.timer<0);
|
|
418
448
|
}
|
|
419
449
|
|
|
420
450
|
if (debugObject)
|
package/src/engineDraw.js
CHANGED
|
@@ -83,7 +83,7 @@ let drawCount;
|
|
|
83
83
|
* Create a tile info object using a grid based system
|
|
84
84
|
* - This can take vecs or floats for easier use and conversion
|
|
85
85
|
* - If an index is passed in, the tile size and index will determine the position
|
|
86
|
-
* @param {Vector2|number} [pos=0] -
|
|
86
|
+
* @param {Vector2|number} [pos=0] - Position of the tile in pixels, or tile index
|
|
87
87
|
* @param {Vector2|number} [size=tileSizeDefault] - Size of tile in pixels
|
|
88
88
|
* @param {number} [textureIndex] - Texture index to use
|
|
89
89
|
* @param {number} [padding] - How many pixels padding around tiles
|
|
@@ -128,6 +128,7 @@ function tile(pos=new Vector2, size=tileSizeDefault, textureIndex=0, padding=0)
|
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
130
|
* Tile Info - Stores info about how to draw a tile
|
|
131
|
+
* @memberof Draw
|
|
131
132
|
*/
|
|
132
133
|
class TileInfo
|
|
133
134
|
{
|
|
@@ -188,7 +189,10 @@ class TileInfo
|
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* Tile Info - Stores info about each texture
|
|
194
|
+
* @memberof Draw
|
|
195
|
+
*/
|
|
192
196
|
class TextureInfo
|
|
193
197
|
{
|
|
194
198
|
/**
|
|
@@ -234,10 +238,11 @@ class TextureInfo
|
|
|
234
238
|
function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
235
239
|
angle=0, mirror, additiveColor, useWebGL=glEnable, screenSpace, context)
|
|
236
240
|
{
|
|
237
|
-
ASSERT(isVector2(pos), '
|
|
238
|
-
ASSERT(isVector2(size), '
|
|
239
|
-
ASSERT(isColor(color)
|
|
240
|
-
ASSERT(isNumber(angle), '
|
|
241
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
242
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
243
|
+
ASSERT(isColor(color), 'color is invalid');
|
|
244
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
245
|
+
ASSERT(!additiveColor || isColor(additiveColor), 'additiveColor must be a color');
|
|
241
246
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
242
247
|
|
|
243
248
|
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
@@ -332,10 +337,10 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
332
337
|
* @memberof Draw */
|
|
333
338
|
function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
334
339
|
{
|
|
335
|
-
ASSERT(isVector2(pos), '
|
|
336
|
-
ASSERT(isVector2(size), '
|
|
337
|
-
ASSERT(isColor(colorTop) && isColor(colorBottom), '
|
|
338
|
-
ASSERT(isNumber(angle), '
|
|
340
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
341
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
342
|
+
ASSERT(isColor(colorTop) && isColor(colorBottom), 'color is invalid');
|
|
343
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
339
344
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
340
345
|
if (useWebGL)
|
|
341
346
|
{
|
|
@@ -393,11 +398,11 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
393
398
|
* @memberof Draw */
|
|
394
399
|
function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
|
|
395
400
|
{
|
|
396
|
-
ASSERT(Array.isArray(points), '
|
|
397
|
-
ASSERT(isNumber(width), '
|
|
398
|
-
ASSERT(isColor(color), '
|
|
399
|
-
ASSERT(isVector2(pos), '
|
|
400
|
-
ASSERT(isNumber(angle), '
|
|
401
|
+
ASSERT(Array.isArray(points), 'points must be an array');
|
|
402
|
+
ASSERT(isNumber(width), 'width must be a number');
|
|
403
|
+
ASSERT(isColor(color), 'color is invalid');
|
|
404
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
405
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
401
406
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
402
407
|
if (useWebGL)
|
|
403
408
|
{
|
|
@@ -468,8 +473,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
468
473
|
* @memberof Draw */
|
|
469
474
|
function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, lineColor=BLACK, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
470
475
|
{
|
|
471
|
-
ASSERT(isVector2(size), '
|
|
472
|
-
ASSERT(isNumber(sides), '
|
|
476
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
477
|
+
ASSERT(isNumber(sides), 'sides must be a number');
|
|
473
478
|
|
|
474
479
|
// build regular polygon points
|
|
475
480
|
const points = [];
|
|
@@ -495,12 +500,13 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
495
500
|
* @memberof Draw */
|
|
496
501
|
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
497
502
|
{
|
|
498
|
-
ASSERT(isVector2(pos), '
|
|
499
|
-
ASSERT(Array.isArray(points), '
|
|
500
|
-
ASSERT(isColor(color) && isColor(lineColor), '
|
|
501
|
-
ASSERT(isNumber(lineWidth), '
|
|
502
|
-
ASSERT(isNumber(angle), '
|
|
503
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
504
|
+
ASSERT(Array.isArray(points), 'points must be an array');
|
|
505
|
+
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
506
|
+
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
507
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
503
508
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
509
|
+
|
|
504
510
|
if (useWebGL)
|
|
505
511
|
{
|
|
506
512
|
let scale = 1;
|
|
@@ -547,13 +553,14 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
547
553
|
* @memberof Draw */
|
|
548
554
|
function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
549
555
|
{
|
|
550
|
-
ASSERT(isVector2(pos), '
|
|
551
|
-
ASSERT(isVector2(size), '
|
|
552
|
-
ASSERT(isColor(color) && isColor(lineColor), '
|
|
553
|
-
ASSERT(isNumber(angle), '
|
|
554
|
-
ASSERT(isNumber(lineWidth), '
|
|
555
|
-
ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, '
|
|
556
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
557
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
558
|
+
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
559
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
560
|
+
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
561
|
+
ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'invalid lineWidth');
|
|
556
562
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
563
|
+
|
|
557
564
|
if (useWebGL)
|
|
558
565
|
{
|
|
559
566
|
// draw as a regular polygon
|
|
@@ -590,21 +597,32 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
590
597
|
* @memberof Draw */
|
|
591
598
|
function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useWebGL=glEnable, screenSpace=false, context)
|
|
592
599
|
{
|
|
593
|
-
ASSERT(isNumber(size), '
|
|
600
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
594
601
|
drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
|
|
595
602
|
}
|
|
596
603
|
|
|
604
|
+
/**
|
|
605
|
+
* @callback Canvas2DDrawFunction - A function that draws to a 2D canvas context
|
|
606
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
607
|
+
* @memberof Draw
|
|
608
|
+
*/
|
|
609
|
+
|
|
597
610
|
/** Draw directly to a 2d canvas context in world space
|
|
598
611
|
* @param {Vector2} pos
|
|
599
612
|
* @param {Vector2} size
|
|
600
613
|
* @param {number} angle
|
|
601
614
|
* @param {boolean} [mirror]
|
|
602
|
-
* @param {
|
|
615
|
+
* @param {Canvas2DDrawFunction} [drawFunction]
|
|
603
616
|
* @param {boolean} [screenSpace=false]
|
|
604
617
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
605
618
|
* @memberof Draw */
|
|
606
619
|
function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpace=false, context=drawContext)
|
|
607
620
|
{
|
|
621
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
622
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
623
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
624
|
+
ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
|
|
625
|
+
|
|
608
626
|
if (!screenSpace)
|
|
609
627
|
{
|
|
610
628
|
// transform from world space to screen space
|
|
@@ -672,6 +690,16 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
672
690
|
* @memberof Draw */
|
|
673
691
|
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, maxWidth, context=overlayContext)
|
|
674
692
|
{
|
|
693
|
+
ASSERT(isString(text), 'text must be a string');
|
|
694
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
695
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
696
|
+
ASSERT(isColor(color), 'color must be a color');
|
|
697
|
+
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
698
|
+
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
699
|
+
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
700
|
+
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
701
|
+
ASSERT(isString(font), 'font must be a string');
|
|
702
|
+
|
|
675
703
|
context.fillStyle = color.toString();
|
|
676
704
|
context.strokeStyle = lineColor.toString();
|
|
677
705
|
context.lineWidth = lineWidth;
|
|
@@ -873,6 +901,7 @@ let engineFontImage;
|
|
|
873
901
|
* - 96 characters (from space to tilde) are stored in an image
|
|
874
902
|
* - Uses a default 8x8 font if none is supplied
|
|
875
903
|
* - You can also use fonts from the main tile sheet
|
|
904
|
+
* @memberof Draw
|
|
876
905
|
* @example
|
|
877
906
|
* // use built in font
|
|
878
907
|
* const font = new FontImage;
|
|
@@ -929,7 +958,7 @@ class FontImage
|
|
|
929
958
|
* @param {boolean} [center]
|
|
930
959
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
931
960
|
*/
|
|
932
|
-
drawTextScreen(text, pos, scale=4, center, context=overlayContext)
|
|
961
|
+
drawTextScreen(text, pos, scale=4, center=true, context=overlayContext)
|
|
933
962
|
{
|
|
934
963
|
context.save();
|
|
935
964
|
const size = this.tileSize;
|
package/src/engineExport.js
CHANGED
|
@@ -178,6 +178,7 @@ export
|
|
|
178
178
|
isColor,
|
|
179
179
|
isVector2,
|
|
180
180
|
isNumber,
|
|
181
|
+
isString,
|
|
181
182
|
|
|
182
183
|
// Default Colors
|
|
183
184
|
WHITE,
|
|
@@ -313,7 +314,7 @@ export
|
|
|
313
314
|
tileCollisionGetData,
|
|
314
315
|
tileCollisionTest,
|
|
315
316
|
tileCollisionRaycast,
|
|
316
|
-
|
|
317
|
+
tileLayersLoad,
|
|
317
318
|
TileLayerData,
|
|
318
319
|
CanvasLayer,
|
|
319
320
|
TileLayer,
|