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.
Files changed (66) hide show
  1. package/README.md +15 -12
  2. package/dist/littlejs.d.ts +94 -74
  3. package/dist/littlejs.esm.js +422 -306
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +416 -306
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +414 -304
  8. package/examples/box2d/game.js +1 -1
  9. package/examples/box2d/gameObjects.js +1 -1
  10. package/examples/box2d/scenes.js +1 -1
  11. package/examples/breakoutTutorial/README.md +44 -41
  12. package/examples/breakoutTutorial/game.js +2 -2
  13. package/examples/electron/build.bat +7 -0
  14. package/examples/electron/build.js +124 -0
  15. package/examples/electron/electron.js +35 -0
  16. package/examples/electron/game.js +140 -0
  17. package/examples/electron/index.html +13 -0
  18. package/examples/electron/package.json +12 -0
  19. package/examples/electron/tiles.png +0 -0
  20. package/examples/empty/game.js +1 -0
  21. package/examples/htmlMenu/game.js +1 -1
  22. package/examples/index.html +154 -93
  23. package/examples/module/game.js +5 -2
  24. package/examples/particles/index.html +12 -3
  25. package/examples/platformer/gameEffects.js +3 -3
  26. package/examples/shorts/base.html +1 -1
  27. package/examples/shorts/blending.js +9 -5
  28. package/examples/shorts/box2d.js +1 -1
  29. package/examples/shorts/box2dCar.js +1 -1
  30. package/examples/shorts/clock.js +1 -1
  31. package/examples/shorts/colors.js +2 -2
  32. package/examples/shorts/maze.js +1 -1
  33. package/examples/shorts/nineSlice.js +9 -9
  34. package/examples/shorts/piano.js +2 -2
  35. package/examples/shorts/raycasting.js +2 -2
  36. package/examples/shorts/shapes.js +9 -9
  37. package/examples/shorts/slidingPuzzle.js +2 -2
  38. package/examples/shorts/starfield.js +5 -7
  39. package/examples/shorts/systemFont.js +1 -1
  40. package/examples/shorts/uiSystem.js +1 -0
  41. package/examples/starter/build.js +9 -8
  42. package/examples/starter/game.js +5 -2
  43. package/examples/starter/index.html +2 -2
  44. package/examples/stress/index.html +5 -5
  45. package/examples/style.css +5 -2
  46. package/examples/typescript/build.js +1 -1
  47. package/examples/typescript/game.js +2 -2
  48. package/examples/typescript/game.ts +5 -2
  49. package/examples/uiSystem/game.js +2 -1
  50. package/package.json +2 -2
  51. package/plugins/box2d.js +19 -94
  52. package/plugins/drawUtilities.js +24 -18
  53. package/plugins/postProcess.js +7 -0
  54. package/plugins/uiSystem.js +4 -4
  55. package/src/engine.js +7 -2
  56. package/src/engineAudio.js +1 -1
  57. package/src/engineDebug.js +2 -2
  58. package/src/engineDraw.js +243 -150
  59. package/src/engineExport.js +6 -0
  60. package/src/engineInput.js +2 -2
  61. package/src/engineMedals.js +4 -4
  62. package/src/engineObject.js +3 -3
  63. package/src/engineParticles.js +8 -1
  64. package/src/engineSettings.js +55 -8
  65. package/src/engineUtilities.js +7 -7
  66. package/src/engineWebGL.js +25 -5
@@ -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>
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
- <button id=buttonRefresh onclick=refreshCode()>Refresh</button>
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=filterExamples(1)>Clear</button>
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', 'How to draw geometric shapes'),
63
- new ExampleInfo('Colors', 'colors.js', 'How to use the color object'),
64
- new ExampleInfo('Blending', 'blending.js', 'How to use blending modes'),
65
- new ExampleInfo('Timers', 'timers.js', 'How to use the timer object'),
66
- new ExampleInfo('Texture', 'texture.js', 'How to display a full texture'),
67
- new ExampleInfo('Particles', 'particles.js', 'How to use the particle system'),
68
- new ExampleInfo('Play Sound', 'playSound.js', 'How to play sounds with zzfx'),
69
- new ExampleInfo('System Font', 'systemFont.js', 'Demo of the included system font'),
70
- new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'How to set up a simple sprite atlas'),
71
- new ExampleInfo('Animation', 'animation.js', 'How to animate sprites'),
72
- new ExampleInfo('Clock', 'clock.js', 'Simple clock showing the time'),
73
- new ExampleInfo('Tile Layer', 'tileLayer.js', 'How to use the tile layer object'),
74
- new ExampleInfo('Piano', 'piano.js', 'A simple piano keyboard demo'),
75
- new ExampleInfo('Maze Generator', 'maze.js', 'Generates a random maze'),
76
- new ExampleInfo('Starfield', 'starfield.js', 'A moving starfield effect'),
77
- new ExampleInfo('Medals', 'medals.js', 'How to use the medal system'),
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', 'Platformer jumping game'),
80
- new ExampleInfo('Top Down Game', 'topDown.js', 'Top down adventure game'),
81
- new ExampleInfo('Tilted View', 'tiltedView.js', 'Pseudo 3D view with tilted camera'),
82
- new ExampleInfo('Space Game', 'spaceGame.js', 'Space ship game with parallax starfield'),
83
- new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander style game'),
84
- new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird style game'),
85
- new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style game'),
86
- new ExampleInfo('Pong Game', 'pongGame.js', 'Simple pong style game'),
87
- new ExampleInfo('Sliding Puzzle', 'slidingPuzzle.js', 'Sliding tile puzzle game'),
88
- new ExampleInfo('Raycasting Game', 'raycasting.js', 'Pseudo 3D raycasting system'),
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', 'Demo of the Box2D physics plugin'),
91
- new ExampleInfo('Box2d Car', 'box2dCar.js', 'Controllable vehicle using the Box2D physics plugin'),
92
- new ExampleInfo('Post Processing', 'postProcess.js', 'Post processing plugin demo'),
93
- new ExampleInfo('UI System Plugin', 'uiSystem.js', 'Simple example of LittleJS UI plugin'),
94
- new ExampleInfo('Nine Slice', 'nineSlice.js', 'Demo of nine slice and three slice drawing'),
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 starter project', true),
97
- new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', 'Tutorial Breakout example', true),
98
- new ExampleInfo('Breakout Game', 'breakout', 'Block breaking game with post-processing effects', true),
99
- new ExampleInfo('Platforming Game', 'platformer', 'Platformer/shooter with level data loading', true),
100
- new ExampleInfo('Puzzle Game', 'puzzle', 'Match 3 puzzle game with HD rendering', true),
101
- new ExampleInfo('Stress Test', 'stress', 'Max sprite/object test and music system demo', true),
102
- new ExampleInfo('Box2D Plugin', 'box2d', 'Demo of the Box2D physics plugin', true),
103
- new ExampleInfo('HTML Menus', 'htmlMenu', 'Shows how to combine HTML with LittleJS', true),
104
- new ExampleInfo('UI System Plugin', 'uiSystem', 'How to use LittleJS UI plugin', true),
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 hash
116
- const selectedExample = location.hash.split`#`[1] || '';
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
- location.hash = example.filename;
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
- buttonRefresh.disabled = largeExample;
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 refreshCode()
277
+ function restartCode()
270
278
  {
271
- // manually refresh/run the code regardless of live edit setting
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
- // create error event listeners
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 += `\n\n` + event.reason.stack;
312
-
313
- // try to extract line number from stack trace
314
- const lineMatch = event.reason.stack.match(/:(\d+):(\d+)/);
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 console methods
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
- if (!condition)
339
- setErrorMessage('Assertion failed!\n' + (output || '') + '\n' + (new Error).stack);
340
- originalAssert.apply(this, arguments);
341
- };
363
+ // setup buttons
364
+ buttonScreenshot.onclick = () =>
365
+ {
366
+ if (iframeContent.debugScreenshot)
367
+ iframeContent.debugScreenshot();
368
+ }
342
369
 
343
- buttonScreenshot.onclick = () =>
344
- {
345
- if (iframeContent.debugScreenshot)
346
- iframeContent.debugScreenshot();
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
- // pause/resume functionality
350
- buttonPause.onclick = () =>
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
- // fullscreen functionality
362
- buttonFullscreen.onclick = ()=> iframeContent.toggleFullscreen();
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 (!codeMirror || !(lineNumber > 0))
464
+ if (!lineNumber || lineNumber <= 0)
439
465
  return;
440
466
 
441
- clearErrorLine();
442
- --lineNumber; // codeMirror uses 0-based line numbers
443
- errorLineMarker = codeMirror.getDoc().addLineClass(lineNumber, 'background', 'error-line');
444
- //codeMirror.scrollIntoView({line: lineNumber});
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 savedTheme = localStorage.getItem('littleJS-theme') || defaultTheme;
487
- const savedFontSize = localStorage.getItem('littleJS-fontSize') || defaultFontSize;
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/5.65.18/' + filename;
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('littleJSExamples_theme', theme);
545
- localStorage.setItem('littleJSExamples_fontSize', size);
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;
@@ -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 without using webgl
117
- LJS.drawRect(vec2(16,8), vec2(20,14), hsl(0,0,.6), 0, false);
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(0);
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(0);
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
- particleSystem.pos = LJS.mouseIsDown(0) ? LJS.mousePos : vec2();
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,5 +1,5 @@
1
1
  <!DOCTYPE html><meta charset=utf-8><body>
2
- <script src=../../dist/littlejs.js?11212></script>
2
+ <script src=../../dist/littlejs.js?1133></script>
3
3
  <script src=../../dist/box2d.wasm.js></script>
4
4
 
5
5
  <!-- LittleJS Engine Source
@@ -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(-4,-2), vec2(7), circleTile, rgb(1,0,0));
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(-8,-2), vec2(7), circleTile, rgb(0,0,1));
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, rgb(1,0,0,.5));
14
- drawTile(vec2(6, 2), vec2(7), circleTile, rgb(0,1,0,.5));
15
- drawTile(vec2(8,-2), vec2(7), circleTile, rgb(0,0,1,.5));
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
  }
@@ -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));
@@ -1,7 +1,7 @@
1
1
  function gameRender()
2
2
  {
3
3
  // draw background
4
- for(let i=12; i--;)
4
+ for (let i=12; i--;)
5
5
  {
6
6
  const a = i/6*PI;
7
7
  const pos = vec2(0,7).rotate(a);
@@ -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;
@@ -2,7 +2,7 @@ function generateMaze(size)
2
2
  {
3
3
  const maze = [], w = size.x, h = size.y;
4
4
  maze[1 + w] = 1; // start point
5
- for(let k=w*h*9|0; k--;)
5
+ for (let k=w*h*9|0; k--;)
6
6
  {
7
7
  // get a random position on odd coordinates
8
8
  const jx = randInt(w/2-(w%2?0:2)|0)*2 + 1;
@@ -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(13, 6), nineSliceTile, hsl(.1,.5,.9), .5);
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 thick border and additive color
15
- drawThreeSlice(vec2(-7,-4), vec2(9, 7), threeSliceTile, WHITE, 2, hsl(.6,1,.5));
16
- drawTextOverlay('Three Slice\nThick Border', vec2(-7,-4), 1, BLACK);
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 thick border
19
- drawThreeSliceScreen(vec2(700,400), vec2(400,150), threeSliceTile);
20
- drawTextScreen('Three Slice\nScreen Space', vec2(700,400), 30, BLACK);
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
  }
@@ -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, .11), color = WHITE;
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(.11), color = WHITE;
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);