littlejsengine 1.9.11 → 1.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -13
- package/dist/littlejs.d.ts +129 -10
- package/dist/littlejs.esm.js +220 -75
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +188 -73
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +187 -72
- package/examples/box2d/game.js +4 -1
- package/examples/box2d/index.html +7 -6
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/index.html +5 -4
- package/examples/breakoutTutorial/README.md +9 -7
- package/examples/breakoutTutorial/game.js +6 -6
- package/examples/breakoutTutorial/index.html +4 -3
- package/examples/electron/game.js +1 -1
- package/examples/electron/index.html +4 -3
- package/examples/empty/game.js +1 -1
- package/examples/empty/index.html +2 -1
- package/examples/htmlMenu/game.js +67 -0
- package/examples/htmlMenu/index.html +56 -0
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +33 -0
- package/examples/js13k/build/index.html +1 -0
- package/examples/js13k/build/index.js +1 -0
- package/examples/js13k/build/tiles.png +0 -0
- package/examples/js13k/game - Copy.zip +0 -0
- package/examples/js13k/game.js +1 -1
- package/examples/js13k/game.zip +0 -0
- package/examples/js13k/index.html +17 -14
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +3 -2
- package/examples/particles/index.html +4 -3
- package/examples/platformer/game.js +7 -5
- package/examples/platformer/gameCharacter.js +12 -11
- package/examples/platformer/gameEffects.js +3 -4
- package/examples/platformer/gameLevel.js +18 -34
- package/examples/platformer/gameObjects.js +9 -15
- package/examples/platformer/index.html +10 -9
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +4 -3
- package/examples/starter/build/index.html +2 -0
- package/examples/starter/build/index.js +1 -0
- package/examples/starter/build/tiles.png +0 -0
- package/examples/starter/game.js +4 -4
- package/examples/starter/game.zip +0 -0
- package/examples/starter/index.html +15 -14
- package/examples/stress/index.html +3 -2
- package/examples/typescript/build/build/littlejs.esm.js +4327 -0
- package/examples/typescript/build/dist/littlejs.esm.js +4425 -0
- package/examples/typescript/build/examples/typescript/build.js +24 -0
- package/examples/typescript/build/examples/typescript/game.js +100 -0
- package/examples/typescript/build/examples/typescript/test/build/littlejs.esm.js +3934 -0
- package/examples/typescript/build/examples/typescript/test/examples/typescript/build.js +86 -0
- package/examples/typescript/build/examples/typescript/test/examples/typescript/game.js +92 -0
- package/examples/typescript/game.js +1 -1
- package/examples/typescript/game.ts +1 -1
- package/examples/typescript/index.html +3 -2
- package/examples/uiSystem/game.js +134 -0
- package/examples/uiSystem/index.html +25 -0
- package/examples/uiSystem/tiles.png +0 -0
- package/jsconfig.json +11 -0
- package/package.json +1 -1
- package/plugins/box2d.js +2 -1
- package/plugins/postProcess.js +14 -8
- package/plugins/uiSystem.js +243 -0
- package/src/engine.js +42 -22
- package/src/engineAudio.js +10 -13
- package/src/engineDebug.js +1 -1
- package/src/engineDraw.js +102 -27
- package/src/engineExport.js +32 -2
- package/src/engineInput.js +1 -3
- package/src/engineObject.js +1 -0
- package/src/engineSettings.js +1 -1
- package/src/engineTileLayer.js +2 -1
- package/src/engineUtilities.js +2 -1
- package/src/engineWebGL.js +26 -4
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS User Interface Plugin
|
|
3
|
+
* - Nested Menus
|
|
4
|
+
* - Text
|
|
5
|
+
* - Buttons
|
|
6
|
+
* - Checkboxes
|
|
7
|
+
* - Images
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
|
|
14
|
+
// ui defaults
|
|
15
|
+
let uiDefaultColor = WHITE;
|
|
16
|
+
let uiDefaultLineColor = BLACK;
|
|
17
|
+
let uiDefaultTextColor = BLACK;
|
|
18
|
+
let uiDefaultButtonColor = hsl(0,0,.5);
|
|
19
|
+
let uiDefaultHoverColor = hsl(0,0,.7);
|
|
20
|
+
let uiDefaultLineWidth = 4;
|
|
21
|
+
let uiDefaultFont = 'arial';
|
|
22
|
+
|
|
23
|
+
// ui system
|
|
24
|
+
let uiObjects = [];
|
|
25
|
+
let uiContext;
|
|
26
|
+
|
|
27
|
+
function initUISystem(context=overlayContext)
|
|
28
|
+
{
|
|
29
|
+
uiContext = context;
|
|
30
|
+
engineAddPlugin(uiUpdate, uiRender);
|
|
31
|
+
|
|
32
|
+
// setup recursive update and render
|
|
33
|
+
function uiUpdate()
|
|
34
|
+
{
|
|
35
|
+
function updateObject(o)
|
|
36
|
+
{
|
|
37
|
+
if (!o.visible)
|
|
38
|
+
return;
|
|
39
|
+
if (o.parent)
|
|
40
|
+
o.pos = o.localPos.add(o.parent.pos);
|
|
41
|
+
o.update();
|
|
42
|
+
for(const c of o.children)
|
|
43
|
+
updateObject(c)
|
|
44
|
+
}
|
|
45
|
+
uiObjects.forEach(o=> o.parent || updateObject(o));
|
|
46
|
+
}
|
|
47
|
+
function uiRender()
|
|
48
|
+
{
|
|
49
|
+
function renderObject(o)
|
|
50
|
+
{
|
|
51
|
+
if (!o.visible)
|
|
52
|
+
return;
|
|
53
|
+
o.render();
|
|
54
|
+
for(const c of o.children)
|
|
55
|
+
renderObject(c)
|
|
56
|
+
}
|
|
57
|
+
uiObjects.forEach(o=> o.parent || renderObject(o));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function drawUIRect(pos, size, color=uiDefaultColor, lineWidth=uiDefaultLineWidth, lineColor=uiDefaultLineColor)
|
|
62
|
+
{
|
|
63
|
+
uiContext.fillStyle = color.toString();
|
|
64
|
+
uiContext.beginPath();
|
|
65
|
+
uiContext.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
|
|
66
|
+
uiContext.fill();
|
|
67
|
+
if (lineWidth)
|
|
68
|
+
{
|
|
69
|
+
uiContext.strokeStyle = lineColor.toString();
|
|
70
|
+
uiContext.lineWidth = lineWidth;
|
|
71
|
+
uiContext.stroke();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function drawUILine(posA, posB, thickness=uiDefaultLineWidth, color=uiDefaultLineColor)
|
|
76
|
+
{
|
|
77
|
+
uiContext.strokeStyle = color.toString();
|
|
78
|
+
uiContext.lineWidth = thickness;
|
|
79
|
+
uiContext.beginPath();
|
|
80
|
+
uiContext.lineTo(posA.x, posA.y);
|
|
81
|
+
uiContext.lineTo(posB.x, posB.y);
|
|
82
|
+
uiContext.stroke();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function drawUITile(pos, size, tileInfo, color=uiDefaultColor, angle=0, mirror=false, additiveColor=BLACK)
|
|
86
|
+
{
|
|
87
|
+
drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, false, true, uiContext);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function drawUIText(text, pos, size, color=uiDefaultColor, lineWidth=uiDefaultLineWidth, lineColor=uiDefaultLineColor, align='center', font=uiDefaultFont)
|
|
91
|
+
{
|
|
92
|
+
drawTextScreen(text, pos, size.y, color, lineWidth, lineColor, align, font, uiContext, size.x);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
96
|
+
|
|
97
|
+
class UIObject
|
|
98
|
+
{
|
|
99
|
+
constructor(localPos, size=vec2())
|
|
100
|
+
{
|
|
101
|
+
this.localPos = localPos.copy();
|
|
102
|
+
this.pos = localPos.copy();
|
|
103
|
+
this.size = size.copy();
|
|
104
|
+
this.color = uiDefaultColor;
|
|
105
|
+
this.lineColor = uiDefaultLineColor;
|
|
106
|
+
this.textColor = uiDefaultTextColor;
|
|
107
|
+
this.hoverColor = uiDefaultHoverColor;
|
|
108
|
+
this.lineWidth = uiDefaultLineWidth;
|
|
109
|
+
this.font = uiDefaultFont;
|
|
110
|
+
this.visible = true;
|
|
111
|
+
this.children = [];
|
|
112
|
+
this.parent = null;
|
|
113
|
+
uiObjects.push(this);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
addChild(child)
|
|
117
|
+
{
|
|
118
|
+
ASSERT(!child.parent && !this.children.includes(child));
|
|
119
|
+
this.children.push(child);
|
|
120
|
+
child.parent = this;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
removeChild(child)
|
|
124
|
+
{
|
|
125
|
+
ASSERT(child.parent == this && this.children.includes(child));
|
|
126
|
+
this.children.splice(this.children.indexOf(child), 1);
|
|
127
|
+
child.parent = 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
update()
|
|
131
|
+
{
|
|
132
|
+
// track mouse input
|
|
133
|
+
const mouseWasOver = this.mouseIsOver;
|
|
134
|
+
this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
|
|
135
|
+
if (this.mouseIsOver && !mouseWasOver)
|
|
136
|
+
this.onEnter();
|
|
137
|
+
if (!this.mouseIsOver && mouseWasOver)
|
|
138
|
+
this.onLeave();
|
|
139
|
+
if (mouseWasPressed(0) && this.mouseIsOver)
|
|
140
|
+
{
|
|
141
|
+
this.mouseIsHeld = true;
|
|
142
|
+
this.onPress();
|
|
143
|
+
}
|
|
144
|
+
else if (this.mouseIsHeld && !mouseIsDown(0))
|
|
145
|
+
{
|
|
146
|
+
this.mouseIsHeld = false;
|
|
147
|
+
if (this.mouseIsOver)
|
|
148
|
+
this.onClick();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
render()
|
|
152
|
+
{
|
|
153
|
+
if (this.size.x && this.size.y)
|
|
154
|
+
drawUIRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// callback functions
|
|
158
|
+
onEnter() {}
|
|
159
|
+
onLeave() {}
|
|
160
|
+
onPress() {}
|
|
161
|
+
onClick() {}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
165
|
+
|
|
166
|
+
class UIText extends UIObject
|
|
167
|
+
{
|
|
168
|
+
constructor(pos, size, text, align='center', font=fontDefault)
|
|
169
|
+
{
|
|
170
|
+
super(pos, size);
|
|
171
|
+
|
|
172
|
+
this.text = text;
|
|
173
|
+
this.align = align;
|
|
174
|
+
this.font = font;
|
|
175
|
+
this.lineWidth = 0;
|
|
176
|
+
}
|
|
177
|
+
render()
|
|
178
|
+
{
|
|
179
|
+
drawUIText(this.text, this.pos, this.size, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
184
|
+
|
|
185
|
+
class UITile extends UIObject
|
|
186
|
+
{
|
|
187
|
+
constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false, additiveColor=BLACK)
|
|
188
|
+
{
|
|
189
|
+
super(pos, size);
|
|
190
|
+
|
|
191
|
+
this.tileInfo = tileInfo;
|
|
192
|
+
this.color = color;
|
|
193
|
+
this.angle = angle;
|
|
194
|
+
this.mirror = mirror;
|
|
195
|
+
this.additiveColor = additiveColor;
|
|
196
|
+
}
|
|
197
|
+
render()
|
|
198
|
+
{
|
|
199
|
+
drawUITile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
204
|
+
|
|
205
|
+
class UIButton extends UIObject
|
|
206
|
+
{
|
|
207
|
+
constructor(pos, size, text)
|
|
208
|
+
{
|
|
209
|
+
super(pos, size);
|
|
210
|
+
this.text = text;
|
|
211
|
+
this.color = uiDefaultButtonColor;
|
|
212
|
+
}
|
|
213
|
+
render()
|
|
214
|
+
{
|
|
215
|
+
const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
|
|
216
|
+
const color = this.mouseIsOver? this.hoverColor : this.color;
|
|
217
|
+
drawUIRect(this.pos, this.size, color, this.lineWidth, lineColor);
|
|
218
|
+
drawTextScreen(this.text, this.pos, this.size.y*.8,
|
|
219
|
+
this.textColor, undefined, undefined, this.align, this.font, uiContext);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
224
|
+
|
|
225
|
+
class UICheckbox extends UIObject
|
|
226
|
+
{
|
|
227
|
+
constructor(pos, size, checked=false)
|
|
228
|
+
{
|
|
229
|
+
super(pos, size);
|
|
230
|
+
this.checked = checked;
|
|
231
|
+
}
|
|
232
|
+
onClick() { this.checked = !this.checked; }
|
|
233
|
+
render()
|
|
234
|
+
{
|
|
235
|
+
drawUIRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
|
|
236
|
+
if (this.checked)
|
|
237
|
+
{
|
|
238
|
+
// draw an X if checked
|
|
239
|
+
drawUILine(this.pos.add(this.size.multiply(vec2(-.5,-.5))), this.pos.add(this.size.multiply(vec2(.5,.5))), this.lineWidth, this.lineColor);
|
|
240
|
+
drawUILine(this.pos.add(this.size.multiply(vec2(-.5,.5))), this.pos.add(this.size.multiply(vec2(.5,-.5))), this.lineWidth, this.lineColor);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
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.
|
|
33
|
+
const engineVersion = '1.10.4';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {Number}
|
|
@@ -74,7 +74,6 @@ let timeReal = 0;
|
|
|
74
74
|
* @default false
|
|
75
75
|
* @memberof Engine */
|
|
76
76
|
let paused = false;
|
|
77
|
-
|
|
78
77
|
/** Set if game is paused
|
|
79
78
|
* @param {Boolean} isPaused
|
|
80
79
|
* @memberof Engine */
|
|
@@ -107,9 +106,10 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
107
106
|
* @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
|
|
108
107
|
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
109
108
|
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
110
|
-
* @param {Array} [imageSources=[
|
|
109
|
+
* @param {Array} [imageSources=[]] - Image to load
|
|
110
|
+
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
|
|
111
111
|
* @memberof Engine */
|
|
112
|
-
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[
|
|
112
|
+
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
113
113
|
{
|
|
114
114
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
115
115
|
|
|
@@ -151,6 +151,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
151
151
|
for (const o of engineObjects)
|
|
152
152
|
o.parent || o.updateTransforms();
|
|
153
153
|
inputUpdate();
|
|
154
|
+
pluginUpdateList.forEach(f=>f());
|
|
154
155
|
debugUpdate();
|
|
155
156
|
gameUpdatePost();
|
|
156
157
|
inputUpdatePost();
|
|
@@ -266,16 +267,21 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
// setup html
|
|
269
|
-
const
|
|
270
|
+
const styleRoot =
|
|
270
271
|
'margin:0;overflow:hidden;' + // fill the window
|
|
272
|
+
'width:100vw;height:100vh;' + // fill the window
|
|
273
|
+
'display:flex;' + // use flexbox
|
|
274
|
+
'align-items:center;' + // horizontal center
|
|
275
|
+
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
276
|
+
'justify-content:center;' + // vertical center
|
|
271
277
|
'background:#000;' + // set background color
|
|
272
278
|
'user-select:none;' + // prevent hold to select
|
|
273
279
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
274
280
|
(!touchInputEnable ? '' : // no touch css setttings
|
|
275
281
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
276
282
|
'-webkit-touch-callout:none');// compatibility for ios
|
|
277
|
-
|
|
278
|
-
|
|
283
|
+
rootElement.style.cssText = styleRoot;
|
|
284
|
+
rootElement.appendChild(mainCanvas = document.createElement('canvas'));
|
|
279
285
|
mainContext = mainCanvas.getContext('2d');
|
|
280
286
|
|
|
281
287
|
// init stuff and start engine
|
|
@@ -285,13 +291,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
285
291
|
glInit();
|
|
286
292
|
|
|
287
293
|
// create overlay canvas for hud to appear above gl canvas
|
|
288
|
-
|
|
294
|
+
rootElement.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
289
295
|
overlayContext = overlayCanvas.getContext('2d');
|
|
290
296
|
|
|
291
297
|
// set canvas style
|
|
292
|
-
const styleCanvas = 'position:absolute;
|
|
293
|
-
|
|
294
|
-
(glCanvas
|
|
298
|
+
const styleCanvas = 'position:absolute'; // allow canvases to overlap
|
|
299
|
+
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
300
|
+
if (glCanvas)
|
|
301
|
+
glCanvas.style.cssText = styleCanvas;
|
|
295
302
|
updateCanvas();
|
|
296
303
|
|
|
297
304
|
// create promises for loading images
|
|
@@ -308,19 +315,32 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
308
315
|
})
|
|
309
316
|
);
|
|
310
317
|
|
|
311
|
-
|
|
312
|
-
showSplashScreen && promises.push(new Promise(resolve =>
|
|
318
|
+
if (!imageSources.length)
|
|
313
319
|
{
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
updateSplash();
|
|
317
|
-
function updateSplash()
|
|
320
|
+
// no images to load
|
|
321
|
+
promises.push(new Promise(resolve =>
|
|
318
322
|
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
323
|
+
textureInfos[0] = new TextureInfo(new Image);
|
|
324
|
+
resolve();
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (showSplashScreen)
|
|
329
|
+
{
|
|
330
|
+
// draw splash screen
|
|
331
|
+
promises.push(new Promise(resolve =>
|
|
332
|
+
{
|
|
333
|
+
let t = 0;
|
|
334
|
+
console.log(`${engineName} Engine v${engineVersion}`);
|
|
335
|
+
updateSplash();
|
|
336
|
+
function updateSplash()
|
|
337
|
+
{
|
|
338
|
+
clearInput();
|
|
339
|
+
drawEngineSplashScreen(t+=.01);
|
|
340
|
+
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
341
|
+
}
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
324
344
|
|
|
325
345
|
// load all of the images
|
|
326
346
|
Promise.all(promises).then(startEngine);
|
package/src/engineAudio.js
CHANGED
|
@@ -320,17 +320,6 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
320
320
|
{
|
|
321
321
|
if (!soundEnable || headlessMode) return;
|
|
322
322
|
|
|
323
|
-
// prevent sounds from building up if they can't be played
|
|
324
|
-
if (audioContext.state != 'running')
|
|
325
|
-
{
|
|
326
|
-
// fix stalled audio
|
|
327
|
-
audioContext.resume().then(()=>
|
|
328
|
-
playSamples(sampleChannels, volume, rate, pan, loop, sampleRate, gainNode));
|
|
329
|
-
|
|
330
|
-
// prevent suspended sounds from building up
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
323
|
// create buffer and source
|
|
335
324
|
const channelCount = sampleChannels.length;
|
|
336
325
|
const sampleLength = sampleChannels[0].length;
|
|
@@ -352,8 +341,16 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
352
341
|
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
353
342
|
source.connect(pannerNode).connect(gainNode);
|
|
354
343
|
|
|
355
|
-
// play
|
|
356
|
-
|
|
344
|
+
// play the sound
|
|
345
|
+
if (audioContext.state != 'running')
|
|
346
|
+
{
|
|
347
|
+
// fix stalled audio and play
|
|
348
|
+
audioContext.resume().then(()=>source.start());
|
|
349
|
+
}
|
|
350
|
+
else
|
|
351
|
+
source.start();
|
|
352
|
+
|
|
353
|
+
// return sound
|
|
357
354
|
return source;
|
|
358
355
|
}
|
|
359
356
|
|
package/src/engineDebug.js
CHANGED
|
@@ -124,7 +124,6 @@ function debugPoint(pos, color, time, angle)
|
|
|
124
124
|
* @memberof Debug */
|
|
125
125
|
function debugLine(posA, posB, color, thickness=.1, time)
|
|
126
126
|
{
|
|
127
|
-
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
128
127
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
129
128
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
130
129
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
@@ -200,6 +199,7 @@ function debugShowErrors()
|
|
|
200
199
|
|
|
201
200
|
const showError = (message)=>
|
|
202
201
|
{
|
|
202
|
+
// replace entire page with error message
|
|
203
203
|
document.body.style.backgroundColor = '#111';
|
|
204
204
|
document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
|
|
205
205
|
}
|
package/src/engineDraw.js
CHANGED
|
@@ -58,12 +58,13 @@ let drawCount;
|
|
|
58
58
|
///////////////////////////////////////////////////////////////////////////////
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Create a tile info object
|
|
61
|
+
* Create a tile info object using a grid based system
|
|
62
62
|
* - This can take vecs or floats for easier use and conversion
|
|
63
63
|
* - If an index is passed in, the tile size and index will determine the position
|
|
64
|
-
* @param {(Number|Vector2)} [pos=
|
|
64
|
+
* @param {(Number|Vector2)} [pos=0] - Index of tile in sheet
|
|
65
65
|
* @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
|
|
66
66
|
* @param {Number} [textureIndex] - Texture index to use
|
|
67
|
+
* @param {Number} [padding] - How many pixels padding around tiles
|
|
67
68
|
* @return {TileInfo}
|
|
68
69
|
* @example
|
|
69
70
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -72,7 +73,7 @@ let drawCount;
|
|
|
72
73
|
* tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
|
|
73
74
|
* @memberof Draw
|
|
74
75
|
*/
|
|
75
|
-
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
76
|
+
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
76
77
|
{
|
|
77
78
|
if (headlessMode)
|
|
78
79
|
return new TileInfo;
|
|
@@ -84,17 +85,17 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
|
84
85
|
size = vec2(size);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
//
|
|
88
|
+
// use pos as a tile index
|
|
89
|
+
const textureInfo = textureInfos[textureIndex];
|
|
90
|
+
ASSERT(textureInfo, 'Texture not loaded');
|
|
91
|
+
const sizePadded = size.add(vec2(padding*2));
|
|
92
|
+
const cols = textureInfo.size.x / sizePadded.x |0;
|
|
88
93
|
if (typeof pos === 'number')
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
ASSERT(textureInfo, 'Texture not loaded');
|
|
92
|
-
const cols = textureInfo.size.x / size.x |0;
|
|
93
|
-
pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
|
|
94
|
-
}
|
|
94
|
+
pos = vec2(pos%cols, pos/cols|0);
|
|
95
|
+
pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
|
|
95
96
|
|
|
96
97
|
// return a tile info object
|
|
97
|
-
return new TileInfo(pos, size, textureIndex);
|
|
98
|
+
return new TileInfo(pos, size, textureIndex, padding);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
/**
|
|
@@ -106,8 +107,9 @@ class TileInfo
|
|
|
106
107
|
* @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
|
|
107
108
|
* @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
|
|
108
109
|
* @param {Number} [textureIndex] - Texture index to use
|
|
110
|
+
* @param {Number} [padding] - How many pixels padding around tiles
|
|
109
111
|
*/
|
|
110
|
-
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
112
|
+
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
111
113
|
{
|
|
112
114
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
113
115
|
this.pos = pos.copy();
|
|
@@ -115,6 +117,8 @@ class TileInfo
|
|
|
115
117
|
this.size = size.copy();
|
|
116
118
|
/** @property {Number} - Texture index to use */
|
|
117
119
|
this.textureIndex = textureIndex;
|
|
120
|
+
/** @property {Number} - How many pixels padding around tiles */
|
|
121
|
+
this.padding = padding;
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
/** Returns a copy of this tile offset by a vector
|
|
@@ -131,7 +135,7 @@ class TileInfo
|
|
|
131
135
|
frame(frame)
|
|
132
136
|
{
|
|
133
137
|
ASSERT(typeof frame == 'number');
|
|
134
|
-
return this.offset(vec2(frame*this.size.x, 0));
|
|
138
|
+
return this.offset(vec2(frame*(this.size.x+this.padding*2), 0));
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
/** Returns the texture info for this tile
|
|
@@ -156,8 +160,6 @@ class TextureInfo
|
|
|
156
160
|
this.size = vec2(image.width, image.height);
|
|
157
161
|
/** @property {WebGLTexture} - webgl texture */
|
|
158
162
|
this.glTexture = glEnable && glCreateTexture(image);
|
|
159
|
-
/** @property {Vector2} - size to adjust tile to fix bleeding */
|
|
160
|
-
this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
|
|
@@ -226,11 +228,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
226
228
|
if (textureInfo)
|
|
227
229
|
{
|
|
228
230
|
// calculate uvs and render
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const
|
|
231
|
+
const sizeInverse = vec2(1).divide(textureInfo.size);
|
|
232
|
+
const x = tileInfo.pos.x * sizeInverse.x;
|
|
233
|
+
const y = tileInfo.pos.y * sizeInverse.y;
|
|
234
|
+
const w = tileInfo.size.x * sizeInverse.x;
|
|
235
|
+
const h = tileInfo.size.y * sizeInverse.y;
|
|
236
|
+
const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
|
|
234
237
|
glSetTexture(textureInfo.glTexture);
|
|
235
238
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
236
239
|
x + tileImageFixBleed.x, y + tileImageFixBleed.y,
|
|
@@ -247,6 +250,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
247
250
|
{
|
|
248
251
|
// normal canvas 2D rendering method (slower)
|
|
249
252
|
showWatermark && ++drawCount;
|
|
253
|
+
size = vec2(size.x, -size.y); // fix upside down sprites
|
|
250
254
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
251
255
|
{
|
|
252
256
|
if (textureInfo)
|
|
@@ -300,6 +304,74 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
|
|
|
300
304
|
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
|
|
301
305
|
}
|
|
302
306
|
|
|
307
|
+
/** Draw colored polygon using passed in points
|
|
308
|
+
* @param {Array} points - Array of Vector2 points
|
|
309
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
310
|
+
* @param {Number} [lineWidth=0]
|
|
311
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
312
|
+
* @param {Boolean} [screenSpace=false]
|
|
313
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
314
|
+
* @memberof Draw */
|
|
315
|
+
function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
316
|
+
{
|
|
317
|
+
context.fillStyle = color.toString();
|
|
318
|
+
context.beginPath();
|
|
319
|
+
for (const point of screenSpace ? points : points.map(worldToScreen))
|
|
320
|
+
context.lineTo(point.x, point.y);
|
|
321
|
+
context.closePath();
|
|
322
|
+
context.fill();
|
|
323
|
+
if (lineWidth)
|
|
324
|
+
{
|
|
325
|
+
context.strokeStyle = lineColor.toString();
|
|
326
|
+
context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
|
|
327
|
+
context.stroke();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Draw colored ellipse using passed in point
|
|
332
|
+
* @param {Vector2} pos
|
|
333
|
+
* @param {Number} [width=1]
|
|
334
|
+
* @param {Number} [height=1]
|
|
335
|
+
* @param {Number} [angle=0]
|
|
336
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
337
|
+
* @param {Number} [lineWidth=0]
|
|
338
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
339
|
+
* @param {Boolean} [screenSpace=false]
|
|
340
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
341
|
+
* @memberof Draw */
|
|
342
|
+
function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
343
|
+
{
|
|
344
|
+
if (!screenSpace)
|
|
345
|
+
{
|
|
346
|
+
pos = worldToScreen(pos);
|
|
347
|
+
width *= cameraScale;
|
|
348
|
+
height *= cameraScale;
|
|
349
|
+
lineWidth *= cameraScale;
|
|
350
|
+
}
|
|
351
|
+
context.fillStyle = color.toString();
|
|
352
|
+
context.beginPath();
|
|
353
|
+
context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
|
|
354
|
+
context.fill();
|
|
355
|
+
if (lineWidth)
|
|
356
|
+
{
|
|
357
|
+
context.strokeStyle = lineColor.toString();
|
|
358
|
+
context.lineWidth = lineWidth;
|
|
359
|
+
context.stroke();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Draw colored circle using passed in point
|
|
364
|
+
* @param {Vector2} pos
|
|
365
|
+
* @param {Number} [radius=1]
|
|
366
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
367
|
+
* @param {Number} [lineWidth=0]
|
|
368
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
369
|
+
* @param {Boolean} [screenSpace=false]
|
|
370
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
371
|
+
* @memberof Draw */
|
|
372
|
+
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
373
|
+
{ drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
|
|
374
|
+
|
|
303
375
|
/** Draw directly to a 2d canvas context in world space
|
|
304
376
|
* @param {Vector2} pos
|
|
305
377
|
* @param {Vector2} size
|
|
@@ -354,10 +426,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
354
426
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
355
427
|
* @param {String} [font=fontDefault]
|
|
356
428
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
429
|
+
* @param {Number} [maxWidth]
|
|
357
430
|
* @memberof Draw */
|
|
358
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
431
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context, maxWidth)
|
|
359
432
|
{
|
|
360
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
|
|
433
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context, maxWidth);
|
|
361
434
|
}
|
|
362
435
|
|
|
363
436
|
/** Draw text on overlay canvas in screen space
|
|
@@ -371,8 +444,9 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
371
444
|
* @param {CanvasTextAlign} [textAlign]
|
|
372
445
|
* @param {String} [font=fontDefault]
|
|
373
446
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
447
|
+
* @param {Number} [maxWidth]
|
|
374
448
|
* @memberof Draw */
|
|
375
|
-
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
449
|
+
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext, maxWidth=undefined)
|
|
376
450
|
{
|
|
377
451
|
context.fillStyle = color.toString();
|
|
378
452
|
context.lineWidth = lineWidth;
|
|
@@ -385,8 +459,8 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
385
459
|
pos = pos.copy();
|
|
386
460
|
(text+'').split('\n').forEach(line=>
|
|
387
461
|
{
|
|
388
|
-
lineWidth && context.strokeText(line, pos.x, pos.y);
|
|
389
|
-
context.fillText(line, pos.x, pos.y);
|
|
462
|
+
lineWidth && context.strokeText(line, pos.x, pos.y, maxWidth);
|
|
463
|
+
context.fillText(line, pos.x, pos.y, maxWidth);
|
|
390
464
|
pos.y += size;
|
|
391
465
|
});
|
|
392
466
|
}
|
|
@@ -489,11 +563,12 @@ function isFullscreen() { return !!document.fullscreenElement; }
|
|
|
489
563
|
* @memberof Draw */
|
|
490
564
|
function toggleFullscreen()
|
|
491
565
|
{
|
|
566
|
+
const rootElement = mainCanvas.parentElement;
|
|
492
567
|
if (isFullscreen())
|
|
493
568
|
{
|
|
494
569
|
if (document.exitFullscreen)
|
|
495
570
|
document.exitFullscreen();
|
|
496
571
|
}
|
|
497
|
-
else if (
|
|
498
|
-
|
|
572
|
+
else if (rootElement.requestFullscreen)
|
|
573
|
+
rootElement.requestFullscreen();
|
|
499
574
|
}
|