littlejsengine 1.9.7 → 1.9.9
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/LICENSE +22 -0
- package/README.md +13 -8
- package/dist/littlejs.d.ts +75 -69
- package/dist/littlejs.esm.js +313 -354
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +307 -350
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +244 -319
- package/examples/box2d/game.js +137 -0
- package/examples/box2d/index.html +12 -0
- package/examples/box2d/scenes.js +412 -0
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +3 -3
- package/examples/breakout/index.html +4 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/build.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/js13k/build.js +19 -1
- package/examples/js13k/index.html +13 -13
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/gameEffects.js +2 -2
- package/examples/platformer/gameLevel.js +0 -1
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/build.js +1 -1
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/index.html +1 -1
- package/package.json +1 -1
- package/plugins/Box2D_v2.3.1_min.wasm.js +630 -0
- package/plugins/Box2D_v2.3.1_min.wasm.wasm +0 -0
- package/plugins/box2d.js +879 -0
- package/plugins/newgrounds.js +169 -0
- package/plugins/postProcess.js +102 -0
- package/src/engine.js +48 -29
- package/src/engineAudio.js +20 -12
- package/src/engineBuild.js +1 -1
- package/src/engineDebug.js +68 -33
- package/src/engineDraw.js +1 -1
- package/src/engineExport.js +6 -4
- package/src/engineInput.js +7 -4
- package/src/engineMedals.js +40 -171
- package/src/engineObject.js +33 -2
- package/src/engineRelease.js +5 -2
- package/src/engineSettings.js +14 -2
- package/src/engineUtilities.js +75 -2
- package/src/engineWebGL.js +1 -94
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Newgrounds API
|
|
3
|
+
* - NewgroundsMedal extends Medal with Newgrounds API functionality
|
|
4
|
+
* - Call newgroundsInit to enable Newgrounds functionality
|
|
5
|
+
* - Uses CryptoJS for encryption if optional cipher is provided
|
|
6
|
+
* - Keeps connection alive and logs views
|
|
7
|
+
* - Functions to interact with scoreboards
|
|
8
|
+
* - Functions to unlock medals
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
|
|
15
|
+
/** Newgrounds medal auto unlocks in newgronds API */
|
|
16
|
+
class NewgroundsMedal extends Medal
|
|
17
|
+
{
|
|
18
|
+
/** Create a medal object and adds it to the list of medals */
|
|
19
|
+
constructor(id, name, description, icon, src)
|
|
20
|
+
{ super(id, name, description, icon, src); }
|
|
21
|
+
|
|
22
|
+
/** Unlocks a medal if not already unlocked */
|
|
23
|
+
unlock()
|
|
24
|
+
{
|
|
25
|
+
super.unlock();
|
|
26
|
+
newgrounds && newgrounds.unlockMedal(this.id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
31
|
+
|
|
32
|
+
/** Global Newgrounds object */
|
|
33
|
+
let newgrounds;
|
|
34
|
+
|
|
35
|
+
/** This can used to enable Newgrounds functionality
|
|
36
|
+
* @param {Number} app_id - The newgrounds App ID
|
|
37
|
+
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
38
|
+
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher
|
|
39
|
+
* @memberof Medals */
|
|
40
|
+
function newgroundsInit(app_id, cipher, cryptoJS)
|
|
41
|
+
{ newgrounds = new Newgrounds(app_id, cipher, cryptoJS); }
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Newgrounds API wrapper object
|
|
45
|
+
* @example
|
|
46
|
+
* // create a newgrounds object, replace the app id with your own
|
|
47
|
+
* const app_id = '52123:1ZuSTQ7l';
|
|
48
|
+
* newgrounds = new Newgrounds(app_id);
|
|
49
|
+
*/
|
|
50
|
+
class Newgrounds
|
|
51
|
+
{
|
|
52
|
+
/** Create a newgrounds object
|
|
53
|
+
* @param {String} app_id - The newgrounds App ID
|
|
54
|
+
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
55
|
+
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
|
|
56
|
+
constructor(app_id, cipher, cryptoJS)
|
|
57
|
+
{
|
|
58
|
+
ASSERT(!newgrounds, 'there can only be one newgrounds object');
|
|
59
|
+
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
60
|
+
|
|
61
|
+
this.app_id = app_id;
|
|
62
|
+
this.cipher = cipher;
|
|
63
|
+
this.cryptoJS = cryptoJS;
|
|
64
|
+
this.host = location ? location.hostname : '';
|
|
65
|
+
|
|
66
|
+
// get session id from url search params
|
|
67
|
+
const url = new URL(location.href);
|
|
68
|
+
this.session_id = url.searchParams.get('ngio_session_id');
|
|
69
|
+
|
|
70
|
+
if (!this.session_id)
|
|
71
|
+
return; // only use newgrounds when logged in
|
|
72
|
+
|
|
73
|
+
// get medals
|
|
74
|
+
const medalsResult = this.call('Medal.getList');
|
|
75
|
+
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
76
|
+
debugMedals && console.log(this.medals);
|
|
77
|
+
for (const newgroundsMedal of this.medals)
|
|
78
|
+
{
|
|
79
|
+
const medal = medals[newgroundsMedal['id']];
|
|
80
|
+
if (medal)
|
|
81
|
+
{
|
|
82
|
+
// copy newgrounds medal data
|
|
83
|
+
medal.image = new Image;
|
|
84
|
+
medal.image.src = newgroundsMedal['icon'];
|
|
85
|
+
medal.name = newgroundsMedal['name'];
|
|
86
|
+
medal.description = newgroundsMedal['description'];
|
|
87
|
+
medal.unlocked = newgroundsMedal['unlocked'];
|
|
88
|
+
medal.difficulty = newgroundsMedal['difficulty'];
|
|
89
|
+
medal.value = newgroundsMedal['value'];
|
|
90
|
+
|
|
91
|
+
if (medal.value) // add value to description
|
|
92
|
+
medal.description = medal.description + ` (${ medal.value })`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// get scoreboards
|
|
97
|
+
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
98
|
+
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
99
|
+
debugMedals && console.log(this.scoreboards);
|
|
100
|
+
|
|
101
|
+
// keep the session alive with a ping every 5 minutes
|
|
102
|
+
const keepAliveMS = 5 * 60 * 1e3;
|
|
103
|
+
setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Send message to unlock a medal by id
|
|
107
|
+
* @param {Number} id - The medal id */
|
|
108
|
+
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
|
|
109
|
+
|
|
110
|
+
/** Send message to post score
|
|
111
|
+
* @param {Number} id - The scoreboard id
|
|
112
|
+
* @param {Number} value - The score value */
|
|
113
|
+
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
|
|
114
|
+
|
|
115
|
+
/** Get scores from a scoreboard
|
|
116
|
+
* @param {Number} id - The scoreboard id
|
|
117
|
+
* @param {String} [user] - A user's id or name
|
|
118
|
+
* @param {Number} [social] - If true, only social scores will be loaded
|
|
119
|
+
* @param {Number} [skip] - Number of scores to skip before start
|
|
120
|
+
* @param {Number} [limit] - Number of scores to include in the list
|
|
121
|
+
* @return {Object} - The response JSON object
|
|
122
|
+
*/
|
|
123
|
+
getScores(id, user, social=0, skip=0, limit=10)
|
|
124
|
+
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
125
|
+
|
|
126
|
+
/** Send message to log a view */
|
|
127
|
+
logView() { return this.call('App.logView', {'host':this.host}, true); }
|
|
128
|
+
|
|
129
|
+
/** Send a message to call a component of the Newgrounds API
|
|
130
|
+
* @param {String} component - Name of the component
|
|
131
|
+
* @param {Object} [parameters] - Parameters to use for call
|
|
132
|
+
* @param {Boolean} [async] - If true, don't wait for response before continuing
|
|
133
|
+
* @return {Object} - The response JSON object
|
|
134
|
+
*/
|
|
135
|
+
call(component, parameters, async=false)
|
|
136
|
+
{
|
|
137
|
+
const call = {'component':component, 'parameters':parameters};
|
|
138
|
+
if (this.cipher)
|
|
139
|
+
{
|
|
140
|
+
// encrypt using AES-128 Base64 with cryptoJS
|
|
141
|
+
const cryptoJS = this.cryptoJS;
|
|
142
|
+
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
143
|
+
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
144
|
+
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
145
|
+
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
146
|
+
call['parameters'] = 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// build the input object
|
|
150
|
+
const input =
|
|
151
|
+
{
|
|
152
|
+
'app_id': this.app_id,
|
|
153
|
+
'session_id': this.session_id,
|
|
154
|
+
'call': call
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// build post data
|
|
158
|
+
const formData = new FormData();
|
|
159
|
+
formData.append('input', JSON.stringify(input));
|
|
160
|
+
|
|
161
|
+
// send post data
|
|
162
|
+
const xmlHttp = new XMLHttpRequest();
|
|
163
|
+
const url = 'https://newgrounds.io/gateway_v3.php';
|
|
164
|
+
xmlHttp.open('POST', url, !debugMedals && async);
|
|
165
|
+
xmlHttp.send(formData);
|
|
166
|
+
debugMedals && console.log(xmlHttp.responseText);
|
|
167
|
+
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Post Processing Plugin
|
|
3
|
+
* - Supports shadertoy style post processing shaders
|
|
4
|
+
* - call glInitPostProcess
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
10
|
+
// post processing - can be enabled to pass other canvases through a final shader
|
|
11
|
+
|
|
12
|
+
let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
13
|
+
|
|
14
|
+
/** Set up a post processing shader
|
|
15
|
+
* @param {String} shaderCode
|
|
16
|
+
* @param {Boolean} includeOverlay
|
|
17
|
+
* @memberof WebGL */
|
|
18
|
+
function initPostProcess(shaderCode, includeOverlay=false)
|
|
19
|
+
{
|
|
20
|
+
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
21
|
+
if (headlessMode) return;
|
|
22
|
+
|
|
23
|
+
if (!shaderCode) // default shader pass through
|
|
24
|
+
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
25
|
+
|
|
26
|
+
// create the shader
|
|
27
|
+
glPostShader = glCreateProgram(
|
|
28
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
29
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
30
|
+
'in vec2 p;'+ // position
|
|
31
|
+
'void main(){'+ // shader entry point
|
|
32
|
+
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
33
|
+
'}' // end of shader
|
|
34
|
+
,
|
|
35
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
36
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
37
|
+
'uniform sampler2D iChannel0;'+ // input texture
|
|
38
|
+
'uniform vec3 iResolution;'+ // size of output texture
|
|
39
|
+
'uniform float iTime;'+ // time
|
|
40
|
+
'out vec4 c;'+ // out color
|
|
41
|
+
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
42
|
+
'void main(){'+ // shader entry point
|
|
43
|
+
'mainImage(c,gl_FragCoord.xy);'+ // call post process function
|
|
44
|
+
'c.a=1.;'+ // always use full alpha
|
|
45
|
+
'}' // end of shader
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// create buffer and texture
|
|
49
|
+
glPostTexture = glCreateTexture(undefined);
|
|
50
|
+
glPostIncludeOverlay = includeOverlay;
|
|
51
|
+
|
|
52
|
+
// hide the original 2d canvas
|
|
53
|
+
mainCanvas.style.visibility = 'hidden';
|
|
54
|
+
if (glPostIncludeOverlay)
|
|
55
|
+
overlayCanvas.style.visibility = 'hidden';
|
|
56
|
+
|
|
57
|
+
// Render the post processing shader, called automatically by the engine
|
|
58
|
+
engineAddPlugin(undefined, postProcessRender);
|
|
59
|
+
function postProcessRender()
|
|
60
|
+
{
|
|
61
|
+
if (headlessMode) return;
|
|
62
|
+
|
|
63
|
+
// prepare to render post process shader
|
|
64
|
+
if (glEnable)
|
|
65
|
+
{
|
|
66
|
+
glFlush(); // clear out the buffer
|
|
67
|
+
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
68
|
+
}
|
|
69
|
+
else
|
|
70
|
+
{
|
|
71
|
+
// set the viewport
|
|
72
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// copy overlay canvas so it will be included in post processing
|
|
76
|
+
glPostIncludeOverlay && mainContext.drawImage(overlayCanvas, 0, 0);
|
|
77
|
+
|
|
78
|
+
// setup shader program to draw one triangle
|
|
79
|
+
glContext.useProgram(glPostShader);
|
|
80
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
81
|
+
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
|
|
82
|
+
glContext.disable(gl_BLEND);
|
|
83
|
+
|
|
84
|
+
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
85
|
+
glContext.activeTexture(gl_TEXTURE0);
|
|
86
|
+
glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
|
|
87
|
+
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
|
|
88
|
+
|
|
89
|
+
// set vertex position attribute
|
|
90
|
+
const vertexByteStride = 8;
|
|
91
|
+
const pLocation = glContext.getAttribLocation(glPostShader, 'p');
|
|
92
|
+
glContext.enableVertexAttribArray(pLocation);
|
|
93
|
+
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
|
|
94
|
+
|
|
95
|
+
// set uniforms and draw
|
|
96
|
+
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
97
|
+
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
98
|
+
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
99
|
+
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
100
|
+
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/engine.js
CHANGED
|
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
|
|
|
30
30
|
* @type {String}
|
|
31
31
|
* @default
|
|
32
32
|
* @memberof Engine */
|
|
33
|
-
const engineVersion = '1.9.
|
|
33
|
+
const engineVersion = '1.9.9';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {Number}
|
|
@@ -84,6 +84,22 @@ function setPaused(isPaused) { paused = isPaused; }
|
|
|
84
84
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
85
85
|
|
|
86
86
|
///////////////////////////////////////////////////////////////////////////////
|
|
87
|
+
// plugin hooks
|
|
88
|
+
|
|
89
|
+
const pluginUpdateList = [], pluginRenderList = [];
|
|
90
|
+
|
|
91
|
+
/** Add a new update function for a plugin
|
|
92
|
+
* @param {Function} [updateFunction]
|
|
93
|
+
* @param {Function} [renderFunction]
|
|
94
|
+
* @memberof Engine */
|
|
95
|
+
function engineAddPlugin(updateFunction, renderFunction)
|
|
96
|
+
{
|
|
97
|
+
updateFunction && pluginUpdateList.push(updateFunction);
|
|
98
|
+
renderFunction && pluginRenderList.push(renderFunction);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
102
|
+
// Main engine functions
|
|
87
103
|
|
|
88
104
|
/** Startup LittleJS engine with your callback functions
|
|
89
105
|
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
@@ -159,6 +175,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
159
175
|
// update game and objects
|
|
160
176
|
inputUpdate();
|
|
161
177
|
gameUpdate();
|
|
178
|
+
pluginUpdateList.forEach(f=>f());
|
|
162
179
|
engineObjectsUpdate();
|
|
163
180
|
|
|
164
181
|
// do post update
|
|
@@ -180,8 +197,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
180
197
|
for (const o of engineObjects)
|
|
181
198
|
o.destroyed || o.render();
|
|
182
199
|
gameRenderPost();
|
|
183
|
-
|
|
184
|
-
medalsRender();
|
|
200
|
+
pluginRenderList.forEach(f=>f());
|
|
185
201
|
touchGamepadRender();
|
|
186
202
|
debugRender();
|
|
187
203
|
glCopyToContext(mainContext);
|
|
@@ -253,10 +269,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
253
269
|
const styleBody =
|
|
254
270
|
'margin:0;overflow:hidden;' + // fill the window
|
|
255
271
|
'background:#000;' + // set background color
|
|
256
|
-
'
|
|
257
|
-
'user-select:none;' + // prevent mobile hold to select
|
|
272
|
+
'user-select:none;' + // prevent hold to select
|
|
258
273
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
259
|
-
'
|
|
274
|
+
(!touchInputEnable ? '' : // no touch css setttings
|
|
275
|
+
'touch-action:none;' + // prevent mobile pinch to resize
|
|
276
|
+
'-webkit-touch-callout:none');// compatibility for ios
|
|
260
277
|
document.body.style.cssText = styleBody;
|
|
261
278
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
262
279
|
mainContext = mainCanvas.getContext('2d');
|
|
@@ -471,21 +488,23 @@ function drawEngineSplashScreen(t)
|
|
|
471
488
|
x.setLineDash([99*p2,99]);
|
|
472
489
|
|
|
473
490
|
// cab top
|
|
474
|
-
rect(7,
|
|
475
|
-
rect(7,
|
|
476
|
-
rect(25,
|
|
477
|
-
rect(25,
|
|
478
|
-
rect(25,
|
|
491
|
+
rect(7,16,18,-8,color(2,2));
|
|
492
|
+
rect(7,8,18,4,color(2,3));
|
|
493
|
+
rect(25,8,8,8,color(2,1));
|
|
494
|
+
rect(25,8,-18,8);
|
|
495
|
+
rect(25,8,8,8);
|
|
479
496
|
|
|
480
497
|
// cab
|
|
481
|
-
rect(25,
|
|
482
|
-
rect(11,
|
|
483
|
-
rect(11,
|
|
484
|
-
rect(11,
|
|
485
|
-
rect(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
rect(
|
|
498
|
+
rect(25,16,7,23,color());
|
|
499
|
+
rect(11,39,14,-23,color(1,1));
|
|
500
|
+
rect(11,16,14,18,color(1,2));
|
|
501
|
+
rect(11,16,14,8,color(1,3));
|
|
502
|
+
rect(25,16,-14,24);
|
|
503
|
+
|
|
504
|
+
// cab window
|
|
505
|
+
rect(15,29,6,-9,color(2,2));
|
|
506
|
+
circle(15,21,5,0,PI/2,color(2,4),1);
|
|
507
|
+
rect(21,21,-6,9);
|
|
489
508
|
|
|
490
509
|
// little stack
|
|
491
510
|
rect(37,14,9,6,color(3,2));
|
|
@@ -493,16 +512,16 @@ function drawEngineSplashScreen(t)
|
|
|
493
512
|
rect(37,14,9,6);
|
|
494
513
|
|
|
495
514
|
// big stack
|
|
496
|
-
rect(50,20,10,-8,color(0,1))
|
|
497
|
-
rect(50,20,6.5,-8,color(0,2))
|
|
498
|
-
rect(50,20,3.5,-8,color(0,3))
|
|
499
|
-
rect(50,20,10,-8)
|
|
500
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
501
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
502
|
-
circle(55,2,11.4,.5,PI-.5)
|
|
503
|
-
rect(45,7,20,-7,color(0,2))
|
|
504
|
-
rect(45,-1,20,4,color(0,3))
|
|
505
|
-
rect(45,-1,20,8)
|
|
515
|
+
rect(50,20,10,-8,color(0,1));
|
|
516
|
+
rect(50,20,6.5,-8,color(0,2));
|
|
517
|
+
rect(50,20,3.5,-8,color(0,3));
|
|
518
|
+
rect(50,20,10,-8);
|
|
519
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
520
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
521
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
522
|
+
rect(45,7,20,-7,color(0,2));
|
|
523
|
+
rect(45,-1,20,4,color(0,3));
|
|
524
|
+
rect(45,-1,20,8);
|
|
506
525
|
|
|
507
526
|
// engine
|
|
508
527
|
for (let i=5; i--;)
|
package/src/engineAudio.js
CHANGED
|
@@ -112,7 +112,17 @@ class Sound
|
|
|
112
112
|
|
|
113
113
|
// play the sound
|
|
114
114
|
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
115
|
-
|
|
115
|
+
this.gainNode = audioContext.createGain();
|
|
116
|
+
return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Set the sound volume
|
|
120
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
121
|
+
*/
|
|
122
|
+
setVolume(volume=1)
|
|
123
|
+
{
|
|
124
|
+
if (this.gainNode)
|
|
125
|
+
this.gainNode.gain.value = volume;
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
/** Stop the last instance of this sound that was played */
|
|
@@ -300,15 +310,16 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
300
310
|
let audioSuspended = false;
|
|
301
311
|
|
|
302
312
|
/** Play cached audio samples with given settings
|
|
303
|
-
* @param {Array}
|
|
304
|
-
* @param {Number}
|
|
305
|
-
* @param {Number}
|
|
306
|
-
* @param {Number}
|
|
307
|
-
* @param {Boolean}
|
|
308
|
-
* @param {Number}
|
|
313
|
+
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
314
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
315
|
+
* @param {Number} [rate] - The playback rate to use
|
|
316
|
+
* @param {Number} [pan] - How much to apply stereo panning
|
|
317
|
+
* @param {Boolean} [loop] - True if the sound should loop when it reaches the end
|
|
318
|
+
* @param {Number} [sampleRate=44100] - Sample rate for the sound
|
|
319
|
+
* @param {GainNode} [gainNode] - Optional gain node for volume control while playing
|
|
309
320
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
310
321
|
* @memberof Audio */
|
|
311
|
-
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
322
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR, gainNode)
|
|
312
323
|
{
|
|
313
324
|
if (!soundEnable || headlessMode) return;
|
|
314
325
|
|
|
@@ -334,11 +345,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
334
345
|
source.playbackRate.value = rate;
|
|
335
346
|
source.loop = loop;
|
|
336
347
|
|
|
337
|
-
// set master gain volume
|
|
338
|
-
setSoundVolume(soundVolume);
|
|
339
|
-
|
|
340
348
|
// create and connect gain node
|
|
341
|
-
|
|
349
|
+
gainNode = gainNode || audioContext.createGain();
|
|
342
350
|
gainNode.gain.value = volume;
|
|
343
351
|
gainNode.connect(audioGainNode);
|
|
344
352
|
|
package/src/engineBuild.js
CHANGED
|
@@ -137,7 +137,7 @@ function closureCompilerStep(filename)
|
|
|
137
137
|
fs.copyFileSync(filename, filenameTemp);
|
|
138
138
|
try
|
|
139
139
|
{
|
|
140
|
-
child_process.execSync(`npx google-closure-compiler --js=${filenameTemp} --js_output_file=${filename} --
|
|
140
|
+
child_process.execSync(`npx google-closure-compiler --js=${filenameTemp} --js_output_file=${filename} --warning_level=VERBOSE --jscomp_off=*`);
|
|
141
141
|
fs.rmSync(filenameTemp);
|
|
142
142
|
}
|
|
143
143
|
catch (e) { handleError(e, 'Failed to run Closure Compiler step!'); }
|
package/src/engineDebug.js
CHANGED
|
@@ -76,6 +76,20 @@ function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
|
76
76
|
debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Draw a debug poly in world space
|
|
80
|
+
* @param {Vector2} pos
|
|
81
|
+
* @param {Array} points
|
|
82
|
+
* @param {String} [color]
|
|
83
|
+
* @param {Number} [time]
|
|
84
|
+
* @param {Number} [angle]
|
|
85
|
+
* @param {Boolean} [fill]
|
|
86
|
+
* @memberof Debug */
|
|
87
|
+
function debugPoly(pos, points, color='#fff', time=0, angle=0, fill=false)
|
|
88
|
+
{
|
|
89
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
90
|
+
debugPrimitives.push({pos, points, color, time:new Timer(time), angle, fill});
|
|
91
|
+
}
|
|
92
|
+
|
|
79
93
|
/** Draw a debug circle in world space
|
|
80
94
|
* @param {Vector2} pos
|
|
81
95
|
* @param {Number} [radius]
|
|
@@ -85,7 +99,7 @@ function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
|
85
99
|
* @memberof Debug */
|
|
86
100
|
function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
|
|
87
101
|
{
|
|
88
|
-
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
102
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
89
103
|
debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
|
|
90
104
|
}
|
|
91
105
|
|
|
@@ -95,7 +109,11 @@ function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
|
|
|
95
109
|
* @param {Number} [time]
|
|
96
110
|
* @param {Number} [angle]
|
|
97
111
|
* @memberof Debug */
|
|
98
|
-
function debugPoint(pos, color, time, angle)
|
|
112
|
+
function debugPoint(pos, color, time, angle)
|
|
113
|
+
{
|
|
114
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
115
|
+
debugRect(pos, undefined, color, time, angle);
|
|
116
|
+
}
|
|
99
117
|
|
|
100
118
|
/** Draw a debug line in world space
|
|
101
119
|
* @param {Vector2} posA
|
|
@@ -106,19 +124,20 @@ function debugPoint(pos, color, time, angle) {debugRect(pos, undefined, color, t
|
|
|
106
124
|
* @memberof Debug */
|
|
107
125
|
function debugLine(posA, posB, color, thickness=.1, time)
|
|
108
126
|
{
|
|
127
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
109
128
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
110
129
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
111
130
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
112
131
|
}
|
|
113
132
|
|
|
114
|
-
/** Draw a debug axis aligned bounding box in world space
|
|
133
|
+
/** Draw a debug combined axis aligned bounding box in world space
|
|
115
134
|
* @param {Vector2} pA - position A
|
|
116
135
|
* @param {Vector2} sA - size A
|
|
117
136
|
* @param {Vector2} pB - position B
|
|
118
137
|
* @param {Vector2} sB - size B
|
|
119
138
|
* @param {String} [color]
|
|
120
139
|
* @memberof Debug */
|
|
121
|
-
function
|
|
140
|
+
function debugOverlap(pA, sA, pB, sB, color)
|
|
122
141
|
{
|
|
123
142
|
const minPos = vec2(min(pA.x - sA.x/2, pB.x - sB.x/2), min(pA.y - sA.y/2, pB.y - sB.y/2));
|
|
124
143
|
const maxPos = vec2(max(pA.x + sA.x/2, pB.x + sB.x/2), max(pA.y + sA.y/2, pB.y + sB.y/2));
|
|
@@ -136,7 +155,7 @@ function debugAABB(pA, sA, pB, sB, color)
|
|
|
136
155
|
* @memberof Debug */
|
|
137
156
|
function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
|
|
138
157
|
{
|
|
139
|
-
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
158
|
+
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
140
159
|
debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
|
|
141
160
|
}
|
|
142
161
|
|
|
@@ -235,7 +254,7 @@ function debugRender()
|
|
|
235
254
|
const stickScale = 1;
|
|
236
255
|
const buttonScale = .2;
|
|
237
256
|
const centerPos = cameraPos;
|
|
238
|
-
const sticks =
|
|
257
|
+
const sticks = gamepadStickData[i];
|
|
239
258
|
for (let j = sticks.length; j--;)
|
|
240
259
|
{
|
|
241
260
|
const drawPos = centerPos.add(vec2(j*stickScale*2, i*stickScale*3));
|
|
@@ -255,6 +274,7 @@ function debugRender()
|
|
|
255
274
|
}
|
|
256
275
|
}
|
|
257
276
|
|
|
277
|
+
let debugObject;
|
|
258
278
|
if (debugOverlay)
|
|
259
279
|
{
|
|
260
280
|
const saveContext = mainContext;
|
|
@@ -265,11 +285,13 @@ function debugRender()
|
|
|
265
285
|
debugRect(cameraPos, cameraSize.subtract(vec2(.1)), '#f008');
|
|
266
286
|
|
|
267
287
|
// mouse pick
|
|
268
|
-
let bestDistance = Infinity
|
|
288
|
+
let bestDistance = Infinity;
|
|
269
289
|
for (const o of engineObjects)
|
|
270
290
|
{
|
|
271
291
|
if (o.canvas || o.destroyed)
|
|
272
292
|
continue;
|
|
293
|
+
|
|
294
|
+
o.renderDebugInfo();
|
|
273
295
|
if (!o.size.x || !o.size.y)
|
|
274
296
|
continue;
|
|
275
297
|
|
|
@@ -277,33 +299,15 @@ function debugRender()
|
|
|
277
299
|
if (distance < bestDistance)
|
|
278
300
|
{
|
|
279
301
|
bestDistance = distance;
|
|
280
|
-
|
|
302
|
+
debugObject = o;
|
|
281
303
|
}
|
|
282
|
-
|
|
283
|
-
// show object info
|
|
284
|
-
const size = vec2(max(o.size.x, .2), max(o.size.y, .2));
|
|
285
|
-
const color1 = new Color(o.collideTiles?1:0, o.collideSolidObjects?1:0, o.isSolid?1:0, o.parent?.2:.5);
|
|
286
|
-
const color2 = o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8);
|
|
287
|
-
drawRect(o.pos, size, color1, o.angle, false);
|
|
288
|
-
drawRect(o.pos, size.scale(.8), color2, o.angle, false);
|
|
289
|
-
o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5), false);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (bestObject)
|
|
293
|
-
{
|
|
294
|
-
const raycastHitPos = tileCollisionRaycast(bestObject.pos, mousePos);
|
|
295
|
-
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), new Color(0,1,1,.3));
|
|
296
|
-
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5), 0, false);
|
|
297
|
-
drawLine(mousePos, bestObject.pos, .1, raycastHitPos ? new Color(1,0,0,.5) : new Color(0,1,0,.5), false);
|
|
298
|
-
|
|
299
|
-
const debugText = 'mouse pos = ' + mousePos +
|
|
300
|
-
'\nmouse collision = ' + getTileCollisionData(mousePos) +
|
|
301
|
-
'\n\n--- object info ---\n' +
|
|
302
|
-
bestObject.toString();
|
|
303
|
-
drawTextScreen(debugText, mousePosScreen, 24, new Color, .05, undefined, 'center', 'monospace');
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
if (tileCollisionSize.x > 0 && tileCollisionSize.y > 0)
|
|
307
|
+
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(0,0,1,.5), 0, false);
|
|
308
|
+
mainContext = saveContext;
|
|
309
|
+
|
|
310
|
+
//glCopyToContext(mainContext = saveContext);
|
|
307
311
|
}
|
|
308
312
|
|
|
309
313
|
{
|
|
@@ -318,6 +322,7 @@ function debugRender()
|
|
|
318
322
|
const pos = worldToScreen(p.pos);
|
|
319
323
|
overlayContext.translate(pos.x|0, pos.y|0);
|
|
320
324
|
overlayContext.rotate(p.angle);
|
|
325
|
+
overlayContext.scale(1, -1);
|
|
321
326
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
322
327
|
|
|
323
328
|
if (p.text != undefined)
|
|
@@ -327,7 +332,20 @@ function debugRender()
|
|
|
327
332
|
overlayContext.textBaseline = 'middle';
|
|
328
333
|
overlayContext.fillText(p.text, 0, 0);
|
|
329
334
|
}
|
|
330
|
-
else if (p.
|
|
335
|
+
else if (p.points != undefined)
|
|
336
|
+
{
|
|
337
|
+
// poly
|
|
338
|
+
overlayContext.beginPath();
|
|
339
|
+
for (const point of p.points)
|
|
340
|
+
{
|
|
341
|
+
const p2 = point.scale(cameraScale).floor();
|
|
342
|
+
overlayContext.lineTo(p2.x, p2.y);
|
|
343
|
+
}
|
|
344
|
+
overlayContext.closePath();
|
|
345
|
+
p.fill && overlayContext.fill();
|
|
346
|
+
overlayContext.stroke();
|
|
347
|
+
}
|
|
348
|
+
else if (p.size == 0 || p.size.x === 0 && p.size.y === 0)
|
|
331
349
|
{
|
|
332
350
|
// point
|
|
333
351
|
overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
|
|
@@ -336,7 +354,8 @@ function debugRender()
|
|
|
336
354
|
else if (p.size.x != undefined)
|
|
337
355
|
{
|
|
338
356
|
// rect
|
|
339
|
-
const
|
|
357
|
+
const s = p.size.scale(cameraScale).floor();
|
|
358
|
+
const w = s.x, h = s.y;
|
|
340
359
|
p.fill && overlayContext.fillRect(-w/2|0, -h/2|0, w, h);
|
|
341
360
|
overlayContext.strokeRect(-w/2|0, -h/2|0, w, h);
|
|
342
361
|
}
|
|
@@ -355,6 +374,22 @@ function debugRender()
|
|
|
355
374
|
// remove expired primitives
|
|
356
375
|
debugPrimitives = debugPrimitives.filter(r=>r.time<0);
|
|
357
376
|
}
|
|
377
|
+
|
|
378
|
+
if (debugObject)
|
|
379
|
+
{
|
|
380
|
+
const saveContext = mainContext;
|
|
381
|
+
mainContext = overlayContext;
|
|
382
|
+
const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
|
|
383
|
+
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), rgb(0,1,1,.3));
|
|
384
|
+
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), false);
|
|
385
|
+
|
|
386
|
+
const debugText = 'mouse pos = ' + mousePos +
|
|
387
|
+
'\nmouse collision = ' + getTileCollisionData(mousePos) +
|
|
388
|
+
'\n\n--- object info ---\n' +
|
|
389
|
+
debugObject.toString();
|
|
390
|
+
drawTextScreen(debugText, mousePosScreen, 24, rgb(), .05, undefined, 'center', 'monospace');
|
|
391
|
+
mainContext = saveContext;
|
|
392
|
+
}
|
|
358
393
|
|
|
359
394
|
{
|
|
360
395
|
// draw debug overlay
|