@makefully/platypus 4.1.4 → 4.1.6

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 (134) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +4 -2
  3. package/lib/platypus.js +1 -1
  4. package/package.json +7 -2
  5. package/src/AABB.js +685 -0
  6. package/src/ActionState.js +121 -0
  7. package/src/AssetManager.js +206 -0
  8. package/src/CollisionData.js +96 -0
  9. package/src/CollisionDataContainer.js +127 -0
  10. package/src/CollisionShape.js +315 -0
  11. package/src/Component.js +231 -0
  12. package/src/Data.js +64 -0
  13. package/src/DataMap.js +204 -0
  14. package/src/Entity.js +404 -0
  15. package/src/EntityLinker.js +113 -0
  16. package/src/EventHandler.js +115 -0
  17. package/src/EventHandlerList.js +251 -0
  18. package/src/Game.js +840 -0
  19. package/src/Gamepad.js +175 -0
  20. package/src/ID3CaptionPlayer.js +61 -0
  21. package/src/Messenger.js +365 -0
  22. package/src/PIXIAnimation.js +622 -0
  23. package/src/RandomSet.js +140 -0
  24. package/src/SFXPlayer.js +120 -0
  25. package/src/StateMap.js +252 -0
  26. package/src/Storage.js +152 -0
  27. package/src/TickerClient.js +48 -0
  28. package/src/TickerServer.js +23 -0
  29. package/src/TimeEvent.js +50 -0
  30. package/src/TimeEventList.js +100 -0
  31. package/src/VOPlayer.js +684 -0
  32. package/src/Vector.js +702 -0
  33. package/src/components/AIChaser.js +161 -0
  34. package/src/components/AIPacer.js +107 -0
  35. package/src/components/AudioMusic.js +151 -0
  36. package/src/components/AudioSFX.js +673 -0
  37. package/src/components/AudioVO.js +301 -0
  38. package/src/components/CollisionBasic.js +826 -0
  39. package/src/components/CollisionFilter.js +74 -0
  40. package/src/components/CollisionGroup.js +448 -0
  41. package/src/components/CollisionTiles.js +608 -0
  42. package/src/components/ComponentSwitcher.js +173 -0
  43. package/src/components/ControllerInput.js +65 -0
  44. package/src/components/Counter.js +165 -0
  45. package/src/components/EntityCamera.js +215 -0
  46. package/src/components/EntityContainer.js +549 -0
  47. package/src/components/EntityController.js +541 -0
  48. package/src/components/HandlerCamera.js +1052 -0
  49. package/src/components/HandlerCollision.js +1318 -0
  50. package/src/components/HandlerController.js +291 -0
  51. package/src/components/HandlerLogic.js +394 -0
  52. package/src/components/HandlerRender.js +217 -0
  53. package/src/components/Interactive.js +502 -0
  54. package/src/components/LevelBuilder.js +524 -0
  55. package/src/components/LogicAngularMovement.js +154 -0
  56. package/src/components/LogicAttachment.js +265 -0
  57. package/src/components/LogicButton.js +466 -0
  58. package/src/components/LogicCarrier.js +39 -0
  59. package/src/components/LogicDestroyMe.js +58 -0
  60. package/src/components/LogicDirectionalMovement.js +325 -0
  61. package/src/components/LogicDragDrop.js +371 -0
  62. package/src/components/LogicImpactLaunch.js +144 -0
  63. package/src/components/LogicPacingPlatform.js +64 -0
  64. package/src/components/LogicPortable.js +112 -0
  65. package/src/components/LogicPortal.js +115 -0
  66. package/src/components/LogicPushable.js +98 -0
  67. package/src/components/LogicRebounder.js +136 -0
  68. package/src/components/LogicRegionSpawner.js +139 -0
  69. package/src/components/LogicRotationalMovement.js +205 -0
  70. package/src/components/LogicSpawner.js +159 -0
  71. package/src/components/LogicStateMachine.js +382 -0
  72. package/src/components/LogicSwitch.js +77 -0
  73. package/src/components/LogicTeleportee.js +68 -0
  74. package/src/components/LogicTeleporter.js +93 -0
  75. package/src/components/LogicTimer.js +178 -0
  76. package/src/components/LogicWindUpRacer.js +179 -0
  77. package/src/components/Motion.js +371 -0
  78. package/src/components/Mover.js +566 -0
  79. package/src/components/Node.js +289 -0
  80. package/src/components/NodeMap.js +295 -0
  81. package/src/components/NodeResident.js +636 -0
  82. package/src/components/Orientation.js +697 -0
  83. package/src/components/RandomEvents.js +73 -0
  84. package/src/components/RelativePosition.js +204 -0
  85. package/src/components/RelayFamily.js +106 -0
  86. package/src/components/RelayGame.js +53 -0
  87. package/src/components/RelayLinker.js +94 -0
  88. package/src/components/RelayParent.js +57 -0
  89. package/src/components/RelaySelf.js +55 -0
  90. package/src/components/Render.js +178 -0
  91. package/src/components/RenderAnimator.js +275 -0
  92. package/src/components/RenderContainer.js +646 -0
  93. package/src/components/RenderDebug.js +319 -0
  94. package/src/components/RenderDestroyMe.js +59 -0
  95. package/src/components/RenderLottie.js +149 -0
  96. package/src/components/RenderProgress.js +120 -0
  97. package/src/components/RenderSpine.js +833 -0
  98. package/src/components/RenderSprite.js +527 -0
  99. package/src/components/RenderText.js +147 -0
  100. package/src/components/RenderTiles.js +1391 -0
  101. package/src/components/SceneChanger.js +128 -0
  102. package/src/components/TiledLoader.js +1451 -0
  103. package/src/components/Timeline.js +314 -0
  104. package/src/components/Tutorial.js +267 -0
  105. package/src/components/Tween.js +296 -0
  106. package/src/components/VoiceOver.js +373 -0
  107. package/src/components/XHR.js +129 -0
  108. package/src/components/index.js +79 -0
  109. package/src/factory.js +305 -0
  110. package/src/index.js +159 -0
  111. package/src/mixins/PIXI.Graphics.js +41 -0
  112. package/src/platypus.css +68 -0
  113. package/src/sayHello.js +50 -0
  114. package/src/shapes/Capsule.js +74 -0
  115. package/src/shapes/Circle.js +43 -0
  116. package/src/shapes/Ellipse.js +117 -0
  117. package/src/shapes/Point.js +191 -0
  118. package/src/shapes/Polygon.js +112 -0
  119. package/src/shapes/Polyline.js +97 -0
  120. package/src/shapes/Rectangle.js +78 -0
  121. package/src/shapes/RoundedRectangle.js +33 -0
  122. package/src/shapes/Segment.js +346 -0
  123. package/src/shapes/adjustedXY.js +19 -0
  124. package/src/shapes/castFromBox2D.js +92 -0
  125. package/src/shapes/castFromObject.js +53 -0
  126. package/src/shapes/castFromPixiGraphics.js +6 -0
  127. package/src/shapes/castFromPixiShape.js +89 -0
  128. package/src/shapes/castToBox2D.js +190 -0
  129. package/src/shapes/castToPixiGraphics.js +100 -0
  130. package/src/shapes/castToPixiShape.js +61 -0
  131. package/src/shapes/checkPointsMap.js +313 -0
  132. package/src/shapes/index.js +35 -0
  133. package/src/utils/array.js +73 -0
  134. package/src/utils/string.js +39 -0
@@ -0,0 +1,1052 @@
1
+ /*global platypus, window */
2
+ import AABB from '../AABB.js';
3
+ import {Container} from 'pixi.js';
4
+ import Data from '../Data.js';
5
+ import TweenJS from '@tweenjs/tween.js';
6
+ import Vector from '../Vector.js';
7
+ import createComponentClass from '../factory.js';
8
+ import EntityCamera from './EntityCamera.js';
9
+
10
+ const
11
+ DPR = window.devicePixelRatio || 1,
12
+ STATIC = 0,
13
+ PAN = 1,
14
+ FOLLOW = 2,
15
+ validModes = ['static', 'mouse-pan', 'following'],
16
+ deprecatedModes = {
17
+ pan: PAN,
18
+ locked: FOLLOW,
19
+ forward: FOLLOW,
20
+ bounding: FOLLOW,
21
+ 'anchor-bound': FOLLOW
22
+ },
23
+ getCameraMode = (mode = 'static') => {
24
+ if (validModes.indexOf(mode) >= 0) {
25
+ return mode;
26
+ } else if (typeof deprecatedModes[mode] === 'number') {
27
+ const
28
+ alt = validModes[deprecatedModes[mode]];
29
+
30
+ platypus.debug.warn(`"${mode} has been deprecated. Use "${alt}" instead.`);
31
+ return alt;
32
+ } else {
33
+ platypus.debug.warn(`"${mode} is not a valid mode. Available modes include "${validModes.join('", "')}".`);
34
+ return 'static';
35
+ }
36
+ },
37
+ doNothing = function () {
38
+ return false;
39
+ },
40
+
41
+ // These fix coords for touch events filling in for pointer events from the PIXI InteractiveManager
42
+ getClientX = function (event) {
43
+ if (!event.clientX) {
44
+ if (event.touches && event.touches[0] && event.touches[0].clientX) {
45
+ return event.touches[0].clientX;
46
+ }
47
+ return 0;
48
+ }
49
+ return event.clientX;
50
+ },
51
+ getClientY = function (event) {
52
+ if (!event.clientY) {
53
+ if (event.touches && event.touches[0] && event.touches[0].clientY) {
54
+ return event.touches[0].clientY;
55
+ }
56
+ return 0;
57
+ }
58
+ return event.clientY;
59
+ };
60
+
61
+ export default createComponentClass(/** @lends platypus.components.Camera.prototype */{
62
+ id: 'HandlerCamera',
63
+ properties: {
64
+ /**
65
+ * Number specifying width of viewport in world coordinates.
66
+ *
67
+ * @property width
68
+ * @type number
69
+ * @default 0
70
+ **/
71
+ "width": 0,
72
+
73
+ /**
74
+ * Number specifying height of viewport in world coordinates.
75
+ *
76
+ * @property height
77
+ * @type number
78
+ * @default 0
79
+ **/
80
+ "height": 0,
81
+
82
+ /**
83
+ * Specifies whether the camera should be draggable via the mouse by setting to 'pan'.
84
+ *
85
+ * @property mode
86
+ * @type String
87
+ * @default 'static'
88
+ **/
89
+ "mode": "static",
90
+
91
+ /**
92
+ * Whether camera overflows to cover the whole canvas or remains contained within its aspect ratio's boundary.
93
+ *
94
+ * @property overflow
95
+ * @type boolean
96
+ * @default true
97
+ * @deprecated
98
+ */
99
+ "overflow": false,
100
+
101
+ /**
102
+ * Boolean value that determines whether the camera should stretch the world viewport when window is resized. Defaults to false which maintains the proper aspect ratio.
103
+ *
104
+ * @property stretch
105
+ * @type boolean
106
+ * @default: false
107
+ * @deprecated
108
+ */
109
+ "stretch": false,
110
+
111
+ /**
112
+ * Sets how many units the followed entity can move before the camera will re-center. This should be lowered for small-value coordinate systems such as Box2D.
113
+ *
114
+ * @property threshold
115
+ * @type number
116
+ * @default 1
117
+ **/
118
+ "threshold": 1,
119
+
120
+ /**
121
+ * Whether, when following an entity, the camera should rotate to match the entity's orientation.
122
+ *
123
+ * @property rotate
124
+ * @type boolean
125
+ * @default false
126
+ **/
127
+ "rotate": false,
128
+
129
+ /**
130
+ * Number specifying the horizontal center of viewport in world coordinates.
131
+ *
132
+ * @property x
133
+ * @type number
134
+ * @default 0
135
+ **/
136
+ "x": 0,
137
+
138
+ /**
139
+ * Number specifying the vertical center of viewport in world coordinates.
140
+ *
141
+ * @property y
142
+ * @type number
143
+ * @default 0
144
+ **/
145
+ "y": 0
146
+ },
147
+ publicProperties: {
148
+ /**
149
+ * The entity's canvas element is used to determine the window size of the camera.
150
+ *
151
+ * @property canvas
152
+ * @type Canvas
153
+ * @default null
154
+ */
155
+ "canvas": null,
156
+
157
+ /**
158
+ * Sets how quickly the camera should pan to a new position in the horizontal direction.
159
+ *
160
+ * @property transitionX
161
+ * @type number
162
+ * @default 400
163
+ **/
164
+ "transitionX": 400,
165
+
166
+ /**
167
+ * Sets how quickly the camera should pan to a new position in the vertical direction.
168
+ *
169
+ * @property transitionY
170
+ * @type number
171
+ * @default 600
172
+ **/
173
+ "transitionY": 600,
174
+
175
+ /**
176
+ * Sets how quickly the camera should rotate to a new orientation.
177
+ *
178
+ * @property transitionAngle
179
+ * @type number
180
+ * @default: 600
181
+ **/
182
+ "transitionAngle": 600,
183
+
184
+ /**
185
+ * Provides access at the layer level to the camera's world viewport.
186
+ *
187
+ * ```javascript
188
+ * {
189
+ * viewport, //platypus.AABB
190
+ * orientation
191
+ * }
192
+ * ```
193
+ *
194
+ * @property worldCamera
195
+ * @type platypus.Data
196
+ */
197
+ worldCamera: null,
198
+
199
+ /**
200
+ * Sets the z-order of this layer relative to other loaded layers.
201
+ *
202
+ * @property z
203
+ * @type Number
204
+ * @default 0
205
+ */
206
+ "z": 0
207
+ },
208
+
209
+ /**
210
+ * This component controls the game camera deciding where and how it should move. The camera also broadcasts messages when the window resizes or its orientation changes.
211
+ *
212
+ * @memberof platypus.components
213
+ * @uses platypus.Component
214
+ * @constructs
215
+ * @listens platypus.Entity#child-entity-added
216
+ * @listens platypus.Entity#child-entity-updated
217
+ * @listens platypus.Entity#follow
218
+ * @listens platypus.Entity#load
219
+ * @listens platypus.Entity#pointerdown
220
+ * @listens platypus.Entity#pressmove
221
+ * @listens platypus.Entity#pressup
222
+ * @listens platypus.Entity#relocate
223
+ * @listens platypus.Entity#render-world
224
+ * @listens platypus.Entity#resize-camera
225
+ * @listens platypus.Entity#shake
226
+ * @listens platypus.Entity#tick
227
+ * @listens platypus.Entity#world-loaded
228
+ * @fires platypus.Entity#camera-loaded
229
+ * @fires platypus.Entity#camera-update
230
+ * @fires platypus.Entity#render-update
231
+ */
232
+ initialize: function (definition) {
233
+ const
234
+ worldVP = AABB.setUp(this.x, this.y, this.width, this.height),
235
+ worldCamera = Data.setUp(
236
+ "viewport", worldVP,
237
+ "orientation", definition.orientation || 0
238
+ );
239
+
240
+ //The dimensions of the camera in the window
241
+ this.viewport = AABB.setUp(0, 0, 0, 0);
242
+
243
+ //The dimensions of the camera in the game world
244
+ this.worldCamera = worldCamera;
245
+
246
+ //Message object defined here so it's reusable
247
+ this.worldDimensions = AABB.setUp();
248
+
249
+ this.unbounded = true; // no bounds on camera movement
250
+
251
+ //Whether the map has finished loading.
252
+ this.worldIsLoaded = false;
253
+
254
+ this.mode = getCameraMode(this.mode);
255
+
256
+ //FOLLOW MODE
257
+ this.cameras = [];
258
+
259
+ this.xMagnitude = 0;
260
+ this.yMagnitude = 0;
261
+ this.xWaveLength = 0;
262
+ this.yWaveLength = 0;
263
+ this.xShakeTime = 0;
264
+ this.yShakeTime = 0;
265
+ this.shakeTime = 0;
266
+ this.shakeIncrementor = 0;
267
+
268
+ this.direction = true;
269
+ this.stationary = false;
270
+
271
+ this.viewportUpdate = false;
272
+
273
+ if (this.owner.container) {
274
+ this.parentContainer = this.owner.container;
275
+ } else if (this.owner.stage) {
276
+ this.canvas = this.canvas || platypus.game.canvas; //TODO: Probably need to find a better way to handle resizing - DDD 10/4/2015
277
+ this.parentContainer = this.owner.stage;
278
+ this.owner.width = this.canvas.width;
279
+ this.owner.height = this.canvas.height;
280
+ } else {
281
+ platypus.debug.warn('Camera: There appears to be no Container on this entity for the camera to display.');
282
+ }
283
+ this.container = new Container();
284
+ this.container.zIndex = this.z;
285
+ this.container.visible = false;
286
+ this.parentContainer.addChild(this.container);
287
+ this.movedCamera = false;
288
+ },
289
+ events: {
290
+ "load": function () {
291
+ this.resize();
292
+ },
293
+
294
+ "render-world": function (data) {
295
+ this.world = data.world;
296
+ this.container.addChild(this.world);
297
+ },
298
+
299
+ "child-entity-added": function (entity) {
300
+ this.viewportUpdate = true;
301
+
302
+ if (this.worldIsLoaded) {
303
+ const
304
+ msg = Data.setUp(
305
+ "viewport", AABB.setUp(this.worldCamera.viewport),
306
+ "world", this.worldDimensions
307
+ );
308
+
309
+ /**
310
+ * On receiving a "world-loaded" message, the camera broadcasts the world size to all children in the world.
311
+ *
312
+ * @event platypus.Entity#camera-loaded
313
+ * @param camera {Object}
314
+ * @param camera.world {platypus.AABB} The dimensions of the world.
315
+ * @param camera.viewport {platypus.AABB} The AABB describing the camera viewport in world units.
316
+ **/
317
+ entity.triggerEvent('camera-loaded', msg);
318
+
319
+ msg.viewport.recycle();
320
+ msg.recycle();
321
+ }
322
+
323
+ if (typeof entity.cameraFocus === 'number') {
324
+ this.addCamera(entity);
325
+ }
326
+ },
327
+
328
+ "child-entity-updated": function (entity) {
329
+ this.viewportUpdate = true;
330
+
331
+ if (this.worldIsLoaded) {
332
+ const
333
+ {container, owner, stationary, windowPerWorldUnitHeight, windowPerWorldUnitWidth, world, worldCamera} = this,
334
+ {viewport} = worldCamera,
335
+ msg = Data.setUp(
336
+ "viewport", AABB.setUp(viewport),
337
+ "scaleX", windowPerWorldUnitWidth,
338
+ "scaleY", windowPerWorldUnitHeight,
339
+ "orientation", worldCamera.orientation,
340
+ "stationary", stationary,
341
+ "world", this.worldDimensions
342
+ );
343
+
344
+ entity.triggerEvent('camera-update', msg);
345
+ msg.viewport.recycle();
346
+ msg.recycle();
347
+ }
348
+
349
+ if (typeof entity.cameraFocus === 'number') {
350
+ this.addCamera(entity);
351
+ } else {
352
+ this.removeCamera(entity);
353
+ }
354
+ },
355
+
356
+ "child-entity-removed": function (entity) {
357
+ this.viewportUpdate = true;
358
+
359
+ this.removeCamera(entity);
360
+ },
361
+
362
+ "world-loaded": function (values) {
363
+ const
364
+ {owner} = this;
365
+
366
+ this.worldDimensions.set(values.world);
367
+ this.unbounded = !!values.level.infinite;
368
+
369
+ this.worldIsLoaded = true;
370
+ if (values.camera) {
371
+ this.follow(values.camera);
372
+ }
373
+ if (owner.triggerEventOnChildren) {
374
+ const
375
+ msg = Data.setUp(
376
+ "viewport", AABB.setUp(this.worldCamera.viewport),
377
+ "world", this.worldDimensions
378
+ );
379
+
380
+ owner.triggerEventOnChildren('camera-loaded', msg);
381
+
382
+ msg.viewport.recycle();
383
+ msg.recycle();
384
+ }
385
+ this.updateMovementMethods();
386
+ },
387
+
388
+ "pointerdown": function (event) {
389
+ if (this.state === 'mouse-pan') {
390
+ const
391
+ {viewport} = this.worldCamera;
392
+
393
+ if (!this.mouseVector) {
394
+ this.mouseVector = Vector.setUp();
395
+ this.mouseWorldOrigin = Vector.setUp();
396
+ }
397
+ this.mouse = this.mouseVector;
398
+ this.mouse.x = getClientX(event.event);
399
+ this.mouse.y = getClientY(event.event);
400
+ this.mouseWorldOrigin.x = viewport.x;
401
+ this.mouseWorldOrigin.y = viewport.y;
402
+ event.pixiEvent.stopPropagation();
403
+ }
404
+ },
405
+
406
+ "pressmove": function (event) {
407
+ if (this.mouse) {
408
+ if (this.move(this.mouseWorldOrigin.x + ((this.mouse.x - getClientX(event.event)) * DPR) / this.world.worldTransform.a, this.mouseWorldOrigin.y + ((this.mouse.y - getClientY(event.event)) * DPR) / this.world.worldTransform.d)) {
409
+ this.viewportUpdate = true;
410
+ this.movedCamera = true;
411
+ event.pixiEvent.stopPropagation();
412
+ }
413
+ }
414
+ },
415
+
416
+ "pressup": function (event) {
417
+ if (this.mouse) {
418
+ this.mouse = null;
419
+ if (this.movedCamera) {
420
+ this.movedCamera = false;
421
+ event.pixiEvent.stopPropagation();
422
+ }
423
+ }
424
+ },
425
+
426
+ "tick": function ({delta}) {
427
+ if ((this.mode === 'following') && this.lockedFollow(this.cameras, delta)) {
428
+ this.viewportUpdate = true;
429
+ }
430
+
431
+ // Need to update owner's size information for changes to canvas size
432
+ if (this.canvas) {
433
+ this.owner.width = this.canvas.width;
434
+ this.owner.height = this.canvas.height;
435
+ }
436
+
437
+ // Check for owner resizing
438
+ if ((this.owner.width !== this.lastWidth) || (this.owner.height !== this.lastHeight)) {
439
+ this.resize();
440
+ this.lastWidth = this.owner.width;
441
+ this.lastHeight = this.owner.height;
442
+ }
443
+
444
+ if (this.shakeIncrementor < this.shakeTime) {
445
+ const viewport = this.worldCamera.viewport;
446
+
447
+ this.viewportUpdate = true;
448
+ this.shakeIncrementor += delta;
449
+ this.shakeIncrementor = Math.min(this.shakeIncrementor, this.shakeTime);
450
+
451
+ if (this.shakeIncrementor < this.xShakeTime) {
452
+ viewport.moveX(viewport.x + Math.sin((this.shakeIncrementor / this.xWaveLength) * (Math.PI * 2)) * this.xMagnitude);
453
+ }
454
+
455
+ if (this.shakeIncrementor < this.yShakeTime) {
456
+ viewport.moveY(viewport.y + Math.sin((this.shakeIncrementor / this.yWaveLength) * (Math.PI * 2)) * this.yMagnitude);
457
+ }
458
+ }
459
+
460
+ this.updateViewport();
461
+
462
+ if (this.container.zIndex !== this.z) {
463
+ this.container.zIndex = this.z;
464
+ }
465
+ },
466
+
467
+ /**
468
+ * The camera listens for this event to change its world viewport size.
469
+ *
470
+ * @event platypus.Entity#resize-camera
471
+ * @param {Object} [dimensions] List of key/value pairs describing new viewport size
472
+ * @param {number} dimensions.width Width of the camera viewport
473
+ * @param {number} dimensions.height Height of the camera viewport
474
+ * @param {number} dimensions.time Time in millseconds over which to tween the scale change.
475
+ * @param {Boolean} [forceUpdate] Whether to update graphics.
476
+ **/
477
+ "resize-camera": function (dimensions = {}, forceUpdate = false) {
478
+ const
479
+ {width, height, time, ease} = dimensions,
480
+ forcedUpdate = forceUpdate || dimensions.forceUpdate;
481
+
482
+ if (time) {
483
+ const
484
+ tween = new TweenJS.Tween(this);
485
+
486
+ tween.to({width, height}, time);
487
+ if (ease) {
488
+ tween.easing(ease);
489
+ }
490
+ tween.onUpdate(() => {
491
+ this.resize();
492
+ }).start();
493
+ } else {
494
+ if (width && height) {
495
+ this.width = dimensions.width;
496
+ this.height = dimensions.height;
497
+ }
498
+ if (this.canvas) {
499
+ this.owner.width = this.canvas.width;
500
+ this.owner.height = this.canvas.height;
501
+ }
502
+ this.resize();
503
+ }
504
+ if (forcedUpdate) {
505
+ this.updateViewport();
506
+
507
+ /**
508
+ * Sends a 'handle-render' message to all the children in the Container. This bypasses a render pause value and is useful for resizes happening outside the game loop.
509
+ *
510
+ * @event platypus.Entity#render-update
511
+ * @param tick {Object} An object containing tick data.
512
+ */
513
+ this.owner.triggerEvent('render-update');
514
+ }
515
+ },
516
+
517
+ /**
518
+ * The camera listens for this event to change its position in the world.
519
+ *
520
+ * @event platypus.Entity#relocate
521
+ * @param {Vector|Object} location List of key/value pairs describing new location
522
+ * @param {Number} location.x New position along the x-axis.
523
+ * @param {Number} location.y New position along the y-axis.
524
+ * @param {Number} [location.time] The time to transition to the new location.
525
+ * @param {Function} [location.ease] The ease function to use. Defaults to a linear transition.
526
+ */
527
+ "relocate": function (location) {
528
+ if (location.time) {
529
+ const
530
+ worldVP = this.worldCamera.viewport,
531
+ v = Vector.setUp(worldVP.x, worldVP.y),
532
+ tween = new TweenJS.Tween(v);
533
+
534
+ tween.to({x: location.x, y: location.y}, location.time);
535
+ if (location.ease) {
536
+ tween.easing(location.ease);
537
+ }
538
+ tween.onUpdate(() => this.viewportUpdate |= !this.owner.destroyed && this.move(v.x, v.y)).onStop(() => v.recycle()).start();
539
+ } else if (this.move(location.x, location.y)) {
540
+ this.viewportUpdate = true;
541
+ }
542
+ },
543
+
544
+ "follow": function (def) {
545
+ this.follow(def);
546
+ },
547
+
548
+ /**
549
+ * On receiving this message, the camera will shake around its target location.
550
+ *
551
+ * @event platypus.Entity#shake
552
+ * @param {Object} shake
553
+ * @param {number} [shake.xMagnitude] How much to move along the x axis.
554
+ * @param {number} [shake.yMagnitude] How much to move along the y axis.
555
+ * @param {number} [shake.xFrequency] How quickly to shake along the x axis.
556
+ * @param {number} [shake.yFrequency] How quickly to shake along the y axis.
557
+ * @param {number} [shake.time] How long the camera should shake.
558
+ */
559
+ "shake": function (def = {}) {
560
+ const
561
+ xMag = def.xMagnitude || 0,
562
+ yMag = def.yMagnitude || 0,
563
+ xFreq = def.xFrequency || 0, //Cycles per second
564
+ yFreq = def.yFrequency || 0, //Cycles per second
565
+ second = 1000,
566
+ time = def.time || 0;
567
+
568
+ this.viewportUpdate = true;
569
+
570
+ this.shakeIncrementor = 0;
571
+
572
+ this.xMagnitude = xMag;
573
+ this.yMagnitude = yMag;
574
+
575
+ if (xFreq === 0) {
576
+ this.xWaveLength = 1;
577
+ this.xShakeTime = 0;
578
+ } else {
579
+ this.xWaveLength = (second / xFreq);
580
+ this.xShakeTime = Math.ceil(time / this.xWaveLength) * this.xWaveLength;
581
+ }
582
+
583
+ if (yFreq === 0) {
584
+ this.yWaveLength = 1;
585
+ this.yShakeTime = 0;
586
+ } else {
587
+ this.yWaveLength = (second / yFreq);
588
+ this.yShakeTime = Math.ceil(time / this.yWaveLength) * this.yWaveLength;
589
+ }
590
+
591
+ this.shakeTime = Math.max(this.xShakeTime, this.yShakeTime);
592
+ }
593
+ },
594
+
595
+ methods: {
596
+ follow: function ({begin = 0, entity, mode, orientation = 0, time = 0, x, y} = {}) {
597
+ if (begin || time) {
598
+ platypus.debug.warn(`"begin" and "time" syntax has been deprecated. Use the "Timeline" component to set up transitional timings instead.`);
599
+ }
600
+
601
+ this.mode = getCameraMode(mode);
602
+
603
+ switch (this.mode) {
604
+ case 'following':
605
+ this.addCamera(entity);
606
+ break;
607
+ case 'mouse-pan':
608
+ if ((typeof x === 'number') && (typeof y === 'number')) {
609
+ this.move(x, y, orientation);
610
+ this.viewportUpdate = true;
611
+ }
612
+ break;
613
+ default:
614
+ if ((typeof x === 'number') && (typeof y === 'number')) {
615
+ this.move(x, y, orientation);
616
+ this.viewportUpdate = true;
617
+ }
618
+ break;
619
+ }
620
+ },
621
+
622
+ addCamera (entity) {
623
+ const
624
+ needsAdding = this.cameras.indexOf(entity) === -1;
625
+
626
+ if (needsAdding) {
627
+ if (typeof entity.cameraFocus !== 'number') {
628
+ entity.addComponent(new EntityCamera(entity, {}));
629
+ }
630
+
631
+ this.cameras.push(entity);
632
+ this.mode = 'following';
633
+ }
634
+
635
+ return needsAdding;
636
+ },
637
+
638
+ removeCamera (entity) {
639
+ const
640
+ index = this.cameras.indexOf(entity),
641
+ canBeRemoved = index >= 0;
642
+
643
+ if (canBeRemoved) {
644
+ this.cameras.splice(index, 1);
645
+ if (this.cameras.length === 0) {
646
+ this.mode = 'static';
647
+ }
648
+ }
649
+
650
+ return canBeRemoved;
651
+ },
652
+
653
+ move: function (x, y, newOrientation) {
654
+ const
655
+ movedX = this.moveX(x),
656
+ movedY = this.moveY(y),
657
+ movedR = this.rotate && this.reorient(newOrientation || 0);
658
+
659
+ return movedX || movedY || movedR;
660
+ },
661
+
662
+ moveX: doNothing,
663
+
664
+ moveY: doNothing,
665
+
666
+ reorient: function (newOrientation) {
667
+ const
668
+ errMargin = 0.0001,
669
+ {worldCamera} = this;
670
+
671
+ if (Math.abs(worldCamera.orientation - newOrientation) > errMargin) {
672
+ worldCamera.orientation = newOrientation;
673
+ return true;
674
+ }
675
+ return false;
676
+ },
677
+
678
+ lockedFollow (entities) {
679
+ const
680
+ {worldCamera: {viewport: currentBounds}, worldDimensions: {top, bottom, left, right, height, width, x, y}, unbounded} = this,
681
+ sum = entities.reduce((prev, {cameraFocus}) => prev + cameraFocus, 0),
682
+ useCurrentBounds = sum < 1,
683
+ list = entities.filter(({cameraFocus}) => cameraFocus > 0),
684
+ reduce = (property) => [({focus, result}, {cameraFocus, cameraBounds}) => {
685
+ const
686
+ value = cameraBounds[property];
687
+
688
+ if (cameraFocus > focus) {
689
+ cameraFocus = focus;
690
+ }
691
+ return {
692
+ focus: (cameraFocus <= focus) ? focus - cameraFocus : 0,
693
+ result: result + cameraFocus * value
694
+ };
695
+ }, {
696
+ focus: sum,
697
+ result: useCurrentBounds ? currentBounds[property] * (1 - sum) : 0
698
+ }],
699
+ {result: t} = list.sort(({cameraBounds: {top: a}}, {cameraBounds: {top: b}}) => a - b).reduce(...reduce('top')),
700
+ {result: b} = list.sort(({cameraBounds: {bottom: a}}, {cameraBounds: {bottom: b}}) => b - a).reduce(...reduce('bottom')),
701
+ {result: l} = list.sort(({cameraBounds: {left: a}}, {cameraBounds: {left: b}}) => a - b).reduce(...reduce('left')),
702
+ {result: r} = list.sort(({cameraBounds: {right: a}}, {cameraBounds: {right: b}}) => b - a).reduce(...reduce('right')),
703
+ lastBounds = AABB.setUp(currentBounds);
704
+
705
+ currentBounds.setBounds(l, t, r, b);
706
+ if (this.width !== currentBounds.width || this.height !== currentBounds.height) {
707
+ this.width = currentBounds.width;
708
+ this.height = currentBounds.height;
709
+ this.matchAspectRatio();
710
+ this.resize();
711
+ } else {
712
+ this.matchAspectRatio();
713
+ }
714
+
715
+ if (!unbounded) {
716
+ if (width) {
717
+ if (width < currentBounds.width) {
718
+ currentBounds.moveX(x);
719
+ } else if (left > currentBounds.left) {
720
+ currentBounds.moveX(left + currentBounds.halfWidth);
721
+ } else if (right < currentBounds.right) {
722
+ currentBounds.moveX(right - currentBounds.halfWidth);
723
+ }
724
+ }
725
+ if (height) {
726
+ if (height < currentBounds.height) {
727
+ currentBounds.moveY(y);
728
+ } else if (top > currentBounds.top) {
729
+ currentBounds.moveY(top + currentBounds.halfHeight);
730
+ } else if (bottom < currentBounds.bottom) {
731
+ currentBounds.moveY(bottom - currentBounds.halfHeight);
732
+ }
733
+ }
734
+ }
735
+
736
+ if (lastBounds.equals(currentBounds)) {
737
+ lastBounds.recycle();
738
+ return false;
739
+ } else {
740
+ lastBounds.recycle();
741
+ return true;
742
+ }
743
+ },
744
+
745
+ matchAspectRatio () {
746
+ const
747
+ {height, owner, width, worldCamera} = this,
748
+ {viewport} = worldCamera,
749
+ worldAspectRatio = width / height,
750
+ windowAspectRatio = owner.width / owner.height;
751
+
752
+ if (windowAspectRatio > worldAspectRatio) {
753
+ viewport.resize(height * windowAspectRatio, height);
754
+ } else {
755
+ viewport.resize(width, width / windowAspectRatio);
756
+ }
757
+ },
758
+
759
+ clampAspectRatio () {
760
+ const
761
+ {height, owner, viewport, width} = this,
762
+ worldAspectRatio = width / height,
763
+ windowAspectRatio = owner.width / owner.height;
764
+
765
+ if (windowAspectRatio > worldAspectRatio) {
766
+ viewport.resize(viewport.height * worldAspectRatio, viewport.height);
767
+ } else {
768
+ viewport.resize(viewport.width, viewport.width / worldAspectRatio);
769
+ }
770
+ },
771
+
772
+ resize: function () {
773
+ const
774
+ {container, height, owner, viewport, width, worldCamera} = this,
775
+ worldAspectRatio = width / height,
776
+ windowAspectRatio = owner.width / owner.height,
777
+ {viewport: worldVP} = worldCamera;
778
+
779
+ //The dimensions of the camera in the window
780
+ viewport.setAll(owner.width / 2, owner.height / 2, owner.width, owner.height);
781
+
782
+ if (windowAspectRatio > worldAspectRatio) {
783
+ worldVP.resize(height * windowAspectRatio, height);
784
+ } else {
785
+ worldVP.resize(width, width / windowAspectRatio);
786
+ }
787
+
788
+ this.worldPerWindowUnitWidth = worldVP.width / viewport.width;
789
+ this.worldPerWindowUnitHeight = worldVP.height / viewport.height;
790
+ this.windowPerWorldUnitWidth = viewport.width / worldVP.width;
791
+ this.windowPerWorldUnitHeight = viewport.height / worldVP.height;
792
+
793
+ container.updateTransform({
794
+ x: viewport.x - viewport.halfWidth,
795
+ y: viewport.y - viewport.halfHeight
796
+ });
797
+
798
+ this.viewportUpdate = true;
799
+
800
+ this.updateMovementMethods();
801
+ },
802
+
803
+ updateMovementMethods: (function () {
804
+ // This is used to change movement modes as needed rather than doing a check every tick to determine movement type. - DDD 2/29/2016
805
+ const
806
+ centerX = function () {
807
+ const
808
+ {worldDimensions} = this;
809
+
810
+ this.worldCamera.viewport.moveX(worldDimensions.width / 2 + worldDimensions.left);
811
+ this.moveX = doNothing;
812
+ return true;
813
+ },
814
+ centerY = function () {
815
+ const
816
+ {worldDimensions} = this;
817
+
818
+ this.worldCamera.viewport.moveY(worldDimensions.height / 2 + worldDimensions.top);
819
+ this.moveY = doNothing;
820
+ return true;
821
+ },
822
+ containX = function (x) {
823
+ const
824
+ {worldDimensions, worldCamera} = this,
825
+ {viewport} = worldCamera,
826
+ {left, width} = worldDimensions;
827
+
828
+ if (Math.abs(viewport.x - x) > this.threshold) {
829
+ if (x + viewport.halfWidth > width + left) {
830
+ viewport.moveX(width - viewport.halfWidth + left);
831
+ } else if (x < viewport.halfWidth + left) {
832
+ viewport.moveX(viewport.halfWidth + left);
833
+ } else {
834
+ viewport.moveX(x);
835
+ }
836
+ return true;
837
+ }
838
+ return false;
839
+ },
840
+ containY = function (y) {
841
+ const
842
+ {worldDimensions, worldCamera} = this,
843
+ {viewport} = worldCamera,
844
+ {height, top} = worldDimensions;
845
+
846
+ if (Math.abs(viewport.y - y) > this.threshold) {
847
+ if (y + viewport.halfHeight > height + top) {
848
+ viewport.moveY(height - viewport.halfHeight + top);
849
+ } else if (y < viewport.halfHeight + top) {
850
+ viewport.moveY(viewport.halfHeight + top);
851
+ } else {
852
+ viewport.moveY(y);
853
+ }
854
+ return true;
855
+ }
856
+ return false;
857
+ },
858
+ allX = function (x) {
859
+ const
860
+ {threshold, worldCamera} = this,
861
+ {viewport} = worldCamera;
862
+
863
+ if (Math.abs(viewport.x - x) > threshold) {
864
+ viewport.moveX(x);
865
+ return true;
866
+ }
867
+ return false;
868
+ },
869
+ allY = function (y) {
870
+ const
871
+ {threshold, worldCamera} = this,
872
+ {viewport} = worldCamera;
873
+
874
+ if (Math.abs(viewport.y - y) > threshold) {
875
+ viewport.moveY(y);
876
+ return true;
877
+ }
878
+ return false;
879
+ };
880
+
881
+ return function () {
882
+ const
883
+ {threshold, unbounded, worldCamera, worldDimensions} = this,
884
+ {viewport} = worldCamera,
885
+ {height, width} = worldDimensions;
886
+
887
+ if (unbounded || !width) {
888
+ this.moveX = allX;
889
+ } else if (width < viewport.width) {
890
+ this.moveX = centerX;
891
+ } else {
892
+ this.moveX = containX;
893
+ }
894
+
895
+ if (unbounded || !height) {
896
+ this.moveY = allY;
897
+ } else if (height < viewport.height) {
898
+ this.moveY = centerY;
899
+ } else {
900
+ this.moveY = containY;
901
+ }
902
+
903
+ // Make sure camera is correctly contained:
904
+ this.threshold = -1; // forces update
905
+ this.moveX(viewport.x);
906
+ this.moveY(viewport.y);
907
+ this.threshold = threshold;
908
+ };
909
+ }()),
910
+
911
+ updateViewport: function () {
912
+ const
913
+ {container, owner, stationary, viewportUpdate, windowPerWorldUnitHeight, windowPerWorldUnitWidth, world, worldCamera} = this,
914
+ {viewport} = worldCamera,
915
+ msg = Data.setUp(
916
+ "viewport", AABB.setUp(viewport),
917
+ "scaleX", windowPerWorldUnitWidth,
918
+ "scaleY", windowPerWorldUnitHeight,
919
+ "orientation", worldCamera.orientation,
920
+ "stationary", !viewportUpdate && !stationary,
921
+ "world", this.worldDimensions
922
+ );
923
+
924
+ if (viewportUpdate) {
925
+ this.viewportUpdate = false;
926
+ this.stationary = false;
927
+
928
+ // Transform the world to appear within camera
929
+ world.x = -viewport.x;
930
+ world.y = -viewport.y;
931
+ container.x = viewport.halfWidth * msg.scaleX;
932
+ container.y = viewport.halfHeight * msg.scaleY;
933
+ container.scale.x = msg.scaleX;
934
+ container.scale.y = msg.scaleY;
935
+ container.rotation = msg.orientation;
936
+ container.visible = true;
937
+
938
+ /**
939
+ * This component fires "camera-update" when the position of the camera in the world has changed. This event is triggered on both the entity (typically a layer) as well as children of the entity.
940
+ *
941
+ * @event platypus.Entity#camera-update
942
+ * @param message {Object}
943
+ * @param message.world {platypus.AABB} The dimensions of the world map.
944
+ * @param message.orientation {Number} Number describing the orientation of the camera.
945
+ * @param message.scaleX {Number} Number of window pixels that comprise a single world coordinate on the x-axis.
946
+ * @param message.scaleY {Number} Number of window pixels that comprise a single world coordinate on the y-axis.
947
+ * @param message.viewport {platypus.AABB} An AABB describing the world viewport area.
948
+ * @param message.stationary {Boolean} Whether the camera is moving.
949
+ **/
950
+ owner.triggerEvent('camera-update', msg);
951
+ if (owner.triggerEventOnChildren) {
952
+ owner.triggerEventOnChildren('camera-update', msg);
953
+ }
954
+ } else if (!stationary) {
955
+ this.stationary = true;
956
+
957
+ owner.triggerEvent('camera-update', msg);
958
+ if (owner.triggerEventOnChildren) {
959
+ owner.triggerEventOnChildren('camera-update', msg);
960
+ }
961
+ }
962
+
963
+ msg.viewport.recycle();
964
+ msg.recycle();
965
+ },
966
+
967
+ destroy: function () {
968
+ this.parentContainer.removeChild(this.container);
969
+ this.parentContainer = null;
970
+ this.container = null;
971
+ if (this.mouseVector) {
972
+ this.mouseVector.recycle();
973
+ this.mouseWorldOrigin.recycle();
974
+ }
975
+
976
+ this.viewport.recycle();
977
+ this.worldCamera.viewport.recycle();
978
+ this.worldCamera.recycle();
979
+ this.worldDimensions.recycle();
980
+ }
981
+ },
982
+
983
+ publicMethods: {
984
+ /**
985
+ * Returns whether a particular display object intersects the camera's viewport on the canvas.
986
+ *
987
+ * @method platypus.components.Camera#isOnCanvas
988
+ * @param bounds {PIXI.Rectangle|Object} The bounds of the display object.
989
+ * @param bounds.height {Number} The height of the display object.
990
+ * @param bounds.width {Number} The width of the display object.
991
+ * @param bounds.x {Number} The left edge of the display object.
992
+ * @param bounds.y {Number} The top edge of the display object.
993
+ * @return {Boolean} Whether the display object intersects the camera's bounds.
994
+ */
995
+ isOnCanvas: function (bounds) {
996
+ const
997
+ {canvas} = this;
998
+
999
+ return !bounds || !((bounds.x + bounds.width < 0) || (bounds.x > canvas.width) || (bounds.y + bounds.height < 0) || (bounds.y > canvas.height));
1000
+ },
1001
+
1002
+ /**
1003
+ * Returns a world coordinate corresponding to a provided window coordinate.
1004
+ *
1005
+ * @method platypus.components.Camera#windowToWorld
1006
+ * @param windowVector {platypus.Vector} A vector describing a window position.
1007
+ * @param withOffset {Boolean} Whether to provide a world position relative to the camera's location.
1008
+ * @param vector {platypus.Vector} If provided, this is used as the return vector.
1009
+ * @return {platypus.Vector} A vector describing a world position.
1010
+ */
1011
+ windowToWorld: function (windowVector, withOffset, vector) {
1012
+ const
1013
+ {worldPerWindowUnitHeight, worldPerWindowUnitWidth, viewport, worldCamera: {viewport: wcViewport}} = this,
1014
+ worldVector = vector || Vector.setUp();
1015
+
1016
+ worldVector.x = windowVector.x * worldPerWindowUnitWidth;
1017
+ worldVector.y = windowVector.y * worldPerWindowUnitHeight;
1018
+
1019
+ if (withOffset !== false) {
1020
+ worldVector.x -= viewport.left * worldPerWindowUnitWidth - wcViewport.left;
1021
+ worldVector.y -= viewport.top * worldPerWindowUnitHeight - wcViewport.top;
1022
+ }
1023
+
1024
+ return worldVector;
1025
+ },
1026
+
1027
+ /**
1028
+ * Returns a window coordinate corresponding to a provided world coordinate.
1029
+ *
1030
+ * @method platypus.components.Camera#worldToWindow
1031
+ * @param worldVector {platypus.Vector} A vector describing a world position.
1032
+ * @param withOffset {Boolean} Whether to provide a window position relative to the camera's location.
1033
+ * @param vector {platypus.Vector} If provided, this is used as the return vector.
1034
+ * @return {platypus.Vector} A vector describing a window position.
1035
+ */
1036
+ worldToWindow: function (worldVector, withOffset, vector) {
1037
+ const
1038
+ {windowPerWorldUnitHeight, windowPerWorldUnitWidth, viewport, worldCamera: {viewport: wcViewport}} = this,
1039
+ windowVector = vector || Vector.setUp();
1040
+
1041
+ windowVector.x = worldVector.x * windowPerWorldUnitWidth;
1042
+ windowVector.y = worldVector.y * windowPerWorldUnitHeight;
1043
+
1044
+ if (withOffset !== false) {
1045
+ windowVector.x -= wcViewport.left * windowPerWorldUnitWidth - viewport.left;
1046
+ windowVector.y -= wcViewport.top * windowPerWorldUnitHeight - viewport.top;
1047
+ }
1048
+
1049
+ return windowVector;
1050
+ }
1051
+ }
1052
+ });