littlejsengine 1.18.4 → 1.18.7
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/COPYRIGHT.txt +38 -0
- package/FAQ.md +633 -0
- package/README.md +19 -1
- package/dist/littlejs.d.ts +349 -54
- package/dist/littlejs.esm.js +1469 -448
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1438 -436
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1438 -436
- package/package.json +8 -1
- package/plugins/box2d.js +32 -7
- package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
- package/plugins/pathFinder.js +758 -0
- package/plugins/pluginExport.js +19 -0
- package/plugins/tween.js +1 -1
- package/plugins/tweenSystem.js +509 -0
- package/plugins/uiSystem.js +150 -11
- package/src/engine.js +2 -141
- package/src/engineAudio.js +8 -5
- package/src/engineBuild.mjs +4 -2
- package/src/engineDebug.js +1 -1
- package/src/engineDraw.js +21 -3
- package/src/engineExport.js +12 -12
- package/src/engineInput.js +1 -1
- package/src/engineLogo.js +146 -0
- package/src/engineMath.js +10 -3
- package/src/engineObject.js +11 -5
- package/src/engineParticles.js +6 -3
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +0 -47
- package/src/engineUtilities.js +7 -2
- package/src/engineWebGL.js +34 -7
- package/src/jsconfig.json +3 -1
- package/.github/workflows/test.yml +0 -17
- package/AI.md +0 -173
- package/CLAUDE.md +0 -1
- package/examples/box2d/game.js +0 -192
- package/examples/box2d/gameObjects.js +0 -564
- package/examples/box2d/index.html +0 -9
- package/examples/box2d/scenes.js +0 -194
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +0 -175
- package/examples/breakout/gameObjects.js +0 -147
- package/examples/breakout/index.html +0 -8
- package/examples/breakout/tiles.png +0 -0
- package/examples/breakoutTutorial/README.md +0 -521
- package/examples/breakoutTutorial/game.js +0 -191
- package/examples/breakoutTutorial/images/1.png +0 -0
- package/examples/breakoutTutorial/images/10.png +0 -0
- package/examples/breakoutTutorial/images/11.png +0 -0
- package/examples/breakoutTutorial/images/2.png +0 -0
- package/examples/breakoutTutorial/images/3.png +0 -0
- package/examples/breakoutTutorial/images/4.png +0 -0
- package/examples/breakoutTutorial/images/5.png +0 -0
- package/examples/breakoutTutorial/images/6.png +0 -0
- package/examples/breakoutTutorial/images/7.png +0 -0
- package/examples/breakoutTutorial/images/8.png +0 -0
- package/examples/breakoutTutorial/images/9.png +0 -0
- package/examples/breakoutTutorial/index.html +0 -8
- package/examples/electron/build.mjs +0 -127
- package/examples/electron/electron.js +0 -35
- package/examples/electron/game.js +0 -54
- package/examples/electron/index.html +0 -13
- package/examples/electron/package.json +0 -5
- package/examples/electron/tiles.png +0 -0
- package/examples/empty/game.js +0 -51
- package/examples/empty/index.html +0 -5
- package/examples/empty/tiles.png +0 -0
- package/examples/favicon.png +0 -0
- package/examples/games.jpg +0 -0
- package/examples/htmlMenu/game.js +0 -82
- package/examples/htmlMenu/index.html +0 -45
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +0 -62
- package/examples/logo.png +0 -0
- package/examples/logo2.png +0 -0
- package/examples/module/build.mjs +0 -124
- package/examples/module/game.js +0 -132
- package/examples/module/index.html +0 -10
- package/examples/module/tiles.png +0 -0
- package/examples/particles/index.html +0 -426
- package/examples/particles/tiles.png +0 -0
- package/examples/platformer/data/gameLevelData.tmx +0 -143
- package/examples/platformer/data/gameLevelData.tsx +0 -4
- package/examples/platformer/game.js +0 -147
- package/examples/platformer/gameCharacter.js +0 -309
- package/examples/platformer/gameEffects.js +0 -278
- package/examples/platformer/gameLevel.js +0 -205
- package/examples/platformer/gameLevelData.json +0 -171
- package/examples/platformer/gameObjects.js +0 -378
- package/examples/platformer/gamePlayer.js +0 -35
- package/examples/platformer/index.html +0 -9
- package/examples/platformer/tiles.png +0 -0
- package/examples/platformer/tilesLevel.png +0 -0
- package/examples/puzzle/game.js +0 -331
- package/examples/puzzle/index.html +0 -8
- package/examples/puzzle/tiles.png +0 -0
- package/examples/screenshot.jpg +0 -0
- package/examples/shorts/animation.js +0 -18
- package/examples/shorts/base.html +0 -54
- package/examples/shorts/blending.js +0 -14
- package/examples/shorts/box2d.js +0 -51
- package/examples/shorts/box2dCar.js +0 -59
- package/examples/shorts/box2dPool.js +0 -107
- package/examples/shorts/box2dTileLayer.js +0 -48
- package/examples/shorts/cameraDrag.js +0 -22
- package/examples/shorts/clock.js +0 -21
- package/examples/shorts/colors.js +0 -25
- package/examples/shorts/debugDraw.js +0 -37
- package/examples/shorts/empty.js +0 -30
- package/examples/shorts/flappyGame.js +0 -55
- package/examples/shorts/fontImage.js +0 -17
- package/examples/shorts/fps.js +0 -90
- package/examples/shorts/helloWorld.js +0 -11
- package/examples/shorts/hillGlideGame.js +0 -63
- package/examples/shorts/input.js +0 -64
- package/examples/shorts/landerGame.js +0 -57
- package/examples/shorts/maze.js +0 -48
- package/examples/shorts/medals.js +0 -51
- package/examples/shorts/music.js +0 -78
- package/examples/shorts/musicPlayer.js +0 -137
- package/examples/shorts/nineSlice.js +0 -40
- package/examples/shorts/parallax.js +0 -72
- package/examples/shorts/particles.js +0 -28
- package/examples/shorts/piano.js +0 -44
- package/examples/shorts/platformer.js +0 -45
- package/examples/shorts/pongGame.js +0 -41
- package/examples/shorts/postProcess.js +0 -62
- package/examples/shorts/sequencer.js +0 -124
- package/examples/shorts/shader.js +0 -29
- package/examples/shorts/shapes.js +0 -21
- package/examples/shorts/slidingPuzzle.js +0 -52
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/sound.js +0 -36
- package/examples/shorts/spaceGame.js +0 -58
- package/examples/shorts/spriteAtlas.js +0 -31
- package/examples/shorts/starfield.js +0 -15
- package/examples/shorts/texture.js +0 -16
- package/examples/shorts/textureWrapped.js +0 -12
- package/examples/shorts/tileLayer.js +0 -48
- package/examples/shorts/tileRaycast.js +0 -39
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +0 -63
- package/examples/shorts/timers.js +0 -53
- package/examples/shorts/topDown.js +0 -38
- package/examples/shorts/tween.js +0 -24
- package/examples/shorts/uiSystem.js +0 -61
- package/examples/shorts/video.webm +0 -0
- package/examples/shorts/videoPlayer.js +0 -34
- package/examples/shorts.js +0 -743
- package/examples/starter/build.bat +0 -7
- package/examples/starter/build.mjs +0 -126
- package/examples/starter/game.js +0 -137
- package/examples/starter/index.html +0 -35
- package/examples/starter/tiles.png +0 -0
- package/examples/stress/index.html +0 -173
- package/examples/style.css +0 -150
- package/examples/tweenSystem/game.js +0 -171
- package/examples/tweenSystem/index.html +0 -10
- package/examples/tweenSystem/tiles.png +0 -0
- package/examples/typescript/build.mjs +0 -60
- package/examples/typescript/game.js +0 -100
- package/examples/typescript/game.ts +0 -132
- package/examples/typescript/index.html +0 -10
- package/examples/typescript/tiles.png +0 -0
- package/examples/typescript/tsconfig.json +0 -17
- package/examples/uiSystem/game.js +0 -139
- package/examples/uiSystem/index.html +0 -10
- package/examples/uiSystem/tiles.png +0 -0
- package/jsconfig.json +0 -12
- package/plugins/desktop.ini +0 -2
- package/reference.md +0 -448
- package/test/math.test.mjs +0 -774
- package/test/setup.mjs +0 -22
- package/test/smoke.test.mjs +0 -274
- package/test/tween.test.mjs +0 -576
- package/test/util.test.mjs +0 -80
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Engine Logo
|
|
3
|
+
* - Draws the LittleJS splash screen logo
|
|
4
|
+
* - Used internally during engine startup
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10
|
+
function drawEngineLogo(t)
|
|
11
|
+
{
|
|
12
|
+
const blackAndWhite = 0;
|
|
13
|
+
const showName = 1;
|
|
14
|
+
|
|
15
|
+
// LittleJS Logo and Splash Screen
|
|
16
|
+
const x = mainContext;
|
|
17
|
+
const dpr = canvasPixelRatio ?? (devicePixelRatio || 1);
|
|
18
|
+
const w = mainCanvas.width = innerWidth * dpr;
|
|
19
|
+
const h = mainCanvas.height = innerHeight * dpr;
|
|
20
|
+
{
|
|
21
|
+
// background
|
|
22
|
+
const p3 = percent(t, 1, .8);
|
|
23
|
+
const p4 = percent(t, 0, .5);
|
|
24
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,hypot(w,h)*.6);
|
|
25
|
+
g.addColorStop(0,hsl(0,0,lerp(0,p3/2,p4),p3).toString());
|
|
26
|
+
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
27
|
+
x.save();
|
|
28
|
+
x.fillStyle = g;
|
|
29
|
+
x.fillRect(0,0,w,h);
|
|
30
|
+
}
|
|
31
|
+
const gradient = (X1,Y1,X2,Y2,C,S=1)=>
|
|
32
|
+
{
|
|
33
|
+
if (C >= 0)
|
|
34
|
+
{
|
|
35
|
+
if (blackAndWhite)
|
|
36
|
+
x.fillStyle = '#fff';
|
|
37
|
+
else
|
|
38
|
+
{
|
|
39
|
+
const g = x.fillStyle = x.createLinearGradient(X1,Y1,X2,Y2);
|
|
40
|
+
g.addColorStop(0,color(C,2));
|
|
41
|
+
g.addColorStop(1,color(C,1));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
x.fillStyle = '#000';
|
|
46
|
+
C >= -1 ? (x.fill(), S && x.stroke()) : x.stroke();
|
|
47
|
+
}
|
|
48
|
+
const circle = (X,Y,R,A=0,B=2*PI,C,S)=>
|
|
49
|
+
{
|
|
50
|
+
x.beginPath();
|
|
51
|
+
x.arc(X,Y,R,p*A,p*B);
|
|
52
|
+
gradient(X,Y-R,X,Y+R,C,S);
|
|
53
|
+
}
|
|
54
|
+
const rect = (X,Y,W,H,C)=>
|
|
55
|
+
{
|
|
56
|
+
x.beginPath();
|
|
57
|
+
x.rect(X,Y,W,H*p);
|
|
58
|
+
gradient(X,Y+H,X+W,Y,C);
|
|
59
|
+
}
|
|
60
|
+
const poly = (points,C,Y,H)=>
|
|
61
|
+
{
|
|
62
|
+
x.beginPath();
|
|
63
|
+
for (const p of points)
|
|
64
|
+
x.lineTo(p.x, p.y);
|
|
65
|
+
x.closePath();
|
|
66
|
+
gradient(0, Y, 0, Y+H,C);
|
|
67
|
+
}
|
|
68
|
+
const color = (c,l)=> l?`hsl(${[.95,.56,.13][c%3]*360} 99%${[0,50,75][l]}%`:'#000';
|
|
69
|
+
|
|
70
|
+
// center and fit to screen
|
|
71
|
+
const alpha = oscillate(1,1,t);
|
|
72
|
+
const p = percent(alpha, .1, .5);
|
|
73
|
+
const size = min(6, min(w,h)/99);
|
|
74
|
+
x.translate(w/2,h/2);
|
|
75
|
+
x.scale(size,size);
|
|
76
|
+
x.translate(-40,-35);
|
|
77
|
+
p < 1 && x.setLineDash([99*p,99]);
|
|
78
|
+
x.lineJoin = x.lineCap = 'round';
|
|
79
|
+
x.lineWidth = .1 + p*1.9;
|
|
80
|
+
//x.strokeStyle='#fff7';
|
|
81
|
+
|
|
82
|
+
if (showName)
|
|
83
|
+
{
|
|
84
|
+
// engine name text
|
|
85
|
+
const Y = 54;
|
|
86
|
+
const s = 'LittleJS';
|
|
87
|
+
x.font = '900 15.5px arial';
|
|
88
|
+
x.lineWidth = .1+p*3.9;
|
|
89
|
+
x.textAlign = 'center';
|
|
90
|
+
x.textBaseline = 'top';
|
|
91
|
+
rect(11,Y+1,59,8*p,-1);
|
|
92
|
+
x.beginPath();
|
|
93
|
+
|
|
94
|
+
let w2 = 0;
|
|
95
|
+
for (let i=0;i<s.length;++i)
|
|
96
|
+
w2 += x.measureText(s[i]).width;
|
|
97
|
+
for (let j=2;j--;)
|
|
98
|
+
for (let i=0,X=40-w2/2;i<s.length;++i)
|
|
99
|
+
{
|
|
100
|
+
const w = x.measureText(s[i]).width, X2 = X+w/2;
|
|
101
|
+
gradient(X2,Y,X2+2,Y+13,i>5?1:0);
|
|
102
|
+
x[j?'strokeText':'fillText'](s[i],X2,Y+.5,17*p);
|
|
103
|
+
X += w;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
x.lineWidth = .1 + p*1.9;
|
|
107
|
+
rect(3,Y,73,0); // bottom
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
rect(7,15,26,-7,0); // cab top
|
|
111
|
+
rect(25,15,8,25,-1); // cab front
|
|
112
|
+
rect(10,40,15,-25,1); // cab back
|
|
113
|
+
rect(14,21,7,9,2); // cab window
|
|
114
|
+
rect(38,20,6,-6,2); // little stack
|
|
115
|
+
|
|
116
|
+
// big stack
|
|
117
|
+
rect(49,20,10,-6,0);
|
|
118
|
+
const stackPoints = [vec2(44,8),vec2(64,8),vec2(59,8+6*p),vec2(49,8+6*p)];
|
|
119
|
+
poly(stackPoints,2,8,6*p);
|
|
120
|
+
rect(44,8,20,-7,0);
|
|
121
|
+
|
|
122
|
+
// engine
|
|
123
|
+
for (let i=5;i--;) circle(59-i*6*p,30,10,0,2*PI,1,0);
|
|
124
|
+
circle(59,30,4,0,7,2); // light
|
|
125
|
+
|
|
126
|
+
// engine outline
|
|
127
|
+
rect(35,20,24,0); // top
|
|
128
|
+
circle(59,30,10); // front
|
|
129
|
+
circle(47,30,10,PI/2,PI*3/2); // middle
|
|
130
|
+
circle(35,30,10,PI/2,PI*3/2); // back
|
|
131
|
+
rect(7,40,13,7,-1); // bottom back
|
|
132
|
+
rect(17,40,43,14,-1); // bottom center
|
|
133
|
+
|
|
134
|
+
// wheels
|
|
135
|
+
for (let i=3;i--;) for (let j=2;j--;) circle(17+15*i,47,j?7:1,0,2*PI,2);
|
|
136
|
+
|
|
137
|
+
// cowcatcher
|
|
138
|
+
for (let i=2;i--;)
|
|
139
|
+
{
|
|
140
|
+
let w=6, s=7, o=53+w*p*i
|
|
141
|
+
const points = [vec2(o+s,54),vec2(o,40),vec2(o+w*p,40),vec2(o+s+w*p,54)];
|
|
142
|
+
poly(points,0,40,14);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
x.restore();
|
|
146
|
+
}
|
package/src/engineMath.js
CHANGED
|
@@ -153,7 +153,11 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
|
153
153
|
* @return {number}
|
|
154
154
|
* @memberof Math */
|
|
155
155
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
156
|
-
{
|
|
156
|
+
{
|
|
157
|
+
ASSERT(wrapSize > 0, 'distanceWrap wrapSize must be > 0');
|
|
158
|
+
const d = (valueA - valueB) % wrapSize;
|
|
159
|
+
return d*2 % wrapSize - d;
|
|
160
|
+
}
|
|
157
161
|
|
|
158
162
|
/** Linearly interpolates between values passed in with wrapping
|
|
159
163
|
* @param {number} valueA
|
|
@@ -470,6 +474,7 @@ class RandomGenerator
|
|
|
470
474
|
* @param {number} [seed] - Starting seed or engine default seed */
|
|
471
475
|
constructor(seed = 123456789)
|
|
472
476
|
{
|
|
477
|
+
ASSERT(seed !== 0, 'RandomGenerator seed must be non-zero (xorshift is fixed at 0)');
|
|
473
478
|
/** @property {number} - random seed */
|
|
474
479
|
this.seed = seed;
|
|
475
480
|
}
|
|
@@ -763,8 +768,10 @@ class Vector2
|
|
|
763
768
|
* @return {Vector2} */
|
|
764
769
|
floor() { return new Vector2(floor(this.x), floor(this.y)); }
|
|
765
770
|
|
|
766
|
-
/** Returns a copy of this vector snapped to a grid
|
|
767
|
-
*
|
|
771
|
+
/** Returns a copy of this vector snapped to a grid. Note that `grid` is
|
|
772
|
+
* the number of snap steps per unit (so `grid=2` snaps to halves and
|
|
773
|
+
* `grid=0.5` snaps to twos), not the cell size.
|
|
774
|
+
* @param {number} grid - snap steps per unit
|
|
768
775
|
* @return {Vector2} */
|
|
769
776
|
snap(grid)
|
|
770
777
|
{
|
package/src/engineObject.js
CHANGED
|
@@ -132,10 +132,18 @@ class EngineObject
|
|
|
132
132
|
const parent = this.parent;
|
|
133
133
|
if (parent)
|
|
134
134
|
{
|
|
135
|
-
//
|
|
135
|
+
// compose with parent transform inline to avoid intermediate vector allocs
|
|
136
136
|
const mirror = parent.getMirrorSign();
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
const lp = this.localPos, pp = parent.pos;
|
|
138
|
+
const lx = lp.x*mirror, ly = lp.y, pa = parent.angle;
|
|
139
|
+
if (pa)
|
|
140
|
+
{
|
|
141
|
+
const c = cos(-pa), s = sin(-pa);
|
|
142
|
+
this.pos = new Vector2(lx*c - ly*s + pp.x, lx*s + ly*c + pp.y);
|
|
143
|
+
}
|
|
144
|
+
else
|
|
145
|
+
this.pos = new Vector2(lx + pp.x, ly + pp.y);
|
|
146
|
+
this.angle = mirror*this.localAngle + pa;
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
// update children
|
|
@@ -501,8 +509,6 @@ class EngineObject
|
|
|
501
509
|
* @return {string} */
|
|
502
510
|
toString()
|
|
503
511
|
{
|
|
504
|
-
if (!debug) return;
|
|
505
|
-
|
|
506
512
|
let text = 'type = ' + this.constructor.name;
|
|
507
513
|
if (this.pos.x || this.pos.y)
|
|
508
514
|
text += '\npos = ' + this.pos;
|
package/src/engineParticles.js
CHANGED
|
@@ -315,11 +315,14 @@ class ParticleEmitter extends EngineObject
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
///////////////////////////////////////////////////////////////////////////////
|
|
318
|
+
// scratch vector reused by Particle.render to avoid per-frame allocations
|
|
319
|
+
const particleDrawPos = new Vector2;
|
|
320
|
+
|
|
318
321
|
/**
|
|
319
322
|
* Particle Object - Created automatically by Particle Emitters
|
|
320
323
|
* @memberof Particles
|
|
321
324
|
*/
|
|
322
|
-
class Particle
|
|
325
|
+
class Particle
|
|
323
326
|
{
|
|
324
327
|
/**
|
|
325
328
|
* Create a particle with the passed in settings
|
|
@@ -494,14 +497,14 @@ class Particle
|
|
|
494
497
|
additive && setBlendMode(true);
|
|
495
498
|
|
|
496
499
|
// update the position and angle for drawing
|
|
497
|
-
const pos = this.pos.
|
|
500
|
+
const pos = particleDrawPos.set(this.pos.x, this.pos.y);
|
|
498
501
|
let angle = this.angle;
|
|
499
502
|
if (localSpace)
|
|
500
503
|
{
|
|
501
504
|
// in local space of emitter
|
|
502
505
|
const a = emitter.angle;
|
|
503
506
|
const c = cos(a), s = sin(a);
|
|
504
|
-
pos.set(emitter.pos.x + pos.x*c - pos.y*s,
|
|
507
|
+
pos.set(emitter.pos.x + pos.x*c - pos.y*s,
|
|
505
508
|
emitter.pos.y + pos.x*s + pos.y*c);
|
|
506
509
|
angle += a;
|
|
507
510
|
}
|
package/src/engineRelease.js
CHANGED
package/src/engineSettings.js
CHANGED
|
@@ -322,33 +322,6 @@ let soundDefaultRange = 40;
|
|
|
322
322
|
* @memberof Settings */
|
|
323
323
|
let soundDefaultTaper = .7;
|
|
324
324
|
|
|
325
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
326
|
-
// Medals settings
|
|
327
|
-
|
|
328
|
-
/** How long to show medals for in seconds
|
|
329
|
-
* @type {number}
|
|
330
|
-
* @default
|
|
331
|
-
* @memberof Settings */
|
|
332
|
-
let medalDisplayTime = 5;
|
|
333
|
-
|
|
334
|
-
/** How quickly to slide on/off medals in seconds
|
|
335
|
-
* @type {number}
|
|
336
|
-
* @default
|
|
337
|
-
* @memberof Settings */
|
|
338
|
-
let medalDisplaySlideTime = .5;
|
|
339
|
-
|
|
340
|
-
/** Size of medal display
|
|
341
|
-
* @type {Vector2}
|
|
342
|
-
* @default Vector2(640,80)
|
|
343
|
-
* @memberof Settings */
|
|
344
|
-
let medalDisplaySize = vec2(640, 80);
|
|
345
|
-
|
|
346
|
-
/** Set to stop medals from being unlockable (like if cheats are enabled)
|
|
347
|
-
* @type {boolean}
|
|
348
|
-
* @default
|
|
349
|
-
* @memberof Settings */
|
|
350
|
-
let medalsPreventUnlock = false;
|
|
351
|
-
|
|
352
325
|
///////////////////////////////////////////////////////////////////////////////
|
|
353
326
|
// Setters for global variables
|
|
354
327
|
|
|
@@ -610,26 +583,6 @@ function setSoundDefaultRange(range) { soundDefaultRange = range; }
|
|
|
610
583
|
* @memberof Settings */
|
|
611
584
|
function setSoundDefaultTaper(taper) { soundDefaultTaper = taper; }
|
|
612
585
|
|
|
613
|
-
/** Set how long to show medals for in seconds
|
|
614
|
-
* @param {number} time
|
|
615
|
-
* @memberof Settings */
|
|
616
|
-
function setMedalDisplayTime(time) { medalDisplayTime = time; }
|
|
617
|
-
|
|
618
|
-
/** Set how quickly to slide on/off medals in seconds
|
|
619
|
-
* @param {number} time
|
|
620
|
-
* @memberof Settings */
|
|
621
|
-
function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
|
|
622
|
-
|
|
623
|
-
/** Set size of medal display
|
|
624
|
-
* @param {Vector2} size
|
|
625
|
-
* @memberof Settings */
|
|
626
|
-
function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
|
|
627
|
-
|
|
628
|
-
/** Set to stop medals from being unlockable
|
|
629
|
-
* @param {boolean} preventUnlock
|
|
630
|
-
* @memberof Settings */
|
|
631
|
-
function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }
|
|
632
|
-
|
|
633
586
|
/** Set if watermark with FPS should be shown
|
|
634
587
|
* @param {boolean} show
|
|
635
588
|
* @memberof Debug */
|
package/src/engineUtilities.js
CHANGED
|
@@ -190,9 +190,14 @@ function readSaveData(saveName, defaultSaveData)
|
|
|
190
190
|
{
|
|
191
191
|
ASSERT(isString(saveName), 'loadData requires saveName string');
|
|
192
192
|
|
|
193
|
-
// replace undefined values with defaults
|
|
193
|
+
// replace undefined values with defaults; tolerate corrupt JSON
|
|
194
194
|
const data = localStorage[saveName];
|
|
195
|
-
|
|
195
|
+
let loadedData = {};
|
|
196
|
+
if (data)
|
|
197
|
+
{
|
|
198
|
+
try { loadedData = JSON.parse(data); }
|
|
199
|
+
catch { LOG('readSaveData: corrupt JSON for', saveName, '— using defaults'); }
|
|
200
|
+
}
|
|
196
201
|
return { ...defaultSaveData, ...loadedData };
|
|
197
202
|
}
|
|
198
203
|
|
package/src/engineWebGL.js
CHANGED
|
@@ -99,7 +99,7 @@ function glInit(rootElement)
|
|
|
99
99
|
// reinit WebGL and restore textures
|
|
100
100
|
initWebGL();
|
|
101
101
|
for (const info of glTextureInfos)
|
|
102
|
-
info.glTexture = glCreateTexture(info.image);
|
|
102
|
+
info.glTexture = glCreateTexture(info.image, info.wrap);
|
|
103
103
|
pluginList.forEach(plugin=>plugin.glContextRestored?.());
|
|
104
104
|
});
|
|
105
105
|
|
|
@@ -315,6 +315,30 @@ function glSetTexture(texture)
|
|
|
315
315
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
/** Set the wrap mode (REPEAT or CLAMP_TO_EDGE) on an existing WebGL texture
|
|
319
|
+
* Flushes the current batch only if the texture is the active one
|
|
320
|
+
* @param {WebGLTexture} texture
|
|
321
|
+
* @param {boolean} [wrap] - true for REPEAT, false for CLAMP_TO_EDGE
|
|
322
|
+
* @memberof WebGL */
|
|
323
|
+
function glSetTextureWrap(texture, wrap=true)
|
|
324
|
+
{
|
|
325
|
+
if (!glContext || !texture) return;
|
|
326
|
+
|
|
327
|
+
// flush only if changing wrap on the currently bound texture
|
|
328
|
+
const isCurrent = texture === glActiveTexture;
|
|
329
|
+
if (isCurrent)
|
|
330
|
+
glFlush();
|
|
331
|
+
else
|
|
332
|
+
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
333
|
+
|
|
334
|
+
const wrapMode = wrap ? glContext.REPEAT : glContext.CLAMP_TO_EDGE;
|
|
335
|
+
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_WRAP_S, wrapMode);
|
|
336
|
+
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_WRAP_T, wrapMode);
|
|
337
|
+
|
|
338
|
+
if (!isCurrent && glActiveTexture)
|
|
339
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
340
|
+
}
|
|
341
|
+
|
|
318
342
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
319
343
|
* @param {string} source
|
|
320
344
|
* @param {number} type
|
|
@@ -359,9 +383,10 @@ function glCreateProgram(vsSource, fsSource)
|
|
|
359
383
|
/** Create WebGL texture from an image and init the texture settings
|
|
360
384
|
* Restores the active texture when done
|
|
361
385
|
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas} [image]
|
|
386
|
+
* @param {boolean} [wrap] - true for REPEAT, false for CLAMP_TO_EDGE
|
|
362
387
|
* @return {WebGLTexture}
|
|
363
388
|
* @memberof WebGL */
|
|
364
|
-
function glCreateTexture(image)
|
|
389
|
+
function glCreateTexture(image, wrap=false)
|
|
365
390
|
{
|
|
366
391
|
if (!glContext) return;
|
|
367
392
|
|
|
@@ -387,8 +412,9 @@ function glCreateTexture(image)
|
|
|
387
412
|
const minFilter = mipMap ? glContext.LINEAR_MIPMAP_LINEAR : magFilter;
|
|
388
413
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, magFilter);
|
|
389
414
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
|
|
390
|
-
|
|
391
|
-
glContext.texParameteri(glContext.TEXTURE_2D, glContext.
|
|
415
|
+
const wrapMode = wrap ? glContext.REPEAT : glContext.CLAMP_TO_EDGE;
|
|
416
|
+
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_WRAP_S, wrapMode);
|
|
417
|
+
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_WRAP_T, wrapMode);
|
|
392
418
|
if (mipMap)
|
|
393
419
|
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
394
420
|
|
|
@@ -440,7 +466,7 @@ function glRegisterTextureInfo(textureInfo)
|
|
|
440
466
|
if (textureInfo.glTexture)
|
|
441
467
|
glSetTextureData(textureInfo.glTexture, textureInfo.image);
|
|
442
468
|
else
|
|
443
|
-
textureInfo.glTexture = glCreateTexture(textureInfo.image);
|
|
469
|
+
textureInfo.glTexture = glCreateTexture(textureInfo.image, textureInfo.wrap);
|
|
444
470
|
}
|
|
445
471
|
|
|
446
472
|
/** Tells WebGL to destroy the glTexture and stop tracking it
|
|
@@ -659,6 +685,7 @@ function glSetRenderTarget(texture, clear=false)
|
|
|
659
685
|
glFlush();
|
|
660
686
|
glRenderTarget = undefined;
|
|
661
687
|
glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
|
|
688
|
+
glContext.viewport(0, 0, mainCanvasSize.x, mainCanvasSize.y);
|
|
662
689
|
}
|
|
663
690
|
}
|
|
664
691
|
|
|
@@ -781,9 +808,9 @@ function glPolyStrip(points)
|
|
|
781
808
|
return area;
|
|
782
809
|
}
|
|
783
810
|
|
|
784
|
-
// ensure counter-clockwise winding
|
|
811
|
+
// ensure counter-clockwise winding (slice first so we don't mutate caller's array)
|
|
785
812
|
if (signedArea(points) < 0)
|
|
786
|
-
points = points.reverse();
|
|
813
|
+
points = points.slice().reverse();
|
|
787
814
|
|
|
788
815
|
// check if point is inside triangle
|
|
789
816
|
const e = 1e-9;
|
package/src/jsconfig.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
name: Test
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
pull_request:
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
test:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
steps:
|
|
11
|
-
- uses: actions/checkout@v5
|
|
12
|
-
- uses: actions/setup-node@v5
|
|
13
|
-
with:
|
|
14
|
-
node-version: '22'
|
|
15
|
-
- run: npm install
|
|
16
|
-
- run: npm run build
|
|
17
|
-
- run: npm test
|
package/AI.md
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# LittleJS Project - AI Agent Instructions
|
|
2
|
-
|
|
3
|
-
These instructions are for making changes in the LittleJS repo safely. Optimize for small diffs, clarity, and ease of use.
|
|
4
|
-
|
|
5
|
-
## Non-negotiable rules
|
|
6
|
-
|
|
7
|
-
- **Prefer minimal, local changes.** Do not refactor for style unless asked.
|
|
8
|
-
- **No new runtime dependencies.** Keep LittleJS dependency-free at runtime.
|
|
9
|
-
- **Do not hand-edit generated build artifacts.**
|
|
10
|
-
- Treat `dist/` as generated output.
|
|
11
|
-
- Make changes in `src/` (and `plugins/` when appropriate), then run the build.
|
|
12
|
-
- **Match surrounding style.** Follow the conventions in the files you touch.
|
|
13
|
-
- **Avoid breaking public APIs.** If a change could break users, call it out clearly and offer a compatible alternative.
|
|
14
|
-
|
|
15
|
-
If anything in this doc conflicts with the actual repo behavior, follow the repo behavior and update this doc.
|
|
16
|
-
|
|
17
|
-
## Key resources
|
|
18
|
-
|
|
19
|
-
- `README.md` - Overview and getting started
|
|
20
|
-
- `reference.md` - API quick reference
|
|
21
|
-
- `examples/` - Working examples demonstrating engine features
|
|
22
|
-
|
|
23
|
-
## Architecture overview
|
|
24
|
-
|
|
25
|
-
LittleJS is a modular HTML5 game engine with:
|
|
26
|
-
|
|
27
|
-
- **Core engine**: `src/engine*.js` (main loop, objects, rendering, physics, input, etc.)
|
|
28
|
-
- **Plugins**: `plugins/*.js` (optional features like Box2D, post-processing, UI, audio helpers, etc.)
|
|
29
|
-
- **Build system**: `src/engineBuild.mjs` (concatenates modules into distributable bundles)
|
|
30
|
-
|
|
31
|
-
## Repo structure and file types
|
|
32
|
-
|
|
33
|
-
### Engine source (`src/*.js`)
|
|
34
|
-
- Modular architecture (one subsystem per file)
|
|
35
|
-
- Concatenated at build time (internal source does not use ES modules)
|
|
36
|
-
- Code is vanilla JavaScript with type info expressed via JSDoc comments
|
|
37
|
-
|
|
38
|
-
### Build output (`dist/`)
|
|
39
|
-
Common outputs include:
|
|
40
|
-
- `littlejs.js` - Full bundle (debug features included)
|
|
41
|
-
- `littlejs.release.js` - Production bundle (debug stripped)
|
|
42
|
-
- `littlejs.esm.js` - ES module build (import/export)
|
|
43
|
-
- `littlejs.esm.min.js` - Minified ES module
|
|
44
|
-
- `littlejs.d.ts` - TypeScript definitions
|
|
45
|
-
|
|
46
|
-
Use via script tag or ES module import:
|
|
47
|
-
- `<script src="dist/littlejs.js"></script>`
|
|
48
|
-
- `import * as LJS from './dist/littlejs.esm.js'`
|
|
49
|
-
|
|
50
|
-
Prefer adding new optional features as plugins when it keeps the core simpler.
|
|
51
|
-
|
|
52
|
-
### Examples
|
|
53
|
-
- `examples/starter/` - Plain JavaScript global usage via `<script>` (recommended starting point)
|
|
54
|
-
- `examples/module/` - ES module import pattern
|
|
55
|
-
- `examples/typescript/` - TypeScript example usage
|
|
56
|
-
- `examples/shorts/*.js` - Single-file demos loaded by the shorts harness
|
|
57
|
-
|
|
58
|
-
### Short examples (`examples/shorts/*.js`)
|
|
59
|
-
Short examples are special:
|
|
60
|
-
- Pure JS code file, no HTML
|
|
61
|
-
- No imports, do not use LJS namespace - engine APIs are available globally
|
|
62
|
-
- Override hooks: `gameInit()`, `gameUpdate()`, `gameUpdatePost()`, `gameRender()`, `gameRenderPost()`
|
|
63
|
-
|
|
64
|
-
## Coding conventions
|
|
65
|
-
|
|
66
|
-
### Factory functions vs constructors
|
|
67
|
-
Prefer factory functions for core types:
|
|
68
|
-
- `vec2(x, y)` not `new Vector2(x, y)`
|
|
69
|
-
- `rgb(r, g, b, a)` or `hsl(h, s, l, a)` not `new Color(...)`
|
|
70
|
-
- `tile(index, size)` for tile info
|
|
71
|
-
|
|
72
|
-
Use constructors for game objects and complex types:
|
|
73
|
-
- `new EngineObject(pos, size)`
|
|
74
|
-
- `new ParticleEmitter(...)`
|
|
75
|
-
- `new Sound(zzfxParams)`
|
|
76
|
-
- `new Timer(duration)`
|
|
77
|
-
|
|
78
|
-
### Naming
|
|
79
|
-
- `camelCase` for variables and functions
|
|
80
|
-
- `PascalCase` for classes
|
|
81
|
-
- `UPPER_CASE` for constants that are truly constant (like `PI`)
|
|
82
|
-
|
|
83
|
-
### Code style
|
|
84
|
-
- Use JSDoc with `@memberof` grouping (namespaces: Engine, Math, Draw, Input, Audio, Debug, Settings, etc.)
|
|
85
|
-
- Prefer single-line comments: `// comment`
|
|
86
|
-
- Use `ASSERT(condition, 'error message')` for validation (stripped in release)
|
|
87
|
-
- Use `LOG(...)` for debug output (stripped in release)
|
|
88
|
-
|
|
89
|
-
### Type checking
|
|
90
|
-
Use built-in type helpers for validation:
|
|
91
|
-
```javascript
|
|
92
|
-
isNumber(n) // true if number and not NaN
|
|
93
|
-
isString(s) // true if not null/undefined (has toString)
|
|
94
|
-
isArray(a) // true if array
|
|
95
|
-
isVector2(v) // true if valid Vector2
|
|
96
|
-
isColor(c) // true if valid Color
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Global variables
|
|
100
|
-
- Engine time: `time`, `timeReal`, `frame`, `timeDelta`
|
|
101
|
-
- Camera: `cameraPos`, `cameraScale`, `cameraAngle`
|
|
102
|
-
- Input: `mousePos`, `mousePosScreen`, `mouseWheel`
|
|
103
|
-
- State: `paused`, `debug`, `debugOverlay`
|
|
104
|
-
- Settings are in `engineSettings.js` with corresponding setter functions
|
|
105
|
-
|
|
106
|
-
## Common patterns
|
|
107
|
-
|
|
108
|
-
### Game structure
|
|
109
|
-
```javascript
|
|
110
|
-
function gameInit() { } // Called once after engine starts
|
|
111
|
-
function gameUpdate() { } // Called every frame for game logic
|
|
112
|
-
function gameUpdatePost() { } // Called after physics, even when paused
|
|
113
|
-
function gameRender() { } // Called before objects render
|
|
114
|
-
function gameRenderPost() { } // Called after objects render
|
|
115
|
-
|
|
116
|
-
engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### Creating objects
|
|
120
|
-
```javascript
|
|
121
|
-
class Player extends EngineObject {
|
|
122
|
-
constructor(pos) {
|
|
123
|
-
super(pos, vec2(1), tile(0, 16));
|
|
124
|
-
this.setCollision();
|
|
125
|
-
}
|
|
126
|
-
update() {
|
|
127
|
-
super.update();
|
|
128
|
-
// custom logic
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### Common drawing functions
|
|
134
|
-
```javascript
|
|
135
|
-
drawRect(pos, size, color) // solid rectangle
|
|
136
|
-
drawTile(pos, size, tileInfo, color) // sprite from tile sheet
|
|
137
|
-
drawText(text, pos, size, color) // text rendering
|
|
138
|
-
drawLine(posA, posB, thickness, color) // line between points
|
|
139
|
-
drawEllipse(pos, size, color) // filled ellipse
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## Common pitfalls
|
|
143
|
-
|
|
144
|
-
- **New public APIs must be added to `src/engineExport.js`** - Variables and functions added to engine source files are accessible in script-tag builds automatically, but the ESM build (`littlejs.esm.js`) and TypeScript definitions (`littlejs.d.ts`) only include what's listed in `engineExport.js`. Plugin exports go in `plugins/pluginExport.js`. Forgetting this means ESM/TS users can't access the new API.
|
|
145
|
-
- **ASSERT and LOG are stripped in release builds** - Don't rely on side effects
|
|
146
|
-
- **Don't modify constant colors** - `WHITE`, `BLACK`, `RED`, etc. are frozen; use `.copy()` first
|
|
147
|
-
- **Time variables are global** - `time`, `frame` update automatically each frame
|
|
148
|
-
- **Fixed 60 FPS timestep** - Physics runs at 60 FPS regardless of display refresh rate
|
|
149
|
-
- **WebGL is enabled by default** - Set `glEnable = false` before `engineInit()` for Canvas2D only
|
|
150
|
-
- **Tile coordinates are bottom-left origin** - Y increases upward in world space
|
|
151
|
-
|
|
152
|
-
## Developer workflows
|
|
153
|
-
|
|
154
|
-
### Build
|
|
155
|
-
```bash
|
|
156
|
-
npm run build
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Testing
|
|
160
|
-
```bash
|
|
161
|
-
npm test
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
- Tests target `dist/littlejs.esm.js` — rebuild with `npm run build` after changing source.
|
|
165
|
-
- [test/setup.mjs](test/setup.mjs) stubs minimal DOM and enables headless mode. Tests shouldn't call `engineInit`, `render()`, or assume `time` advances.
|
|
166
|
-
- Zero test dependencies — uses Node's built-in `node --test`. Match the style in [test/](test/) when adding new ones.
|
|
167
|
-
- CI runs build + test on every push/PR ([.github/workflows/test.yml](.github/workflows/test.yml)).
|
|
168
|
-
|
|
169
|
-
### Debug features
|
|
170
|
-
- Press `Esc` to toggle debug overlay
|
|
171
|
-
- Number keys toggle visualizations
|
|
172
|
-
- `+`/`-` keys control time scale
|
|
173
|
-
- Debug functions: `debugRect()`, `debugCircle()`, `debugLine()`, `debugText()`
|
package/CLAUDE.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
See @AI.md for instructions.
|