littlejsengine 1.1.18 → 1.2.3
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 +17 -11
- package/build.bat +1 -0
- package/engine/engine.all.js +495 -504
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +418 -259
- package/engine/engine.js +31 -23
- package/engine/engineAudio.js +21 -7
- package/engine/engineBuild.bat +9 -1
- package/engine/engineBuildSetup.bat +2 -1
- package/engine/engineDebug.js +81 -247
- package/engine/engineDraw.js +109 -21
- package/engine/engineFont.png +0 -0
- package/engine/engineInput.js +103 -50
- package/engine/engineMedals.js +17 -20
- package/engine/engineObject.js +14 -23
- package/engine/engineParticles.js +8 -6
- package/engine/engineRelease.js +3 -1
- package/engine/engineSettings.js +20 -10
- package/engine/engineTileLayer.js +19 -18
- package/engine/engineUtilities.js +14 -12
- package/engine/engineWebGL.js +59 -68
- package/engine/index.d.ts +1617 -0
- package/examples/breakout/game.js +9 -7
- package/examples/breakout/gameObjects.js +3 -3
- package/examples/breakout/index.html +6 -4
- package/examples/platformer/game.js +5 -5
- package/examples/platformer/gameEffects.js +11 -14
- package/examples/platformer/gameLevel.js +14 -7
- package/examples/platformer/gameObjects.js +3 -3
- package/examples/platformer/gamePlayer.js +13 -8
- package/examples/platformer/index.html +9 -7
- package/examples/platformer/tiles.png +0 -0
- package/examples/puzzle/game.js +2 -2
- package/examples/puzzle/index.html +5 -3
- package/examples/screenshot.jpg +0 -0
- package/examples/stress/index.html +7 -5
- package/game.js +9 -8
- package/index.html +16 -14
- package/package.json +1 -1
package/engine/engineDraw.js
CHANGED
|
@@ -92,17 +92,13 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
92
92
|
{
|
|
93
93
|
// calculate uvs and render
|
|
94
94
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
95
|
-
const uvSizeX = tileSize.x
|
|
96
|
-
const uvSizeY = tileSize.y
|
|
95
|
+
const uvSizeX = tileSize.x / tileImageSize.x;
|
|
96
|
+
const uvSizeY = tileSize.y / tileImageSize.y;
|
|
97
97
|
const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
|
|
98
|
-
|
|
99
|
-
// shrink uvs to prevent bleeding
|
|
100
|
-
const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
|
|
101
|
-
const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
|
|
102
98
|
|
|
103
99
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
104
|
-
uvX +
|
|
105
|
-
uvX -
|
|
100
|
+
uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
|
|
101
|
+
uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
|
|
106
102
|
color.rgbaInt(), additiveColor.rgbaInt());
|
|
107
103
|
}
|
|
108
104
|
}
|
|
@@ -121,10 +117,10 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
121
117
|
{
|
|
122
118
|
// calculate uvs and render
|
|
123
119
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
124
|
-
const sX = (tileIndex%cols)*tileSize.x +
|
|
125
|
-
const sY = (tileIndex/cols|0)*tileSize.y +
|
|
126
|
-
const sWidth = tileSize.x - 2*
|
|
127
|
-
const sHeight = tileSize.y - 2*
|
|
120
|
+
const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
|
|
121
|
+
const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
|
|
122
|
+
const sWidth = tileSize.x - 2*tileFixBleedScale;
|
|
123
|
+
const sHeight = tileSize.y - 2*tileFixBleedScale;
|
|
128
124
|
context.globalAlpha = color.a; // only alpha is supported
|
|
129
125
|
context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
|
|
130
126
|
}
|
|
@@ -207,6 +203,18 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
207
203
|
context.restore();
|
|
208
204
|
}
|
|
209
205
|
|
|
206
|
+
/** Enable normal or additive blend mode
|
|
207
|
+
* @param {Boolean} [additive=0]
|
|
208
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
209
|
+
* @memberof Draw */
|
|
210
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
211
|
+
{
|
|
212
|
+
if (glEnable && useWebGL)
|
|
213
|
+
glSetBlendMode(additive);
|
|
214
|
+
else
|
|
215
|
+
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
216
|
+
}
|
|
217
|
+
|
|
210
218
|
/** Draw text on overlay canvas in world space
|
|
211
219
|
* @param {String} text
|
|
212
220
|
* @param {Vector2} pos
|
|
@@ -232,16 +240,96 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
232
240
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
233
241
|
}
|
|
234
242
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
*
|
|
239
|
-
|
|
243
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
247
|
+
* <br> - 96 characters (from space to tilde) are stored in an image
|
|
248
|
+
* <br> - Uses a default 8x8 font if none is supplied
|
|
249
|
+
* <br> - You can also use fonts from the main tile sheet
|
|
250
|
+
* @example
|
|
251
|
+
* // use built in font
|
|
252
|
+
* const font = new ImageFont;
|
|
253
|
+
*
|
|
254
|
+
* // draw text
|
|
255
|
+
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
256
|
+
*/
|
|
257
|
+
|
|
258
|
+
let engineFontImage;
|
|
259
|
+
|
|
260
|
+
class FontImage
|
|
240
261
|
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
262
|
+
/** Create an image font
|
|
263
|
+
* @param {Image} [image] - The image the font is stored in, if undefined the default font is used
|
|
264
|
+
* @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
|
|
265
|
+
* @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
|
|
266
|
+
* @param {Number} [startTileIndex=0] - Tile index in image where font starts
|
|
267
|
+
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
268
|
+
*/
|
|
269
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
270
|
+
{
|
|
271
|
+
if (!image && !engineFontImage)
|
|
272
|
+
{
|
|
273
|
+
// load default font image
|
|
274
|
+
engineFontImage = new Image();
|
|
275
|
+
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.image = image || engineFontImage;
|
|
279
|
+
this.tileSize = tileSize;
|
|
280
|
+
this.paddingSize = paddingSize;
|
|
281
|
+
this.startTileIndex = startTileIndex;
|
|
282
|
+
this.context = context;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Draw text in screen space using the image font
|
|
286
|
+
* @param {String} text
|
|
287
|
+
* @param {Vector2} pos
|
|
288
|
+
* @param {Number} [scale=4]
|
|
289
|
+
* @param {Boolean} [center]
|
|
290
|
+
*/
|
|
291
|
+
drawTextScreen(text, pos, scale=4, center)
|
|
292
|
+
{
|
|
293
|
+
const context = this.context;
|
|
294
|
+
context.save();
|
|
295
|
+
context.imageSmoothingEnabled = !cavasPixelated;
|
|
296
|
+
|
|
297
|
+
const size = this.tileSize;
|
|
298
|
+
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
299
|
+
const cols = this.image.width / this.tileSize.x |0;
|
|
300
|
+
text.split('\n').forEach((line, i)=>
|
|
301
|
+
{
|
|
302
|
+
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
303
|
+
for(let j=line.length; j--;)
|
|
304
|
+
{
|
|
305
|
+
// draw each character
|
|
306
|
+
let charCode = line[j].charCodeAt();
|
|
307
|
+
if (charCode < 32 || charCode > 127)
|
|
308
|
+
charCode = 127; // unknown character
|
|
309
|
+
|
|
310
|
+
// get the character source location and draw it
|
|
311
|
+
const tile = this.startTileIndex + charCode - 32;
|
|
312
|
+
const x = tile % cols;
|
|
313
|
+
const y = tile / cols |0;
|
|
314
|
+
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
315
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
316
|
+
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
context.restore();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Draw text in world space using the image font
|
|
324
|
+
* @param {String} text
|
|
325
|
+
* @param {Vector2} pos
|
|
326
|
+
* @param {Number} [scale=.25]
|
|
327
|
+
* @param {Boolean} [center]
|
|
328
|
+
*/
|
|
329
|
+
drawText(text, pos, scale=1, center)
|
|
330
|
+
{
|
|
331
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
332
|
+
}
|
|
245
333
|
}
|
|
246
334
|
|
|
247
335
|
///////////////////////////////////////////////////////////////////////////////
|
|
Binary file
|
package/engine/engineInput.js
CHANGED
|
@@ -32,7 +32,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
|
|
|
32
32
|
|
|
33
33
|
/** Clears all input
|
|
34
34
|
* @memberof Input */
|
|
35
|
-
const clearInput = ()=> inputData
|
|
35
|
+
const clearInput = ()=> inputData = [[]];
|
|
36
36
|
|
|
37
37
|
/** Returns true if mouse button is down
|
|
38
38
|
* @param {Number} button
|
|
@@ -101,7 +101,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
|
|
|
101
101
|
///////////////////////////////////////////////////////////////////////////////
|
|
102
102
|
// Input update called by engine
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
// store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
|
|
105
|
+
// mouse and keyboard are stored together in device 0, gamepads are in devices > 0
|
|
106
|
+
let inputData = [[]];
|
|
105
107
|
|
|
106
108
|
function inputUpdate()
|
|
107
109
|
{
|
|
@@ -127,36 +129,38 @@ function inputUpdatePost()
|
|
|
127
129
|
///////////////////////////////////////////////////////////////////////////////
|
|
128
130
|
// Keyboard event handlers
|
|
129
131
|
|
|
130
|
-
onkeydown = e=>
|
|
132
|
+
onkeydown = (e)=>
|
|
131
133
|
{
|
|
132
134
|
if (debug && e.target != document.body) return;
|
|
133
135
|
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
134
136
|
debug || e.preventDefault();
|
|
135
137
|
}
|
|
136
|
-
onkeyup = e=>
|
|
138
|
+
onkeyup = (e)=>
|
|
137
139
|
{
|
|
138
140
|
if (debug && e.target != document.body) return;
|
|
139
141
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
140
142
|
}
|
|
141
|
-
const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
143
|
+
const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
142
144
|
|
|
143
145
|
///////////////////////////////////////////////////////////////////////////////
|
|
144
146
|
// Mouse event handlers
|
|
145
147
|
|
|
146
|
-
onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
147
|
-
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
148
|
-
onmousemove = e=>
|
|
148
|
+
onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
149
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
150
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
151
|
+
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
152
|
+
oncontextmenu = (e)=> !1; // prevent right click menu
|
|
153
|
+
|
|
154
|
+
// convert a mouse or touch event position to screen space
|
|
155
|
+
const mouseToScreen = (mousePos)=>
|
|
149
156
|
{
|
|
150
157
|
if (!mainCanvas)
|
|
151
|
-
return; // fix bug that can occur if user clicks before page loads
|
|
158
|
+
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
152
159
|
|
|
153
|
-
// convert mouse pos to canvas space
|
|
154
160
|
const rect = mainCanvas.getBoundingClientRect();
|
|
155
|
-
|
|
156
|
-
vec2(percent(
|
|
161
|
+
return mainCanvasSize.multiply(
|
|
162
|
+
vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
|
|
157
163
|
}
|
|
158
|
-
onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
159
|
-
oncontextmenu = e=> !1; // prevent right click menu
|
|
160
164
|
|
|
161
165
|
///////////////////////////////////////////////////////////////////////////////
|
|
162
166
|
// Gamepad input
|
|
@@ -164,6 +168,21 @@ oncontextmenu = e=> !1; // prevent right click menu
|
|
|
164
168
|
const stickData = [];
|
|
165
169
|
function gamepadsUpdate()
|
|
166
170
|
{
|
|
171
|
+
if (touchGamepadEnable && touchGamepadTimer.isSet())
|
|
172
|
+
{
|
|
173
|
+
// read virtual analog stick
|
|
174
|
+
const sticks = stickData[0] || (stickData[0] = []);
|
|
175
|
+
sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
|
|
176
|
+
|
|
177
|
+
// read virtual gamepad buttons
|
|
178
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
179
|
+
for (let i=10; i--;)
|
|
180
|
+
{
|
|
181
|
+
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
182
|
+
data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
167
186
|
if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
|
|
168
187
|
return;
|
|
169
188
|
|
|
@@ -181,8 +200,8 @@ function gamepadsUpdate()
|
|
|
181
200
|
// read clamp dead zone of analog sticks
|
|
182
201
|
const deadZone = .3, deadZoneMax = .8;
|
|
183
202
|
const applyDeadZone = (v)=>
|
|
184
|
-
v > deadZone ? percent( v,
|
|
185
|
-
v < -deadZone ? -percent(-v,
|
|
203
|
+
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
204
|
+
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
186
205
|
|
|
187
206
|
// read analog sticks
|
|
188
207
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
@@ -194,6 +213,7 @@ function gamepadsUpdate()
|
|
|
194
213
|
const button = gamepad.buttons[j];
|
|
195
214
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
196
215
|
isUsingGamepad |= !i && button.pressed;
|
|
216
|
+
touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
|
|
197
217
|
}
|
|
198
218
|
|
|
199
219
|
if (gamepadDirectionEmulateStick)
|
|
@@ -207,6 +227,17 @@ function gamepadsUpdate()
|
|
|
207
227
|
}
|
|
208
228
|
}
|
|
209
229
|
|
|
230
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
231
|
+
|
|
232
|
+
/** Pulse the vibration hardware if it exists
|
|
233
|
+
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
234
|
+
* @memberof Input */
|
|
235
|
+
const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
|
|
236
|
+
|
|
237
|
+
/** Cancel any ongoing vibration
|
|
238
|
+
* @memberof Input */
|
|
239
|
+
const vibrateStop = ()=> vibrate(0);
|
|
240
|
+
|
|
210
241
|
///////////////////////////////////////////////////////////////////////////////
|
|
211
242
|
// Touch input
|
|
212
243
|
|
|
@@ -214,7 +245,9 @@ function gamepadsUpdate()
|
|
|
214
245
|
* @const {boolean}
|
|
215
246
|
* @memberof Input */
|
|
216
247
|
const isTouchDevice = window.ontouchstart !== undefined;
|
|
217
|
-
|
|
248
|
+
|
|
249
|
+
// try to enable touch mouse
|
|
250
|
+
if (isTouchDevice)
|
|
218
251
|
{
|
|
219
252
|
// handle all touch events the same way
|
|
220
253
|
let wasTouching, hadTouchInput;
|
|
@@ -245,10 +278,12 @@ if (touchMouseEnable && isTouchDevice)
|
|
|
245
278
|
}
|
|
246
279
|
|
|
247
280
|
///////////////////////////////////////////////////////////////////////////////
|
|
248
|
-
//
|
|
281
|
+
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
282
|
+
|
|
283
|
+
// touch input internal variables
|
|
284
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
|
|
249
285
|
|
|
250
286
|
// create the touch gamepad, called automatically by the engine
|
|
251
|
-
const touchGamepadTimer = new Timer;
|
|
252
287
|
function touchGamepadCreate()
|
|
253
288
|
{
|
|
254
289
|
if (!touchGamepadEnable || !isTouchDevice)
|
|
@@ -258,6 +293,10 @@ function touchGamepadCreate()
|
|
|
258
293
|
{
|
|
259
294
|
if (!touchGamepadEnable)
|
|
260
295
|
return;
|
|
296
|
+
|
|
297
|
+
// clear touch gamepad input
|
|
298
|
+
touchGamepadStick = vec2();
|
|
299
|
+
touchGamepadButtons = [];
|
|
261
300
|
|
|
262
301
|
const touching = e.touches.length;
|
|
263
302
|
if (touching)
|
|
@@ -267,6 +306,13 @@ function touchGamepadCreate()
|
|
|
267
306
|
// set that gamepad is active
|
|
268
307
|
isUsingGamepad = 1;
|
|
269
308
|
touchGamepadTimer.set();
|
|
309
|
+
|
|
310
|
+
if (paused)
|
|
311
|
+
{
|
|
312
|
+
// touch anywhere to press start when paused
|
|
313
|
+
touchGamepadButtons[9] = 1;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
270
316
|
}
|
|
271
317
|
|
|
272
318
|
// get center of left and right sides
|
|
@@ -274,38 +320,34 @@ function touchGamepadCreate()
|
|
|
274
320
|
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
275
321
|
const startCenter = mainCanvasSize.scale(.5);
|
|
276
322
|
|
|
277
|
-
// update virtual gamepad
|
|
278
|
-
const data = inputData[1] || (inputData[1] = []);
|
|
279
|
-
const sticks = stickData[0] || (stickData[0] = []);
|
|
280
|
-
sticks[0] = vec2();
|
|
281
|
-
|
|
282
323
|
// check each touch point
|
|
283
|
-
const buttons = [];
|
|
284
324
|
for (const touch of e.touches)
|
|
285
325
|
{
|
|
286
|
-
const touchPos = vec2(touch.clientX, touch.clientY);
|
|
326
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
287
327
|
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
288
328
|
{
|
|
289
329
|
// virtual analog stick
|
|
290
|
-
|
|
291
|
-
|
|
330
|
+
if (touchGamepadAnalog)
|
|
331
|
+
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
332
|
+
else
|
|
333
|
+
{
|
|
334
|
+
// 8 way dpad
|
|
335
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
336
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
337
|
+
}
|
|
292
338
|
}
|
|
293
339
|
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
294
340
|
{
|
|
295
341
|
// virtual face buttons
|
|
296
342
|
const button = touchPos.subtract(buttonCenter).direction();
|
|
297
|
-
|
|
343
|
+
touchGamepadButtons[button] = 1;
|
|
298
344
|
}
|
|
299
345
|
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
300
346
|
{
|
|
301
|
-
// virtual start button
|
|
302
|
-
|
|
347
|
+
// virtual start button in center
|
|
348
|
+
touchGamepadButtons[9] = 1;
|
|
303
349
|
}
|
|
304
350
|
}
|
|
305
|
-
|
|
306
|
-
// read virtual buttons
|
|
307
|
-
for (let j=10; j--;)
|
|
308
|
-
data[j] = buttons[j] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
309
351
|
}
|
|
310
352
|
}
|
|
311
353
|
|
|
@@ -315,37 +357,48 @@ function touchGamepadRender()
|
|
|
315
357
|
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
316
358
|
return;
|
|
317
359
|
|
|
318
|
-
// fade off when not touching
|
|
319
|
-
const alpha = percent(touchGamepadTimer.get(), 4,
|
|
320
|
-
if (!alpha)
|
|
360
|
+
// fade off when not touching or paused
|
|
361
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
362
|
+
if (!alpha || paused)
|
|
321
363
|
return;
|
|
322
364
|
|
|
323
365
|
// setup the canvas
|
|
324
366
|
overlayContext.save();
|
|
325
367
|
overlayContext.globalAlpha = alpha*touchGamepadAlpha;
|
|
326
|
-
overlayContext.fillStyle = '#fff6';
|
|
327
368
|
overlayContext.strokeStyle = '#fff';
|
|
328
369
|
overlayContext.lineWidth = 3;
|
|
329
370
|
|
|
330
|
-
// draw start button
|
|
331
|
-
//overlayContext.beginPath();
|
|
332
|
-
//overlayContext.arc(mainCanvasSize.x/2, mainCanvasSize.y/2, touchGamepadSize*.9, 0,9);
|
|
333
|
-
//overlayContext.fill();
|
|
334
|
-
//overlayContext.stroke();
|
|
335
|
-
|
|
336
371
|
// draw left analog stick
|
|
372
|
+
overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
337
373
|
overlayContext.beginPath();
|
|
338
|
-
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize*.9, 0,9);
|
|
339
|
-
overlayContext.fill();
|
|
340
|
-
overlayContext.stroke();
|
|
341
374
|
|
|
375
|
+
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
376
|
+
if (touchGamepadAnalog)
|
|
377
|
+
{
|
|
378
|
+
overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
379
|
+
overlayContext.fill();
|
|
380
|
+
overlayContext.stroke();
|
|
381
|
+
}
|
|
382
|
+
else // draw cross shaped gamepad
|
|
383
|
+
{
|
|
384
|
+
for(let i=10; i--;)
|
|
385
|
+
{
|
|
386
|
+
const angle = i*PI/4;
|
|
387
|
+
overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
388
|
+
i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
389
|
+
i==1 && overlayContext.fill();
|
|
390
|
+
}
|
|
391
|
+
overlayContext.stroke();
|
|
392
|
+
}
|
|
393
|
+
|
|
342
394
|
// draw right face buttons
|
|
343
395
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
344
396
|
for (let i=4; i--;)
|
|
345
397
|
{
|
|
346
|
-
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize
|
|
398
|
+
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
|
|
399
|
+
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
347
400
|
overlayContext.beginPath();
|
|
348
|
-
overlayContext.arc(pos.x, pos.y, touchGamepadSize
|
|
401
|
+
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
349
402
|
overlayContext.fill();
|
|
350
403
|
overlayContext.stroke();
|
|
351
404
|
}
|
package/engine/engineMedals.js
CHANGED
|
@@ -35,7 +35,7 @@ function medalsInit(saveName)
|
|
|
35
35
|
{
|
|
36
36
|
// check if medals are unlocked
|
|
37
37
|
medalsSaveName = saveName;
|
|
38
|
-
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
38
|
+
debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -68,13 +68,9 @@ class Medal
|
|
|
68
68
|
this.name = name;
|
|
69
69
|
this.description = description;
|
|
70
70
|
this.icon = icon;
|
|
71
|
-
|
|
71
|
+
this.image = new Image();
|
|
72
72
|
if (src)
|
|
73
|
-
{
|
|
74
|
-
// load image
|
|
75
|
-
this.image = new Image();
|
|
76
73
|
this.image.src = src;
|
|
77
|
-
}
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -104,21 +100,23 @@ class Medal
|
|
|
104
100
|
const y = -medalDisplayHeight*hidePercent;
|
|
105
101
|
|
|
106
102
|
// draw containing rect and clip to that region
|
|
107
|
-
context.
|
|
103
|
+
context.save();
|
|
104
|
+
context.beginPath();
|
|
108
105
|
context.fillStyle = '#ddd'
|
|
109
106
|
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
110
|
-
context.strokeStyle =
|
|
111
|
-
context.lineWidth =
|
|
107
|
+
context.strokeStyle = '#000';
|
|
108
|
+
context.lineWidth = 3;
|
|
112
109
|
context.stroke();
|
|
113
110
|
context.clip();
|
|
114
111
|
|
|
115
112
|
// draw the icon and text
|
|
116
113
|
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
117
114
|
context.textAlign = 'left';
|
|
118
|
-
context.font = '
|
|
119
|
-
context.fillText(this.name, x+medalDisplayIconSize+
|
|
120
|
-
context.font = '
|
|
121
|
-
context.
|
|
115
|
+
context.font = '38px '+ fontDefault;
|
|
116
|
+
context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
|
|
117
|
+
context.font = '24px '+ fontDefault;
|
|
118
|
+
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
119
|
+
context.restore();
|
|
122
120
|
}
|
|
123
121
|
|
|
124
122
|
/** Render the icon for a medal
|
|
@@ -130,11 +128,11 @@ class Medal
|
|
|
130
128
|
{
|
|
131
129
|
// draw the image or icon
|
|
132
130
|
const context = overlayContext;
|
|
131
|
+
context.fillStyle = '#000';
|
|
133
132
|
context.textAlign = 'center';
|
|
134
133
|
context.textBaseline = 'middle';
|
|
135
|
-
context.font = size*.
|
|
136
|
-
|
|
137
|
-
if (this.image)
|
|
134
|
+
context.font = size*.7 + 'px '+ fontDefault;
|
|
135
|
+
if (this.image.src)
|
|
138
136
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
139
137
|
else
|
|
140
138
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
@@ -210,7 +208,6 @@ class Newgrounds
|
|
|
210
208
|
if (medal)
|
|
211
209
|
{
|
|
212
210
|
// copy newgrounds medal data
|
|
213
|
-
medal.image = new Image();
|
|
214
211
|
medal.image.src = newgroundsMedal['icon'];
|
|
215
212
|
medal.name = newgroundsMedal['name'];
|
|
216
213
|
medal.description = newgroundsMedal['description'];
|
|
@@ -238,9 +235,6 @@ class Newgrounds
|
|
|
238
235
|
* @param {Number} value - The score value */
|
|
239
236
|
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
240
237
|
|
|
241
|
-
/** Send message to log a view */
|
|
242
|
-
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
243
|
-
|
|
244
238
|
/** Get scores from a scoreboard
|
|
245
239
|
* @param {Number} id - The scoreboard id
|
|
246
240
|
* @param {String} [user=0] - A user's id or name
|
|
@@ -252,6 +246,9 @@ class Newgrounds
|
|
|
252
246
|
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
253
247
|
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
254
248
|
|
|
249
|
+
/** Send message to log a view */
|
|
250
|
+
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
251
|
+
|
|
255
252
|
/** Send a message to call a component of the Newgrounds API
|
|
256
253
|
* @param {String} component - Name of the component
|
|
257
254
|
* @param {Object} [parameters=0] - Parameters to use for call
|
package/engine/engineObject.js
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
class EngineObject
|
|
33
33
|
{
|
|
34
34
|
/** Create an engine object and adds it to the list of objects
|
|
35
|
-
* @param {Vector2} [position=new Vector2(
|
|
35
|
+
* @param {Vector2} [position=new Vector2()] - World space position of the object
|
|
36
36
|
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
37
37
|
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
38
38
|
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
@@ -77,12 +77,15 @@ class EngineObject
|
|
|
77
77
|
this.gravityScale = 1;
|
|
78
78
|
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
79
79
|
this.renderOrder = renderOrder;
|
|
80
|
+
/** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
|
|
81
|
+
this.velocity = new Vector2();
|
|
82
|
+
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
83
|
+
this.angleVelocity = 0;
|
|
80
84
|
|
|
81
85
|
// init other internal object stuff
|
|
82
86
|
this.spawnTime = time;
|
|
83
|
-
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
84
|
-
this.collideTiles = 1;
|
|
85
87
|
this.children = [];
|
|
88
|
+
this.collideTiles = 1;
|
|
86
89
|
|
|
87
90
|
// add to list of objects
|
|
88
91
|
engineObjects.push(this);
|
|
@@ -101,8 +104,8 @@ class EngineObject
|
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
// limit max speed to prevent missing collisions
|
|
104
|
-
this.velocity.x = clamp(this.velocity.x, objectMaxSpeed,
|
|
105
|
-
this.velocity.y = clamp(this.velocity.y, objectMaxSpeed,
|
|
107
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
108
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
106
109
|
|
|
107
110
|
// apply physics
|
|
108
111
|
const oldPos = this.pos.copy();
|
|
@@ -150,7 +153,7 @@ class EngineObject
|
|
|
150
153
|
// if already was touching, try to push away
|
|
151
154
|
const deltaPos = oldPos.subtract(o.pos);
|
|
152
155
|
const length = deltaPos.length();
|
|
153
|
-
const pushAwayAccel = .001; // push away if
|
|
156
|
+
const pushAwayAccel = .001; // push away if already overlapping
|
|
154
157
|
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
155
158
|
this.velocity = this.velocity.add(velocity);
|
|
156
159
|
if (o.mass) // push away if not fixed
|
|
@@ -192,8 +195,8 @@ class EngineObject
|
|
|
192
195
|
|
|
193
196
|
// lerp betwen elastic or inelastic based on elasticity
|
|
194
197
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
195
|
-
this.velocity.y = lerp(elasticity,
|
|
196
|
-
o.velocity.y = lerp(elasticity,
|
|
198
|
+
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
199
|
+
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
197
200
|
}
|
|
198
201
|
}
|
|
199
202
|
if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
|
|
@@ -213,8 +216,8 @@ class EngineObject
|
|
|
213
216
|
|
|
214
217
|
// lerp betwen elastic or inelastic based on elasticity
|
|
215
218
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
216
|
-
this.velocity.x = lerp(elasticity,
|
|
217
|
-
o.velocity.x = lerp(elasticity,
|
|
219
|
+
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
220
|
+
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
218
221
|
}
|
|
219
222
|
else // bounce if other object is fixed
|
|
220
223
|
this.velocity.x *= -this.elasticity;
|
|
@@ -244,7 +247,7 @@ class EngineObject
|
|
|
244
247
|
|
|
245
248
|
// adjust next velocity to settle on ground
|
|
246
249
|
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
247
|
-
if (o < 0 && o >
|
|
250
|
+
if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
248
251
|
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
249
252
|
|
|
250
253
|
// move to previous position
|
|
@@ -345,18 +348,6 @@ class EngineObject
|
|
|
345
348
|
{
|
|
346
349
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
347
350
|
|
|
348
|
-
// track collidable objects in separate list
|
|
349
|
-
if (collideSolidObjects && !this.collideSolidObjects)
|
|
350
|
-
{
|
|
351
|
-
ASSERT(!engineObjectsCollide.includes(this));
|
|
352
|
-
engineObjectsCollide.push(this);
|
|
353
|
-
}
|
|
354
|
-
else if (!collideSolidObjects && this.collideSolidObjects)
|
|
355
|
-
{
|
|
356
|
-
ASSERT(engineObjectsCollide.includes(this))
|
|
357
|
-
engineObjectsCollide.splice(engineObjectsCollide.indexOf(this), 1);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
351
|
this.collideSolidObjects = collideSolidObjects;
|
|
361
352
|
this.isSolid = isSolid;
|
|
362
353
|
this.collideTiles = collideTiles;
|