littlejsengine 1.14.9 → 1.14.11
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/dist/littlejs.d.ts +12 -11
- package/dist/littlejs.esm.js +121 -73
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +120 -73
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +102 -56
- package/examples/index.html +52 -47
- package/examples/platformer/gameCharacter.js +6 -2
- package/examples/platformer/gameLevel.js +1 -1
- package/examples/platformer/gameObjects.js +7 -5
- package/examples/shorts/blending.js +1 -1
- package/examples/shorts/landerGame.js +26 -2
- package/examples/shorts/music.js +3 -2
- package/examples/shorts/particles.js +11 -11
- package/examples/shorts/pongGame.js +0 -1
- package/examples/shorts/raycasting.js +4 -1
- package/examples/shorts/slidingPuzzle.js +13 -5
- package/examples/shorts/starfield.js +1 -1
- package/examples/shorts/uiSystem.js +0 -4
- package/examples/uiSystem/game.js +4 -4
- package/package.json +1 -1
- package/plugins/box2d.js +1 -1
- package/plugins/newgrounds.js +4 -4
- package/plugins/uiSystem.js +1 -1
- package/reference.md +0 -2
- package/src/engine.js +20 -12
- package/src/engineDebug.js +19 -17
- package/src/engineExport.js +1 -0
- package/src/engineInput.js +49 -22
- package/src/engineRelease.js +1 -0
- package/src/engineSettings.js +6 -0
- package/src/engineTileLayer.js +7 -1
- package/src/engineUtilities.js +12 -14
package/dist/littlejs.release.js
CHANGED
|
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
|
|
|
33
33
|
* @type {string}
|
|
34
34
|
* @default
|
|
35
35
|
* @memberof Engine */
|
|
36
|
-
const engineVersion = '1.14.
|
|
36
|
+
const engineVersion = '1.14.11';
|
|
37
37
|
|
|
38
38
|
/** Frames per second to update
|
|
39
39
|
* @type {number}
|
|
@@ -122,12 +122,12 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
122
122
|
* @example
|
|
123
123
|
* // Basic engine startup
|
|
124
124
|
* engineInit(
|
|
125
|
-
* () => {
|
|
126
|
-
* () => { updateGameLogic(); },
|
|
127
|
-
* () => { updateUI(); },
|
|
128
|
-
* () => { drawBackground(); },
|
|
129
|
-
* () => { drawHUD(); },
|
|
130
|
-
* ['tiles.png', 'tilesLevel.png']
|
|
125
|
+
* () => { LOG('Game initialized!'); }, // gameInit
|
|
126
|
+
* () => { updateGameLogic(); }, // gameUpdate
|
|
127
|
+
* () => { updateUI(); }, // gameUpdatePost
|
|
128
|
+
* () => { drawBackground(); }, // gameRender
|
|
129
|
+
* () => { drawHUD(); }, // gameRenderPost
|
|
130
|
+
* ['tiles.png', 'tilesLevel.png'] // images to load
|
|
131
131
|
* );
|
|
132
132
|
* @memberof Engine */
|
|
133
133
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
@@ -173,16 +173,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
173
173
|
if (!debugSpeedUp)
|
|
174
174
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
|
|
175
175
|
|
|
176
|
+
let wasUpdated = false;
|
|
176
177
|
if (paused)
|
|
177
178
|
{
|
|
179
|
+
// update everything except the game and objects
|
|
180
|
+
wasUpdated = true;
|
|
178
181
|
updateCanvas();
|
|
182
|
+
inputUpdate();
|
|
183
|
+
pluginUpdateList.forEach(f=>f());
|
|
179
184
|
|
|
180
185
|
// update object transforms even when paused
|
|
181
186
|
for (const o of engineObjects)
|
|
182
187
|
o.parent || o.updateTransforms();
|
|
183
188
|
|
|
184
|
-
|
|
185
|
-
pluginUpdateList.forEach(f=>f());
|
|
189
|
+
// do post update
|
|
186
190
|
debugUpdate();
|
|
187
191
|
gameUpdatePost();
|
|
188
192
|
inputUpdatePost();
|
|
@@ -199,12 +203,13 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
// update multiple frames if necessary in case of slow framerate
|
|
202
|
-
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
|
|
206
|
+
for (; frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
|
|
203
207
|
{
|
|
204
208
|
// increment frame and update time
|
|
205
209
|
time = frame++ / frameRate;
|
|
206
210
|
|
|
207
211
|
// update game and objects
|
|
212
|
+
wasUpdated = true;
|
|
208
213
|
updateCanvas();
|
|
209
214
|
inputUpdate();
|
|
210
215
|
gameUpdate();
|
|
@@ -215,7 +220,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
215
220
|
debugUpdate();
|
|
216
221
|
gameUpdatePost();
|
|
217
222
|
inputUpdatePost();
|
|
218
|
-
|
|
219
223
|
if (debugVideoCaptureIsActive())
|
|
220
224
|
renderFrame();
|
|
221
225
|
}
|
|
@@ -232,6 +236,10 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
232
236
|
{
|
|
233
237
|
if (headlessMode) return;
|
|
234
238
|
|
|
239
|
+
// canvas must be updated before rendering
|
|
240
|
+
if (!wasUpdated)
|
|
241
|
+
updateCanvas();
|
|
242
|
+
|
|
235
243
|
// render sort then render while removing destroyed objects
|
|
236
244
|
enginePreRender();
|
|
237
245
|
gameRender();
|
|
@@ -391,7 +399,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
391
399
|
promises.push(new Promise(resolve =>
|
|
392
400
|
{
|
|
393
401
|
let t = 0;
|
|
394
|
-
|
|
402
|
+
LOG(`${engineName} Engine v${engineVersion}`);
|
|
395
403
|
updateSplash();
|
|
396
404
|
function updateSplash()
|
|
397
405
|
{
|
|
@@ -690,6 +698,7 @@ const debugMedals = 0;
|
|
|
690
698
|
|
|
691
699
|
// debug commands are automatically removed from the final build
|
|
692
700
|
function ASSERT (){}
|
|
701
|
+
function LOG (){}
|
|
693
702
|
function debugInit (){}
|
|
694
703
|
function debugUpdate (){}
|
|
695
704
|
function debugRender (){}
|
|
@@ -927,7 +936,12 @@ function wave(frequency=1, amplitude=1, t=time, offset=0)
|
|
|
927
936
|
* @param {number} t - time in seconds
|
|
928
937
|
* @return {string}
|
|
929
938
|
* @memberof Utilities */
|
|
930
|
-
function formatTime(t)
|
|
939
|
+
function formatTime(t)
|
|
940
|
+
{
|
|
941
|
+
const sign = t < 0 ? '-' : '';
|
|
942
|
+
t = abs(t)|0;
|
|
943
|
+
return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60;
|
|
944
|
+
}
|
|
931
945
|
|
|
932
946
|
/** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
|
|
933
947
|
* @param {string} url - URL of JSON file
|
|
@@ -1263,11 +1277,7 @@ class Vector2
|
|
|
1263
1277
|
direction()
|
|
1264
1278
|
{ return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
|
|
1265
1279
|
|
|
1266
|
-
/** Returns a copy of this vector
|
|
1267
|
-
* @return {Vector2} */
|
|
1268
|
-
invert() { return new Vector2(this.y, -this.x); }
|
|
1269
|
-
|
|
1270
|
-
/** Returns a copy of this vector absolute values
|
|
1280
|
+
/** Returns a copy of this vector with absolute values
|
|
1271
1281
|
* @return {Vector2} */
|
|
1272
1282
|
abs() { return new Vector2(abs(this.x), abs(this.y)); }
|
|
1273
1283
|
|
|
@@ -1309,13 +1319,10 @@ class Vector2
|
|
|
1309
1319
|
toString(digits=3)
|
|
1310
1320
|
{
|
|
1311
1321
|
ASSERT_NUMBER_VALID(digits);
|
|
1312
|
-
if (
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
else
|
|
1317
|
-
return `(${this.x}, ${this.y})`;
|
|
1318
|
-
}
|
|
1322
|
+
if (this.isValid())
|
|
1323
|
+
return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
|
|
1324
|
+
else
|
|
1325
|
+
return `(${this.x}, ${this.y})`;
|
|
1319
1326
|
}
|
|
1320
1327
|
|
|
1321
1328
|
/** Checks if this is a valid vector
|
|
@@ -1704,7 +1711,7 @@ class Timer
|
|
|
1704
1711
|
|
|
1705
1712
|
/** Returns this timer expressed as a string
|
|
1706
1713
|
* @return {string} */
|
|
1707
|
-
toString() {
|
|
1714
|
+
toString() { return this.isSet() ? Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|
|
1708
1715
|
|
|
1709
1716
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1710
1717
|
* @return {number} */
|
|
@@ -1932,6 +1939,12 @@ let touchGamepadEnable = false;
|
|
|
1932
1939
|
* @memberof Settings */
|
|
1933
1940
|
let touchGamepadAnalog = true;
|
|
1934
1941
|
|
|
1942
|
+
/** Number of buttons on touch gamepad
|
|
1943
|
+
* @type {number}
|
|
1944
|
+
* @default
|
|
1945
|
+
* @memberof Settings */
|
|
1946
|
+
let touchGamepadButtonCount = 4;
|
|
1947
|
+
|
|
1935
1948
|
/** Size of virtual gamepad for touch devices in pixels
|
|
1936
1949
|
* @type {number}
|
|
1937
1950
|
* @default
|
|
@@ -3962,7 +3975,7 @@ function inputInit()
|
|
|
3962
3975
|
function onMouseLeave()
|
|
3963
3976
|
{
|
|
3964
3977
|
// set mouse position and delta when leaving canvas
|
|
3965
|
-
mousePosScreen = vec2(
|
|
3978
|
+
mousePosScreen = vec2(-1);
|
|
3966
3979
|
mouseDeltaScreen = vec2(0);
|
|
3967
3980
|
}
|
|
3968
3981
|
}
|
|
@@ -4017,10 +4030,12 @@ function gamepadsUpdate()
|
|
|
4017
4030
|
const data = inputData[1] || (inputData[1] = []);
|
|
4018
4031
|
for (let i=10; i--;)
|
|
4019
4032
|
{
|
|
4020
|
-
const
|
|
4021
|
-
|
|
4022
|
-
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
4033
|
+
const wasDown = gamepadIsDown(i,0);
|
|
4034
|
+
data[i] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
4023
4035
|
}
|
|
4036
|
+
|
|
4037
|
+
// disable normal gamepads when touch gamepad is active
|
|
4038
|
+
return;
|
|
4024
4039
|
}
|
|
4025
4040
|
|
|
4026
4041
|
// return if gamepads are disabled or not supported
|
|
@@ -4095,6 +4110,15 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
|
4095
4110
|
// touch gamepad internal variables
|
|
4096
4111
|
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
|
|
4097
4112
|
|
|
4113
|
+
function touchGamepadButtonCenter()
|
|
4114
|
+
{
|
|
4115
|
+
// draw right face buttons
|
|
4116
|
+
let center = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
4117
|
+
if (touchGamepadButtonCount <= 2)
|
|
4118
|
+
center.x += touchGamepadSize/2;
|
|
4119
|
+
return center;
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4098
4122
|
// enable touch input mouse passthrough
|
|
4099
4123
|
function touchInputInit()
|
|
4100
4124
|
{
|
|
@@ -4142,8 +4166,8 @@ function touchInputInit()
|
|
|
4142
4166
|
// set was touching
|
|
4143
4167
|
wasTouching = touching;
|
|
4144
4168
|
|
|
4145
|
-
// prevent default handling like copy
|
|
4146
|
-
if (inputPreventDefault && document.hasFocus()
|
|
4169
|
+
// prevent default handling like copy, magnifier lens, and scrolling
|
|
4170
|
+
if (inputPreventDefault && document.hasFocus() && e.cancelable)
|
|
4147
4171
|
e.preventDefault();
|
|
4148
4172
|
|
|
4149
4173
|
// must return true so the document will get focus
|
|
@@ -4172,25 +4196,36 @@ function touchInputInit()
|
|
|
4172
4196
|
|
|
4173
4197
|
// get center of left and right sides
|
|
4174
4198
|
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
4175
|
-
const buttonCenter =
|
|
4199
|
+
const buttonCenter = touchGamepadButtonCenter();
|
|
4176
4200
|
const startCenter = mainCanvasSize.scale(.5);
|
|
4177
4201
|
|
|
4178
4202
|
// check each touch point
|
|
4179
4203
|
for (const touch of e.touches)
|
|
4180
4204
|
{
|
|
4181
4205
|
const touchPos = mouseEventToScreen(vec2(touch.clientX, touch.clientY));
|
|
4182
|
-
if (
|
|
4206
|
+
if (stickCenter.distance(touchPos) < touchGamepadSize)
|
|
4183
4207
|
{
|
|
4184
4208
|
// virtual analog stick
|
|
4185
4209
|
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
4186
4210
|
}
|
|
4187
|
-
else if (
|
|
4211
|
+
else if (buttonCenter.distance(touchPos) < touchGamepadSize)
|
|
4188
4212
|
{
|
|
4189
4213
|
// virtual face buttons
|
|
4190
|
-
|
|
4191
|
-
|
|
4214
|
+
let button = buttonCenter.subtract(touchPos).direction();
|
|
4215
|
+
button = mod(button+2, 4);
|
|
4216
|
+
if (touchGamepadButtonCount === 1)
|
|
4217
|
+
button = 0;
|
|
4218
|
+
else if (touchGamepadButtonCount === 2)
|
|
4219
|
+
{
|
|
4220
|
+
const delta = buttonCenter.subtract(touchPos);
|
|
4221
|
+
button = -delta.x < delta.y ? 1 : 0;
|
|
4222
|
+
}
|
|
4223
|
+
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
4224
|
+
button = button === 3 ? 2 : button === 2 ? 3 : button;
|
|
4225
|
+
if (button < touchGamepadButtonCount)
|
|
4226
|
+
touchGamepadButtons[button] = 1;
|
|
4192
4227
|
}
|
|
4193
|
-
else if (
|
|
4228
|
+
else if (startCenter.distance(touchPos) < touchGamepadSize && !wasTouching)
|
|
4194
4229
|
{
|
|
4195
4230
|
// virtual start button in center
|
|
4196
4231
|
touchGamepadButtons[9] = 1;
|
|
@@ -4221,11 +4256,10 @@ function touchGamepadRender()
|
|
|
4221
4256
|
// draw left analog stick
|
|
4222
4257
|
context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
4223
4258
|
context.beginPath();
|
|
4224
|
-
|
|
4225
|
-
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
4259
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
4226
4260
|
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
4227
4261
|
{
|
|
4228
|
-
context.arc(
|
|
4262
|
+
context.arc(stickCenter.x, stickCenter.y, touchGamepadSize/2, 0, 9);
|
|
4229
4263
|
context.fill();
|
|
4230
4264
|
context.stroke();
|
|
4231
4265
|
}
|
|
@@ -4234,21 +4268,27 @@ function touchGamepadRender()
|
|
|
4234
4268
|
for (let i=10; i--;)
|
|
4235
4269
|
{
|
|
4236
4270
|
const angle = i*PI/4;
|
|
4237
|
-
context.arc(
|
|
4238
|
-
i%2 && context.arc(
|
|
4271
|
+
context.arc(stickCenter.x, stickCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
4272
|
+
i%2 && context.arc(stickCenter.x, stickCenter.y, touchGamepadSize*.33, angle, angle);
|
|
4239
4273
|
i===1 && context.fill();
|
|
4240
4274
|
}
|
|
4241
4275
|
context.stroke();
|
|
4242
4276
|
}
|
|
4243
4277
|
|
|
4244
4278
|
// draw right face buttons
|
|
4245
|
-
const
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4279
|
+
const buttonCenter = touchGamepadButtonCenter();
|
|
4280
|
+
const buttonSize = touchGamepadButtonCount > 1 ? touchGamepadSize/4 : touchGamepadSize/2;
|
|
4281
|
+
for (let i=0; i<touchGamepadButtonCount; i++)
|
|
4282
|
+
{
|
|
4283
|
+
let j = mod(i-1, 4);
|
|
4284
|
+
let button = touchGamepadButtonCount > 2 ?
|
|
4285
|
+
j : min(j, touchGamepadButtonCount-1);
|
|
4286
|
+
// fix button locations (swap 2 and 3 to match gamepad layout)
|
|
4287
|
+
button = button === 3 ? 2 : button === 2 ? 3 : button;
|
|
4288
|
+
const pos = buttonCenter.add(vec2().setDirection(j, touchGamepadSize/2));
|
|
4289
|
+
context.fillStyle = touchGamepadButtons[button] ? '#fff' : '#000';
|
|
4250
4290
|
context.beginPath();
|
|
4251
|
-
context.arc(pos.x, pos.y,
|
|
4291
|
+
context.arc(pos.x, pos.y, buttonSize, 0,9);
|
|
4252
4292
|
context.fill();
|
|
4253
4293
|
context.stroke();
|
|
4254
4294
|
}
|
|
@@ -5038,6 +5078,12 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
|
|
|
5038
5078
|
const tileLayer = new TileCollisionLayer(vec2(), levelSize, tileInfo, layerRenderOrder);
|
|
5039
5079
|
tileLayers[layerIndex] = tileLayer;
|
|
5040
5080
|
|
|
5081
|
+
// apply layer color
|
|
5082
|
+
const layerColor = dataLayer.color || WHITE;
|
|
5083
|
+
if (dataLayer.tintcolor)
|
|
5084
|
+
layerColor.setHex(dataLayer.tintcolor);
|
|
5085
|
+
ASSERT(isColor(layerColor), 'layer color is not a color');
|
|
5086
|
+
|
|
5041
5087
|
for (let x=levelSize.x; x--;)
|
|
5042
5088
|
for (let y=levelSize.y; y--;)
|
|
5043
5089
|
{
|
|
@@ -5045,7 +5091,7 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
|
|
|
5045
5091
|
const data = dataLayer.data[x + y*levelSize.x];
|
|
5046
5092
|
if (data)
|
|
5047
5093
|
{
|
|
5048
|
-
const layerData = new TileLayerData(data-1);
|
|
5094
|
+
const layerData = new TileLayerData(data-1, 0, false, layerColor);
|
|
5049
5095
|
tileLayer.setData(pos, layerData);
|
|
5050
5096
|
|
|
5051
5097
|
// set collision for top layer
|
|
@@ -6939,7 +6985,7 @@ class NewgroundsPlugin
|
|
|
6939
6985
|
// get medals
|
|
6940
6986
|
const medalsResult = this.call('Medal.getList');
|
|
6941
6987
|
this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
|
|
6942
|
-
debugMedals &&
|
|
6988
|
+
debugMedals && LOG(this.medals);
|
|
6943
6989
|
for (const newgroundsMedal of this.medals)
|
|
6944
6990
|
{
|
|
6945
6991
|
const medal = medals[newgroundsMedal['id']];
|
|
@@ -6962,7 +7008,7 @@ class NewgroundsPlugin
|
|
|
6962
7008
|
// get scoreboards
|
|
6963
7009
|
const scoreboardResult = this.call('ScoreBoard.getBoards');
|
|
6964
7010
|
this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
|
|
6965
|
-
debugMedals &&
|
|
7011
|
+
debugMedals && LOG(this.scoreboards);
|
|
6966
7012
|
|
|
6967
7013
|
// keep the session alive with a ping every minute
|
|
6968
7014
|
const keepAliveMS = 60 * 1e3;
|
|
@@ -7031,10 +7077,10 @@ class NewgroundsPlugin
|
|
|
7031
7077
|
try { xmlHttp.send(formData); }
|
|
7032
7078
|
catch(e)
|
|
7033
7079
|
{
|
|
7034
|
-
debugMedals &&
|
|
7080
|
+
debugMedals && LOG('newgrounds call failed', e);
|
|
7035
7081
|
return;
|
|
7036
7082
|
}
|
|
7037
|
-
debugMedals &&
|
|
7083
|
+
debugMedals && LOG(xmlHttp.responseText);
|
|
7038
7084
|
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
7039
7085
|
}
|
|
7040
7086
|
}
|
|
@@ -7604,7 +7650,7 @@ class UIObject
|
|
|
7604
7650
|
const mouseDown = mouseIsDown(0);
|
|
7605
7651
|
const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
|
|
7606
7652
|
if (!uiSystem.hoverObject)
|
|
7607
|
-
if (mousePress || !mouseDown
|
|
7653
|
+
if (mousePress || isActive || (!mouseDown && !isTouchDevice))
|
|
7608
7654
|
{
|
|
7609
7655
|
const size = this.size.add(vec2(isTouchDevice && this.extraTouchSize || 0));
|
|
7610
7656
|
if (isOverlapping(this.pos, size, mousePosScreen))
|
|
@@ -9631,7 +9677,7 @@ async function box2dInit()
|
|
|
9631
9677
|
}
|
|
9632
9678
|
function box2dRender()
|
|
9633
9679
|
{
|
|
9634
|
-
if (box2dDebug || debugPhysics
|
|
9680
|
+
if (box2dDebug || debugPhysics)
|
|
9635
9681
|
box2d.world.DrawDebugData();
|
|
9636
9682
|
}
|
|
9637
9683
|
|
package/examples/index.html
CHANGED
|
@@ -53,7 +53,7 @@ Theme:
|
|
|
53
53
|
|
|
54
54
|
class ExampleInfo
|
|
55
55
|
{
|
|
56
|
-
constructor(name, filename, description='', largeExample=false)
|
|
56
|
+
constructor(name, filename, description='', largeExample=false, tags='')
|
|
57
57
|
{
|
|
58
58
|
if (filename && !largeExample)
|
|
59
59
|
filename = 'shorts/' + filename;
|
|
@@ -61,62 +61,66 @@ class ExampleInfo
|
|
|
61
61
|
this.filename = filename;
|
|
62
62
|
this.description = description;
|
|
63
63
|
this.largeExample = largeExample;
|
|
64
|
+
this.tags = tags;
|
|
64
65
|
this.isHeading = !filename;
|
|
65
66
|
this.text = this.name;
|
|
66
67
|
if (this.description)
|
|
67
68
|
this.text += ' - ' + this.description;
|
|
69
|
+
this.selectText = this.text;
|
|
70
|
+
if (this.tags)
|
|
71
|
+
this.selectText += ' (' + this.tags + ')';
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
const exampleList =
|
|
72
76
|
[
|
|
73
77
|
new ExampleInfo('--- SHORT EXAMPLES ---'),
|
|
74
|
-
new ExampleInfo('Hello World', 'helloWorld.js', 'Minimal starter example'),
|
|
75
|
-
new ExampleInfo('Texture', 'texture.js', 'Texture display and manipulation'),
|
|
76
|
-
new ExampleInfo('Animation', 'animation.js', 'Sprite animation system'),
|
|
77
|
-
new ExampleInfo('Shapes', 'shapes.js', 'Draw geometric shapes and primitives'),
|
|
78
|
-
new ExampleInfo('Colors', 'colors.js', 'Color manipulation and HSL'),
|
|
79
|
-
new ExampleInfo('Starfield', 'starfield.js', 'Animated parallax starfield'),
|
|
80
|
-
new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'Sprite atlas and tile rendering'),
|
|
81
|
-
new ExampleInfo('Blending', 'blending.js', 'Additive blending and transparency'),
|
|
82
|
-
new ExampleInfo('Tile Layer', 'tileLayer.js', 'Tile layer rendering system'),
|
|
83
|
-
new ExampleInfo('Particles', 'particles.js', 'Particle system'),
|
|
84
|
-
new ExampleInfo('Timers', 'timers.js', 'Timer objects and UI'),
|
|
85
|
-
new ExampleInfo('Sound Effects', 'sound.js', 'ZzFX sound effect generator'),
|
|
86
|
-
new ExampleInfo('Music', 'music.js', 'Music playback and controls'),
|
|
87
|
-
new ExampleInfo('Font Image', 'systemFont.js', 'Bitmap font system with built-in system font'),
|
|
88
|
-
new ExampleInfo('Parallax', 'parallax.js', 'Parallax scrolling mountains'),
|
|
89
|
-
new ExampleInfo('Maze Generator', 'maze.js', 'Procedural maze generation'),
|
|
90
|
-
new ExampleInfo('Medals', 'medals.js', 'Achievement system'),
|
|
91
|
-
new ExampleInfo('Piano', 'piano.js', 'Interactive piano keyboard'),
|
|
92
|
-
new ExampleInfo('Clock', 'clock.js', 'Animated analog clock'),
|
|
78
|
+
new ExampleInfo('Hello World', 'helloWorld.js', 'Minimal starter example', false, 'beginner, gradient, text, tiles'),
|
|
79
|
+
new ExampleInfo('Texture', 'texture.js', 'Texture display and manipulation', false, 'sprites, loading, tiles'),
|
|
80
|
+
new ExampleInfo('Animation', 'animation.js', 'Sprite animation system', false, 'frames, loop, tiles, sprites'),
|
|
81
|
+
new ExampleInfo('Shapes', 'shapes.js', 'Draw geometric shapes and primitives', false, 'circle, ellipses, rectangles, polygon, lines'),
|
|
82
|
+
new ExampleInfo('Colors', 'colors.js', 'Color manipulation and HSL', false, 'hue, saturation, blending, rectangles'),
|
|
83
|
+
new ExampleInfo('Starfield', 'starfield.js', 'Animated parallax starfield', false, 'space, movement, depth, rectangles'),
|
|
84
|
+
new ExampleInfo('Sprite Atlas', 'spriteAtlas.js', 'Sprite atlas and tile rendering', false, 'sheet, frames, tiles'),
|
|
85
|
+
new ExampleInfo('Blending', 'blending.js', 'Additive blending and transparency', false, 'alpha, color, tiles, smooth'),
|
|
86
|
+
new ExampleInfo('Tile Layer', 'tileLayer.js', 'Tile layer rendering system', false, 'level, map, grid, particles'),
|
|
87
|
+
new ExampleInfo('Particles', 'particles.js', 'Particle system', false, 'effects, emitter, physics, fire, smoke'),
|
|
88
|
+
new ExampleInfo('Timers', 'timers.js', 'Timer objects and UI', false, 'delay, interval, slider'),
|
|
89
|
+
new ExampleInfo('Sound Effects', 'sound.js', 'ZzFX sound effect generator', false, 'audio, volume, ui'),
|
|
90
|
+
new ExampleInfo('Music', 'music.js', 'Music playback and controls', false, 'audio, streaming, volume, ui'),
|
|
91
|
+
new ExampleInfo('Font Image', 'systemFont.js', 'Bitmap font system with built-in system font', false, 'text, characters'),
|
|
92
|
+
new ExampleInfo('Parallax', 'parallax.js', 'Parallax scrolling mountains', false, 'generative, canvas, background'),
|
|
93
|
+
new ExampleInfo('Maze Generator', 'maze.js', 'Procedural maze generation', false, 'generative, level, tiles, map, grid'),
|
|
94
|
+
new ExampleInfo('Medals', 'medals.js', 'Achievement system', false, 'unlock, progress, newgrounds'),
|
|
95
|
+
new ExampleInfo('Piano', 'piano.js', 'Interactive piano keyboard', false, 'music, notes, ui, instrument'),
|
|
96
|
+
new ExampleInfo('Clock', 'clock.js', 'Animated analog clock', false, 'time, rotation, lines, rectangles'),
|
|
93
97
|
new ExampleInfo('--- MINI GAMES ---'),
|
|
94
|
-
new ExampleInfo('Pong Game', 'pongGame.js', 'Classic paddle ball bouncing'),
|
|
95
|
-
new ExampleInfo('Platformer Game', 'platformer.js', 'Jump and run side view'),
|
|
96
|
-
new ExampleInfo('Top Down Game', 'topDown.js', 'Top-down style camera'),
|
|
97
|
-
new ExampleInfo('Tilted View Game', 'tiltedView.js', 'Pseudo-3D oblique view'),
|
|
98
|
-
new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird style game'),
|
|
99
|
-
new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander style game'),
|
|
100
|
-
new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style sliding game'),
|
|
101
|
-
new ExampleInfo('Sliding Puzzle', 'slidingPuzzle.js', '15 tile sliding puzzle'),
|
|
102
|
-
new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship shooter with parallax'),
|
|
103
|
-
new ExampleInfo('Raycasting 3D Game', 'raycasting.js', '
|
|
98
|
+
new ExampleInfo('Pong Game', 'pongGame.js', 'Classic paddle ball bouncing', false, 'objects, collision'),
|
|
99
|
+
new ExampleInfo('Platformer Game', 'platformer.js', 'Jump and run side view', false, 'objects, gravity, level, tiles, camera'),
|
|
100
|
+
new ExampleInfo('Top Down Game', 'topDown.js', 'Top-down style camera', false, 'objects, movement, exploration'),
|
|
101
|
+
new ExampleInfo('Tilted View Game', 'tiltedView.js', 'Pseudo-3D oblique view', false, 'isometric, depth'),
|
|
102
|
+
new ExampleInfo('Flappy Game', 'flappyGame.js', 'Flappy bird style game', false, 'objects, obstacles'),
|
|
103
|
+
new ExampleInfo('Lander Game', 'landerGame.js', 'Lunar lander style game', false, 'objects, physics'),
|
|
104
|
+
new ExampleInfo('Hill Glide Game', 'hillGlideGame.js', 'Tiny wings style sliding game', false, 'objects, physics, speed'),
|
|
105
|
+
new ExampleInfo('Sliding Puzzle', 'slidingPuzzle.js', '15 tile sliding puzzle', false, 'objects, numbers, ui'),
|
|
106
|
+
new ExampleInfo('Space Game', 'spaceGame.js', 'Spaceship shooter with parallax', false, 'objects, weapons, stars, camera, rotation'),
|
|
107
|
+
new ExampleInfo('Raycasting 3D Game', 'raycasting.js', 'Pseudo 3D raycasting demo', false, '3D, maze, camera'),
|
|
104
108
|
new ExampleInfo('--- PLUGINS ---'),
|
|
105
|
-
new ExampleInfo('Box2d Demo', 'box2d.js', 'Box2D physics plugin'),
|
|
106
|
-
new ExampleInfo('Box2d Car', 'box2dCar.js', 'Drivable car with Box2D physics'),
|
|
107
|
-
new ExampleInfo('Post Processing', 'postProcess.js', 'Shader effects and filters'),
|
|
108
|
-
new ExampleInfo('UI System Plugin', 'uiSystem.js', 'Buttons, sliders, and checkboxes'),
|
|
109
|
-
new ExampleInfo('Nine Slice', 'nineSlice.js', 'Scalable UI panels'),
|
|
109
|
+
new ExampleInfo('Box2d Demo', 'box2d.js', 'Box2D physics plugin', false, 'objects, mouse'),
|
|
110
|
+
new ExampleInfo('Box2d Car', 'box2dCar.js', 'Drivable car with Box2D physics', false, 'objects, vehicle, suspension, wheels'),
|
|
111
|
+
new ExampleInfo('Post Processing', 'postProcess.js', 'Shader effects and filters', false, 'webgl, visual, effect'),
|
|
112
|
+
new ExampleInfo('UI System Plugin', 'uiSystem.js', 'Buttons, sliders, and checkboxes', false, 'objects, widgets, interactive'),
|
|
113
|
+
new ExampleInfo('Nine Slice', 'nineSlice.js', 'Scalable UI panels', false, 'three slice, stretch, corners, text, tiles'),
|
|
110
114
|
new ExampleInfo('--- FULL EXAMPLES ---'),
|
|
111
|
-
new ExampleInfo('Starter', 'starter', 'Clean project template', true),
|
|
112
|
-
new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', 'Step-by-step breakout tutorial', true),
|
|
113
|
-
new ExampleInfo('Breakout Game', 'breakout', 'Complete breakout game', true),
|
|
114
|
-
new ExampleInfo('Platforming Game', 'platformer', '
|
|
115
|
-
new ExampleInfo('Puzzle Game', 'puzzle', 'Match
|
|
116
|
-
new ExampleInfo('Stress Test', 'stress', 'Performance and music test', true),
|
|
117
|
-
new ExampleInfo('Box2D Plugin', 'box2d', 'Full Box2D physics demo', true),
|
|
118
|
-
new ExampleInfo('HTML Menus', 'htmlMenu', 'HTML UI integration', true),
|
|
119
|
-
new ExampleInfo('UI System Plugin', 'uiSystem', 'Complete UI system demo', true),
|
|
115
|
+
new ExampleInfo('Starter', 'starter', 'Clean project template', true, 'base, empty, particles'),
|
|
116
|
+
new ExampleInfo('Breakout Tutorial', 'breakoutTutorial', 'Step-by-step breakout game tutorial', true, 'objects, physics, score'),
|
|
117
|
+
new ExampleInfo('Breakout Game', 'breakout', 'Complete breakout game', true, 'objects, physics, score'),
|
|
118
|
+
new ExampleInfo('Platforming Game', 'platformer', 'Platformer with level loading', true, 'jump, world, tiles, pixel art, sprites'),
|
|
119
|
+
new ExampleInfo('Puzzle Game', 'puzzle', 'Match 3 style puzzle game', true, 'match, swap, sprites'),
|
|
120
|
+
new ExampleInfo('Stress Test', 'stress', 'Performance and music test', true, 'optimization, tiles, sprites'),
|
|
121
|
+
new ExampleInfo('Box2D Plugin', 'box2d', 'Full Box2D physics demo', true, 'objects, bodies, joints'),
|
|
122
|
+
new ExampleInfo('HTML Menus', 'htmlMenu', 'HTML UI integration', true, 'web, browser, overlay, button, slider, textbox'),
|
|
123
|
+
new ExampleInfo('UI System Plugin', 'uiSystem', 'Complete UI system demo', true, 'menu, overlay, button, slider, checkbox'),
|
|
120
124
|
];
|
|
121
125
|
|
|
122
126
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -313,9 +317,10 @@ function filterExamples(reset=0)
|
|
|
313
317
|
if (!example.filename)
|
|
314
318
|
continue;
|
|
315
319
|
|
|
316
|
-
// Check if search term matches name or
|
|
320
|
+
// Check if search term matches name, description, or tags
|
|
317
321
|
if (example.name.toLowerCase().includes(searchTerm) ||
|
|
318
|
-
example.description.toLowerCase().includes(searchTerm)
|
|
322
|
+
example.description.toLowerCase().includes(searchTerm) ||
|
|
323
|
+
example.tags.toLowerCase().includes(searchTerm))
|
|
319
324
|
matchingIndices.push(i);
|
|
320
325
|
}
|
|
321
326
|
|
|
@@ -345,7 +350,7 @@ function filterExamples(reset=0)
|
|
|
345
350
|
else if (matchingIndices.includes(i))
|
|
346
351
|
{
|
|
347
352
|
// add matching example
|
|
348
|
-
const o = new Option(example.
|
|
353
|
+
const o = new Option(example.selectText);
|
|
349
354
|
o.originalIndex = i;
|
|
350
355
|
selectExample.add(o);
|
|
351
356
|
}
|
|
@@ -22,10 +22,9 @@ const {vec2, hsl, Timer} = LJS;
|
|
|
22
22
|
export class Character extends GameObjects.GameObject
|
|
23
23
|
{
|
|
24
24
|
constructor(pos)
|
|
25
|
-
{
|
|
25
|
+
{
|
|
26
26
|
super(pos, vec2(.6,.95));
|
|
27
27
|
|
|
28
|
-
this.weapon = new GameObjects.Weapon(pos, this);
|
|
29
28
|
this.lastPos = pos;
|
|
30
29
|
this.groundTimer = new Timer;
|
|
31
30
|
this.jumpTimer = new Timer;
|
|
@@ -41,6 +40,11 @@ export class Character extends GameObjects.GameObject
|
|
|
41
40
|
this.health = 1;
|
|
42
41
|
this.climbingLadder = false;
|
|
43
42
|
this.setCollision(true,false);
|
|
43
|
+
|
|
44
|
+
// attach weapon
|
|
45
|
+
this.weapon = new GameObjects.Weapon(pos);
|
|
46
|
+
this.weapon.renderOrder = this.renderOrder + 1;
|
|
47
|
+
this.addChild(this.weapon, vec2(this.size.x,0));
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
update()
|
|
@@ -50,7 +50,7 @@ function loadLevelData()
|
|
|
50
50
|
{
|
|
51
51
|
// load the level data
|
|
52
52
|
const tileMapData = Game.gameLevelData;
|
|
53
|
-
tileLayers = LJS.tileCollisionLoad(tileMapData, tile(0,16,1));
|
|
53
|
+
tileLayers = LJS.tileCollisionLoad(tileMapData, tile(0,16,1), 0, 1);
|
|
54
54
|
levelSize = tileLayers[0].size;
|
|
55
55
|
playerStartPos = vec2(0, levelSize.y); // default player start
|
|
56
56
|
foregroundTileLayer = tileLayers[0];
|
|
@@ -101,10 +101,14 @@ export class Coin extends LJS.EngineObject
|
|
|
101
101
|
|
|
102
102
|
render()
|
|
103
103
|
{
|
|
104
|
+
// side of coin
|
|
105
|
+
LJS.drawTile(this.pos, vec2(.1, .6), 0, this.color);
|
|
106
|
+
|
|
104
107
|
// make it appear to spin
|
|
105
108
|
const t = LJS.time+this.pos.x/4+this.pos.y/4;
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
const spinSize = vec2(.5+.5*Math.sin(t*2*LJS.PI), 1);
|
|
110
|
+
if (spinSize.x > .1)
|
|
111
|
+
LJS.drawTile(this.pos, spinSize, this.tileInfo, this.color);
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
update()
|
|
@@ -227,7 +231,7 @@ export class Grenade extends GameObject
|
|
|
227
231
|
|
|
228
232
|
export class Weapon extends LJS.EngineObject
|
|
229
233
|
{
|
|
230
|
-
constructor(pos
|
|
234
|
+
constructor(pos)
|
|
231
235
|
{
|
|
232
236
|
super(pos, vec2(.6), Game.spriteAtlas.gun);
|
|
233
237
|
|
|
@@ -238,10 +242,8 @@ export class Weapon extends LJS.EngineObject
|
|
|
238
242
|
this.damage = 1;
|
|
239
243
|
|
|
240
244
|
// prepare to fire
|
|
241
|
-
this.renderOrder = parent.renderOrder + 1;
|
|
242
245
|
this.fireTimeBuffer = this.localAngle = 0;
|
|
243
246
|
this.recoilTimer = new Timer;
|
|
244
|
-
parent.addChild(this, vec2(.6,0));
|
|
245
247
|
|
|
246
248
|
// shell effect
|
|
247
249
|
this.addChild(this.shellEmitter = new LJS.ParticleEmitter(
|
|
@@ -4,14 +4,38 @@ class Player extends EngineObject
|
|
|
4
4
|
{
|
|
5
5
|
super(pos, vec2(1,2), tile(8), 0, RED);
|
|
6
6
|
this.setCollision(); // make object collide
|
|
7
|
+
this.angleDamping = .95;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
update()
|
|
10
11
|
{
|
|
11
12
|
super.update();
|
|
13
|
+
|
|
14
|
+
// space ship controls
|
|
15
|
+
const moveInput = keyDirection();
|
|
16
|
+
this.applyAngularAcceleration(moveInput.x * .002);
|
|
17
|
+
this.applyAcceleration(vec2().setAngle(this.angle, moveInput.y*.002));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// explode when it collides
|
|
21
|
+
collideWithObject()
|
|
22
|
+
{
|
|
23
|
+
// don't explode if player is almost still and not tilted
|
|
24
|
+
if (abs(this.angle) < .2)
|
|
25
|
+
if (this.velocity.length() < .05)
|
|
26
|
+
return true;
|
|
12
27
|
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
this.destroy();
|
|
29
|
+
new ParticleEmitter(
|
|
30
|
+
this.pos, 0, // pos, angle
|
|
31
|
+
1, .1, 300, 3.14, // emitSize, emitTime, emitRate, emitCone
|
|
32
|
+
0, // tileInfo
|
|
33
|
+
rgb(1,.5,.1), rgb(1,.1,.1), // colorStartA, colorStartB
|
|
34
|
+
rgb(1,.5,.1,0), rgb(1,.1,.1,0), // colorEndA, colorEndB
|
|
35
|
+
.7, .8, .2, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
|
|
36
|
+
.9, 1, -.2, 3.14, .05, // damp, angleDamp, gravity, particleCone, fade
|
|
37
|
+
1, 0, 1 // randomness, collide, additive
|
|
38
|
+
);
|
|
15
39
|
}
|
|
16
40
|
}
|
|
17
41
|
|