littlejsengine 1.18.2 → 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/LICENSE +5 -27
- package/README.md +19 -1
- package/dist/littlejs.d.ts +550 -57
- package/dist/littlejs.esm.js +2208 -492
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +2165 -480
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +2166 -481
- package/package.json +8 -1
- package/plugins/box2d.js +36 -9
- package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
- package/plugins/pathFinder.js +758 -0
- package/plugins/pluginExport.js +26 -0
- package/plugins/tween.js +509 -0
- package/plugins/tweenSystem.js +509 -0
- package/plugins/uiSystem.js +151 -12
- package/src/engine.js +9 -145
- package/src/engineAudio.js +8 -5
- package/src/engineBuild.mjs +4 -1
- package/src/engineDebug.js +1 -1
- package/src/engineDraw.js +154 -5
- package/src/engineExport.js +17 -12
- package/src/engineInput.js +27 -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 +17 -47
- package/src/engineUtilities.js +7 -2
- package/src/engineWebGL.js +31 -8
- package/src/jsconfig.json +3 -1
- package/.claude/settings.local.json +0 -7
- package/.github/workflows/test.yml +0 -17
- package/AI.md +0 -172
- 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 -800
- 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/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/uiSystem.js +0 -61
- package/examples/shorts/video.webm +0 -0
- package/examples/shorts/videoPlayer.js +0 -34
- 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/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 -447
- package/test/math.test.mjs +0 -774
- package/test/setup.mjs +0 -22
- package/test/smoke.test.mjs +0 -258
- package/test/util.test.mjs +0 -80
package/src/engineExport.js
CHANGED
|
@@ -11,6 +11,7 @@ export
|
|
|
11
11
|
frameRate,
|
|
12
12
|
timeDelta,
|
|
13
13
|
engineObjects,
|
|
14
|
+
engineObjectsCollide,
|
|
14
15
|
frame,
|
|
15
16
|
time,
|
|
16
17
|
timeReal,
|
|
@@ -33,6 +34,7 @@ export
|
|
|
33
34
|
// Debug
|
|
34
35
|
ASSERT,
|
|
35
36
|
LOG,
|
|
37
|
+
debugPointSize,
|
|
36
38
|
debugRect,
|
|
37
39
|
debugPoly,
|
|
38
40
|
debugCircle,
|
|
@@ -51,6 +53,7 @@ export
|
|
|
51
53
|
cameraPos,
|
|
52
54
|
cameraAngle,
|
|
53
55
|
cameraScale,
|
|
56
|
+
timeScale,
|
|
54
57
|
canvasColorTiles,
|
|
55
58
|
canvasClearColor,
|
|
56
59
|
canvasMaxSize,
|
|
@@ -76,11 +79,14 @@ export
|
|
|
76
79
|
gravity,
|
|
77
80
|
particleEmitRateScale,
|
|
78
81
|
glEnable,
|
|
82
|
+
glCircleSides,
|
|
79
83
|
gamepadsEnable,
|
|
80
84
|
gamepadDirectionEmulateStick,
|
|
81
85
|
inputWASDEmulateDirection,
|
|
86
|
+
touchInputEnable,
|
|
82
87
|
touchGamepadEnable,
|
|
83
88
|
touchGamepadCenterButtonSize,
|
|
89
|
+
touchGamepadButtonCount,
|
|
84
90
|
touchGamepadAnalog,
|
|
85
91
|
touchGamepadSize,
|
|
86
92
|
touchGamepadAlpha,
|
|
@@ -90,14 +96,12 @@ export
|
|
|
90
96
|
soundVolume,
|
|
91
97
|
soundDefaultRange,
|
|
92
98
|
soundDefaultTaper,
|
|
93
|
-
medalDisplayTime,
|
|
94
|
-
medalDisplaySlideTime,
|
|
95
|
-
medalDisplaySize,
|
|
96
99
|
|
|
97
100
|
// Setters for globals
|
|
98
101
|
setCameraPos,
|
|
99
102
|
setCameraAngle,
|
|
100
103
|
setCameraScale,
|
|
104
|
+
setTimeScale,
|
|
101
105
|
setCanvasColorTiles,
|
|
102
106
|
setCanvasClearColor,
|
|
103
107
|
setCanvasMaxSize,
|
|
@@ -123,6 +127,7 @@ export
|
|
|
123
127
|
setObjectMaxSpeed,
|
|
124
128
|
setGravity,
|
|
125
129
|
setParticleEmitRateScale,
|
|
130
|
+
setGLCircleSides,
|
|
126
131
|
setTouchInputEnable,
|
|
127
132
|
setGamepadsEnable,
|
|
128
133
|
setGamepadDirectionEmulateStick,
|
|
@@ -138,10 +143,6 @@ export
|
|
|
138
143
|
setSoundVolume,
|
|
139
144
|
setSoundDefaultRange,
|
|
140
145
|
setSoundDefaultTaper,
|
|
141
|
-
setMedalDisplayTime,
|
|
142
|
-
setMedalDisplaySlideTime,
|
|
143
|
-
setMedalDisplaySize,
|
|
144
|
-
setMedalsPreventUnlock,
|
|
145
146
|
setDebugWatermark,
|
|
146
147
|
setDebugKey,
|
|
147
148
|
|
|
@@ -171,6 +172,7 @@ export
|
|
|
171
172
|
percentLerp,
|
|
172
173
|
smoothStep,
|
|
173
174
|
nearestPowerOfTwo,
|
|
175
|
+
isPowerOfTwo,
|
|
174
176
|
isOverlapping,
|
|
175
177
|
isIntersecting,
|
|
176
178
|
lineTest,
|
|
@@ -246,9 +248,11 @@ export
|
|
|
246
248
|
drawTile,
|
|
247
249
|
drawRect,
|
|
248
250
|
drawRectGradient,
|
|
251
|
+
drawTextureWrapped,
|
|
249
252
|
drawLineList,
|
|
250
253
|
drawLine,
|
|
251
254
|
drawPoly,
|
|
255
|
+
drawRegularPoly,
|
|
252
256
|
drawEllipse,
|
|
253
257
|
drawCircle,
|
|
254
258
|
drawCanvas2D,
|
|
@@ -262,6 +266,7 @@ export
|
|
|
262
266
|
toggleFullscreen,
|
|
263
267
|
setCursor,
|
|
264
268
|
getCameraSize,
|
|
269
|
+
isOnScreen,
|
|
265
270
|
|
|
266
271
|
// WebGL
|
|
267
272
|
glCanvas,
|
|
@@ -269,6 +274,7 @@ export
|
|
|
269
274
|
glAntialias,
|
|
270
275
|
glClearCanvas,
|
|
271
276
|
glSetTexture,
|
|
277
|
+
glSetTextureWrap,
|
|
272
278
|
glCompileShader,
|
|
273
279
|
glCreateProgram,
|
|
274
280
|
glCreateTexture,
|
|
@@ -310,8 +316,11 @@ export
|
|
|
310
316
|
gamepadWasPressed,
|
|
311
317
|
gamepadWasReleased,
|
|
312
318
|
gamepadStick,
|
|
319
|
+
gamepadStickCount,
|
|
313
320
|
gamepadDpad,
|
|
314
321
|
gamepadConnected,
|
|
322
|
+
gamepadVibrate,
|
|
323
|
+
gamepadVibrateStop,
|
|
315
324
|
vibrate,
|
|
316
325
|
vibrateStop,
|
|
317
326
|
pointerLockRequest,
|
|
@@ -322,6 +331,7 @@ export
|
|
|
322
331
|
audioContext,
|
|
323
332
|
audioMasterGain,
|
|
324
333
|
audioDefaultSampleRate,
|
|
334
|
+
audioIsRunning,
|
|
325
335
|
Sound,
|
|
326
336
|
SoundInstance,
|
|
327
337
|
speak,
|
|
@@ -349,9 +359,4 @@ export
|
|
|
349
359
|
ParticleEmitter,
|
|
350
360
|
Particle,
|
|
351
361
|
|
|
352
|
-
// Medals
|
|
353
|
-
medals,
|
|
354
|
-
medalsPreventUnlock,
|
|
355
|
-
medalsInit,
|
|
356
|
-
Medal,
|
|
357
362
|
}
|
package/src/engineInput.js
CHANGED
|
@@ -66,7 +66,7 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
|
66
66
|
* This is useful to disable for html menus so the browser can handle input normally
|
|
67
67
|
* @param {boolean} preventDefault
|
|
68
68
|
* @memberof Input */
|
|
69
|
-
function setInputPreventDefault(preventDefault) { inputPreventDefault = preventDefault; }
|
|
69
|
+
function setInputPreventDefault(preventDefault=true) { inputPreventDefault = preventDefault; }
|
|
70
70
|
|
|
71
71
|
/** Clears an input key state
|
|
72
72
|
* @param {string|number} key
|
|
@@ -260,6 +260,32 @@ function gamepadStickCount(gamepad=gamepadPrimary)
|
|
|
260
260
|
return gamepadStickData[gamepad]?.length ?? 0;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
/** Pulse a gamepad's vibration hardware using the dual-rumble effect if it exists
|
|
264
|
+
* Strong magnitude is usually the left side motor, weak magnitude is usually the right side motor
|
|
265
|
+
* @param {number} [gamepad] - gamepad index
|
|
266
|
+
* @param {number} [duration] - effect duration in ms
|
|
267
|
+
* @param {number} [strongMagnitude] - strong (left) motor intensity, 0 to 1
|
|
268
|
+
* @param {number} [weakMagnitude] - weak (right) motor intensity, 0 to 1
|
|
269
|
+
* @param {number} [startDelay] - delay in ms before the effect starts
|
|
270
|
+
* @memberof Input */
|
|
271
|
+
function gamepadVibrate(gamepad=gamepadPrimary, duration=200, strongMagnitude=1, weakMagnitude=1, startDelay=0)
|
|
272
|
+
{
|
|
273
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
274
|
+
if (!vibrateEnable || headlessMode) return;
|
|
275
|
+
const pad = navigator?.getGamepads?.()[gamepad];
|
|
276
|
+
pad?.vibrationActuator?.playEffect?.('dual-rumble', {duration, strongMagnitude, weakMagnitude, startDelay});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Stop vibration on a gamepad
|
|
280
|
+
* @memberof Input */
|
|
281
|
+
function gamepadVibrateStop(gamepad=gamepadPrimary)
|
|
282
|
+
{
|
|
283
|
+
ASSERT(isNumber(gamepad), 'gamepad must be a number');
|
|
284
|
+
if (!vibrateEnable || headlessMode) return;
|
|
285
|
+
const pad = navigator?.getGamepads?.()[gamepad];
|
|
286
|
+
pad?.vibrationActuator?.reset?.();
|
|
287
|
+
}
|
|
288
|
+
|
|
263
289
|
///////////////////////////////////////////////////////////////////////////////
|
|
264
290
|
|
|
265
291
|
/** Pulse the vibration hardware if it exists
|
|
@@ -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
|
@@ -27,6 +27,18 @@ let cameraAngle = 0;
|
|
|
27
27
|
* @memberof Settings */
|
|
28
28
|
let cameraScale = 32;
|
|
29
29
|
|
|
30
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
31
|
+
// Time settings
|
|
32
|
+
|
|
33
|
+
/** Scale applied to engine time, can be used for slow motion or fast forward
|
|
34
|
+
* - 1 is normal speed, 2 is double speed, 0.5 is half speed
|
|
35
|
+
* - 0 freezes the simulation without setting the paused flag
|
|
36
|
+
* - Should be >= 0; stacks multiplicatively with the debug +/- shortcut
|
|
37
|
+
* @type {number}
|
|
38
|
+
* @default
|
|
39
|
+
* @memberof Settings */
|
|
40
|
+
let timeScale = 1;
|
|
41
|
+
|
|
30
42
|
///////////////////////////////////////////////////////////////////////////////
|
|
31
43
|
// Display settings
|
|
32
44
|
|
|
@@ -310,33 +322,6 @@ let soundDefaultRange = 40;
|
|
|
310
322
|
* @memberof Settings */
|
|
311
323
|
let soundDefaultTaper = .7;
|
|
312
324
|
|
|
313
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
314
|
-
// Medals settings
|
|
315
|
-
|
|
316
|
-
/** How long to show medals for in seconds
|
|
317
|
-
* @type {number}
|
|
318
|
-
* @default
|
|
319
|
-
* @memberof Settings */
|
|
320
|
-
let medalDisplayTime = 5;
|
|
321
|
-
|
|
322
|
-
/** How quickly to slide on/off medals in seconds
|
|
323
|
-
* @type {number}
|
|
324
|
-
* @default
|
|
325
|
-
* @memberof Settings */
|
|
326
|
-
let medalDisplaySlideTime = .5;
|
|
327
|
-
|
|
328
|
-
/** Size of medal display
|
|
329
|
-
* @type {Vector2}
|
|
330
|
-
* @default Vector2(640,80)
|
|
331
|
-
* @memberof Settings */
|
|
332
|
-
let medalDisplaySize = vec2(640, 80);
|
|
333
|
-
|
|
334
|
-
/** Set to stop medals from being unlockable (like if cheats are enabled)
|
|
335
|
-
* @type {boolean}
|
|
336
|
-
* @default
|
|
337
|
-
* @memberof Settings */
|
|
338
|
-
let medalsPreventUnlock = false;
|
|
339
|
-
|
|
340
325
|
///////////////////////////////////////////////////////////////////////////////
|
|
341
326
|
// Setters for global variables
|
|
342
327
|
|
|
@@ -355,6 +340,11 @@ function setCameraAngle(angle) { cameraAngle = angle; }
|
|
|
355
340
|
* @memberof Settings */
|
|
356
341
|
function setCameraScale(scale) { cameraScale = scale; }
|
|
357
342
|
|
|
343
|
+
/** Set scale applied to engine time
|
|
344
|
+
* @param {number} scale
|
|
345
|
+
* @memberof Settings */
|
|
346
|
+
function setTimeScale(scale) { timeScale = scale; }
|
|
347
|
+
|
|
358
348
|
/** Set if tiles should be colorized when using canvas2d
|
|
359
349
|
* This can be slower but results should look nearly identical to WebGL rendering
|
|
360
350
|
* It can be enabled/disabled at any time
|
|
@@ -593,26 +583,6 @@ function setSoundDefaultRange(range) { soundDefaultRange = range; }
|
|
|
593
583
|
* @memberof Settings */
|
|
594
584
|
function setSoundDefaultTaper(taper) { soundDefaultTaper = taper; }
|
|
595
585
|
|
|
596
|
-
/** Set how long to show medals for in seconds
|
|
597
|
-
* @param {number} time
|
|
598
|
-
* @memberof Settings */
|
|
599
|
-
function setMedalDisplayTime(time) { medalDisplayTime = time; }
|
|
600
|
-
|
|
601
|
-
/** Set how quickly to slide on/off medals in seconds
|
|
602
|
-
* @param {number} time
|
|
603
|
-
* @memberof Settings */
|
|
604
|
-
function setMedalDisplaySlideTime(time) { medalDisplaySlideTime = time; }
|
|
605
|
-
|
|
606
|
-
/** Set size of medal display
|
|
607
|
-
* @param {Vector2} size
|
|
608
|
-
* @memberof Settings */
|
|
609
|
-
function setMedalDisplaySize(size) { medalDisplaySize = size.copy(); }
|
|
610
|
-
|
|
611
|
-
/** Set to stop medals from being unlockable
|
|
612
|
-
* @param {boolean} preventUnlock
|
|
613
|
-
* @memberof Settings */
|
|
614
|
-
function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUnlock; }
|
|
615
|
-
|
|
616
586
|
/** Set if watermark with FPS should be shown
|
|
617
587
|
* @param {boolean} show
|
|
618
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
|
|
|
@@ -304,9 +304,8 @@ function glClearCanvas()
|
|
|
304
304
|
/** Set the WebGL texture, called automatically if using multiple textures
|
|
305
305
|
* - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
306
306
|
* @param {WebGLTexture} texture
|
|
307
|
-
* @param {boolean} [wrap] - Should the texture wrap or clamp
|
|
308
307
|
* @memberof WebGL */
|
|
309
|
-
function glSetTexture(texture
|
|
308
|
+
function glSetTexture(texture)
|
|
310
309
|
{
|
|
311
310
|
// must flush cache with the old texture to set a new one
|
|
312
311
|
if (!glContext || texture === glActiveTexture) return;
|
|
@@ -314,11 +313,30 @@ function glSetTexture(texture, wrap=false)
|
|
|
314
313
|
glFlush();
|
|
315
314
|
glActiveTexture = texture;
|
|
316
315
|
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
316
|
+
}
|
|
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);
|
|
317
333
|
|
|
318
|
-
// set wrap mode
|
|
319
334
|
const wrapMode = wrap ? glContext.REPEAT : glContext.CLAMP_TO_EDGE;
|
|
320
335
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_WRAP_S, wrapMode);
|
|
321
336
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_WRAP_T, wrapMode);
|
|
337
|
+
|
|
338
|
+
if (!isCurrent && glActiveTexture)
|
|
339
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture);
|
|
322
340
|
}
|
|
323
341
|
|
|
324
342
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
@@ -365,9 +383,10 @@ function glCreateProgram(vsSource, fsSource)
|
|
|
365
383
|
/** Create WebGL texture from an image and init the texture settings
|
|
366
384
|
* Restores the active texture when done
|
|
367
385
|
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas} [image]
|
|
386
|
+
* @param {boolean} [wrap] - true for REPEAT, false for CLAMP_TO_EDGE
|
|
368
387
|
* @return {WebGLTexture}
|
|
369
388
|
* @memberof WebGL */
|
|
370
|
-
function glCreateTexture(image)
|
|
389
|
+
function glCreateTexture(image, wrap=false)
|
|
371
390
|
{
|
|
372
391
|
if (!glContext) return;
|
|
373
392
|
|
|
@@ -393,6 +412,9 @@ function glCreateTexture(image)
|
|
|
393
412
|
const minFilter = mipMap ? glContext.LINEAR_MIPMAP_LINEAR : magFilter;
|
|
394
413
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, magFilter);
|
|
395
414
|
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, minFilter);
|
|
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);
|
|
396
418
|
if (mipMap)
|
|
397
419
|
glContext.generateMipmap(glContext.TEXTURE_2D);
|
|
398
420
|
|
|
@@ -444,7 +466,7 @@ function glRegisterTextureInfo(textureInfo)
|
|
|
444
466
|
if (textureInfo.glTexture)
|
|
445
467
|
glSetTextureData(textureInfo.glTexture, textureInfo.image);
|
|
446
468
|
else
|
|
447
|
-
textureInfo.glTexture = glCreateTexture(textureInfo.image);
|
|
469
|
+
textureInfo.glTexture = glCreateTexture(textureInfo.image, textureInfo.wrap);
|
|
448
470
|
}
|
|
449
471
|
|
|
450
472
|
/** Tells WebGL to destroy the glTexture and stop tracking it
|
|
@@ -663,6 +685,7 @@ function glSetRenderTarget(texture, clear=false)
|
|
|
663
685
|
glFlush();
|
|
664
686
|
glRenderTarget = undefined;
|
|
665
687
|
glContext.bindFramebuffer(glContext.FRAMEBUFFER, null);
|
|
688
|
+
glContext.viewport(0, 0, mainCanvasSize.x, mainCanvasSize.y);
|
|
666
689
|
}
|
|
667
690
|
}
|
|
668
691
|
|
|
@@ -785,9 +808,9 @@ function glPolyStrip(points)
|
|
|
785
808
|
return area;
|
|
786
809
|
}
|
|
787
810
|
|
|
788
|
-
// ensure counter-clockwise winding
|
|
811
|
+
// ensure counter-clockwise winding (slice first so we don't mutate caller's array)
|
|
789
812
|
if (signedArea(points) < 0)
|
|
790
|
-
points = points.reverse();
|
|
813
|
+
points = points.slice().reverse();
|
|
791
814
|
|
|
792
815
|
// check if point is inside triangle
|
|
793
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
|