littlejsengine 1.9.11 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/littlejs.d.ts +80 -9
- package/dist/littlejs.esm.js +193 -71
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +175 -69
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +174 -68
- package/examples/box2d/game.js +1 -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/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/game.js +1 -1
- 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 +1 -1
- package/examples/platformer/gameLevel.js +2 -2
- package/examples/platformer/gameObjects.js +0 -1
- package/examples/platformer/index.html +10 -9
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +4 -3
- package/examples/starter/game.js +4 -4
- package/examples/starter/index.html +15 -14
- package/examples/stress/index.html +3 -2
- package/examples/typescript/game.js +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 +6 -7
- package/plugins/uiSystem.js +243 -0
- package/src/engine.js +48 -20
- package/src/engineAudio.js +10 -13
- package/src/engineDebug.js +1 -1
- package/src/engineDraw.js +101 -27
- package/src/engineExport.js +18 -2
- package/src/engineInput.js +2 -2
- package/src/engineSettings.js +1 -1
- package/src/engineUtilities.js +1 -1
- package/src/engineWebGL.js +11 -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.2';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {Number}
|
|
@@ -75,6 +75,12 @@ let timeReal = 0;
|
|
|
75
75
|
* @memberof Engine */
|
|
76
76
|
let paused = false;
|
|
77
77
|
|
|
78
|
+
/** The root element that engine is attached to
|
|
79
|
+
* @type {HTMLElement}
|
|
80
|
+
* @default document.body
|
|
81
|
+
* @memberof Engine */
|
|
82
|
+
let engineRoot;
|
|
83
|
+
|
|
78
84
|
/** Set if game is paused
|
|
79
85
|
* @param {Boolean} isPaused
|
|
80
86
|
* @memberof Engine */
|
|
@@ -108,8 +114,9 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
108
114
|
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
109
115
|
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
110
116
|
* @param {Array} [imageSources=['tiles.png']] - Image to load
|
|
117
|
+
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
|
|
111
118
|
* @memberof Engine */
|
|
112
|
-
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[
|
|
119
|
+
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
113
120
|
{
|
|
114
121
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
115
122
|
|
|
@@ -151,6 +158,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
151
158
|
for (const o of engineObjects)
|
|
152
159
|
o.parent || o.updateTransforms();
|
|
153
160
|
inputUpdate();
|
|
161
|
+
pluginUpdateList.forEach(f=>f());
|
|
154
162
|
debugUpdate();
|
|
155
163
|
gameUpdatePost();
|
|
156
164
|
inputUpdatePost();
|
|
@@ -266,16 +274,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
266
274
|
}
|
|
267
275
|
|
|
268
276
|
// setup html
|
|
269
|
-
const
|
|
277
|
+
const styleRoot =
|
|
270
278
|
'margin:0;overflow:hidden;' + // fill the window
|
|
279
|
+
'width:100vw;height:100vh;' + // fill the window
|
|
280
|
+
'display:flex;' + // use flexbox
|
|
281
|
+
'align-items:center;' + // horizontal center
|
|
282
|
+
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
283
|
+
'justify-content:center;' + // vertical center
|
|
271
284
|
'background:#000;' + // set background color
|
|
272
285
|
'user-select:none;' + // prevent hold to select
|
|
273
286
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
274
287
|
(!touchInputEnable ? '' : // no touch css setttings
|
|
275
288
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
276
289
|
'-webkit-touch-callout:none');// compatibility for ios
|
|
277
|
-
|
|
278
|
-
|
|
290
|
+
engineRoot = rootElement;
|
|
291
|
+
engineRoot.style.cssText = styleRoot;
|
|
292
|
+
engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
|
|
279
293
|
mainContext = mainCanvas.getContext('2d');
|
|
280
294
|
|
|
281
295
|
// init stuff and start engine
|
|
@@ -285,13 +299,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
285
299
|
glInit();
|
|
286
300
|
|
|
287
301
|
// create overlay canvas for hud to appear above gl canvas
|
|
288
|
-
|
|
302
|
+
engineRoot.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
289
303
|
overlayContext = overlayCanvas.getContext('2d');
|
|
290
304
|
|
|
291
305
|
// set canvas style
|
|
292
|
-
const styleCanvas = 'position:absolute;
|
|
293
|
-
|
|
294
|
-
(glCanvas
|
|
306
|
+
const styleCanvas = 'position:absolute'; // allow canvases to overlap
|
|
307
|
+
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
308
|
+
if (glCanvas)
|
|
309
|
+
glCanvas.style.cssText = styleCanvas;
|
|
295
310
|
updateCanvas();
|
|
296
311
|
|
|
297
312
|
// create promises for loading images
|
|
@@ -308,19 +323,32 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
308
323
|
})
|
|
309
324
|
);
|
|
310
325
|
|
|
311
|
-
|
|
312
|
-
showSplashScreen && promises.push(new Promise(resolve =>
|
|
326
|
+
if (!imageSources.length)
|
|
313
327
|
{
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
updateSplash();
|
|
317
|
-
function updateSplash()
|
|
328
|
+
// no images to load
|
|
329
|
+
promises.push(new Promise(resolve =>
|
|
318
330
|
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
331
|
+
textureInfos[0] = new TextureInfo(new Image);
|
|
332
|
+
resolve();
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (showSplashScreen)
|
|
337
|
+
{
|
|
338
|
+
// draw splash screen
|
|
339
|
+
promises.push(new Promise(resolve =>
|
|
340
|
+
{
|
|
341
|
+
let t = 0;
|
|
342
|
+
console.log(`${engineName} Engine v${engineVersion}`);
|
|
343
|
+
updateSplash();
|
|
344
|
+
function updateSplash()
|
|
345
|
+
{
|
|
346
|
+
clearInput();
|
|
347
|
+
drawEngineSplashScreen(t+=.01);
|
|
348
|
+
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
349
|
+
}
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
324
352
|
|
|
325
353
|
// load all of the images
|
|
326
354
|
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)
|
|
@@ -284,6 +288,74 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
284
288
|
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
285
289
|
}
|
|
286
290
|
|
|
291
|
+
/** Draw colored polygon using passed in points
|
|
292
|
+
* @param {Array} points - Array of Vector2 points
|
|
293
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
294
|
+
* @param {Number} [lineWidth=0]
|
|
295
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
296
|
+
* @param {Boolean} [screenSpace=false]
|
|
297
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
298
|
+
* @memberof Draw */
|
|
299
|
+
function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
300
|
+
{
|
|
301
|
+
context.fillStyle = color.toString();
|
|
302
|
+
context.beginPath();
|
|
303
|
+
for (const point of screenSpace ? points : points.map(worldToScreen))
|
|
304
|
+
context.lineTo(point.x, point.y);
|
|
305
|
+
context.closePath();
|
|
306
|
+
context.fill();
|
|
307
|
+
if (lineWidth)
|
|
308
|
+
{
|
|
309
|
+
context.strokeStyle = lineColor.toString();
|
|
310
|
+
context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
|
|
311
|
+
context.stroke();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Draw colored ellipse using passed in point
|
|
316
|
+
* @param {Vector2} pos
|
|
317
|
+
* @param {Number} [width=1]
|
|
318
|
+
* @param {Number} [height=1]
|
|
319
|
+
* @param {Number} [angle=0]
|
|
320
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
321
|
+
* @param {Number} [lineWidth=0]
|
|
322
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
323
|
+
* @param {Boolean} [screenSpace=false]
|
|
324
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
325
|
+
* @memberof Draw */
|
|
326
|
+
function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
327
|
+
{
|
|
328
|
+
if (!screenSpace)
|
|
329
|
+
{
|
|
330
|
+
pos = worldToScreen(pos);
|
|
331
|
+
width *= cameraScale;
|
|
332
|
+
height *= cameraScale;
|
|
333
|
+
lineWidth *= cameraScale;
|
|
334
|
+
}
|
|
335
|
+
context.fillStyle = color.toString();
|
|
336
|
+
context.beginPath();
|
|
337
|
+
context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
|
|
338
|
+
context.fill();
|
|
339
|
+
if (lineWidth)
|
|
340
|
+
{
|
|
341
|
+
context.strokeStyle = lineColor.toString();
|
|
342
|
+
context.lineWidth = lineWidth;
|
|
343
|
+
context.stroke();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Draw colored circle using passed in point
|
|
348
|
+
* @param {Vector2} pos
|
|
349
|
+
* @param {Number} [radius=1]
|
|
350
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
351
|
+
* @param {Number} [lineWidth=0]
|
|
352
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
353
|
+
* @param {Boolean} [screenSpace=false]
|
|
354
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
355
|
+
* @memberof Draw */
|
|
356
|
+
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
357
|
+
{ drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
|
|
358
|
+
|
|
287
359
|
/** Draw colored line between two points
|
|
288
360
|
* @param {Vector2} posA
|
|
289
361
|
* @param {Vector2} posB
|
|
@@ -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
|
}
|
|
@@ -494,6 +568,6 @@ function toggleFullscreen()
|
|
|
494
568
|
if (document.exitFullscreen)
|
|
495
569
|
document.exitFullscreen();
|
|
496
570
|
}
|
|
497
|
-
else if (
|
|
498
|
-
|
|
571
|
+
else if (engineRoot.requestFullscreen)
|
|
572
|
+
engineRoot.requestFullscreen();
|
|
499
573
|
}
|