littlejsengine 1.13.4 → 1.14.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 +4 -3
- package/dist/littlejs.d.ts +395 -249
- package/dist/littlejs.esm.js +1550 -754
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1528 -744
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1504 -720
- package/examples/box2d/game.js +4 -4
- package/examples/box2d/gameObjects.js +1 -1
- package/examples/breakout/game.js +30 -25
- package/examples/breakoutTutorial/README.md +1 -1
- package/examples/breakoutTutorial/game.js +1 -1
- package/examples/electron/build.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/empty/game.js +1 -1
- package/examples/htmlMenu/index.html +7 -7
- package/examples/index.html +208 -97
- package/examples/platformer/gameEffects.js +20 -25
- package/examples/platformer/gameLevel.js +6 -1
- package/examples/shorts/base.html +1 -1
- package/examples/shorts/flappyGame.js +2 -1
- package/examples/shorts/helloWorld.js +1 -1
- package/examples/shorts/music.js +106 -0
- package/examples/shorts/parallax.js +71 -0
- package/examples/shorts/piano.js +7 -8
- package/examples/shorts/postProcess.js +25 -8
- package/examples/shorts/shapes.js +12 -18
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/uiSystem.js +15 -8
- package/examples/starter/index.html +2 -2
- package/examples/stress/index.html +14 -7
- package/examples/style.css +14 -4
- package/examples/typescript/build.js +1 -1
- package/examples/uiSystem/game.js +11 -11
- package/package.json +1 -1
- package/plugins/box2d.js +12 -10
- package/plugins/drawUtilities.js +5 -5
- package/plugins/newgrounds.js +6 -6
- package/plugins/pluginExport.js +1 -1
- package/plugins/uiSystem.js +103 -79
- package/plugins/zzfxm.js +10 -10
- package/reference.md +94 -56
- package/src/engine.js +28 -24
- package/src/engineAudio.js +284 -109
- package/src/engineBuild.js +11 -11
- package/src/engineDebug.js +29 -29
- package/src/engineDraw.js +285 -112
- package/src/engineExport.js +28 -16
- package/src/engineInput.js +57 -67
- package/src/engineMedals.js +25 -25
- package/src/engineObject.js +28 -25
- package/src/engineParticles.js +59 -59
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +20 -13
- package/src/engineTileLayer.js +34 -35
- package/src/engineUtilities.js +66 -59
- package/src/engineWebGL.js +466 -66
- /package/examples/shorts/{playSound.js → sound.js} +0 -0
package/examples/index.html
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
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?9>
|
|
5
5
|
<meta charset=utf-8>
|
|
6
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
|
6
7
|
</head><body>
|
|
7
8
|
<div id=container1>
|
|
8
9
|
<div id=container3>
|
|
9
10
|
<p id=titleInfo>LittleJS Example Browser</p>
|
|
10
11
|
<p id=exampleInfo></p>
|
|
11
12
|
<a id=exampleLink target='_blank'></a>
|
|
13
|
+
<div id=divCodeOptions>
|
|
12
14
|
<hr>
|
|
13
|
-
<div>
|
|
14
15
|
Theme:
|
|
15
16
|
<select style=width:120px onchange=loadTheme() id=selectTheme></select>
|
|
16
17
|
<select style=width:40px onchange=loadTheme() id=selectFontSize>
|
|
@@ -20,24 +21,31 @@ Theme:
|
|
|
20
21
|
<option value=20px>L</option>
|
|
21
22
|
<option value=24px>XL</option>
|
|
22
23
|
</select>
|
|
23
|
-
<input type=checkbox id=checkboxLiveEdit checked>Live Edit
|
|
24
|
-
<input type=checkbox id=
|
|
25
|
-
</div>
|
|
24
|
+
<span class=nowrap><input type=checkbox id=checkboxLiveEdit checked>Live Edit</span>
|
|
25
|
+
<span class=nowrap><input type=checkbox id=checkboxJumpToErrors>Jump to Errors</span>
|
|
26
|
+
</div>
|
|
27
|
+
<div id=divTextareas>
|
|
26
28
|
<textarea id=textareaCode oninput=codeInput() spellcheck=false></textarea>
|
|
27
29
|
<textarea id=textareaError readonly spellcheck=false></textarea>
|
|
28
30
|
<textarea id=textareaConsole readonly spellcheck=false></textarea>
|
|
29
31
|
</div>
|
|
32
|
+
</div>
|
|
30
33
|
<div id=container2>
|
|
34
|
+
<div style='display:flex;justify-content:center'>
|
|
31
35
|
<div id=iframeContainer></div>
|
|
36
|
+
</div>
|
|
32
37
|
<div>
|
|
33
|
-
<input id=inputSearch placeholder='Search examples...' oninput=filterExamples() onkeydown='if(event.key
|
|
38
|
+
<input id=inputSearch placeholder='Search examples...' oninput=filterExamples() onkeydown='if(event.key===`Escape`)filterExamples(1)'>
|
|
34
39
|
<button id=buttonRestart onclick=restartCode()>Restart</button>
|
|
35
40
|
<button id=buttonPause>Pause</button>
|
|
36
|
-
<
|
|
41
|
+
<span id=container4>
|
|
37
42
|
<button id=buttonFullscreen>Fullscreen</button>
|
|
38
|
-
<
|
|
43
|
+
<button id=buttonScreenshot>Screenshot</button>
|
|
44
|
+
<span class=nowrap><input type=checkbox id=checkboxWebGL checked>WebGL</span>
|
|
45
|
+
</span>
|
|
39
46
|
</div>
|
|
40
|
-
<
|
|
47
|
+
<span id=selectExampleText class=nowrap>Select an example...</span>
|
|
48
|
+
<select id=selectExample onchange=setExample() size=2></select>
|
|
41
49
|
</div>
|
|
42
50
|
</div>
|
|
43
51
|
<script>
|
|
@@ -66,21 +74,23 @@ const exampleList =
|
|
|
66
74
|
new ExampleInfo('Timers', 'timers.js', 'Timer object and timing functions - [utilities, time]'),
|
|
67
75
|
new ExampleInfo('Texture', 'texture.js', 'Display and manipulate textures - [graphics, sprites]'),
|
|
68
76
|
new ExampleInfo('Particles', 'particles.js', 'Particle system basics - [effects, particles]'),
|
|
69
|
-
new ExampleInfo('
|
|
77
|
+
new ExampleInfo('Sound Effects', 'sound.js', 'Example sound effects with ZzFX - [audio, sound]'),
|
|
78
|
+
new ExampleInfo('Music', 'music.js', 'Example sound loading, play, pause, seek, and stop - [audio, music]'),
|
|
70
79
|
new ExampleInfo('System Font', 'systemFont.js', 'Built-in bitmap font rendering - [text, font]'),
|
|
71
80
|
new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'Sprite atlas and tile rendering - [graphics, sprites]'),
|
|
72
81
|
new ExampleInfo('Animation', 'animation.js', 'Sprite animation techniques - [animation, sprites]'),
|
|
73
82
|
new ExampleInfo('Clock', 'clock.js', 'Real-time clock display - [time, ui, utilities]'),
|
|
83
|
+
new ExampleInfo('Medals', 'medals.js', 'Achievement system demo - [ui, achievements]'),
|
|
74
84
|
new ExampleInfo('Tile Layer', 'tileLayer.js', 'Tile layer rendering system - [tiles, levels]'),
|
|
75
85
|
new ExampleInfo('Piano', 'piano.js', 'Interactive piano keyboard - [audio, input, ui]'),
|
|
76
86
|
new ExampleInfo('Maze Generator', 'maze.js', 'Procedural maze generation - [algorithm, generation]'),
|
|
77
|
-
new ExampleInfo('Starfield', 'starfield.js', 'Animated
|
|
78
|
-
new ExampleInfo('
|
|
87
|
+
new ExampleInfo('Starfield', 'starfield.js', 'Animated starfield - [effects, space]'),
|
|
88
|
+
new ExampleInfo('Parallax', 'parallax.js', 'Parallax mountains - [graphics, effects]'),
|
|
79
89
|
new ExampleInfo('--- MINI GAMES ---'),
|
|
80
90
|
new ExampleInfo('Platformer Game', 'platformer.js', 'Jump and run platformer - [game, platformer, physics]'),
|
|
81
91
|
new ExampleInfo('Top Down Game', 'topDown.js', 'Adventure with collision - [game, topdown, rpg]'),
|
|
82
92
|
new ExampleInfo('Tilted View', 'tiltedView.js', 'Pseudo 3D tilted perspective - [3d, camera, perspective]'),
|
|
83
|
-
new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship with parallax
|
|
93
|
+
new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship with parallax starfield - [game, space, shooter]'),
|
|
84
94
|
new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander physics game - [game, physics, thrust]'),
|
|
85
95
|
new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird clone - [game, arcade, flying]'),
|
|
86
96
|
new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style glider - [game, physics, flying]'),
|
|
@@ -108,32 +118,90 @@ const exampleList =
|
|
|
108
118
|
///////////////////////////////////////////////////////////////////////////////
|
|
109
119
|
|
|
110
120
|
// global variables
|
|
111
|
-
let iframeExample
|
|
121
|
+
let iframeExample; // iframe of the current loaded example
|
|
122
|
+
let inputTimeout; // timeout to debounce input
|
|
123
|
+
let consoleAutoScroll; // allow console to scroll automatically
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
function initExampleBrowser()
|
|
126
|
+
{
|
|
127
|
+
// load the examples into the list
|
|
128
|
+
filterExamples();
|
|
129
|
+
|
|
130
|
+
// set the selected example from the URL parameters
|
|
131
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
132
|
+
const selectedExample = urlParams.get('example') || '';
|
|
133
|
+
let exampleIndex = exampleList.findIndex((example) => example.filename === selectedExample);
|
|
134
|
+
if (exampleIndex < 0)
|
|
135
|
+
exampleIndex = 1;
|
|
136
|
+
selectExample.selectedIndex = exampleIndex;
|
|
137
|
+
setExample();
|
|
115
138
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (exampleIndex < 0)
|
|
121
|
-
exampleIndex = 1;
|
|
122
|
-
selectExample.selectedIndex = exampleIndex;
|
|
123
|
-
setExample();
|
|
139
|
+
// apply responsive layout
|
|
140
|
+
addEventListener('resize', resizeWindow);
|
|
141
|
+
resizeWindow();
|
|
142
|
+
}
|
|
124
143
|
|
|
125
|
-
|
|
144
|
+
function resizeWindow()
|
|
126
145
|
{
|
|
127
|
-
//
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
// tweak layout for touch devices
|
|
147
|
+
const isTouchDevice = window.ontouchstart !== undefined;
|
|
148
|
+
if (isTouchDevice)
|
|
149
|
+
container4.style.display = 'none';
|
|
150
|
+
else
|
|
151
|
+
selectExampleText.style.display = 'none';
|
|
152
|
+
|
|
153
|
+
const windowAspect = innerWidth / innerHeight;
|
|
154
|
+
const verticalLayout = windowAspect < 1;
|
|
155
|
+
if (verticalLayout)
|
|
156
|
+
{
|
|
157
|
+
// vertical layout for thin screens
|
|
158
|
+
if (container2.parentNode != container3)
|
|
159
|
+
container3.insertBefore(container2, divCodeOptions);
|
|
160
|
+
// resize iframe to the window
|
|
161
|
+
setFrameSize(innerWidth-35);
|
|
162
|
+
|
|
163
|
+
// fix code mirror sizing glitch
|
|
164
|
+
codeMirror && codeMirror.setSize(innerWidth-35, null);
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
{
|
|
168
|
+
// horizontal layout for wide screens
|
|
169
|
+
if (container2.parentNode != container1)
|
|
170
|
+
container1.appendChild(container2);
|
|
171
|
+
|
|
172
|
+
// show full controls
|
|
173
|
+
container4.style.display = '';
|
|
174
|
+
|
|
175
|
+
// resize iframe to fit half the window
|
|
176
|
+
setFrameSize(innerWidth/2);
|
|
177
|
+
|
|
178
|
+
// fix code mirror sizing glitch
|
|
179
|
+
codeMirror && codeMirror.setSize(innerWidth/2 - 35, null);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function setFrameSize(w)
|
|
183
|
+
{
|
|
184
|
+
const aspect = 16 / 9; // HD aspect ratio
|
|
185
|
+
w = w | 0;
|
|
186
|
+
const h = w / aspect | 0;
|
|
187
|
+
iframeContainer.style.width = w + 'px';
|
|
188
|
+
iframeContainer.style.height = h + 'px';
|
|
189
|
+
|
|
190
|
+
if (iframeExample)
|
|
191
|
+
{
|
|
192
|
+
// ensure iframe fills container tightly
|
|
193
|
+
iframeExample.style.width = w + 'px';
|
|
194
|
+
iframeExample.style.height = h + 'px';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// fix code mirror after layout changes
|
|
198
|
+
if (codeMirror)
|
|
199
|
+
{
|
|
200
|
+
// fix glitch with code mirror sizing
|
|
201
|
+
setTimeout(()=>codeMirror.refresh(), 500);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
135
204
|
}
|
|
136
|
-
onresize();
|
|
137
205
|
|
|
138
206
|
///////////////////////////////////////////////////////////////////////////////
|
|
139
207
|
// setting examples
|
|
@@ -157,7 +225,7 @@ function setExample()
|
|
|
157
225
|
exampleInfo.innerText = example.name + (example.description ? ' - ' + example.description : '');
|
|
158
226
|
const filename = 'examples/' + example.filename;
|
|
159
227
|
exampleLink.href = 'https://github.com/KilledByAPixel/LittleJS/tree/main/' + filename;
|
|
160
|
-
exampleLink.innerText =
|
|
228
|
+
exampleLink.innerText = 'View code on GitHub';
|
|
161
229
|
|
|
162
230
|
// update URL parameter
|
|
163
231
|
const url = new URL(window.location);
|
|
@@ -175,11 +243,7 @@ async function loadFile(filename, largeExample)
|
|
|
175
243
|
textareaCode.disabled = largeExample;
|
|
176
244
|
|
|
177
245
|
// disable buttons in large example mode
|
|
178
|
-
|
|
179
|
-
buttonRestart.disabled = largeExample;
|
|
180
|
-
buttonScreenshot.disabled = largeExample;
|
|
181
|
-
buttonFullscreen.disabled = largeExample;
|
|
182
|
-
checkboxWebGL.disabled = largeExample;
|
|
246
|
+
setFrameControlsEnabled(!largeExample);
|
|
183
247
|
|
|
184
248
|
if (largeExample)
|
|
185
249
|
{
|
|
@@ -254,7 +318,7 @@ function filterExamples(reset=0)
|
|
|
254
318
|
if (!selectExample.options.length)
|
|
255
319
|
{
|
|
256
320
|
// show a message if no matches
|
|
257
|
-
const o = new Option(
|
|
321
|
+
const o = new Option(`No examples found matching '${searchTerm}'`);
|
|
258
322
|
o.disabled = true;
|
|
259
323
|
selectExample.add(o);
|
|
260
324
|
}
|
|
@@ -281,9 +345,20 @@ function restartCode()
|
|
|
281
345
|
setCode(code);
|
|
282
346
|
}
|
|
283
347
|
|
|
284
|
-
function
|
|
348
|
+
function setFrameControlsEnabled(enabled=true)
|
|
285
349
|
{
|
|
286
|
-
|
|
350
|
+
buttonPause.disabled = !enabled;
|
|
351
|
+
buttonRestart.disabled = !enabled;
|
|
352
|
+
buttonScreenshot.disabled = !enabled;
|
|
353
|
+
buttonFullscreen.disabled = !enabled;
|
|
354
|
+
checkboxWebGL.disabled = !enabled;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function setCode(code, filename)
|
|
358
|
+
{
|
|
359
|
+
const largeExample = !!filename;
|
|
360
|
+
filename = filename || 'shorts/base.html';
|
|
361
|
+
|
|
287
362
|
clearTimeout(inputTimeout);
|
|
288
363
|
if (iframeExample)
|
|
289
364
|
iframeContainer.removeChild(iframeExample);
|
|
@@ -292,18 +367,26 @@ function setCode(code, textFilename)
|
|
|
292
367
|
unsetConsoleMessage();
|
|
293
368
|
iframeExample = document.createElement('iframe');
|
|
294
369
|
iframeContainer.appendChild(iframeExample);
|
|
370
|
+
|
|
295
371
|
iframeExample.onload = ()=>
|
|
296
372
|
{
|
|
297
373
|
if (largeExample)
|
|
298
374
|
return;
|
|
299
375
|
|
|
376
|
+
// get the iframe content window and document
|
|
300
377
|
const iframeContent = iframeExample.contentWindow;
|
|
301
378
|
const iframeDocument = iframeContent.document;
|
|
379
|
+
if (!iframeContent.engineInit)
|
|
380
|
+
{
|
|
381
|
+
setErrorMessage(`Failed to load ${filename}`);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
302
384
|
|
|
303
385
|
// intercept errors
|
|
304
386
|
function getErrorLine(stack)
|
|
305
387
|
{
|
|
306
|
-
// try to extract line number from stack trace
|
|
388
|
+
// try to extract line number from stack trace
|
|
389
|
+
// look for <anonymous> or injectedScript to find user code
|
|
307
390
|
const anonymousMatch = stack.match(/(<anonymous>|injectedScript):(\d+)/);
|
|
308
391
|
return anonymousMatch ? parseInt(anonymousMatch[2]) : -1;
|
|
309
392
|
}
|
|
@@ -332,7 +415,7 @@ function setCode(code, textFilename)
|
|
|
332
415
|
|
|
333
416
|
// intercept asserts
|
|
334
417
|
const originalAssert = iframeContent.console.assert;
|
|
335
|
-
iframeContent.console.assert = function (condition, output)
|
|
418
|
+
iframeContent.console.assert = function (condition, ...output)
|
|
336
419
|
{
|
|
337
420
|
if (!condition)
|
|
338
421
|
{
|
|
@@ -340,7 +423,10 @@ function setCode(code, textFilename)
|
|
|
340
423
|
const errorLine = getErrorLine(stack);
|
|
341
424
|
if (errorLine >= 0)
|
|
342
425
|
setErrorLine(errorLine);
|
|
343
|
-
|
|
426
|
+
|
|
427
|
+
// format output parameters properly
|
|
428
|
+
const outputMessage = output.length > 0 ? output.map(m => stringifyMessage(m)).join(' ') : '';
|
|
429
|
+
setErrorMessage('Assertion failed!\n' + outputMessage + '\n' + stack);
|
|
344
430
|
}
|
|
345
431
|
originalAssert.apply(this, arguments);
|
|
346
432
|
};
|
|
@@ -360,7 +446,7 @@ function setCode(code, textFilename)
|
|
|
360
446
|
['log','info','warn','error','debug'].forEach(f=>interceptConsole(f));
|
|
361
447
|
|
|
362
448
|
{
|
|
363
|
-
//
|
|
449
|
+
// hook up buttons
|
|
364
450
|
buttonScreenshot.onclick = () =>
|
|
365
451
|
{
|
|
366
452
|
if (iframeContent.debugScreenshot)
|
|
@@ -383,22 +469,34 @@ function setCode(code, textFilename)
|
|
|
383
469
|
buttonFullscreen.onclick = ()=> iframeContent.toggleFullscreen();
|
|
384
470
|
}
|
|
385
471
|
|
|
386
|
-
// set WebGL based on checkbox
|
|
387
|
-
iframeContent.setGLEnable(checkboxWebGL.checked);
|
|
388
|
-
checkboxWebGL.onchange = ()=> iframeContent.setGLEnable(checkboxWebGL.checked);
|
|
389
|
-
|
|
390
472
|
// create a script element that overrides the default functions
|
|
391
473
|
const overrideScript = iframeDocument.createElement('script');
|
|
392
474
|
iframeDocument.body.appendChild(overrideScript);
|
|
393
475
|
overrideScript.text = code;
|
|
394
|
-
|
|
395
|
-
if (textareaError.style.display
|
|
396
|
-
return;
|
|
397
|
-
|
|
476
|
+
|
|
477
|
+
if (textareaError.style.display === 'block')
|
|
478
|
+
return;
|
|
479
|
+
|
|
398
480
|
// start LittleJS engine
|
|
399
|
-
iframeContent.engineInit(iframeContent.gameInit, iframeContent.gameUpdate, iframeContent.gameUpdatePost, iframeContent.gameRender, iframeContent.gameRenderPost, ['tiles.png?'+Date.now()])
|
|
481
|
+
iframeContent.engineInit(iframeContent.gameInit, iframeContent.gameUpdate, iframeContent.gameUpdatePost, iframeContent.gameRender, iframeContent.gameRenderPost, ['tiles.png?'+Date.now()])
|
|
482
|
+
.catch(error =>
|
|
483
|
+
{
|
|
484
|
+
const errorLine = getErrorLine(error.stack);
|
|
485
|
+
if (errorLine >= 0)
|
|
486
|
+
setErrorLine(errorLine);
|
|
487
|
+
setErrorMessage(error.message + '\n' + error.stack);
|
|
488
|
+
throw error;
|
|
489
|
+
})
|
|
490
|
+
.then(()=>
|
|
491
|
+
{
|
|
492
|
+
// setup frame controls
|
|
493
|
+
setFrameControlsEnabled();
|
|
494
|
+
iframeContent.setGLEnable(checkboxWebGL.checked);
|
|
495
|
+
checkboxWebGL.onchange = ()=> iframeContent.setGLEnable(checkboxWebGL.checked);
|
|
496
|
+
})
|
|
400
497
|
}
|
|
401
|
-
|
|
498
|
+
|
|
499
|
+
iframeExample.src = filename + '?' + Date.now();
|
|
402
500
|
}
|
|
403
501
|
|
|
404
502
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -427,9 +525,9 @@ function setMessage(message, element, clear=true)
|
|
|
427
525
|
element.value = message;
|
|
428
526
|
else
|
|
429
527
|
element.value += '\n' + message;
|
|
430
|
-
element.style.display = message ? 'block' : '
|
|
528
|
+
element.style.display = message ? 'block' : '';
|
|
431
529
|
|
|
432
|
-
if (element
|
|
530
|
+
if (element === textareaConsole)
|
|
433
531
|
{
|
|
434
532
|
const maxConsoleLines = 100;
|
|
435
533
|
const lines = element.value.split('\n');
|
|
@@ -448,7 +546,7 @@ function setMessage(message, element, clear=true)
|
|
|
448
546
|
|
|
449
547
|
function unsetMessage(element)
|
|
450
548
|
{
|
|
451
|
-
element.style.display = '
|
|
549
|
+
element.style.display = '';
|
|
452
550
|
element.value = '';
|
|
453
551
|
}
|
|
454
552
|
|
|
@@ -469,13 +567,13 @@ function setErrorLine(lineNumber)
|
|
|
469
567
|
clearErrorLine();
|
|
470
568
|
--lineNumber; // codeMirror uses 0-based line numbers
|
|
471
569
|
errorLineMarker = codeMirror.getDoc().addLineClass(lineNumber, 'background', 'error-line');
|
|
472
|
-
if (
|
|
570
|
+
if (checkboxJumpToErrors.checked)
|
|
473
571
|
{
|
|
474
572
|
codeMirror.scrollIntoView({line: lineNumber, ch: 0});
|
|
475
573
|
codeMirror.focus();
|
|
476
574
|
}
|
|
477
575
|
}
|
|
478
|
-
else if (textareaCode &&
|
|
576
|
+
else if (textareaCode && checkboxJumpToErrors.checked)
|
|
479
577
|
{
|
|
480
578
|
// for textarea, calculate character position and set cursor
|
|
481
579
|
const lines = textareaCode.value.split('\n');
|
|
@@ -485,7 +583,7 @@ function setErrorLine(lineNumber)
|
|
|
485
583
|
charPos += lines[i].length + 1; // +1 for newline character
|
|
486
584
|
}
|
|
487
585
|
textareaCode.setSelectionRange(charPos, charPos);
|
|
488
|
-
if (
|
|
586
|
+
if (checkboxJumpToErrors.checked)
|
|
489
587
|
{
|
|
490
588
|
const line = textareaCode.value.split('\n');
|
|
491
589
|
lineNumber = Math.min(Math.max(0, lineNumber-1), lines.length-1);
|
|
@@ -504,7 +602,8 @@ function setErrorLine(lineNumber)
|
|
|
504
602
|
function setErrorMessage(message)
|
|
505
603
|
{
|
|
506
604
|
// prevent overwriting an existing error message
|
|
507
|
-
const errorMessageIsVisible = textareaError.style.display
|
|
605
|
+
const errorMessageIsVisible = textareaError.style.display === 'block';
|
|
606
|
+
setFrameControlsEnabled(false);
|
|
508
607
|
if (!errorMessageIsVisible)
|
|
509
608
|
setMessage(message, textareaError);
|
|
510
609
|
}
|
|
@@ -520,10 +619,23 @@ function onScrollConsole()
|
|
|
520
619
|
textareaConsole.addEventListener('scroll', onScrollConsole);
|
|
521
620
|
|
|
522
621
|
///////////////////////////////////////////////////////////////////////////////
|
|
523
|
-
//
|
|
524
|
-
|
|
622
|
+
// load saved preferences from localStorage
|
|
525
623
|
const defaultTheme = 'littlejs';
|
|
526
624
|
const defaultFontSize = '16px';
|
|
625
|
+
const savePrefix = 'LittleJSExamples_';
|
|
626
|
+
const savedTheme = localStorage.getItem(savePrefix+'theme') || defaultTheme;
|
|
627
|
+
const savedFontSize = localStorage.getItem(savePrefix+'fontSize') || defaultFontSize;
|
|
628
|
+
selectFontSize.value = savedFontSize;
|
|
629
|
+
textareaCode.style.fontSize = savedFontSize;
|
|
630
|
+
|
|
631
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
632
|
+
// setup code mirror
|
|
633
|
+
|
|
634
|
+
const useCodeMirror = true;
|
|
635
|
+
let codeMirror; // code mirror instance
|
|
636
|
+
let errorLineMarker; // marker for error line in code mirror
|
|
637
|
+
let codeIsJS; // is the current code javascript
|
|
638
|
+
|
|
527
639
|
const themes =
|
|
528
640
|
[
|
|
529
641
|
'littlejs',
|
|
@@ -539,46 +651,40 @@ const themes =
|
|
|
539
651
|
'yonce'
|
|
540
652
|
];
|
|
541
653
|
|
|
542
|
-
//
|
|
543
|
-
const savePrefix = 'LittleJSExamples_';
|
|
544
|
-
const savedTheme = localStorage.getItem(savePrefix+'theme') || defaultTheme;
|
|
545
|
-
const savedFontSize = localStorage.getItem(savePrefix+'fontSize') || defaultFontSize;
|
|
546
|
-
|
|
654
|
+
// load theme and font size
|
|
547
655
|
for (const theme of themes)
|
|
548
656
|
{
|
|
549
657
|
if (theme != 'littlejs')
|
|
550
658
|
addCodeMirrorElement(`theme/${theme}.min.css`, 'link', 'stylesheet');
|
|
551
659
|
const o = new Option(theme);
|
|
552
|
-
o.selected = theme
|
|
660
|
+
o.selected = theme === savedTheme;
|
|
553
661
|
selectTheme.add(o);
|
|
554
662
|
}
|
|
555
663
|
|
|
556
|
-
|
|
557
|
-
selectFontSize.value = savedFontSize;
|
|
558
|
-
textareaCode.style.fontSize = savedFontSize;
|
|
559
|
-
addCodeMirrorElement('codemirror.min.js', 'script').onload = ()=>
|
|
560
|
-
addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet').onload = ()=>
|
|
561
|
-
addCodeMirrorElement('addon/edit/matchbrackets.js', 'script').onload = ()=>
|
|
562
|
-
addCodeMirrorElement('mode/javascript/javascript.min.js', 'script').onload = ()=>
|
|
563
|
-
initCodeMirror();
|
|
564
|
-
|
|
565
|
-
function initCodeMirror()
|
|
664
|
+
if (useCodeMirror)
|
|
566
665
|
{
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
codeMirror = CodeMirror.fromTextArea(textareaCode,
|
|
666
|
+
addCodeMirrorElement('codemirror.min.js', 'script').onload = ()=>
|
|
667
|
+
addCodeMirrorElement('codemirror.min.css', 'link', 'stylesheet').onload = ()=>
|
|
668
|
+
addCodeMirrorElement('addon/edit/matchbrackets.js', 'script').onload = ()=>
|
|
669
|
+
addCodeMirrorElement('mode/javascript/javascript.min.js', 'script').onload = ()=>
|
|
572
670
|
{
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
671
|
+
if (codeMirror)
|
|
672
|
+
return; // prevent duplicate initialization
|
|
673
|
+
|
|
674
|
+
const textareaCode = document.getElementById('textareaCode');
|
|
675
|
+
codeMirror = CodeMirror.fromTextArea(textareaCode,
|
|
676
|
+
{
|
|
677
|
+
theme: savedTheme,
|
|
678
|
+
indentUnit: 4,
|
|
679
|
+
mode: codeIsJS ? 'javascript' : 'text',
|
|
680
|
+
lineNumbers: true,
|
|
681
|
+
lineWrapping: true,
|
|
682
|
+
matchBrackets: true,
|
|
683
|
+
});
|
|
684
|
+
codeMirror.on('change', codeInput);
|
|
685
|
+
loadTheme();
|
|
686
|
+
resizeWindow(); // fix cursor positioning issue on startup
|
|
687
|
+
}
|
|
582
688
|
}
|
|
583
689
|
|
|
584
690
|
function addCodeMirrorElement(filename, type, rel)
|
|
@@ -587,7 +693,7 @@ function addCodeMirrorElement(filename, type, rel)
|
|
|
587
693
|
const e = document.createElement(type);
|
|
588
694
|
e.rel = rel;
|
|
589
695
|
filename = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/' + filename;
|
|
590
|
-
if (type
|
|
696
|
+
if (type === 'link')
|
|
591
697
|
e.href = filename;
|
|
592
698
|
else
|
|
593
699
|
e.src = filename;
|
|
@@ -614,6 +720,11 @@ function loadTheme()
|
|
|
614
720
|
}
|
|
615
721
|
}
|
|
616
722
|
|
|
723
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
724
|
+
|
|
725
|
+
// start up the browser
|
|
726
|
+
initExampleBrowser()
|
|
727
|
+
|
|
617
728
|
</script>
|
|
618
729
|
</body>
|
|
619
730
|
</html>
|
|
@@ -82,7 +82,7 @@ export function explosion(pos, radius=3)
|
|
|
82
82
|
GameLevel.decorateTile(pos.add(vec2(x,y)).floor());
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// update
|
|
85
|
+
// update WebGL texture
|
|
86
86
|
GameLevel.updateWebGL(GameLevel.foregroundTileLayer);
|
|
87
87
|
|
|
88
88
|
// kill/push objects
|
|
@@ -153,7 +153,7 @@ export function destroyTile(pos, makeSound = 1, cleanup = 1)
|
|
|
153
153
|
layer.setData(pos, new LJS.TileLayerData, true);
|
|
154
154
|
layer.setCollisionData(pos, GameLevel.tileType_empty);
|
|
155
155
|
|
|
156
|
-
// cleanup neighbors and rebuild
|
|
156
|
+
// cleanup neighbors and rebuild WebGL
|
|
157
157
|
if (cleanup)
|
|
158
158
|
{
|
|
159
159
|
for (let i=-1;i<=1;++i)
|
|
@@ -184,13 +184,7 @@ export class Sky extends LJS.EngineObject
|
|
|
184
184
|
{
|
|
185
185
|
// fill background with a gradient
|
|
186
186
|
const canvas = LJS.mainCanvas;
|
|
187
|
-
|
|
188
|
-
for (let y=0; y<canvas.height; y+=gradientDelta)
|
|
189
|
-
{
|
|
190
|
-
// draw horizontal lines to create a gradient
|
|
191
|
-
const color = this.skyColor.lerp(this.horizonColor, y/canvas.height);
|
|
192
|
-
LJS.drawRect(vec2(0,y), vec2(canvas.width*2, gradientDelta*2), color, 0, undefined, true)
|
|
193
|
-
}
|
|
187
|
+
LJS.drawRectGradient(LJS.cameraPos, LJS.getCameraSize(), this.skyColor, this.horizonColor);
|
|
194
188
|
|
|
195
189
|
// draw stars
|
|
196
190
|
LJS.setBlendMode(true);
|
|
@@ -216,21 +210,20 @@ export class Sky extends LJS.EngineObject
|
|
|
216
210
|
|
|
217
211
|
export class ParallaxLayer extends LJS.CanvasLayer
|
|
218
212
|
{
|
|
219
|
-
constructor(depth)
|
|
213
|
+
constructor(pos, topColor, bottomColor, depth)
|
|
220
214
|
{
|
|
221
215
|
const renderOrder = depth - 3e3;
|
|
222
216
|
const canvasSize = vec2(512, 256);
|
|
223
|
-
super(
|
|
224
|
-
this.
|
|
217
|
+
super(pos, vec2(), 0, renderOrder, canvasSize);
|
|
218
|
+
this.centerPos = pos;
|
|
225
219
|
this.depth = depth;
|
|
226
220
|
|
|
227
221
|
// create a gradient for the mountains
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
for (let i = canvasSize.y; i--;)
|
|
222
|
+
const w = canvasSize.x, h = canvasSize.y;
|
|
223
|
+
for (let i = h; i--;)
|
|
231
224
|
{
|
|
232
225
|
// draw a 1 pixel gradient line on the left side of the canvas
|
|
233
|
-
const p = i/
|
|
226
|
+
const p = i/h;
|
|
234
227
|
this.context.fillStyle = topColor.lerp(bottomColor, p);
|
|
235
228
|
this.context.fillRect(0, i, 1, 1);
|
|
236
229
|
}
|
|
@@ -239,40 +232,42 @@ export class ParallaxLayer extends LJS.CanvasLayer
|
|
|
239
232
|
const pointiness = .2; // how pointy the mountains are
|
|
240
233
|
const levelness = .005; // how much the mountains level out
|
|
241
234
|
const slopeRange = 1; // max slope of the mountains
|
|
242
|
-
const startGroundLevel =
|
|
235
|
+
const startGroundLevel = h/2;
|
|
243
236
|
let y = startGroundLevel, groundSlope = LJS.rand(-slopeRange, slopeRange);
|
|
244
|
-
for (let x=
|
|
237
|
+
for (let x=w; x--;)
|
|
245
238
|
{
|
|
246
239
|
// pull slope towards start ground level
|
|
247
240
|
y += groundSlope -= (y-startGroundLevel)*levelness;
|
|
248
241
|
|
|
249
|
-
//
|
|
242
|
+
// randomly change slope
|
|
250
243
|
if (LJS.rand() < pointiness)
|
|
251
244
|
groundSlope = LJS.rand(-slopeRange, slopeRange);
|
|
252
245
|
|
|
253
246
|
// draw 1 pixel wide vertical slice of mountain
|
|
254
|
-
this.context.drawImage(this.canvas, 0, 0, 1,
|
|
247
|
+
this.context.drawImage(this.canvas, 0, 0, 1, h, x, y, 1, h - y);
|
|
255
248
|
}
|
|
256
249
|
|
|
257
250
|
// remove gradient sliver from left side
|
|
258
|
-
this.context.clearRect(0,0,1,
|
|
251
|
+
this.context.clearRect(0,0,1,h);
|
|
259
252
|
|
|
260
|
-
// make
|
|
253
|
+
// make WebGL texture
|
|
261
254
|
this.useWebGL(LJS.glEnable);
|
|
262
255
|
}
|
|
263
256
|
|
|
264
257
|
render()
|
|
265
258
|
{
|
|
259
|
+
const canvasSize = vec2(this.canvas.width, this.canvas.height);
|
|
266
260
|
const depth = this.depth
|
|
267
261
|
const distance = 4 + depth;
|
|
268
262
|
const parallax = vec2(150, 30).scale(depth**2+1);
|
|
269
|
-
const levelCenter =
|
|
270
|
-
const cameraDeltaFromCenter = LJS.cameraPos.subtract(levelCenter)
|
|
263
|
+
const levelCenter = this.centerPos;
|
|
264
|
+
const cameraDeltaFromCenter = LJS.cameraPos.subtract(levelCenter)
|
|
265
|
+
.divide(levelCenter.scale(-1).divide(parallax));
|
|
271
266
|
const scale = distance/LJS.cameraScale;
|
|
272
267
|
const positonOffset = vec2(0, 2-depth);
|
|
273
268
|
const cameraOffset = cameraDeltaFromCenter.scale(1/LJS.cameraScale);
|
|
274
269
|
this.pos = LJS.cameraPos.add(positonOffset).add(cameraOffset);
|
|
275
|
-
this.size =
|
|
270
|
+
this.size = canvasSize.scale(scale);
|
|
276
271
|
super.render();
|
|
277
272
|
}
|
|
278
273
|
}
|
|
@@ -39,7 +39,12 @@ export function buildLevel()
|
|
|
39
39
|
|
|
40
40
|
// create parallax layers
|
|
41
41
|
for (let i=3; i--;)
|
|
42
|
-
|
|
42
|
+
{
|
|
43
|
+
const pos = levelSize.scale(.5);
|
|
44
|
+
const topColor = levelColor.mutate(.2).lerp(sky.skyColor, .8 - i*.15);
|
|
45
|
+
const bottomColor = levelColor.subtract(LJS.CLEAR_WHITE).mutate(.2);
|
|
46
|
+
new GameEffects.ParallaxLayer(pos, topColor, bottomColor, i );
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
function loadLevelData()
|