littlejsengine 1.13.1 → 1.13.4
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/README.md +15 -12
- package/dist/littlejs.d.ts +94 -74
- package/dist/littlejs.esm.js +422 -306
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +416 -306
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +414 -304
- package/examples/box2d/game.js +1 -1
- package/examples/box2d/gameObjects.js +1 -1
- package/examples/box2d/scenes.js +1 -1
- package/examples/breakoutTutorial/README.md +44 -41
- package/examples/breakoutTutorial/game.js +2 -2
- package/examples/electron/build.bat +7 -0
- package/examples/electron/build.js +124 -0
- package/examples/electron/electron.js +35 -0
- package/examples/electron/game.js +140 -0
- package/examples/electron/index.html +13 -0
- package/examples/electron/package.json +12 -0
- package/examples/electron/tiles.png +0 -0
- package/examples/empty/game.js +1 -0
- package/examples/htmlMenu/game.js +1 -1
- package/examples/index.html +154 -93
- package/examples/module/game.js +5 -2
- package/examples/particles/index.html +12 -3
- package/examples/platformer/gameEffects.js +3 -3
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/blending.js +9 -5
- package/examples/shorts/box2d.js +1 -1
- package/examples/shorts/box2dCar.js +1 -1
- package/examples/shorts/clock.js +1 -1
- package/examples/shorts/colors.js +2 -2
- package/examples/shorts/maze.js +1 -1
- package/examples/shorts/nineSlice.js +9 -9
- package/examples/shorts/piano.js +2 -2
- package/examples/shorts/raycasting.js +2 -2
- package/examples/shorts/shapes.js +9 -9
- package/examples/shorts/slidingPuzzle.js +2 -2
- package/examples/shorts/starfield.js +5 -7
- package/examples/shorts/systemFont.js +1 -1
- package/examples/shorts/uiSystem.js +1 -0
- package/examples/starter/build.js +9 -8
- package/examples/starter/game.js +5 -2
- package/examples/starter/index.html +2 -2
- package/examples/stress/index.html +5 -5
- package/examples/style.css +5 -2
- package/examples/typescript/build.js +1 -1
- package/examples/typescript/game.js +2 -2
- package/examples/typescript/game.ts +5 -2
- package/examples/uiSystem/game.js +2 -1
- package/package.json +2 -2
- package/plugins/box2d.js +19 -94
- package/plugins/drawUtilities.js +24 -18
- package/plugins/postProcess.js +7 -0
- package/plugins/uiSystem.js +4 -4
- package/src/engine.js +7 -2
- package/src/engineAudio.js +1 -1
- package/src/engineDebug.js +2 -2
- package/src/engineDraw.js +243 -150
- package/src/engineExport.js +6 -0
- package/src/engineInput.js +2 -2
- package/src/engineMedals.js +4 -4
- package/src/engineObject.js +3 -3
- package/src/engineParticles.js +8 -1
- package/src/engineSettings.js +55 -8
- package/src/engineUtilities.js +7 -7
- package/src/engineWebGL.js +25 -5
package/examples/index.html
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!DOCTYPE html><head>
|
|
2
2
|
<title>LittleJS Example Browser</title>
|
|
3
3
|
<link rel=icon type=image/png href=favicon.png>
|
|
4
|
-
<link rel=stylesheet href=style.css?
|
|
4
|
+
<link rel=stylesheet href=style.css?6>
|
|
5
5
|
<meta charset=utf-8>
|
|
6
6
|
</head><body>
|
|
7
7
|
<div id=container1>
|
|
@@ -21,7 +21,7 @@ Theme:
|
|
|
21
21
|
<option value=24px>XL</option>
|
|
22
22
|
</select>
|
|
23
23
|
<input type=checkbox id=checkboxLiveEdit checked>Live Edit
|
|
24
|
-
<
|
|
24
|
+
<input type=checkbox id=checkboxJumpToError>Jump to Error
|
|
25
25
|
</div>
|
|
26
26
|
<textarea id=textareaCode oninput=codeInput() spellcheck=false></textarea>
|
|
27
27
|
<textarea id=textareaError readonly spellcheck=false></textarea>
|
|
@@ -31,10 +31,11 @@ Theme:
|
|
|
31
31
|
<div id=iframeContainer></div>
|
|
32
32
|
<div>
|
|
33
33
|
<input id=inputSearch placeholder='Search examples...' oninput=filterExamples() onkeydown='if(event.key=="Escape")filterExamples(1)'>
|
|
34
|
-
<button onclick=
|
|
34
|
+
<button id=buttonRestart onclick=restartCode()>Restart</button>
|
|
35
35
|
<button id=buttonPause>Pause</button>
|
|
36
36
|
<button id=buttonScreenshot>Screenshot</button>
|
|
37
37
|
<button id=buttonFullscreen>Fullscreen</button>
|
|
38
|
+
<input type=checkbox id=checkboxWebGL checked>WebGL
|
|
38
39
|
</div>
|
|
39
40
|
<select id=selectExample autofocus onchange=setExample() size=2></select>
|
|
40
41
|
</div>
|
|
@@ -58,50 +59,50 @@ class ExampleInfo
|
|
|
58
59
|
const exampleList =
|
|
59
60
|
[
|
|
60
61
|
new ExampleInfo('--- SHORT EXAMPLES ---'),
|
|
61
|
-
new ExampleInfo('Hello World', 'helloWorld.js', 'Minimal example'),
|
|
62
|
-
new ExampleInfo('Shapes', 'shapes.js', '
|
|
63
|
-
new ExampleInfo('Colors', 'colors.js', '
|
|
64
|
-
new ExampleInfo('Blending', 'blending.js', '
|
|
65
|
-
new ExampleInfo('Timers', 'timers.js', '
|
|
66
|
-
new ExampleInfo('Texture', 'texture.js', '
|
|
67
|
-
new ExampleInfo('Particles', 'particles.js', '
|
|
68
|
-
new ExampleInfo('Play Sound', 'playSound.js', '
|
|
69
|
-
new ExampleInfo('System Font', 'systemFont.js', '
|
|
70
|
-
new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', '
|
|
71
|
-
new ExampleInfo('Animation', 'animation.js', '
|
|
72
|
-
new ExampleInfo('Clock', 'clock.js', '
|
|
73
|
-
new ExampleInfo('Tile Layer', 'tileLayer.js', '
|
|
74
|
-
new ExampleInfo('Piano', 'piano.js', '
|
|
75
|
-
new ExampleInfo('Maze Generator', 'maze.js', '
|
|
76
|
-
new ExampleInfo('Starfield', 'starfield.js', '
|
|
77
|
-
new ExampleInfo('Medals', 'medals.js', '
|
|
62
|
+
new ExampleInfo('Hello World', 'helloWorld.js', 'Minimal starter example - [basic, beginner]'),
|
|
63
|
+
new ExampleInfo('Shapes', 'shapes.js', 'Draw geometric shapes - [graphics, primitives, basic]'),
|
|
64
|
+
new ExampleInfo('Colors', 'colors.js', 'Color object and HSL usage - [graphics, color, basic]'),
|
|
65
|
+
new ExampleInfo('Blending', 'blending.js', 'Blending modes and transparency - [graphics, effects]'),
|
|
66
|
+
new ExampleInfo('Timers', 'timers.js', 'Timer object and timing functions - [utilities, time]'),
|
|
67
|
+
new ExampleInfo('Texture', 'texture.js', 'Display and manipulate textures - [graphics, sprites]'),
|
|
68
|
+
new ExampleInfo('Particles', 'particles.js', 'Particle system basics - [effects, particles]'),
|
|
69
|
+
new ExampleInfo('Play Sound', 'playSound.js', 'Sound effects with ZzFX - [audio, sound]'),
|
|
70
|
+
new ExampleInfo('System Font', 'systemFont.js', 'Built-in bitmap font rendering - [text, font]'),
|
|
71
|
+
new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'Sprite atlas and tile rendering - [graphics, sprites]'),
|
|
72
|
+
new ExampleInfo('Animation', 'animation.js', 'Sprite animation techniques - [animation, sprites]'),
|
|
73
|
+
new ExampleInfo('Clock', 'clock.js', 'Real-time clock display - [time, ui, utilities]'),
|
|
74
|
+
new ExampleInfo('Tile Layer', 'tileLayer.js', 'Tile layer rendering system - [tiles, levels]'),
|
|
75
|
+
new ExampleInfo('Piano', 'piano.js', 'Interactive piano keyboard - [audio, input, ui]'),
|
|
76
|
+
new ExampleInfo('Maze Generator', 'maze.js', 'Procedural maze generation - [algorithm, generation]'),
|
|
77
|
+
new ExampleInfo('Starfield', 'starfield.js', 'Animated parallax starfield - [effects, space]'),
|
|
78
|
+
new ExampleInfo('Medals', 'medals.js', 'Achievement system demo - [ui, achievements]'),
|
|
78
79
|
new ExampleInfo('--- MINI GAMES ---'),
|
|
79
|
-
new ExampleInfo('Platformer Game', 'platformer.js', '
|
|
80
|
-
new ExampleInfo('Top Down Game', 'topDown.js', '
|
|
81
|
-
new ExampleInfo('Tilted View', 'tiltedView.js', 'Pseudo 3D
|
|
82
|
-
new ExampleInfo('Space Game', 'spaceGame.js', '
|
|
83
|
-
new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander
|
|
84
|
-
new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird
|
|
85
|
-
new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style game'),
|
|
86
|
-
new ExampleInfo('Pong Game', 'pongGame.js', '
|
|
87
|
-
new ExampleInfo('Sliding Puzzle', 'slidingPuzzle.js', '
|
|
88
|
-
new ExampleInfo('Raycasting Game', 'raycasting.js', 'Pseudo 3D raycasting
|
|
80
|
+
new ExampleInfo('Platformer Game', 'platformer.js', 'Jump and run platformer - [game, platformer, physics]'),
|
|
81
|
+
new ExampleInfo('Top Down Game', 'topDown.js', 'Adventure with collision - [game, topdown, rpg]'),
|
|
82
|
+
new ExampleInfo('Tilted View', 'tiltedView.js', 'Pseudo 3D tilted perspective - [3d, camera, perspective]'),
|
|
83
|
+
new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship with parallax scrolling - [game, space, shooter]'),
|
|
84
|
+
new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander physics game - [game, physics, thrust]'),
|
|
85
|
+
new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird clone - [game, arcade, flying]'),
|
|
86
|
+
new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style glider - [game, physics, flying]'),
|
|
87
|
+
new ExampleInfo('Pong Game', 'pongGame.js', 'Classic paddle ball game - [game, classic, arcade]'),
|
|
88
|
+
new ExampleInfo('Sliding Puzzle', 'slidingPuzzle.js', 'Tile sliding puzzle game - [game, puzzle, logic]'),
|
|
89
|
+
new ExampleInfo('Raycasting Game', 'raycasting.js', 'Pseudo 3D raycasting demo - [3d, raycasting, wolfenstein]'),
|
|
89
90
|
new ExampleInfo('--- PLUGINS ---'),
|
|
90
|
-
new ExampleInfo('Box2d Demo', 'box2d.js', '
|
|
91
|
-
new ExampleInfo('Box2d Car', 'box2dCar.js', '
|
|
92
|
-
new ExampleInfo('Post Processing', 'postProcess.js', '
|
|
93
|
-
new ExampleInfo('UI System Plugin', 'uiSystem.js', '
|
|
94
|
-
new ExampleInfo('Nine Slice', 'nineSlice.js', '
|
|
91
|
+
new ExampleInfo('Box2d Demo', 'box2d.js', 'Box2D physics plugin demo - [physics, box2d, plugin]'),
|
|
92
|
+
new ExampleInfo('Box2d Car', 'box2dCar.js', 'Drivable car with Box2D - [physics, box2d, vehicle]'),
|
|
93
|
+
new ExampleInfo('Post Processing', 'postProcess.js', 'Shader effects and filters - [graphics, shaders, effects]'),
|
|
94
|
+
new ExampleInfo('UI System Plugin', 'uiSystem.js', 'UI elements and widgets - [ui, plugin, interface]'),
|
|
95
|
+
new ExampleInfo('Nine Slice', 'nineSlice.js', 'Scalable UI panel rendering - [ui, graphics, scaling]'),
|
|
95
96
|
new ExampleInfo('--- FULL EXAMPLES ---'),
|
|
96
|
-
new ExampleInfo('Starter', 'starter', 'Clean
|
|
97
|
-
new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', '
|
|
98
|
-
new ExampleInfo('Breakout Game', 'breakout', '
|
|
99
|
-
new ExampleInfo('Platforming Game', 'platformer', '
|
|
100
|
-
new ExampleInfo('Puzzle Game', 'puzzle', 'Match
|
|
101
|
-
new ExampleInfo('Stress Test', 'stress', '
|
|
102
|
-
new ExampleInfo('Box2D Plugin', 'box2d', '
|
|
103
|
-
new ExampleInfo('HTML Menus', 'htmlMenu', '
|
|
104
|
-
new ExampleInfo('UI System Plugin', 'uiSystem', '
|
|
97
|
+
new ExampleInfo('Starter', 'starter', 'Clean project template - [template, beginner]', true),
|
|
98
|
+
new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', 'Step-by-step breakout guide - [tutorial, breakout]', true),
|
|
99
|
+
new ExampleInfo('Breakout Game', 'breakout', 'Complete breakout with effects - [game, breakout, complete]', true),
|
|
100
|
+
new ExampleInfo('Platforming Game', 'platformer', 'Full platformer with levels - [game, platformer, complete]', true),
|
|
101
|
+
new ExampleInfo('Puzzle Game', 'puzzle', 'Match-3 puzzle with HD graphics - [game, puzzle, match3]', true),
|
|
102
|
+
new ExampleInfo('Stress Test', 'stress', 'Performance test with music - [performance, test, music]', true),
|
|
103
|
+
new ExampleInfo('Box2D Plugin', 'box2d', 'Complete Box2D physics demo - [physics, box2d, complete]', true),
|
|
104
|
+
new ExampleInfo('HTML Menus', 'htmlMenu', 'HTML integration example - [html, ui, integration]', true),
|
|
105
|
+
new ExampleInfo('UI System Plugin', 'uiSystem', 'Complete UI system demo - [ui, plugin, complete]', true),
|
|
105
106
|
];
|
|
106
107
|
|
|
107
108
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -112,8 +113,9 @@ let iframeExample, inputTimeout, codeMirror, consoleAutoScroll, errorLineMarker,
|
|
|
112
113
|
// load the examples into the list
|
|
113
114
|
filterExamples();
|
|
114
115
|
|
|
115
|
-
// set the selected example from the URL
|
|
116
|
-
const
|
|
116
|
+
// set the selected example from the URL parameters
|
|
117
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
118
|
+
const selectedExample = urlParams.get('example') || '';
|
|
117
119
|
let exampleIndex = exampleList.findIndex((example) => example.filename == selectedExample);
|
|
118
120
|
if (exampleIndex < 0)
|
|
119
121
|
exampleIndex = 1;
|
|
@@ -156,7 +158,12 @@ function setExample()
|
|
|
156
158
|
const filename = 'examples/' + example.filename;
|
|
157
159
|
exampleLink.href = 'https://github.com/KilledByAPixel/LittleJS/tree/main/' + filename;
|
|
158
160
|
exampleLink.innerText = filename;
|
|
159
|
-
|
|
161
|
+
|
|
162
|
+
// update URL parameter
|
|
163
|
+
const url = new URL(window.location);
|
|
164
|
+
url.searchParams.set('example', example.filename);
|
|
165
|
+
window.history.replaceState({}, '', url);
|
|
166
|
+
|
|
160
167
|
loadFile(example.filename, example.largeExample);
|
|
161
168
|
}
|
|
162
169
|
|
|
@@ -169,9 +176,10 @@ async function loadFile(filename, largeExample)
|
|
|
169
176
|
|
|
170
177
|
// disable buttons in large example mode
|
|
171
178
|
buttonPause.disabled = largeExample;
|
|
172
|
-
|
|
179
|
+
buttonRestart.disabled = largeExample;
|
|
173
180
|
buttonScreenshot.disabled = largeExample;
|
|
174
181
|
buttonFullscreen.disabled = largeExample;
|
|
182
|
+
checkboxWebGL.disabled = largeExample;
|
|
175
183
|
|
|
176
184
|
if (largeExample)
|
|
177
185
|
{
|
|
@@ -266,9 +274,9 @@ function codeInput()
|
|
|
266
274
|
inputTimeout = setTimeout(() => setCode(code), 500);
|
|
267
275
|
}
|
|
268
276
|
|
|
269
|
-
function
|
|
277
|
+
function restartCode()
|
|
270
278
|
{
|
|
271
|
-
// manually
|
|
279
|
+
// manually restart/run the code regardless of live edit setting
|
|
272
280
|
const code = codeMirror ? codeMirror.getValue() : textareaCode.value;
|
|
273
281
|
setCode(code);
|
|
274
282
|
}
|
|
@@ -292,7 +300,13 @@ function setCode(code, textFilename)
|
|
|
292
300
|
const iframeContent = iframeExample.contentWindow;
|
|
293
301
|
const iframeDocument = iframeContent.document;
|
|
294
302
|
|
|
295
|
-
//
|
|
303
|
+
// intercept errors
|
|
304
|
+
function getErrorLine(stack)
|
|
305
|
+
{
|
|
306
|
+
// try to extract line number from stack trace, look for <anonymous> or injectedScript to find the user code
|
|
307
|
+
const anonymousMatch = stack.match(/(<anonymous>|injectedScript):(\d+)/);
|
|
308
|
+
return anonymousMatch ? parseInt(anonymousMatch[2]) : -1;
|
|
309
|
+
}
|
|
296
310
|
iframeContent.onerror = (message, source, lineno, colno, error) =>
|
|
297
311
|
{
|
|
298
312
|
let text = message;
|
|
@@ -306,18 +320,32 @@ function setCode(code, textFilename)
|
|
|
306
320
|
iframeContent.onunhandledrejection = (event) =>
|
|
307
321
|
{
|
|
308
322
|
let text = event.reason;
|
|
323
|
+
setErrorMessage(text);
|
|
309
324
|
if (event.reason && event.reason.stack)
|
|
310
325
|
{
|
|
311
|
-
text
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
setErrorLine(parseInt(lineMatch[1]));
|
|
326
|
+
text = event.reason.stack;
|
|
327
|
+
const errorLine = getErrorLine(event.reason.stack);
|
|
328
|
+
if (errorLine >= 0)
|
|
329
|
+
setErrorLine(errorLine);
|
|
316
330
|
}
|
|
317
|
-
setErrorMessage(text);
|
|
318
331
|
};
|
|
319
332
|
|
|
320
|
-
// intercept
|
|
333
|
+
// intercept asserts
|
|
334
|
+
const originalAssert = iframeContent.console.assert;
|
|
335
|
+
iframeContent.console.assert = function (condition, output)
|
|
336
|
+
{
|
|
337
|
+
if (!condition)
|
|
338
|
+
{
|
|
339
|
+
const stack = (new Error).stack;
|
|
340
|
+
const errorLine = getErrorLine(stack);
|
|
341
|
+
if (errorLine >= 0)
|
|
342
|
+
setErrorLine(errorLine);
|
|
343
|
+
setErrorMessage('Assertion failed!\n' + (output || '') + '\n' + stack);
|
|
344
|
+
}
|
|
345
|
+
originalAssert.apply(this, arguments);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// intercept console prints
|
|
321
349
|
const originalConsole = iframeContent.console;
|
|
322
350
|
function interceptConsole(method)
|
|
323
351
|
{
|
|
@@ -331,35 +359,33 @@ function setCode(code, textFilename)
|
|
|
331
359
|
}
|
|
332
360
|
['log','info','warn','error','debug'].forEach(f=>interceptConsole(f));
|
|
333
361
|
|
|
334
|
-
// intercept asserts
|
|
335
|
-
const originalAssert = iframeContent.console.assert;
|
|
336
|
-
iframeContent.console.assert = function (condition, output)
|
|
337
362
|
{
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
363
|
+
// setup buttons
|
|
364
|
+
buttonScreenshot.onclick = () =>
|
|
365
|
+
{
|
|
366
|
+
if (iframeContent.debugScreenshot)
|
|
367
|
+
iframeContent.debugScreenshot();
|
|
368
|
+
}
|
|
342
369
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
iframeContent.
|
|
347
|
-
|
|
370
|
+
// pause/resume functionality
|
|
371
|
+
buttonPause.onclick = () =>
|
|
372
|
+
{
|
|
373
|
+
if (!iframeContent.getPaused || !iframeContent.setPaused)
|
|
374
|
+
return;
|
|
375
|
+
|
|
376
|
+
const paused = !iframeContent.getPaused();
|
|
377
|
+
iframeContent.setPaused(paused);
|
|
378
|
+
buttonPause.textContent = paused ? 'Resume' : 'Pause';
|
|
379
|
+
}
|
|
380
|
+
buttonPause.textContent = 'Pause';
|
|
348
381
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
{
|
|
352
|
-
if (!iframeContent.getPaused || !iframeContent.setPaused)
|
|
353
|
-
return;
|
|
354
|
-
|
|
355
|
-
const paused = !iframeContent.getPaused();
|
|
356
|
-
iframeContent.setPaused(paused);
|
|
357
|
-
buttonPause.textContent = paused ? 'Resume' : 'Pause';
|
|
382
|
+
// fullscreen functionality
|
|
383
|
+
buttonFullscreen.onclick = ()=> iframeContent.toggleFullscreen();
|
|
358
384
|
}
|
|
359
|
-
buttonPause.textContent = 'Pause';
|
|
360
385
|
|
|
361
|
-
//
|
|
362
|
-
|
|
386
|
+
// set WebGL based on checkbox
|
|
387
|
+
iframeContent.setGLEnable(checkboxWebGL.checked);
|
|
388
|
+
checkboxWebGL.onchange = ()=> iframeContent.setGLEnable(checkboxWebGL.checked);
|
|
363
389
|
|
|
364
390
|
// create a script element that overrides the default functions
|
|
365
391
|
const overrideScript = iframeDocument.createElement('script');
|
|
@@ -435,13 +461,44 @@ function clearErrorLine()
|
|
|
435
461
|
|
|
436
462
|
function setErrorLine(lineNumber)
|
|
437
463
|
{
|
|
438
|
-
if (!
|
|
464
|
+
if (!lineNumber || lineNumber <= 0)
|
|
439
465
|
return;
|
|
440
466
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
467
|
+
if (codeMirror)
|
|
468
|
+
{
|
|
469
|
+
clearErrorLine();
|
|
470
|
+
--lineNumber; // codeMirror uses 0-based line numbers
|
|
471
|
+
errorLineMarker = codeMirror.getDoc().addLineClass(lineNumber, 'background', 'error-line');
|
|
472
|
+
if (checkboxJumpToError.checked)
|
|
473
|
+
{
|
|
474
|
+
codeMirror.scrollIntoView({line: lineNumber, ch: 0});
|
|
475
|
+
codeMirror.focus();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
else if (textareaCode && checkboxJumpToError.checked)
|
|
479
|
+
{
|
|
480
|
+
// for textarea, calculate character position and set cursor
|
|
481
|
+
const lines = textareaCode.value.split('\n');
|
|
482
|
+
let charPos = 0;
|
|
483
|
+
for (let i = 0; i < lineNumber - 1 && i < lines.length; i++)
|
|
484
|
+
{
|
|
485
|
+
charPos += lines[i].length + 1; // +1 for newline character
|
|
486
|
+
}
|
|
487
|
+
textareaCode.setSelectionRange(charPos, charPos);
|
|
488
|
+
if (checkboxJumpToError.checked)
|
|
489
|
+
{
|
|
490
|
+
const line = textareaCode.value.split('\n');
|
|
491
|
+
lineNumber = Math.min(Math.max(0, lineNumber-1), lines.length-1);
|
|
492
|
+
|
|
493
|
+
// select error line in textarea
|
|
494
|
+
let start = 0;
|
|
495
|
+
for (let i = 0; i < lineNumber; i++)
|
|
496
|
+
start += lines[i].length + 1;
|
|
497
|
+
textareaCode.selectionStart = start;
|
|
498
|
+
textareaCode.selectionEnd = start + lines[lineNumber].length;
|
|
499
|
+
textareaCode.focus();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
445
502
|
}
|
|
446
503
|
|
|
447
504
|
function setErrorMessage(message)
|
|
@@ -483,8 +540,9 @@ const themes =
|
|
|
483
540
|
];
|
|
484
541
|
|
|
485
542
|
// Load saved preferences from localStorage
|
|
486
|
-
const
|
|
487
|
-
const
|
|
543
|
+
const savePrefix = 'LittleJSExamples_';
|
|
544
|
+
const savedTheme = localStorage.getItem(savePrefix+'theme') || defaultTheme;
|
|
545
|
+
const savedFontSize = localStorage.getItem(savePrefix+'fontSize') || defaultFontSize;
|
|
488
546
|
|
|
489
547
|
for (const theme of themes)
|
|
490
548
|
{
|
|
@@ -498,14 +556,17 @@ for (const theme of themes)
|
|
|
498
556
|
// Set the saved font size
|
|
499
557
|
selectFontSize.value = savedFontSize;
|
|
500
558
|
textareaCode.style.fontSize = savedFontSize;
|
|
501
|
-
addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet');
|
|
502
559
|
addCodeMirrorElement('codemirror.min.js', 'script').onload = ()=>
|
|
560
|
+
addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet').onload = ()=>
|
|
503
561
|
addCodeMirrorElement('addon/edit/matchbrackets.js', 'script').onload = ()=>
|
|
504
562
|
addCodeMirrorElement('mode/javascript/javascript.min.js', 'script').onload = ()=>
|
|
505
563
|
initCodeMirror();
|
|
506
564
|
|
|
507
565
|
function initCodeMirror()
|
|
508
566
|
{
|
|
567
|
+
if (codeMirror)
|
|
568
|
+
return; // prevent duplicate initialization
|
|
569
|
+
|
|
509
570
|
const textareaCode = document.getElementById('textareaCode');
|
|
510
571
|
codeMirror = CodeMirror.fromTextArea(textareaCode,
|
|
511
572
|
{
|
|
@@ -525,7 +586,7 @@ function addCodeMirrorElement(filename, type, rel)
|
|
|
525
586
|
// add element for code mirror
|
|
526
587
|
const e = document.createElement(type);
|
|
527
588
|
e.rel = rel;
|
|
528
|
-
filename = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/
|
|
589
|
+
filename = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/' + filename;
|
|
529
590
|
if (type == 'link')
|
|
530
591
|
e.href = filename;
|
|
531
592
|
else
|
|
@@ -541,8 +602,8 @@ function loadTheme()
|
|
|
541
602
|
const size = selectFontSize.value;
|
|
542
603
|
|
|
543
604
|
// Save preferences to localStorage
|
|
544
|
-
localStorage.setItem('
|
|
545
|
-
localStorage.setItem('
|
|
605
|
+
localStorage.setItem(savePrefix+'theme', theme);
|
|
606
|
+
localStorage.setItem(savePrefix+'fontSize', size);
|
|
546
607
|
|
|
547
608
|
// Apply the theme and font size
|
|
548
609
|
textareaCode.style.fontSize = size;
|
package/examples/module/game.js
CHANGED
|
@@ -101,7 +101,10 @@ function gameUpdate()
|
|
|
101
101
|
|
|
102
102
|
// move particles to mouse location if on screen
|
|
103
103
|
if (LJS.mousePosScreen.x)
|
|
104
|
+
{
|
|
104
105
|
particleEmitter.pos = LJS.mousePos;
|
|
106
|
+
particleEmitter.velocity = LJS.mouseDelta.scale(.3*LJS.timeDelta);
|
|
107
|
+
}
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -113,8 +116,8 @@ function gameUpdatePost()
|
|
|
113
116
|
///////////////////////////////////////////////////////////////////////////////
|
|
114
117
|
function gameRender()
|
|
115
118
|
{
|
|
116
|
-
// draw a grey square in the background
|
|
117
|
-
LJS.drawRect(vec2(16,8), vec2(20,14), hsl(0,0,.6)
|
|
119
|
+
// draw a grey square in the background
|
|
120
|
+
LJS.drawRect(vec2(16,8), vec2(20,14), hsl(0,0,.6));
|
|
118
121
|
|
|
119
122
|
// draw the logo as a tile
|
|
120
123
|
LJS.drawTile(vec2(21,5), vec2(4.5), tile(3,128));
|
|
@@ -94,7 +94,7 @@ function gameInit()
|
|
|
94
94
|
if (type == 'color')
|
|
95
95
|
{
|
|
96
96
|
const color = particleSystem[name];
|
|
97
|
-
defaultValue = color.toString(
|
|
97
|
+
defaultValue = color.toString(false);
|
|
98
98
|
}
|
|
99
99
|
else if (type == 'alpha')
|
|
100
100
|
{
|
|
@@ -266,7 +266,7 @@ function updateSetting(setting, save=1)
|
|
|
266
266
|
if (type == 'color')
|
|
267
267
|
{
|
|
268
268
|
const color = particleSystem[name];
|
|
269
|
-
input.value = color.toString(
|
|
269
|
+
input.value = color.toString(false);
|
|
270
270
|
}
|
|
271
271
|
else if (type == 'alpha')
|
|
272
272
|
{
|
|
@@ -369,7 +369,16 @@ function gameUpdate()
|
|
|
369
369
|
|
|
370
370
|
if (document.activeElement == document.body)
|
|
371
371
|
{
|
|
372
|
-
|
|
372
|
+
if (LJS.mouseIsDown(0))
|
|
373
|
+
{
|
|
374
|
+
particleSystem.pos = LJS.mousePos;
|
|
375
|
+
particleSystem.velocity = LJS.mouseDelta.scale(.3*LJS.timeDelta);
|
|
376
|
+
}
|
|
377
|
+
else
|
|
378
|
+
{
|
|
379
|
+
particleSystem.pos = vec2();
|
|
380
|
+
particleSystem.velocity = vec2();
|
|
381
|
+
}
|
|
373
382
|
if (LJS.mouseWheel)
|
|
374
383
|
{
|
|
375
384
|
// zoom camera
|
|
@@ -185,7 +185,7 @@ export class Sky extends LJS.EngineObject
|
|
|
185
185
|
// fill background with a gradient
|
|
186
186
|
const canvas = LJS.mainCanvas;
|
|
187
187
|
const gradientDelta = 5;
|
|
188
|
-
for(let y=0; y<canvas.height; y+=gradientDelta)
|
|
188
|
+
for (let y=0; y<canvas.height; y+=gradientDelta)
|
|
189
189
|
{
|
|
190
190
|
// draw horizontal lines to create a gradient
|
|
191
191
|
const color = this.skyColor.lerp(this.horizonColor, y/canvas.height);
|
|
@@ -227,7 +227,7 @@ export class ParallaxLayer extends LJS.CanvasLayer
|
|
|
227
227
|
// create a gradient for the mountains
|
|
228
228
|
const topColor = GameLevel.levelColor.mutate(.2).lerp(GameLevel.sky.skyColor, .8 - depth*.15);
|
|
229
229
|
const bottomColor = GameLevel.levelColor.subtract(LJS.CLEAR_WHITE).mutate(.2);
|
|
230
|
-
for(let i = canvasSize.y; i--;)
|
|
230
|
+
for (let i = canvasSize.y; i--;)
|
|
231
231
|
{
|
|
232
232
|
// draw a 1 pixel gradient line on the left side of the canvas
|
|
233
233
|
const p = i/canvasSize.y;
|
|
@@ -241,7 +241,7 @@ export class ParallaxLayer extends LJS.CanvasLayer
|
|
|
241
241
|
const slopeRange = 1; // max slope of the mountains
|
|
242
242
|
const startGroundLevel = canvasSize.y/2;
|
|
243
243
|
let y = startGroundLevel, groundSlope = LJS.rand(-slopeRange, slopeRange);
|
|
244
|
-
for(let x=canvasSize.x; x--;)
|
|
244
|
+
for (let x=canvasSize.x; x--;)
|
|
245
245
|
{
|
|
246
246
|
// pull slope towards start ground level
|
|
247
247
|
y += groundSlope -= (y-startGroundLevel)*levelness;
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
// use smoother textures
|
|
2
|
+
setCanvasPixelated(false);
|
|
3
|
+
setTilesPixelated(false);
|
|
4
|
+
|
|
1
5
|
function gameRender()
|
|
2
6
|
{
|
|
3
7
|
const circleTile = tile(2, 128); // get the circle tile
|
|
4
8
|
|
|
5
9
|
// additive blend
|
|
6
10
|
setBlendMode(1);
|
|
7
|
-
drawTile(vec2(-
|
|
11
|
+
drawTile(vec2(-8,-2), vec2(7), circleTile, rgb(0,0,.5), 0, false, rgb(0,0,.5));
|
|
8
12
|
drawTile(vec2(-6, 2), vec2(7), circleTile, rgb(0,1,0));
|
|
9
|
-
drawTile(vec2(-
|
|
13
|
+
drawTile(vec2(-4,-2), vec2(7), circleTile, rgb(1,0,0));
|
|
10
14
|
|
|
11
15
|
// alpha blend
|
|
12
16
|
setBlendMode(0);
|
|
13
|
-
drawTile(vec2(4,-2), vec2(7), circleTile,
|
|
14
|
-
drawTile(vec2(
|
|
15
|
-
drawTile(vec2(
|
|
17
|
+
drawTile(vec2(4,-2), vec2(7), circleTile, hsl(time/9 ,1,.5), 0, false, hsl(1,1,.5));
|
|
18
|
+
drawTile(vec2(8,-2), vec2(7), circleTile, hsl(time/9+1/3,1,.5,.5));
|
|
19
|
+
drawTile(vec2(6, 2), vec2(7), circleTile, hsl(time/9+2/3,1,.5,.5));
|
|
16
20
|
}
|
package/examples/shorts/box2d.js
CHANGED
|
@@ -10,7 +10,7 @@ async function gameInit()
|
|
|
10
10
|
groundObject.addBox(vec2(100,2));
|
|
11
11
|
|
|
12
12
|
// add some random objects
|
|
13
|
-
for(let i=50; i--;)
|
|
13
|
+
for (let i=50; i--;)
|
|
14
14
|
{
|
|
15
15
|
const o = new Box2dObject(randInCircle(5), vec2(), 0, 0, randColor());
|
|
16
16
|
randInt(2) ? o.addCircle(rand(1,2)) : o.addRandomPoly(rand(1,2));
|
|
@@ -18,7 +18,7 @@ class CarObject extends Box2dObject
|
|
|
18
18
|
this.addBox(vec2(7,2));
|
|
19
19
|
const frequency = 4, maxTorque = 250;
|
|
20
20
|
this.wheels = [];
|
|
21
|
-
for(let i=2; i--;)
|
|
21
|
+
for (let i=2; i--;)
|
|
22
22
|
{
|
|
23
23
|
const wheelPos = pos.add(vec2(i?2:-2, -1));
|
|
24
24
|
const wheel = new Box2dObject(wheelPos, vec2(2), tile(7));
|
package/examples/shorts/clock.js
CHANGED
|
@@ -10,12 +10,12 @@ function gameRender()
|
|
|
10
10
|
// rgb and hsl color
|
|
11
11
|
drawRect(vec2(-8, 0), vec2(3), new Color(1,0,0));
|
|
12
12
|
drawRect(vec2(-4, 0), vec2(3), rgb(0,1,1));
|
|
13
|
-
drawRect(vec2(-0, 0), vec2(3), hsl(0,1,.5));
|
|
13
|
+
drawRect(vec2(-0, 0), vec2(3), hsl(0,1,.5, .5));
|
|
14
14
|
drawRect(vec2( 4, 0), vec2(3), hsl(.6,.5,.5));
|
|
15
15
|
drawRect(vec2( 8, 0), vec2(3), hsl(0,0,1));
|
|
16
16
|
|
|
17
17
|
// color lerpping
|
|
18
|
-
for(let i=5; i--;)
|
|
18
|
+
for (let i=5; i--;)
|
|
19
19
|
{
|
|
20
20
|
const color1 = RED;
|
|
21
21
|
const color2 = YELLOW;
|
package/examples/shorts/maze.js
CHANGED
|
@@ -4,18 +4,18 @@ function gameRender()
|
|
|
4
4
|
const threeSliceTile = tile(19);
|
|
5
5
|
|
|
6
6
|
// draw nine slice with thin border and color
|
|
7
|
-
drawNineSlice(vec2(-7,4), vec2(
|
|
7
|
+
drawNineSlice(vec2(-7,4), vec2(11+wave(.5,2), 6), nineSliceTile, hsl(.1,.5,.9), .5);
|
|
8
8
|
drawTextOverlay('Nine Slice\nThin Border', vec2(-7,4), 1, BLACK);
|
|
9
9
|
|
|
10
|
-
// draw nine slice in screen space with thick border
|
|
11
|
-
drawNineSliceScreen(vec2(700,150), vec2(250), nineSliceTile);
|
|
10
|
+
// draw nine slice in screen space with thick border and rotation
|
|
11
|
+
drawNineSliceScreen(vec2(700,150), vec2(250), nineSliceTile, 32, 2, time/2);
|
|
12
12
|
drawTextScreen('Nine Slice\nScreen Space', vec2(700,150), 30, BLACK);
|
|
13
13
|
|
|
14
|
-
// draw three slice with
|
|
15
|
-
drawThreeSlice(vec2(-7,-4), vec2(9, 7), threeSliceTile, WHITE, 2, hsl(
|
|
16
|
-
drawTextOverlay('Three Slice\
|
|
14
|
+
// draw three slice with variable border and additive color
|
|
15
|
+
drawThreeSlice(vec2(-7,-4), vec2(9, 7), threeSliceTile, WHITE, 2+wave(.2)*2, hsl(time/30,.5,.5));
|
|
16
|
+
drawTextOverlay('Three Slice\nVariable\n Border', vec2(-7,-4), 1, BLACK);
|
|
17
17
|
|
|
18
|
-
// draw three slice in screen space with
|
|
19
|
-
drawThreeSliceScreen(vec2(700,
|
|
20
|
-
drawTextScreen('Three Slice\nScreen Space', vec2(700,
|
|
18
|
+
// draw three slice in screen space with changing size
|
|
19
|
+
drawThreeSliceScreen(vec2(700,420), vec2(350-wave(.3,90),120+wave(.3,60)), threeSliceTile);
|
|
20
|
+
drawTextScreen('Three Slice\nScreen Space', vec2(700,420), 30, BLACK);
|
|
21
21
|
}
|
package/examples/shorts/piano.js
CHANGED
|
@@ -33,9 +33,9 @@ class PianoKey extends EngineObject
|
|
|
33
33
|
function gameInit()
|
|
34
34
|
{
|
|
35
35
|
// create piano keyboard
|
|
36
|
-
for(let i=15; i--;)
|
|
36
|
+
for (let i=15; i--;)
|
|
37
37
|
keys.unshift(new PianoKey(vec2(i-7,0), vec2(1,4), [0,2,4,5,7,9,11][i%7]+(i/7|0)*12, 1));
|
|
38
|
-
for(let i=10; i--;)
|
|
38
|
+
for (let i=10; i--;)
|
|
39
39
|
keys.unshift(new PianoKey(vec2([1,2,4,5,6][i%5]+(i/5|0)*7-7.5,0), vec2(1,2), [1,3,6,8,10][i%5]+(i/5|0)*12));
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -42,7 +42,7 @@ function gameRender()
|
|
|
42
42
|
{
|
|
43
43
|
// draw horizontal slizes to create floor and ceiling
|
|
44
44
|
const h = 9;
|
|
45
|
-
let pos = vec2(), size = vec2(39, .
|
|
45
|
+
let pos = vec2(), size = vec2(39, .15), color = WHITE;
|
|
46
46
|
for (let y=-h; y<h; y+=.1)
|
|
47
47
|
{
|
|
48
48
|
const p = 1.01-abs(y/h)
|
|
@@ -54,7 +54,7 @@ function gameRender()
|
|
|
54
54
|
{
|
|
55
55
|
// draw vertical slices to create the walls
|
|
56
56
|
const w = 20;
|
|
57
|
-
const pos = vec2(), size = vec2(.
|
|
57
|
+
const pos = vec2(), size = vec2(.15), color = WHITE;
|
|
58
58
|
for (let x=-w; x<w; x+=.1)
|
|
59
59
|
{
|
|
60
60
|
const p = raycast(playerPos, playerAngle + x/w);
|