littlejsengine 1.14.19 → 1.14.28
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 +217 -38
- package/dist/littlejs.esm.js +879 -324
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +861 -324
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +799 -325
- package/examples/box2d/gameObjects.js +6 -10
- package/examples/box2d/scenes.js +2 -2
- package/examples/breakout/gameObjects.js +1 -1
- package/examples/electron/game.js +1 -41
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/game.js +1 -1
- package/examples/index.html +19 -10
- package/examples/module/game.js +5 -7
- package/examples/particles/index.html +6 -9
- package/examples/platformer/gameCharacter.js +4 -3
- package/examples/platformer/gameEffects.js +7 -2
- package/examples/platformer/gameObjects.js +6 -11
- package/examples/shorts/animation.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/box2d.js +2 -2
- package/examples/shorts/box2dCar.js +18 -7
- package/examples/shorts/box2dPool.js +108 -0
- package/examples/shorts/cameraDrag.js +22 -0
- package/examples/shorts/debugDraw.js +37 -0
- package/examples/shorts/flappyGame.js +1 -0
- package/examples/shorts/fps.js +90 -0
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/hillGlideGame.js +1 -3
- package/examples/shorts/input.js +59 -0
- package/examples/shorts/landerGame.js +1 -3
- package/examples/shorts/medals.js +15 -9
- package/examples/shorts/music.js +15 -17
- package/examples/shorts/musicPlayer.js +24 -24
- package/examples/shorts/platformer.js +0 -2
- package/examples/shorts/postProcess.js +1 -1
- package/examples/shorts/sequencer.js +3 -3
- package/examples/shorts/slidingPuzzle.js +1 -1
- package/examples/shorts/spaceGame.js +1 -3
- package/examples/shorts/spriteAtlas.js +6 -8
- package/examples/shorts/starfield.js +1 -1
- package/examples/shorts/tileRaycast.js +39 -0
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +1 -4
- package/examples/shorts/timers.js +1 -2
- package/examples/shorts/topDown.js +0 -2
- package/examples/starter/game.js +5 -7
- package/examples/starter/index.html +2 -2
- package/examples/typescript/game.js +3 -2
- package/examples/typescript/game.ts +5 -7
- package/examples/uiSystem/game.js +2 -2
- package/package.json +1 -1
- package/plugins/box2d.js +170 -50
- package/plugins/pluginExport.js +2 -0
- package/plugins/uiSystem.js +55 -27
- package/src/engine.js +19 -16
- package/src/engineAudio.js +27 -15
- package/src/engineDebug.js +64 -0
- package/src/engineDraw.js +88 -32
- package/src/engineExport.js +16 -0
- package/src/engineInput.js +35 -25
- package/src/engineMedals.js +2 -2
- package/src/engineObject.js +55 -10
- package/src/engineParticles.js +36 -20
- package/src/engineRelease.js +2 -1
- package/src/engineSettings.js +20 -1
- package/src/engineTileLayer.js +64 -59
- package/src/engineUtilities.js +213 -59
- package/src/engineWebGL.js +13 -8
- package/examples/shorts/raycasting.js +0 -79
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.
|
|
33
|
+
const engineVersion = '1.15.0';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {number}
|
|
@@ -161,7 +161,7 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
161
161
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
162
162
|
{
|
|
163
163
|
ASSERT(!mainContext, 'engine already initialized');
|
|
164
|
-
ASSERT(
|
|
164
|
+
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
165
165
|
|
|
166
166
|
// allow passing in empty functions
|
|
167
167
|
gameInit ||= ()=>{};
|
|
@@ -453,21 +453,24 @@ function engineObjectsUpdate()
|
|
|
453
453
|
// recursive object update
|
|
454
454
|
function updateObject(o)
|
|
455
455
|
{
|
|
456
|
-
if (
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
456
|
+
if (o.destroyed)
|
|
457
|
+
return;
|
|
458
|
+
|
|
459
|
+
o.update();
|
|
460
|
+
for (const child of o.children)
|
|
461
|
+
updateObject(child);
|
|
462
462
|
}
|
|
463
463
|
for (const o of engineObjects)
|
|
464
464
|
{
|
|
465
|
+
if (o.parent)
|
|
466
|
+
continue;
|
|
467
|
+
|
|
465
468
|
// update top level objects
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
469
|
+
o.update();
|
|
470
|
+
o.updatePhysics();
|
|
471
|
+
for (const child of o.children)
|
|
472
|
+
updateObject(child);
|
|
473
|
+
o.updateTransforms();
|
|
471
474
|
}
|
|
472
475
|
|
|
473
476
|
// remove destroyed objects
|
|
@@ -500,7 +503,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
500
503
|
else if (size instanceof Vector2) // bounding box test
|
|
501
504
|
{
|
|
502
505
|
for (const o of objects)
|
|
503
|
-
isOverlapping(pos, size
|
|
506
|
+
o.isOverlapping(pos, size) && collectedObjects.push(o);
|
|
504
507
|
}
|
|
505
508
|
else // circle test
|
|
506
509
|
{
|
|
@@ -513,7 +516,7 @@ function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
|
513
516
|
|
|
514
517
|
/**
|
|
515
518
|
* @callback ObjectCallbackFunction - Function that processes an object
|
|
516
|
-
* @param {EngineObject}
|
|
519
|
+
* @param {EngineObject} object
|
|
517
520
|
* @memberof Engine
|
|
518
521
|
*/
|
|
519
522
|
|
|
@@ -561,7 +564,7 @@ function drawEngineSplashScreen(t)
|
|
|
561
564
|
// background
|
|
562
565
|
const p3 = percent(t, 1, .8);
|
|
563
566
|
const p4 = percent(t, 0, .5);
|
|
564
|
-
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,
|
|
567
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.7);
|
|
565
568
|
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
566
569
|
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
567
570
|
x.save();
|
package/src/engineAudio.js
CHANGED
|
@@ -66,6 +66,10 @@ class Sound
|
|
|
66
66
|
{
|
|
67
67
|
if (!soundEnable || headlessMode) return;
|
|
68
68
|
|
|
69
|
+
ASSERT(!zzfxSound || isArray(zzfxSound), 'zzfxSound is invalid');
|
|
70
|
+
ASSERT(isNumber(range), 'range must be a number');
|
|
71
|
+
ASSERT(isNumber(taper), 'taper must be a number');
|
|
72
|
+
|
|
69
73
|
/** @property {number} - World space max range of sound */
|
|
70
74
|
this.range = range;
|
|
71
75
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
@@ -104,6 +108,12 @@ class Sound
|
|
|
104
108
|
*/
|
|
105
109
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
|
|
106
110
|
{
|
|
111
|
+
ASSERT(!pos || isVector2(pos), 'pos must be a vec2');
|
|
112
|
+
ASSERT(isNumber(volume), 'volume must be a number');
|
|
113
|
+
ASSERT(isNumber(pitch), 'pitch must be a number');
|
|
114
|
+
ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
|
|
115
|
+
|
|
116
|
+
|
|
107
117
|
if (!soundEnable || headlessMode) return;
|
|
108
118
|
if (!this.sampleChannels) return;
|
|
109
119
|
|
|
@@ -149,6 +159,7 @@ class Sound
|
|
|
149
159
|
*/
|
|
150
160
|
playNote(semitoneOffset=0, pos, volume)
|
|
151
161
|
{
|
|
162
|
+
ASSERT(isNumber(semitoneOffset), 'semitoneOffset must be a number');
|
|
152
163
|
const pitch = getNoteFrequency(semitoneOffset, 1);
|
|
153
164
|
return this.play(pos, volume, pitch, 0);
|
|
154
165
|
}
|
|
@@ -157,7 +168,7 @@ class Sound
|
|
|
157
168
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
158
169
|
*/
|
|
159
170
|
getDuration()
|
|
160
|
-
{ return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
|
|
171
|
+
{ return this.sampleChannels && this.sampleRate ? this.sampleChannels[0].length / this.sampleRate : 0; }
|
|
161
172
|
|
|
162
173
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
163
174
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
@@ -199,6 +210,7 @@ class SoundWave extends Sound
|
|
|
199
210
|
super(undefined, range, taper);
|
|
200
211
|
if (!soundEnable || headlessMode) return;
|
|
201
212
|
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
213
|
+
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
202
214
|
|
|
203
215
|
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
204
216
|
this.onloadCallback = onloadCallback;
|
|
@@ -415,7 +427,7 @@ class SoundInstance
|
|
|
415
427
|
/** Get the total duration of this sound
|
|
416
428
|
* @return {number} - Total duration in seconds
|
|
417
429
|
*/
|
|
418
|
-
getDuration() { return this.sound.getDuration() / this.rate; }
|
|
430
|
+
getDuration() { return this.rate ? this.sound.getDuration() / this.rate : 0; }
|
|
419
431
|
|
|
420
432
|
/** Get source of this sound instance
|
|
421
433
|
* @return {AudioBufferSourceNode}
|
|
@@ -605,10 +617,10 @@ function zzfxG
|
|
|
605
617
|
|
|
606
618
|
// biquad LP/HP filter
|
|
607
619
|
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
608
|
-
|
|
609
|
-
a0 = 1 + alpha, a1 = -2*
|
|
610
|
-
b0 = (1 + sign(filter) *
|
|
611
|
-
b1 = -(sign(filter) +
|
|
620
|
+
cosw = cos(w), alpha = sin(w) / 2 / quality,
|
|
621
|
+
a0 = 1 + alpha, a1 = -2*cosw / a0, a2 = (1 - alpha) / a0,
|
|
622
|
+
b0 = (1 + sign(filter) * cosw) / 2 / a0,
|
|
623
|
+
b1 = -(sign(filter) + cosw) / a0, b2 = b0,
|
|
612
624
|
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
613
625
|
|
|
614
626
|
// scale by sample rate
|
|
@@ -631,15 +643,15 @@ function zzfxG
|
|
|
631
643
|
if (!(++crush%(bitCrush*100|0))) // bit crush
|
|
632
644
|
{
|
|
633
645
|
s = shape? shape>1? shape>2? shape>3? shape>4? // wave shape
|
|
634
|
-
(t/PI2%1 < shapeCurve/2? 1 : -1) :
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
1-(2*t/PI2%2+2)%2:
|
|
638
|
-
1-4*abs(
|
|
639
|
-
|
|
646
|
+
(t/PI2%1 < shapeCurve/2? 1 : -1) : // 5 square duty
|
|
647
|
+
sin(t**3) : // 4 noise
|
|
648
|
+
max(min(tan(t),1),-1): // 3 tan
|
|
649
|
+
1-(2*t/PI2%2+2)%2: // 2 saw
|
|
650
|
+
1-4*abs(round(t/PI2)-t/PI2): // 1 triangle
|
|
651
|
+
sin(t); // 0 sin
|
|
640
652
|
|
|
641
653
|
s = (repeatTime ?
|
|
642
|
-
1 - tremolo + tremolo*
|
|
654
|
+
1 - tremolo + tremolo*sin(PI2*i/repeatTime) // tremolo
|
|
643
655
|
: 1) *
|
|
644
656
|
(shape>4?s:sign(s)*abs(s)**shapeCurve) * // shape curve
|
|
645
657
|
(i < attack ? i/attack : // attack
|
|
@@ -661,8 +673,8 @@ function zzfxG
|
|
|
661
673
|
}
|
|
662
674
|
|
|
663
675
|
f = (frequency += slide += deltaSlide) *// frequency
|
|
664
|
-
|
|
665
|
-
t += f + f*noise*
|
|
676
|
+
cos(modulation*modOffset++); // modulation
|
|
677
|
+
t += f + f*noise*sin(i**5); // noise
|
|
666
678
|
|
|
667
679
|
if (jump && ++jump > pitchJumpTime) // pitch jump
|
|
668
680
|
{
|
package/src/engineDebug.js
CHANGED
|
@@ -73,6 +73,12 @@ function LOG(...output) { console.log(...output); }
|
|
|
73
73
|
* @memberof Debug */
|
|
74
74
|
function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
75
75
|
{
|
|
76
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
77
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
78
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
79
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
80
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
81
|
+
|
|
76
82
|
if (typeof size === 'number')
|
|
77
83
|
size = vec2(size); // allow passing in floats
|
|
78
84
|
if (isColor(color))
|
|
@@ -93,6 +99,12 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
|
|
|
93
99
|
* @memberof Debug */
|
|
94
100
|
function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
95
101
|
{
|
|
102
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
103
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
104
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
105
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
106
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
107
|
+
|
|
96
108
|
if (isColor(color))
|
|
97
109
|
color = color.toString();
|
|
98
110
|
pos = pos.copy();
|
|
@@ -110,6 +122,11 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
|
|
|
110
122
|
* @memberof Debug */
|
|
111
123
|
function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
|
|
112
124
|
{
|
|
125
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
126
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
127
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
128
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
129
|
+
|
|
113
130
|
if (isColor(color))
|
|
114
131
|
color = color.toString();
|
|
115
132
|
pos = pos.copy();
|
|
@@ -135,6 +152,10 @@ function debugPoint(pos, color, time, angle)
|
|
|
135
152
|
* @memberof Debug */
|
|
136
153
|
function debugLine(posA, posB, color, width=.1, time)
|
|
137
154
|
{
|
|
155
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
156
|
+
ASSERT(isVector2(posB), 'posB must be sa vec2');
|
|
157
|
+
ASSERT(isNumber(width), 'width must be a number');
|
|
158
|
+
|
|
138
159
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
139
160
|
const size = vec2(width, halfDelta.length()*2);
|
|
140
161
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
@@ -149,6 +170,11 @@ function debugLine(posA, posB, color, width=.1, time)
|
|
|
149
170
|
* @memberof Debug */
|
|
150
171
|
function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
151
172
|
{
|
|
173
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
174
|
+
ASSERT(isVector2(posB), 'posB must be a vec2');
|
|
175
|
+
ASSERT(isVector2(sizeA), 'sizeA must be a vec2');
|
|
176
|
+
ASSERT(isVector2(sizeB), 'sizeB must be a vec2');
|
|
177
|
+
|
|
152
178
|
const minPos = vec2(
|
|
153
179
|
min(posA.x - sizeA.x/2, posB.x - sizeB.x/2),
|
|
154
180
|
min(posA.y - sizeA.y/2, posB.y - sizeB.y/2)
|
|
@@ -171,6 +197,14 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
|
|
|
171
197
|
* @memberof Debug */
|
|
172
198
|
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace')
|
|
173
199
|
{
|
|
200
|
+
ASSERT(isString(text), 'text must be a string');
|
|
201
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
202
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
203
|
+
ASSERT(isString(color) || isColor(color), 'color is invalid');
|
|
204
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
205
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
206
|
+
ASSERT(isString(font), 'font must be a string');
|
|
207
|
+
|
|
174
208
|
if (isColor(color))
|
|
175
209
|
color = color.toString();
|
|
176
210
|
pos = pos.copy();
|
|
@@ -622,4 +656,34 @@ function debugVideoCaptureUpdate()
|
|
|
622
656
|
combineCanvases();
|
|
623
657
|
debugVideoCaptureTrack.requestFrame();
|
|
624
658
|
debugVideoCaptureIcon.textContent = '● REC ' + formatTime(debugVideoCaptureTimer);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
662
|
+
// debug utility functions
|
|
663
|
+
|
|
664
|
+
// make color constants immutable with debug assertions
|
|
665
|
+
function debugProtectConstant(obj)
|
|
666
|
+
{
|
|
667
|
+
if (debug)
|
|
668
|
+
{
|
|
669
|
+
// get properties and store original values
|
|
670
|
+
const props = Object.keys(obj), values = {};
|
|
671
|
+
props.forEach(prop => values[prop] = obj[prop]);
|
|
672
|
+
|
|
673
|
+
// replace with getters/setters that assert
|
|
674
|
+
props.forEach(prop =>
|
|
675
|
+
{
|
|
676
|
+
Object.defineProperty(obj, prop, {
|
|
677
|
+
get: () => values[prop],
|
|
678
|
+
set: (value) =>
|
|
679
|
+
{
|
|
680
|
+
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
681
|
+
},
|
|
682
|
+
enumerable: true
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// freeze the object to prevent adding new properties
|
|
688
|
+
return Object.freeze(obj);
|
|
625
689
|
}
|
package/src/engineDraw.js
CHANGED
|
@@ -252,6 +252,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
252
252
|
const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
|
|
253
253
|
if (useWebGL)
|
|
254
254
|
{
|
|
255
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
255
256
|
if (screenSpace)
|
|
256
257
|
{
|
|
257
258
|
// convert to world space
|
|
@@ -347,6 +348,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
347
348
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
348
349
|
if (useWebGL)
|
|
349
350
|
{
|
|
351
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
350
352
|
if (screenSpace)
|
|
351
353
|
{
|
|
352
354
|
// convert to world space
|
|
@@ -358,7 +360,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
358
360
|
const halfSizeX = size.x/2, halfSizeY = size.y/2;
|
|
359
361
|
const colorTopInt = colorTop.rgbaInt();
|
|
360
362
|
const colorBottomInt = colorBottom.rgbaInt();
|
|
361
|
-
const c =
|
|
363
|
+
const c = cos(-angle), s = sin(-angle);
|
|
362
364
|
for (let i=4; i--;)
|
|
363
365
|
{
|
|
364
366
|
const x = i & 1 ? halfSizeX : -halfSizeX;
|
|
@@ -401,7 +403,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
401
403
|
* @memberof Draw */
|
|
402
404
|
function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace, context)
|
|
403
405
|
{
|
|
404
|
-
ASSERT(
|
|
406
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
405
407
|
ASSERT(isNumber(width), 'width must be a number');
|
|
406
408
|
ASSERT(isColor(color), 'color is invalid');
|
|
407
409
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -409,6 +411,7 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
409
411
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
410
412
|
if (useWebGL)
|
|
411
413
|
{
|
|
414
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
412
415
|
let scale = 1;
|
|
413
416
|
if (screenSpace)
|
|
414
417
|
{
|
|
@@ -458,6 +461,8 @@ function drawLine(posA, posB, width=.1, color, pos=vec2(), angle=0, useWebGL, sc
|
|
|
458
461
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
459
462
|
const size = vec2(width, halfDelta.length()*2);
|
|
460
463
|
pos = pos.add(posA.add(halfDelta));
|
|
464
|
+
if (screenSpace)
|
|
465
|
+
halfDelta.y *= -1; // flip angle Y if screen space
|
|
461
466
|
angle += halfDelta.angle();
|
|
462
467
|
drawRect(pos, size, color, angle, useWebGL, screenSpace, context);
|
|
463
468
|
}
|
|
@@ -485,7 +490,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
485
490
|
for (let i=sides; i--;)
|
|
486
491
|
{
|
|
487
492
|
const a = (i/sides)*PI*2;
|
|
488
|
-
points.push(vec2(
|
|
493
|
+
points.push(vec2(sin(a)*sizeX, cos(a)*sizeY));
|
|
489
494
|
}
|
|
490
495
|
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, screenSpace, context);
|
|
491
496
|
}
|
|
@@ -504,7 +509,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
504
509
|
function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(), angle=0, useWebGL=glEnable, screenSpace=false, context=undefined)
|
|
505
510
|
{
|
|
506
511
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
507
|
-
ASSERT(
|
|
512
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
508
513
|
ASSERT(isColor(color) && isColor(lineColor), 'color is invalid');
|
|
509
514
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
510
515
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
@@ -512,6 +517,7 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
512
517
|
|
|
513
518
|
if (useWebGL)
|
|
514
519
|
{
|
|
520
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
515
521
|
let scale = 1;
|
|
516
522
|
if (screenSpace)
|
|
517
523
|
{
|
|
@@ -655,11 +661,12 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
655
661
|
* @param {string} [font=fontDefault]
|
|
656
662
|
* @param {string} [fontStyle]
|
|
657
663
|
* @param {number} [maxWidth]
|
|
664
|
+
* @param {number} [angle]
|
|
658
665
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=drawContext]
|
|
659
666
|
* @memberof Draw */
|
|
660
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, context=drawContext)
|
|
667
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0, context=drawContext)
|
|
661
668
|
{
|
|
662
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, context);
|
|
669
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
|
|
663
670
|
}
|
|
664
671
|
|
|
665
672
|
/** Draw text on overlay canvas in world space
|
|
@@ -674,10 +681,11 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
674
681
|
* @param {string} [font=fontDefault]
|
|
675
682
|
* @param {string} [fontStyle]
|
|
676
683
|
* @param {number} [maxWidth]
|
|
684
|
+
* @param {number} [angle]
|
|
677
685
|
* @memberof Draw */
|
|
678
|
-
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth)
|
|
686
|
+
function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0)
|
|
679
687
|
{
|
|
680
|
-
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, overlayContext);
|
|
688
|
+
drawText(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, overlayContext);
|
|
681
689
|
}
|
|
682
690
|
|
|
683
691
|
/** Draw text on overlay canvas in screen space
|
|
@@ -692,9 +700,10 @@ function drawTextOverlay(text, pos, size=1, color, lineWidth=0, lineColor, textA
|
|
|
692
700
|
* @param {string} [font=fontDefault]
|
|
693
701
|
* @param {string} [fontStyle]
|
|
694
702
|
* @param {number} [maxWidth]
|
|
703
|
+
* @param {number} [angle]
|
|
695
704
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
696
705
|
* @memberof Draw */
|
|
697
|
-
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, context=overlayContext)
|
|
706
|
+
function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=overlayContext)
|
|
698
707
|
{
|
|
699
708
|
ASSERT(isString(text), 'text must be a string');
|
|
700
709
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
@@ -705,6 +714,7 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
705
714
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
706
715
|
ASSERT(isString(font), 'font must be a string');
|
|
707
716
|
ASSERT(isString(fontStyle), 'fontStyle must be a string');
|
|
717
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
708
718
|
|
|
709
719
|
context.fillStyle = color.toString();
|
|
710
720
|
context.strokeStyle = lineColor.toString();
|
|
@@ -714,14 +724,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
714
724
|
context.textBaseline = 'middle';
|
|
715
725
|
|
|
716
726
|
const lines = (text+'').split('\n');
|
|
717
|
-
|
|
718
|
-
|
|
727
|
+
const posY = pos.y - (lines.length-1) * size/2; // center vertically
|
|
728
|
+
context.save();
|
|
729
|
+
context.translate(pos.x, posY);
|
|
730
|
+
context.rotate(-angle);
|
|
731
|
+
let yOffset = 0;
|
|
719
732
|
lines.forEach(line=>
|
|
720
733
|
{
|
|
721
|
-
lineWidth && context.strokeText(line,
|
|
722
|
-
context.fillText(line,
|
|
723
|
-
|
|
734
|
+
lineWidth && context.strokeText(line, 0, yOffset, maxWidth);
|
|
735
|
+
context.fillText(line, 0, yOffset, maxWidth);
|
|
736
|
+
yOffset += size;
|
|
724
737
|
});
|
|
738
|
+
context.restore();
|
|
725
739
|
}
|
|
726
740
|
|
|
727
741
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -733,18 +747,18 @@ function drawTextScreen(text, pos, size=1, color=WHITE, lineWidth=0, lineColor=B
|
|
|
733
747
|
* @memberof Draw */
|
|
734
748
|
function screenToWorld(screenPos)
|
|
735
749
|
{
|
|
736
|
-
let
|
|
737
|
-
let
|
|
750
|
+
let x = (screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale;
|
|
751
|
+
let y = (screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale;
|
|
738
752
|
if (cameraAngle)
|
|
739
753
|
{
|
|
740
754
|
// apply camera rotation
|
|
741
|
-
const
|
|
742
|
-
const rotatedX =
|
|
743
|
-
const rotatedY =
|
|
744
|
-
|
|
745
|
-
|
|
755
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
756
|
+
const rotatedX = x * c - y * s;
|
|
757
|
+
const rotatedY = x * s + y * c;
|
|
758
|
+
x = rotatedX;
|
|
759
|
+
y = rotatedY;
|
|
746
760
|
}
|
|
747
|
-
return new Vector2(
|
|
761
|
+
return new Vector2(x + cameraPos.x, y + cameraPos.y);
|
|
748
762
|
}
|
|
749
763
|
|
|
750
764
|
/** Convert from world to screen space coordinates
|
|
@@ -753,24 +767,64 @@ function screenToWorld(screenPos)
|
|
|
753
767
|
* @memberof Draw */
|
|
754
768
|
function worldToScreen(worldPos)
|
|
755
769
|
{
|
|
756
|
-
let
|
|
757
|
-
let
|
|
770
|
+
let x = worldPos.x - cameraPos.x;
|
|
771
|
+
let y = worldPos.y - cameraPos.y;
|
|
758
772
|
if (cameraAngle)
|
|
759
773
|
{
|
|
760
774
|
// apply inverse camera rotation
|
|
761
|
-
const
|
|
762
|
-
const rotatedX =
|
|
763
|
-
const rotatedY =
|
|
764
|
-
|
|
765
|
-
|
|
775
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
776
|
+
const rotatedX = x * c - y * s;
|
|
777
|
+
const rotatedY = x * s + y * c;
|
|
778
|
+
x = rotatedX;
|
|
779
|
+
y = rotatedY;
|
|
766
780
|
}
|
|
767
781
|
return new Vector2
|
|
768
782
|
(
|
|
769
|
-
|
|
770
|
-
|
|
783
|
+
x * cameraScale + mainCanvasSize.x/2 - .5,
|
|
784
|
+
y * -cameraScale + mainCanvasSize.y/2 - .5
|
|
771
785
|
);
|
|
772
786
|
}
|
|
773
787
|
|
|
788
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
789
|
+
* @param {Vector2} screenDelta
|
|
790
|
+
* @return {Vector2}
|
|
791
|
+
* @memberof Draw */
|
|
792
|
+
function screenToWorldDelta(screenDelta)
|
|
793
|
+
{
|
|
794
|
+
let x = screenDelta.x / cameraScale;
|
|
795
|
+
let y = screenDelta.y / -cameraScale;
|
|
796
|
+
if (cameraAngle)
|
|
797
|
+
{
|
|
798
|
+
// apply camera rotation
|
|
799
|
+
const c = cos(-cameraAngle), s = sin(-cameraAngle);
|
|
800
|
+
const rotatedX = x * c - y * s;
|
|
801
|
+
const rotatedY = x * s + y * c;
|
|
802
|
+
x = rotatedX;
|
|
803
|
+
y = rotatedY;
|
|
804
|
+
}
|
|
805
|
+
return new Vector2(x, y);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/** Convert from screen to world space coordinates for a directional vector (no translation)
|
|
809
|
+
* @param {Vector2} worldDelta
|
|
810
|
+
* @return {Vector2}
|
|
811
|
+
* @memberof Draw */
|
|
812
|
+
function worldToScreenDelta(worldDelta)
|
|
813
|
+
{
|
|
814
|
+
let x = worldDelta.x;
|
|
815
|
+
let y = worldDelta.y;
|
|
816
|
+
if (cameraAngle)
|
|
817
|
+
{
|
|
818
|
+
// apply inverse camera rotation
|
|
819
|
+
const c = cos(cameraAngle), s = sin(cameraAngle);
|
|
820
|
+
const rotatedX = x * c - y * s;
|
|
821
|
+
const rotatedY = x * s + y * c;
|
|
822
|
+
x = rotatedX;
|
|
823
|
+
y = rotatedY;
|
|
824
|
+
}
|
|
825
|
+
return new Vector2(x * cameraScale, y * -cameraScale);
|
|
826
|
+
}
|
|
827
|
+
|
|
774
828
|
/** Get the camera's visible area in world space
|
|
775
829
|
* @return {Vector2}
|
|
776
830
|
* @memberof Draw */
|
|
@@ -823,6 +877,8 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
823
877
|
function isBlack(c) { return c.r <= 0 && c.g <= 0 && c.b <= 0 && c.a <= 0; }
|
|
824
878
|
const sx2 = bleedScale;
|
|
825
879
|
const sy2 = bleedScale;
|
|
880
|
+
sWidth = max(1,sWidth|0);
|
|
881
|
+
sHeight = max(1,sHeight|0);
|
|
826
882
|
const sWidth2 = sWidth - 2*bleedScale;
|
|
827
883
|
const sHeight2 = sHeight - 2*bleedScale;
|
|
828
884
|
if (!canvasColorTiles || (additiveColor ? isWhite(color.add(additiveColor)) && additiveColor.a <= 0 : isWhite(color)))
|
|
@@ -837,7 +893,7 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
837
893
|
// copy to offscreen canvas
|
|
838
894
|
workCanvas.width = sWidth;
|
|
839
895
|
workCanvas.height = sHeight;
|
|
840
|
-
workContext.drawImage(image, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
896
|
+
workContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
841
897
|
|
|
842
898
|
// tint image using offscreen work context
|
|
843
899
|
const imageData = workContext.getImageData(0, 0, sWidth, sHeight);
|
package/src/engineExport.js
CHANGED
|
@@ -79,6 +79,7 @@ export
|
|
|
79
79
|
gamepadDirectionEmulateStick,
|
|
80
80
|
inputWASDEmulateDirection,
|
|
81
81
|
touchGamepadEnable,
|
|
82
|
+
touchGamepadCenterButton,
|
|
82
83
|
touchGamepadAnalog,
|
|
83
84
|
touchGamepadSize,
|
|
84
85
|
touchGamepadAlpha,
|
|
@@ -122,6 +123,7 @@ export
|
|
|
122
123
|
setGamepadDirectionEmulateStick,
|
|
123
124
|
setInputWASDEmulateDirection,
|
|
124
125
|
setTouchGamepadEnable,
|
|
126
|
+
setTouchGamepadCenterButton,
|
|
125
127
|
setTouchGamepadAnalog,
|
|
126
128
|
setTouchGamepadSize,
|
|
127
129
|
setTouchGamepadAlpha,
|
|
@@ -140,9 +142,18 @@ export
|
|
|
140
142
|
// Utilities
|
|
141
143
|
PI,
|
|
142
144
|
abs,
|
|
145
|
+
floor,
|
|
146
|
+
ceil,
|
|
147
|
+
round,
|
|
143
148
|
min,
|
|
144
149
|
max,
|
|
145
150
|
sign,
|
|
151
|
+
hypot,
|
|
152
|
+
log2,
|
|
153
|
+
sin,
|
|
154
|
+
cos,
|
|
155
|
+
tan,
|
|
156
|
+
atan2,
|
|
146
157
|
mod,
|
|
147
158
|
clamp,
|
|
148
159
|
percent,
|
|
@@ -162,6 +173,7 @@ export
|
|
|
162
173
|
// Random
|
|
163
174
|
rand,
|
|
164
175
|
randInt,
|
|
176
|
+
randBool,
|
|
165
177
|
randSign,
|
|
166
178
|
randInCircle,
|
|
167
179
|
randVec2,
|
|
@@ -179,6 +191,7 @@ export
|
|
|
179
191
|
isVector2,
|
|
180
192
|
isNumber,
|
|
181
193
|
isString,
|
|
194
|
+
isArray,
|
|
182
195
|
|
|
183
196
|
// Default Colors
|
|
184
197
|
WHITE,
|
|
@@ -210,6 +223,8 @@ export
|
|
|
210
223
|
drawCount,
|
|
211
224
|
screenToWorld,
|
|
212
225
|
worldToScreen,
|
|
226
|
+
screenToWorldDelta,
|
|
227
|
+
worldToScreenDelta,
|
|
213
228
|
drawTile,
|
|
214
229
|
drawRect,
|
|
215
230
|
drawRectGradient,
|
|
@@ -277,6 +292,7 @@ export
|
|
|
277
292
|
mouseDelta,
|
|
278
293
|
mouseDeltaScreen,
|
|
279
294
|
mouseWheel,
|
|
295
|
+
mouseInWindow,
|
|
280
296
|
isUsingGamepad,
|
|
281
297
|
inputPreventDefault,
|
|
282
298
|
setInputPreventDefault,
|