@vpmedia/phaser 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +20 -3
  2. package/dist/phaser.cjs +1 -1
  3. package/dist/phaser.cjs.LICENSE.txt +1 -1
  4. package/dist/phaser.cjs.map +1 -1
  5. package/dist/phaser.js +1 -1
  6. package/dist/phaser.js.LICENSE.txt +1 -1
  7. package/dist/phaser.js.map +1 -1
  8. package/package.json +23 -17
  9. package/src/index.js +142 -0
  10. package/src/phaser/core/animation.js +355 -0
  11. package/src/phaser/core/animation_manager.js +238 -0
  12. package/src/phaser/core/animation_parser.js +133 -0
  13. package/src/phaser/core/array_set.js +107 -0
  14. package/src/phaser/core/cache.js +558 -0
  15. package/src/phaser/core/const.js +106 -0
  16. package/src/phaser/core/device.js +67 -0
  17. package/src/phaser/core/device_util.js +388 -0
  18. package/src/phaser/core/dom.js +207 -0
  19. package/src/phaser/core/event_manager.js +243 -0
  20. package/src/phaser/core/factory.js +74 -0
  21. package/src/phaser/core/frame.js +75 -0
  22. package/src/phaser/core/frame_data.js +84 -0
  23. package/src/phaser/core/frame_util.js +33 -0
  24. package/src/phaser/core/game.js +412 -0
  25. package/src/phaser/core/input.js +401 -0
  26. package/src/phaser/core/input_button.js +102 -0
  27. package/src/phaser/core/input_handler.js +687 -0
  28. package/src/phaser/core/input_mouse.js +289 -0
  29. package/src/phaser/core/input_mspointer.js +197 -0
  30. package/src/phaser/core/input_pointer.js +427 -0
  31. package/src/phaser/core/input_touch.js +157 -0
  32. package/src/phaser/core/loader.js +1057 -0
  33. package/src/phaser/core/loader_parser.js +109 -0
  34. package/src/phaser/core/raf.js +46 -0
  35. package/src/phaser/core/raf_fb.js +75 -0
  36. package/src/phaser/core/raf_to.js +34 -0
  37. package/src/phaser/core/scale_manager.js +806 -0
  38. package/src/phaser/core/scene.js +65 -0
  39. package/src/phaser/core/scene_manager.js +309 -0
  40. package/src/phaser/core/signal.js +175 -0
  41. package/src/phaser/core/signal_binding.js +69 -0
  42. package/src/phaser/core/sound.js +538 -0
  43. package/src/phaser/core/sound_manager.js +364 -0
  44. package/src/phaser/core/stage.js +108 -0
  45. package/src/phaser/core/time.js +203 -0
  46. package/src/phaser/core/timer.js +276 -0
  47. package/src/phaser/core/timer_event.js +21 -0
  48. package/src/phaser/core/tween.js +329 -0
  49. package/src/phaser/core/tween_data.js +258 -0
  50. package/src/phaser/core/tween_easing.js +341 -0
  51. package/src/phaser/core/tween_manager.js +185 -0
  52. package/src/phaser/core/world.js +18 -0
  53. package/src/phaser/display/bitmap_text.js +322 -0
  54. package/src/phaser/display/button.js +194 -0
  55. package/src/phaser/display/canvas/buffer.js +36 -0
  56. package/src/phaser/display/canvas/graphics.js +227 -0
  57. package/src/phaser/display/canvas/masker.js +39 -0
  58. package/src/phaser/display/canvas/pool.js +126 -0
  59. package/src/phaser/display/canvas/renderer.js +123 -0
  60. package/src/phaser/display/canvas/tinter.js +144 -0
  61. package/src/phaser/display/canvas/util.js +159 -0
  62. package/src/phaser/display/display_object.js +597 -0
  63. package/src/phaser/display/graphics.js +723 -0
  64. package/src/phaser/display/graphics_data.js +27 -0
  65. package/src/phaser/display/graphics_data_util.js +15 -0
  66. package/src/phaser/display/group.js +227 -0
  67. package/src/phaser/display/image.js +288 -0
  68. package/src/phaser/display/sprite_batch.js +15 -0
  69. package/src/phaser/display/sprite_util.js +250 -0
  70. package/src/phaser/display/text.js +1089 -0
  71. package/src/phaser/display/webgl/abstract_filter.js +25 -0
  72. package/src/phaser/display/webgl/base_texture.js +68 -0
  73. package/src/phaser/display/webgl/blend_manager.js +35 -0
  74. package/src/phaser/display/webgl/earcut.js +662 -0
  75. package/src/phaser/display/webgl/earcut_node.js +28 -0
  76. package/src/phaser/display/webgl/fast_sprite_batch.js +242 -0
  77. package/src/phaser/display/webgl/filter_manager.js +46 -0
  78. package/src/phaser/display/webgl/filter_texture.js +61 -0
  79. package/src/phaser/display/webgl/graphics.js +624 -0
  80. package/src/phaser/display/webgl/graphics_data.js +42 -0
  81. package/src/phaser/display/webgl/mask_manager.js +36 -0
  82. package/src/phaser/display/webgl/render_texture.js +81 -0
  83. package/src/phaser/display/webgl/renderer.js +234 -0
  84. package/src/phaser/display/webgl/shader/complex.js +74 -0
  85. package/src/phaser/display/webgl/shader/fast.js +97 -0
  86. package/src/phaser/display/webgl/shader/normal.js +225 -0
  87. package/src/phaser/display/webgl/shader/primitive.js +72 -0
  88. package/src/phaser/display/webgl/shader/strip.js +77 -0
  89. package/src/phaser/display/webgl/shader_manager.js +89 -0
  90. package/src/phaser/display/webgl/sprite_batch.js +320 -0
  91. package/src/phaser/display/webgl/stencil_manager.js +170 -0
  92. package/src/phaser/display/webgl/texture.js +117 -0
  93. package/src/phaser/display/webgl/texture_util.js +34 -0
  94. package/src/phaser/display/webgl/util.js +78 -0
  95. package/src/phaser/geom/circle.js +186 -0
  96. package/src/phaser/geom/ellipse.js +65 -0
  97. package/src/phaser/geom/line.js +190 -0
  98. package/src/phaser/geom/matrix.js +147 -0
  99. package/src/phaser/geom/point.js +164 -0
  100. package/src/phaser/geom/polygon.js +140 -0
  101. package/src/phaser/geom/rectangle.js +306 -0
  102. package/src/phaser/geom/rounded_rectangle.js +36 -0
  103. package/src/phaser/geom/util/circle.js +122 -0
  104. package/src/phaser/geom/util/ellipse.js +34 -0
  105. package/src/phaser/geom/util/line.js +135 -0
  106. package/src/phaser/geom/util/matrix.js +53 -0
  107. package/src/phaser/geom/util/point.js +296 -0
  108. package/src/phaser/geom/util/polygon.js +28 -0
  109. package/src/phaser/geom/util/rectangle.js +229 -0
  110. package/src/phaser/geom/util/rounded_rectangle.js +32 -0
  111. package/src/phaser/util/math.js +297 -0
  112. package/src/phaser/util/string.js +32 -0
@@ -0,0 +1,1057 @@
1
+ /**
2
+ * @author Andras Csizmadia <andras@vpmedia.hu>
3
+ * @author Richard Davey <rich@photonstorm.com>
4
+ * @copyright Copyright (c) 2018-present Richard Davey, Photon Storm Ltd., Andras Csizmadia <andras@vpmedia.hu> (www.vpmedia.hu)
5
+ */
6
+ import Signal from './signal';
7
+ import Rectangle from '../geom/rectangle';
8
+ import { canPlayAudio } from './device_util';
9
+ import { TEXTURE_ATLAS_JSON_ARRAY, TEXTURE_ATLAS_JSON_HASH, TEXTURE_ATLAS_XML_STARLING, TEXTURE_ATLAS_JSON_PYXEL } from './const';
10
+
11
+ export const TILED_JSON = 6;
12
+ export const TILEMAP_CSV = 7;
13
+
14
+ export default class {
15
+
16
+ constructor(game) {
17
+ this.game = game;
18
+ this.cache = game.cache;
19
+ this.resetLocked = false;
20
+ this.isLoading = false;
21
+ this.hasLoaded = false;
22
+ this.preloadSprite = null;
23
+ this.crossOrigin = false;
24
+ this.baseURL = '';
25
+ this.path = '';
26
+ this.headers = {
27
+ requestedWith: false,
28
+ json: 'application/json',
29
+ xml: 'application/xml',
30
+ };
31
+ this.onLoadStart = new Signal();
32
+ this.onLoadComplete = new Signal();
33
+ this.onPackComplete = new Signal();
34
+ this.onFileStart = new Signal();
35
+ this.onFileComplete = new Signal();
36
+ this.onFileError = new Signal();
37
+ this.useXDomainRequest = false;
38
+ this._warnedAboutXDomainRequest = false;
39
+ this.enableParallel = true;
40
+ this.maxParallelDownloads = 6;
41
+ this._withSyncPointDepth = 0;
42
+ this._fileList = [];
43
+ this._flightQueue = [];
44
+ this._processingHead = 0;
45
+ this._fileLoadStarted = false;
46
+ this._totalPackCount = 0;
47
+ this._totalFileCount = 0;
48
+ this._loadedPackCount = 0;
49
+ this._loadedFileCount = 0;
50
+ }
51
+
52
+ setPreloadSprite(sprite, direction = 0) {
53
+ this.preloadSprite = {
54
+ sprite,
55
+ direction,
56
+ width: sprite.width,
57
+ height: sprite.height,
58
+ rect: null,
59
+ };
60
+ if (direction === 0) {
61
+ // Horizontal rect
62
+ this.preloadSprite.rect = new Rectangle(0, 0, 1, sprite.height);
63
+ } else {
64
+ // Vertical rect
65
+ this.preloadSprite.rect = new Rectangle(0, 0, sprite.width, 1);
66
+ }
67
+ sprite.crop(this.preloadSprite.rect);
68
+ sprite.visible = true;
69
+ }
70
+
71
+ resize() {
72
+ if (this.preloadSprite && this.preloadSprite.height !== this.preloadSprite.sprite.height) {
73
+ this.preloadSprite.rect.height = this.preloadSprite.sprite.height;
74
+ }
75
+ }
76
+
77
+ checkKeyExists(type, key) {
78
+ return this.getAssetIndex(type, key) > -1;
79
+ }
80
+
81
+ getAssetIndex(type, key) {
82
+ let bestFound = -1;
83
+ for (let i = 0; i < this._fileList.length; i += 1) {
84
+ const file = this._fileList[i];
85
+ if (file.type === type && file.key === key) {
86
+ bestFound = i;
87
+ // An already loaded/loading file may be superceded.
88
+ if (!file.loaded && !file.loading) {
89
+ break;
90
+ }
91
+ }
92
+ }
93
+
94
+ return bestFound;
95
+ }
96
+
97
+ getAsset(type, key) {
98
+ const fileIndex = this.getAssetIndex(type, key);
99
+ if (fileIndex > -1) {
100
+ return { index: fileIndex, file: this._fileList[fileIndex] };
101
+ }
102
+ return null;
103
+ }
104
+
105
+ reset(hard, clearEvents = false) {
106
+ if (this.resetLocked) {
107
+ return;
108
+ }
109
+ if (hard) {
110
+ this.preloadSprite = null;
111
+ }
112
+ this.isLoading = false;
113
+ this._processingHead = 0;
114
+ this._fileList.length = 0;
115
+ this._flightQueue.length = 0;
116
+ this._fileLoadStarted = false;
117
+ this._totalFileCount = 0;
118
+ this._totalPackCount = 0;
119
+ this._loadedPackCount = 0;
120
+ this._loadedFileCount = 0;
121
+ if (clearEvents) {
122
+ this.onLoadStart.removeAll();
123
+ this.onLoadComplete.removeAll();
124
+ this.onPackComplete.removeAll();
125
+ this.onFileStart.removeAll();
126
+ this.onFileComplete.removeAll();
127
+ this.onFileError.removeAll();
128
+ }
129
+ }
130
+
131
+ addToFileList(type, key = '', url = null, properties = null, overwrite = false, extension = null) {
132
+ if (key === undefined || key === '') {
133
+ console.warn('Loader: Invalid or no key given of type ' + type);
134
+ return this;
135
+ }
136
+ if (url === undefined || url === null) {
137
+ if (extension) {
138
+ url = key + extension;
139
+ } else {
140
+ console.warn('Loader: No URL given for file type: ' + type + ' key: ' + key);
141
+ return this;
142
+ }
143
+ }
144
+ const file = {
145
+ type,
146
+ key,
147
+ path: this.path,
148
+ url,
149
+ syncPoint: this._withSyncPointDepth > 0,
150
+ data: null,
151
+ loading: false,
152
+ loaded: false,
153
+ error: false,
154
+ };
155
+ if (properties) {
156
+ const keys = Object.keys(properties);
157
+ for (let i = 0; i < keys.length; i += 1) {
158
+ const prop = keys[i];
159
+ file[prop] = properties[prop];
160
+ }
161
+ }
162
+ const fileIndex = this.getAssetIndex(type, key);
163
+ if (overwrite && fileIndex > -1) {
164
+ const currentFile = this._fileList[fileIndex];
165
+ if (!currentFile.loading && !currentFile.loaded) {
166
+ this._fileList[fileIndex] = file;
167
+ } else {
168
+ this._fileList.push(file);
169
+ this._totalFileCount += 1;
170
+ }
171
+ } else if (fileIndex === -1) {
172
+ this._fileList.push(file);
173
+ this._totalFileCount += 1;
174
+ }
175
+ return this;
176
+ }
177
+
178
+ replaceInFileList(type, key, url, properties) {
179
+ return this.addToFileList(type, key, url, properties, true);
180
+ }
181
+
182
+ pack(key, url, data, callbackContext) {
183
+ const pack = {
184
+ type: 'packfile',
185
+ key: key,
186
+ url: url,
187
+ path: this.path,
188
+ syncPoint: true,
189
+ data: null,
190
+ loading: false,
191
+ loaded: false,
192
+ error: false,
193
+ callbackContext: callbackContext
194
+ };
195
+ if (data) {
196
+ if (typeof data === 'string') {
197
+ data = JSON.parse(data);
198
+ }
199
+ pack.data = data || {};
200
+ pack.loaded = true;
201
+ }
202
+ for (let i = 0; i < this._fileList.length + 1; i += 1) {
203
+ const file = this._fileList[i];
204
+ if (!file || (!file.loaded && !file.loading && file.type !== 'packfile')) {
205
+ this._fileList.splice(i, 0, pack);
206
+ this._totalPackCount += 1;
207
+ break;
208
+ }
209
+ }
210
+ return this;
211
+ }
212
+
213
+ image(key, url, overwrite) {
214
+ return this.addToFileList('image', key, url, undefined, overwrite, '.png');
215
+ }
216
+
217
+ images(keys, urls) {
218
+ if (Array.isArray(urls)) {
219
+ for (let i = 0; i < keys.length; i += 1) {
220
+ this.image(keys[i], urls[i]);
221
+ }
222
+ } else {
223
+ for (let i = 0; i < keys.length; i += 1) {
224
+ this.image(keys[i]);
225
+ }
226
+ }
227
+ return this;
228
+ }
229
+
230
+ text(key, url, overwrite) {
231
+ return this.addToFileList('text', key, url, undefined, overwrite, '.txt');
232
+ }
233
+
234
+ json(key, url, overwrite) {
235
+ return this.addToFileList('json', key, url, undefined, overwrite, '.json');
236
+ }
237
+
238
+ shader(key, url, overwrite) {
239
+ return this.addToFileList('shader', key, url, undefined, overwrite, '.frag');
240
+ }
241
+
242
+ xml(key, url, overwrite) {
243
+ return this.addToFileList('xml', key, url, undefined, overwrite, '.xml');
244
+ }
245
+
246
+ script(key, url, callback = false, callbackContext = this) {
247
+ return this.addToFileList('script', key, url, { syncPoint: true, callback, callbackContext }, false, '.js');
248
+ }
249
+
250
+ binary(key, url, callback = false, callbackContext = this) {
251
+ return this.addToFileList('binary', key, url, { callback, callbackContext }, false, '.bin');
252
+ }
253
+
254
+ spritesheet(key, url, frameWidth, frameHeight, frameMax = -1, margin = 0, spacing = 0) {
255
+ return this.addToFileList('spritesheet', key, url, { frameWidth, frameHeight, frameMax, margin, spacing }, false, '.png');
256
+ }
257
+
258
+ audio(key, urls, autoDecode = true) {
259
+ if (this.game.sound.noAudio) {
260
+ return this;
261
+ }
262
+ if (typeof urls === 'string') {
263
+ urls = [urls];
264
+ }
265
+ return this.addToFileList('audio', key, urls, { buffer: null, autoDecode });
266
+ }
267
+
268
+ audioSprite(key, urls, jsonURL, jsonData, autoDecode = true) {
269
+ if (this.game.sound.noAudio) {
270
+ return this;
271
+ }
272
+ this.audio(key, urls, autoDecode);
273
+ if (jsonURL) {
274
+ this.json(key + '-audioatlas', jsonURL);
275
+ } else if (jsonData) {
276
+ if (typeof jsonData === 'string') {
277
+ jsonData = JSON.parse(jsonData);
278
+ }
279
+ this.cache.addJSON(key + '-audioatlas', '', jsonData);
280
+ }
281
+ return this;
282
+ }
283
+
284
+ video() {
285
+ // TODO
286
+ console.warn('loader.video() is not implemented');
287
+ return this;
288
+ }
289
+
290
+ tilemap() {
291
+ // TODO
292
+ console.warn('loader.tilemap() is not implemented');
293
+ return this;
294
+ }
295
+
296
+ bitmapFont(key, textureURL = null, atlasURL = null, atlasData = null, xSpacing = 0, ySpacing = 0) {
297
+ if (textureURL === undefined || textureURL === null) {
298
+ textureURL = key + '.png';
299
+ }
300
+ if (atlasURL === null && atlasData === null) {
301
+ atlasURL = key + '.xml';
302
+ }
303
+ // A URL to a json/xml atlas has been given
304
+ if (atlasURL) {
305
+ this.addToFileList('bitmapfont', key, textureURL, { atlasURL, xSpacing, ySpacing });
306
+ } else if (typeof atlasData === 'string') {
307
+ // A stringified xml/json atlas has been given
308
+ let json = null;
309
+ let xml = null;
310
+ try {
311
+ json = JSON.parse(atlasData);
312
+ } catch (e) {
313
+ xml = this.parseXml(atlasData);
314
+ }
315
+ if (!xml && !json) {
316
+ throw new Error('Loader. Invalid Bitmap Font atlas given');
317
+ }
318
+ this.addToFileList('bitmapfont', key, textureURL, { atlasURL: null, atlasData: json || xml, atlasType: (json ? 'json' : 'xml'), xSpacing, ySpacing });
319
+ }
320
+ return this;
321
+ }
322
+
323
+ atlasJSONArray(key, textureURL, atlasURL, atlasData) {
324
+ return this.atlas(key, textureURL, atlasURL, atlasData, TEXTURE_ATLAS_JSON_ARRAY);
325
+ }
326
+
327
+ atlasJSONHash(key, textureURL, atlasURL, atlasData) {
328
+ return this.atlas(key, textureURL, atlasURL, atlasData, TEXTURE_ATLAS_JSON_HASH);
329
+ }
330
+
331
+ atlasXML() {
332
+ // TODO
333
+ console.warn('loader.atlasXML() is not implemented');
334
+ }
335
+
336
+ atlas(key, textureURL, atlasURL = null, atlasData = null, format = TEXTURE_ATLAS_JSON_ARRAY) {
337
+ if (textureURL === undefined || textureURL === null) {
338
+ textureURL = key + '.png';
339
+ }
340
+ if (!atlasURL && !atlasData) {
341
+ if (format === TEXTURE_ATLAS_XML_STARLING) {
342
+ atlasURL = key + '.xml';
343
+ } else {
344
+ atlasURL = key + '.json';
345
+ }
346
+ }
347
+ // A URL to a json/xml file has been given
348
+ if (atlasURL) {
349
+ this.addToFileList('textureatlas', key, textureURL, { atlasURL, format });
350
+ } else {
351
+ switch (format) {
352
+ // A json string or object has been given
353
+ case TEXTURE_ATLAS_JSON_ARRAY:
354
+ if (typeof atlasData === 'string') {
355
+ atlasData = JSON.parse(atlasData);
356
+ }
357
+ break;
358
+ // An xml string or object has been given
359
+ case TEXTURE_ATLAS_XML_STARLING:
360
+ if (typeof atlasData === 'string') {
361
+ const xml = this.parseXml(atlasData);
362
+ if (!xml) {
363
+ throw new Error('Invalid Texture Atlas XML given');
364
+ }
365
+ atlasData = xml;
366
+ }
367
+ break;
368
+ default:
369
+ // pass
370
+ break;
371
+ }
372
+ this.addToFileList('textureatlas', key, textureURL, { atlasURL: null, atlasData, format });
373
+ }
374
+ return this;
375
+ }
376
+
377
+ withSyncPoint(callback, callbackContext) {
378
+ this._withSyncPointDepth += 1;
379
+ try {
380
+ callback.call(callbackContext || this, this);
381
+ } finally {
382
+ this._withSyncPointDepth -= 1;
383
+ }
384
+ return this;
385
+ }
386
+
387
+ addSyncPoint(type, key) {
388
+ const asset = this.getAsset(type, key);
389
+ if (asset) {
390
+ asset.file.syncPoint = true;
391
+ }
392
+ return this;
393
+ }
394
+
395
+ removeFile(type, key) {
396
+ const asset = this.getAsset(type, key);
397
+ if (asset) {
398
+ if (!asset.loaded && !asset.loading) {
399
+ this._fileList.splice(asset.index, 1);
400
+ }
401
+ }
402
+ }
403
+
404
+ removeAll() {
405
+ this._fileList.length = 0;
406
+ this._flightQueue.length = 0;
407
+ }
408
+
409
+ start() {
410
+ if (this.isLoading) {
411
+ return;
412
+ }
413
+ this.hasLoaded = false;
414
+ this.isLoading = true;
415
+ this.updateProgress();
416
+ this.processLoadQueue();
417
+ }
418
+
419
+ processLoadQueue() {
420
+ if (!this.isLoading) {
421
+ console.warn('Loader - active loading canceled / reset');
422
+ this.finishedLoading(true);
423
+ return;
424
+ }
425
+ // Empty the flight queue as applicable
426
+ for (let i = 0; i < this._flightQueue.length; i += 1) {
427
+ const file = this._flightQueue[i];
428
+ if (file.loaded || file.error) {
429
+ this._flightQueue.splice(i, 1);
430
+ i -= 1;
431
+ file.loading = false;
432
+ file.requestUrl = null;
433
+ file.requestObject = null;
434
+ if (file.error) {
435
+ this.onFileError.dispatch(file.key, file);
436
+ }
437
+ if (file.type !== 'packfile') {
438
+ this._loadedFileCount += 1;
439
+ this.onFileComplete.dispatch(this.progress, file.key, !file.error, this._loadedFileCount, this._totalFileCount);
440
+ } else if (file.type === 'packfile' && file.error) {
441
+ // Non-error pack files are handled when processing the file queue
442
+ this._loadedPackCount += 1;
443
+ this.onPackComplete.dispatch(file.key, !file.error, this._loadedPackCount, this._totalPackCount);
444
+ }
445
+ }
446
+ }
447
+ // When true further non-pack file downloads are suppressed
448
+ let syncblock = false;
449
+ const inflightLimit = this.enableParallel ? Math.max(1, this.maxParallelDownloads) : 1;
450
+ for (let i = this._processingHead; i < this._fileList.length; i += 1) {
451
+ const file = this._fileList[i];
452
+ // Pack is fetched (ie. has data) and is currently at the start of the process queue.
453
+ if (file.type === 'packfile' && !file.error && file.loaded && i === this._processingHead) {
454
+ // Processing the pack / adds more files
455
+ this.processPack(file);
456
+ this._loadedPackCount += 1;
457
+ this.onPackComplete.dispatch(file.key, !file.error, this._loadedPackCount, this._totalPackCount);
458
+ }
459
+ if (file.loaded || file.error) {
460
+ // Item at the start of file list finished, can skip it in future
461
+ if (i === this._processingHead) {
462
+ this._processingHead = i + 1;
463
+ }
464
+ } else if (!file.loading && this._flightQueue.length < inflightLimit) {
465
+ // -> not loaded/failed, not loading
466
+ if (file.type === 'packfile' && !file.data) {
467
+ // Fetches the pack data: the pack is processed above as it reaches queue-start.
468
+ // (Packs do not trigger onLoadStart or onFileStart.)
469
+ this._flightQueue.push(file);
470
+ file.loading = true;
471
+ this.loadFile(file);
472
+ } else if (!syncblock) {
473
+ if (!this._fileLoadStarted) {
474
+ this._fileLoadStarted = true;
475
+ this.onLoadStart.dispatch();
476
+ }
477
+ this._flightQueue.push(file);
478
+ file.loading = true;
479
+ this.onFileStart.dispatch(this.progress, file.key, file.url);
480
+ this.loadFile(file);
481
+ }
482
+ }
483
+ if (!file.loaded && file.syncPoint) {
484
+ syncblock = true;
485
+ }
486
+ // Stop looking if queue full - or if syncblocked and there are no more packs.
487
+ // (As only packs can be loaded around a syncblock)
488
+ if (this._flightQueue.length >= inflightLimit || (syncblock && this._loadedPackCount === this._totalPackCount)) {
489
+ break;
490
+ }
491
+ }
492
+ this.updateProgress();
493
+ // True when all items in the queue have been advanced over
494
+ // (There should be no inflight items as they are complete - loaded/error.)
495
+ if (this._processingHead >= this._fileList.length) {
496
+ this.finishedLoading();
497
+ } else if (!this._flightQueue.length) {
498
+ // Flight queue is empty but file list is not done being processed.
499
+ // This indicates a critical internal error with no known recovery.
500
+ console.warn('Loader - aborting: processing queue empty, loading may have stalled');
501
+ const _this = this;
502
+ setTimeout(() => {
503
+ _this.finishedLoading(true);
504
+ }, 2000);
505
+ }
506
+ }
507
+
508
+ finishedLoading(abnormal) {
509
+ if (this.hasLoaded) {
510
+ return;
511
+ }
512
+ this.hasLoaded = true;
513
+ this.isLoading = false;
514
+ // If there were no files make sure to trigger the event anyway, for consistency
515
+ if (!abnormal && !this._fileLoadStarted) {
516
+ this._fileLoadStarted = true;
517
+ this.onLoadStart.dispatch();
518
+ }
519
+ // https://github.com/photonstorm/phaser-ce/pull/54/
520
+ this.reset();
521
+ this.onLoadComplete.dispatch();
522
+ this.game.state.loadComplete();
523
+ // https://github.com/photonstorm/phaser-ce/pull/54/
524
+ // this.reset();
525
+ }
526
+
527
+ asyncComplete(file, errorMessage = '') {
528
+ file.loaded = true;
529
+ file.error = !!errorMessage;
530
+ if (errorMessage) {
531
+ file.errorMessage = errorMessage;
532
+ console.warn(file, errorMessage);
533
+ }
534
+ this.processLoadQueue();
535
+ }
536
+
537
+ processPack(pack) {
538
+ const packData = pack.data[pack.key];
539
+ if (!packData) {
540
+ console.warn('Missing loader pack key', pack.key);
541
+ return;
542
+ }
543
+ for (let i = 0; i < packData.length; i += 1) {
544
+ const file = packData[i];
545
+ switch (file.type) {
546
+ case "image":
547
+ this.image(file.key, file.url, file.overwrite);
548
+ break;
549
+ case "text":
550
+ this.text(file.key, file.url, file.overwrite);
551
+ break;
552
+ case "json":
553
+ this.json(file.key, file.url, file.overwrite);
554
+ break;
555
+ case "xml":
556
+ this.xml(file.key, file.url, file.overwrite);
557
+ break;
558
+ case "script":
559
+ this.script(file.key, file.url, file.callback, pack.callbackContext || this);
560
+ break;
561
+ case "binary":
562
+ this.binary(file.key, file.url, file.callback, pack.callbackContext || this);
563
+ break;
564
+ case "spritesheet":
565
+ this.spritesheet(file.key, file.url, file.frameWidth, file.frameHeight, file.frameMax, file.margin, file.spacing);
566
+ break;
567
+ case "video":
568
+ this.video(file.key, file.urls);
569
+ break;
570
+ case "audio":
571
+ this.audio(file.key, file.urls, file.autoDecode);
572
+ break;
573
+ case "audiosprite":
574
+ this.audioSprite(file.key, file.urls, file.jsonURL, file.jsonData, file.autoDecode);
575
+ break;
576
+ case "tilemap":
577
+ // TODO
578
+ // this.tilemap(file.key, file.url, file.data, Phaser.Tilemap[file.format]);
579
+ break;
580
+ case "physics":
581
+ // TODO
582
+ // this.physics(file.key, file.url, file.data, Phaser.Loader[file.format]);
583
+ break;
584
+ case "bitmapFont":
585
+ this.bitmapFont(file.key, file.textureURL, file.atlasURL, file.atlasData, file.xSpacing, file.ySpacing);
586
+ break;
587
+ case "atlasJSONArray":
588
+ this.atlasJSONArray(file.key, file.textureURL, file.atlasURL, file.atlasData);
589
+ break;
590
+ case "atlasJSONHash":
591
+ this.atlasJSONHash(file.key, file.textureURL, file.atlasURL, file.atlasData);
592
+ break;
593
+ case "atlasXML":
594
+ this.atlasXML(file.key, file.textureURL, file.atlasURL, file.atlasData);
595
+ break;
596
+ case "atlas":
597
+ this.atlas(file.key, file.textureURL, file.atlasURL, file.atlasData, file.format === 'TEXTURE_ATLAS_JSON_HASH' ? TEXTURE_ATLAS_JSON_HASH : TEXTURE_ATLAS_JSON_ARRAY);
598
+ break;
599
+ case "shader":
600
+ this.shader(file.key, file.url, file.overwrite);
601
+ break;
602
+ }
603
+ }
604
+ }
605
+
606
+ transformUrl(url, file) {
607
+ if (!url) {
608
+ return false;
609
+ }
610
+ if (url.match(/^(?:blob:|data:|http:\/\/|https:\/\/|\/\/)/)) {
611
+ return url;
612
+ }
613
+ return this.baseURL + file.path + url;
614
+ }
615
+
616
+ loadFile(file) {
617
+ switch (file.type) {
618
+ case 'packfile':
619
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.fileComplete);
620
+ break;
621
+ case 'image':
622
+ case 'spritesheet':
623
+ case 'textureatlas':
624
+ case 'bitmapfont':
625
+ this.loadImageTag(file);
626
+ break;
627
+ case 'audio':
628
+ file.url = this.getAudioURL(file.url);
629
+ if (file.url) {
630
+ // WebAudio or Audio Tag?
631
+ if (this.game.sound.usingWebAudio) {
632
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'arraybuffer', this.fileComplete);
633
+ } else if (this.game.sound.usingAudioTag) {
634
+ this.loadAudioTag(file);
635
+ }
636
+ } else {
637
+ this.fileError(file, null, 'No supported audio URL specified or device does not have audio playback support');
638
+ }
639
+ break;
640
+ case 'video':
641
+ file.url = this.getVideoURL(file.url);
642
+ if (file.url) {
643
+ if (file.asBlob) {
644
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'blob', this.fileComplete);
645
+ } else {
646
+ this.loadVideoTag(file);
647
+ }
648
+ } else {
649
+ this.fileError(file, null, 'No supported video URL specified or device does not have video playback support');
650
+ }
651
+ break;
652
+ case 'json':
653
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.jsonLoadComplete);
654
+ break;
655
+ case 'xml':
656
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.xmlLoadComplete);
657
+ break;
658
+ case 'tilemap':
659
+ if (file.format === TILED_JSON) {
660
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.jsonLoadComplete);
661
+ } else if (file.format === TILEMAP_CSV) {
662
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.csvLoadComplete);
663
+ } else {
664
+ this.asyncComplete(file, 'invalid Tilemap format: ' + file.format);
665
+ }
666
+ break;
667
+ case 'text':
668
+ case 'script':
669
+ case 'shader':
670
+ case 'physics':
671
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'text', this.fileComplete);
672
+ break;
673
+ case 'binary':
674
+ this.xhrLoad(file, this.transformUrl(file.url, file), 'arraybuffer', this.fileComplete);
675
+ break;
676
+ default:
677
+ // pass
678
+ break;
679
+ }
680
+ }
681
+
682
+ loadImageTag(file) {
683
+ const _this = this;
684
+ file.data = new Image();
685
+ file.data.name = file.key;
686
+ if (this.crossOrigin) {
687
+ file.data.crossOrigin = this.crossOrigin;
688
+ }
689
+ file.data.onload = () => {
690
+ if (file.data.onload) {
691
+ file.data.onload = null;
692
+ file.data.onerror = null;
693
+ _this.fileComplete(file);
694
+ }
695
+ };
696
+ file.data.onerror = () => {
697
+ if (file.data.onload) {
698
+ file.data.onload = null;
699
+ file.data.onerror = null;
700
+ _this.fileError(file);
701
+ }
702
+ };
703
+ file.data.src = this.transformUrl(file.url, file);
704
+ // Image is immediately-available/cached
705
+ // Special Firefox magic, exclude from cached reload
706
+ // More info here: https://github.com/photonstorm/phaser/issues/2534
707
+ if (!this.game.device.firefox && file.data.complete && file.data.width && file.data.height) {
708
+ file.data.onload = null;
709
+ file.data.onerror = null;
710
+ this.fileComplete(file);
711
+ }
712
+ }
713
+
714
+ loadVideoTag() {
715
+ // TODO
716
+ console.warn('loader.loadVideoTag() is not implemented');
717
+ }
718
+
719
+ loadAudioTag(file) {
720
+ const scope = this;
721
+ if (this.game.sound.touchLocked) {
722
+ // If audio is locked we can't do this yet, so need to queue this load request. Bum.
723
+ file.data = new Audio();
724
+ file.data.name = file.key;
725
+ file.data.preload = 'auto';
726
+ file.data.src = this.transformUrl(file.url, file);
727
+ this.fileComplete(file);
728
+ } else {
729
+ file.data = new Audio();
730
+ file.data.name = file.key;
731
+ const playThroughEvent = () => {
732
+ file.data.removeEventListener('canplaythrough', playThroughEvent, false);
733
+ file.data.onerror = null;
734
+ scope.fileComplete(file);
735
+ };
736
+ file.data.onerror = () => {
737
+ file.data.removeEventListener('canplaythrough', playThroughEvent, false);
738
+ file.data.onerror = null;
739
+ scope.fileError(file);
740
+ };
741
+ file.data.preload = 'auto';
742
+ file.data.src = this.transformUrl(file.url, file);
743
+ file.data.addEventListener('canplaythrough', playThroughEvent, false);
744
+ file.data.load();
745
+ }
746
+ }
747
+
748
+ xhrLoad(file, url, type, onload, onerror) {
749
+ const xhr = new XMLHttpRequest();
750
+ xhr.open('GET', url, true);
751
+ xhr.responseType = type;
752
+ if (this.headers.requestedWith !== false) {
753
+ xhr.setRequestHeader('X-Requested-With', this.headers.requestedWith);
754
+ }
755
+ if (this.headers[file.type]) {
756
+ xhr.setRequestHeader('Accept', this.headers[file.type]);
757
+ }
758
+ onerror = onerror || this.fileError;
759
+ const scope = this;
760
+ xhr.onload = () => {
761
+ try {
762
+ if (xhr.readyState === 4 && xhr.status >= 400 && xhr.status <= 599) { // Handle HTTP status codes of 4xx and 5xx as errors, even if xhr.onerror was not called.
763
+ return onerror.call(scope, file, xhr);
764
+ }
765
+ return onload.call(scope, file, xhr);
766
+ } catch (e) {
767
+ // If this was the last file in the queue and an error is thrown in the create method
768
+ // then it's caught here, so be sure we don't carry on processing it
769
+ if (!scope.hasLoaded) {
770
+ scope.asyncComplete(file, e.message || 'Exception');
771
+ } else {
772
+ console.error(e);
773
+ }
774
+ }
775
+ return null;
776
+ };
777
+ xhr.onerror = () => {
778
+ try {
779
+ return onerror.call(scope, file, xhr);
780
+ } catch (e) {
781
+ if (!scope.hasLoaded) {
782
+ scope.asyncComplete(file, e.message || 'Exception');
783
+ } else {
784
+ console.error(e);
785
+ }
786
+ }
787
+ return null;
788
+ };
789
+ file.requestObject = xhr;
790
+ file.requestUrl = url;
791
+ xhr.send();
792
+ }
793
+
794
+ xhrLoadWithXDR() {
795
+ // TODO
796
+ console.warn('loader.xhrLoadWithXDR() is not implemented');
797
+ }
798
+
799
+ getAudioURL(urls) {
800
+ if (this.game.sound.noAudio) {
801
+ return null;
802
+ }
803
+ for (let i = 0; i < urls.length; i += 1) {
804
+ let url = urls[i];
805
+ let audioType = null;
806
+ if (url.uri) {
807
+ // {uri: .., type: ..} pair
808
+ audioType = url.type;
809
+ url = url.uri;
810
+ if (canPlayAudio(this.game.device, audioType)) {
811
+ return url;
812
+ }
813
+ } else {
814
+ // Assume direct-data URI can be played if not in a paired form; select immediately
815
+ if (url.indexOf('blob:') === 0 || url.indexOf('data:') === 0) {
816
+ return url;
817
+ }
818
+ if (url.indexOf('?') >= 0) {
819
+ // Remove query from URL
820
+ url = url.substr(0, url.indexOf('?'));
821
+ }
822
+ const extension = url.substr((Math.max(0, url.lastIndexOf('.')) || Infinity) + 1);
823
+ audioType = extension.toLowerCase();
824
+ if (canPlayAudio(this.game.device, audioType)) {
825
+ return urls[i];
826
+ }
827
+ }
828
+ }
829
+ return null;
830
+ }
831
+
832
+ fileError(file, xhr, reason) {
833
+ const url = file.requestUrl || this.transformUrl(file.url, file);
834
+ let message = 'error loading asset from URL ' + url;
835
+ if (!reason && xhr) {
836
+ reason = xhr.status;
837
+ }
838
+ if (reason) {
839
+ message = message + ' (' + reason + ')';
840
+ }
841
+ this.asyncComplete(file, message);
842
+ }
843
+
844
+ fileComplete(file, xhr) {
845
+ let loadNext = true;
846
+ switch (file.type) {
847
+ case 'packfile':
848
+ // Pack data must never be false-ish after it is fetched without error
849
+ file.data = JSON.parse(xhr.responseText) || {};
850
+ break;
851
+ case 'image':
852
+ this.cache.addImage(file.key, file.url, file.data);
853
+ break;
854
+ case 'spritesheet':
855
+ this.cache.addSpriteSheet(file.key, file.url, file.data, file.frameWidth, file.frameHeight, file.frameMax, file.margin, file.spacing);
856
+ break;
857
+ case 'textureatlas':
858
+ if (file.atlasURL == null) {
859
+ this.cache.addTextureAtlas(file.key, file.url, file.data, file.atlasData, file.format);
860
+ } else {
861
+ // Load the JSON or XML before carrying on with the next file
862
+ loadNext = false;
863
+ if (file.format === TEXTURE_ATLAS_JSON_ARRAY || file.format === TEXTURE_ATLAS_JSON_HASH || file.format === TEXTURE_ATLAS_JSON_PYXEL) {
864
+ this.xhrLoad(file, this.transformUrl(file.atlasURL, file), 'text', this.jsonLoadComplete);
865
+ } else if (file.format === TEXTURE_ATLAS_XML_STARLING) {
866
+ this.xhrLoad(file, this.transformUrl(file.atlasURL, file), 'text', this.xmlLoadComplete);
867
+ } else {
868
+ throw new Error('Invalid Texture Atlas format: ' + file.format);
869
+ }
870
+ }
871
+ break;
872
+ case 'bitmapfont':
873
+ if (!file.atlasURL) {
874
+ this.cache.addBitmapFont(file.key, file.url, file.data, file.atlasData, file.atlasType, file.xSpacing, file.ySpacing);
875
+ } else {
876
+ // Load the XML before carrying on with the next file
877
+ loadNext = false;
878
+ this.xhrLoad(file, this.transformUrl(file.atlasURL, file), 'text', (bitmapFontFile, bitmapFontXhr) => {
879
+ let json;
880
+ try {
881
+ // Try to parse as JSON, if it fails, then it's hopefully XML
882
+ json = JSON.parse(bitmapFontXhr.responseText);
883
+ } catch (e) {
884
+ // pass
885
+ }
886
+ if (json) {
887
+ bitmapFontFile.atlasType = 'json';
888
+ this.jsonLoadComplete(bitmapFontFile, bitmapFontXhr);
889
+ } else {
890
+ bitmapFontFile.atlasType = 'xml';
891
+ this.xmlLoadComplete(bitmapFontFile, bitmapFontXhr);
892
+ }
893
+ });
894
+ }
895
+ break;
896
+ case 'video':
897
+ if (file.asBlob) {
898
+ try {
899
+ file.data = xhr.response;
900
+ } catch (e) {
901
+ throw new Error('Unable to parse video file as Blob: ' + file.key);
902
+ }
903
+ }
904
+ this.cache.addVideo(file.key, file.url, file.data, file.asBlob);
905
+ break;
906
+ case 'audio':
907
+ if (this.game.sound.usingWebAudio) {
908
+ file.data = xhr.response;
909
+ this.cache.addSound(file.key, file.url, file.data, true, false);
910
+ if (file.autoDecode) {
911
+ this.game.sound.decode(file.key);
912
+ }
913
+ } else {
914
+ this.cache.addSound(file.key, file.url, file.data, false, true);
915
+ }
916
+ break;
917
+ case 'text':
918
+ file.data = xhr.responseText;
919
+ this.cache.addText(file.key, file.url, file.data);
920
+ break;
921
+ case 'shader':
922
+ file.data = xhr.responseText;
923
+ this.cache.addShader(file.key, file.url, file.data);
924
+ break;
925
+ case 'physics':
926
+ this.cache.addPhysicsData(file.key, file.url, JSON.parse(xhr.responseText), file.format);
927
+ break;
928
+ case 'script':
929
+ file.data = document.createElement('script');
930
+ file.data.language = 'javascript';
931
+ file.data.type = 'text/javascript';
932
+ file.data.defer = false;
933
+ file.data.text = xhr.responseText;
934
+ document.head.appendChild(file.data);
935
+ if (file.callback) {
936
+ file.data = file.callback.call(file.callbackContext, file.key, xhr.responseText);
937
+ }
938
+ break;
939
+ case 'binary':
940
+ if (file.callback) {
941
+ file.data = file.callback.call(file.callbackContext, file.key, xhr.response);
942
+ } else {
943
+ file.data = xhr.response;
944
+ }
945
+ this.cache.addBinary(file.key, file.data);
946
+ break;
947
+ default:
948
+ // pass
949
+ break;
950
+ }
951
+ if (loadNext) {
952
+ this.asyncComplete(file);
953
+ }
954
+ }
955
+
956
+ jsonLoadComplete(file, xhr) {
957
+ const data = JSON.parse(xhr.responseText);
958
+ if (file.type === 'tilemap') {
959
+ this.cache.addTilemap(file.key, file.url, data, file.format);
960
+ } else if (file.type === 'bitmapfont') {
961
+ this.cache.addBitmapFont(file.key, file.url, file.data, data, file.atlasType, file.xSpacing, file.ySpacing);
962
+ } else if (file.type === 'json') {
963
+ this.cache.addJSON(file.key, file.url, data);
964
+ } else {
965
+ this.cache.addTextureAtlas(file.key, file.url, file.data, data, file.format);
966
+ }
967
+ this.asyncComplete(file);
968
+ }
969
+
970
+ csvLoadComplete() {
971
+ // TODO
972
+ console.warn('loader.csvLoadComplete() is not implemented');
973
+ }
974
+
975
+ xmlLoadComplete(file, xhr) {
976
+ // Always try parsing the content as XML, regardless of actually response type
977
+ const data = xhr.responseText;
978
+ const xml = this.parseXml(data);
979
+ if (!xml) {
980
+ const responseType = xhr.responseType || xhr.contentType; // contentType for MS-XDomainRequest
981
+ console.warn('Loader - ' + file.key + ': invalid XML (' + responseType + ')');
982
+ this.asyncComplete(file, 'invalid XML');
983
+ return;
984
+ }
985
+ if (file.type === 'bitmapfont') {
986
+ this.cache.addBitmapFont(file.key, file.url, file.data, xml, file.atlasType, file.xSpacing, file.ySpacing);
987
+ } else if (file.type === 'textureatlas') {
988
+ this.cache.addTextureAtlas(file.key, file.url, file.data, xml, file.format);
989
+ } else if (file.type === 'xml') {
990
+ this.cache.addXML(file.key, file.url, xml);
991
+ }
992
+ this.asyncComplete(file);
993
+ }
994
+
995
+ parseXml(data) {
996
+ let xml = null;
997
+ try {
998
+ if (window.DOMParser) {
999
+ const domparser = new DOMParser();
1000
+ xml = domparser.parseFromString(data, 'text/xml');
1001
+ } else {
1002
+ xml = new window.ActiveXObject('Microsoft.XMLDOM');
1003
+ // Why is this 'false'?
1004
+ xml.async = 'false';
1005
+ xml.loadXML(data);
1006
+ }
1007
+ } catch (e) {
1008
+ xml = null;
1009
+ }
1010
+ if (!xml || !xml.documentElement || xml.getElementsByTagName('parsererror').length) {
1011
+ return null;
1012
+ }
1013
+ return xml;
1014
+ }
1015
+
1016
+ updateProgress() {
1017
+ if (this.preloadSprite) {
1018
+ if (this.preloadSprite.direction === 0) {
1019
+ this.preloadSprite.rect.width = Math.floor((this.preloadSprite.width / 100) * this.progress);
1020
+ } else {
1021
+ this.preloadSprite.rect.height = Math.floor((this.preloadSprite.height / 100) * this.progress);
1022
+ }
1023
+ if (this.preloadSprite.sprite) {
1024
+ this.preloadSprite.sprite.updateCrop();
1025
+ } else {
1026
+ // We seem to have lost our sprite - maybe it was destroyed?
1027
+ this.preloadSprite = null;
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ totalLoadedFiles() {
1033
+ return this._loadedFileCount;
1034
+ }
1035
+
1036
+ totalQueuedFiles() {
1037
+ return this._totalFileCount - this._loadedFileCount;
1038
+ }
1039
+
1040
+ totalLoadedPacks() {
1041
+ return this._totalPackCount;
1042
+ }
1043
+
1044
+ totalQueuedPacks() {
1045
+ return this._totalPackCount - this._loadedPackCount;
1046
+ }
1047
+
1048
+ get progressFloat() {
1049
+ const progress = (this._loadedFileCount / this._totalFileCount) * 100;
1050
+ return Math.max(0, Math.min(100, progress || 0));
1051
+ }
1052
+
1053
+ get progress() {
1054
+ return Math.round(this.progressFloat);
1055
+ }
1056
+
1057
+ }