-
Notifications
You must be signed in to change notification settings - Fork 81
/
Copy pathga.js
2722 lines (2275 loc) · 82.9 KB
/
ga.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// ==ClosureCompiler==
// @output_file_name default.js
// @compilation_level SIMPLE_OPTIMIZATIONS
// @language ECMASCRIPT5
// @fileoverview
// @suppress {checkTypes | globalThis | checkVars}
// ==/ClosureCompiler==
/*
Welcome to Ga's source code!
============================
If you're reading this to find out how to use Ga, you've come to the wrong place.
You should take a look inside the `examples` folder.
There's a lot of cool stuff inside the `examples` folder, so check it out!
But if you want to find out how Ga works, this is the place to be.
This source code is organized into chapters.
Yes, chapters.
Just think of it like *Lord of the Rings* or maybe *Harry Potter* and you'll be fine.
Actually, come to think of it, maybe it's more like *50 Shades of Grey*.
Everything is in one big, hulking gainormous file.
Why?
Because `One Thing` is better than `Many Things`.
Just use your text editor's search function to find what you're looking for.
Courage, my love, you can do it!
Table of contents
-----------------
*Prologue: Fixing the WebAudio API*
`AudioContextMonkeyPatch.js`: Chris Wilson's cross browser patch for the WebAudio API.
*Chapter 1: The game engine*
`GA`:The global GA object.
`ga`: A convenience function used to launch Ga.
`Ga.create`: All the code that the Ga engine depends on.
`ga.gameLoop`: the engine's game loop.
`ga.update`: Calls the renderer, updates buttons and drag-and-drop objects each frame.
`ga.start`: Used to get the engine up and running.
`ga.pause`: pause the game loop.
`ga.resume`: resume the game loop.
`ga.hidePointer`: hide the pointer.
`ga.showPointer`: show the pointer.
`ga.fps`: get and set the game's frames per second.
`ga.backgroundColor`: Set the canvas background color.
*Chapter 2: Sprites*
`makeDisplayObject`: Assigns all the basic properties common to all sprite types.
`makeStage`: Create the stage object, which is the parent container for all the sprites.
`ga.remove`: A global convenience method that will remove any sprite from its parent.
`makeCircular`: Adds `diameter` and `radius` properties to sprites if a sprite's `circular` property is set to `true`.
`ga.group`: Creates a parent group container that lets you compose game scenes or composite sprites.
`ga.rectangle`: A basic colored rectangle sprite.
`ga.circle`: A basic colored circle sprite.`
`ga.line`: A line with start and end points.
`ga.text`: Single line dynamic text.
`ga.frame`: A function that returns an object defining the position of a sub-image in an Image object tileset.
`ga.frames`: Lets you define a whole series of sub-images in a tileset.
`ga.filmstrip:` Automatically returns an array of sub-image x and y coordinates for an animated image sequence.
`ga.sprite`: Creates a sprite from an image, `frame`, `filmstrip`, or a frame from a texture atlas.
`ga.button`: An interactive button with `up` `over` and `down` states. Optional `press` and `release` actions.
`makeInteractive`: Assigns `press` and `release` actions to sprites and adds pointer interactivity.
`ga.image`: Access Image objects by their file names.
`ga.json`: Access JSON files by their file names.
`ga.addStatePlayer`: Adds `play`, `stop`, `show`, and `playSequence` methods to sprites.
*Chapter 3: Rendering*
`ga.render`: Ga's canvas rendering method.
*Chapter 4: Ga's helper objects and methods*
`ga.assets`: All the game's assets (files) are stored in this object, and it has a `load` method that manages asset loading.
`makePointer`: Makes a universal pointer object for the mouse and touch.
`keyboard`: A method that creates `key` objects that listen for keyboard events.
`makeKeys`: Used by Ga to create built-in references to the arrow keys and space bar.
`byLayer`: An array sort method that's called when a sprite's `layer` property is changed.
*/
/*
Prologue: Some necessary polyfills
--------------------------
/*
Chapter 1: The game engine
--------------------------
This fist chapter is all about the Ga's game engine code. This is the code that
launches Ga, sets the defaults, creates a canvas element, starts loading asssets,
setups up the current game state,
and generally gets things up and running. This is probably the best place to start
to learn how the engine works.
*/
//### GA
//`GA` is the global instance of the program.
var GA = GA || {};
//### GA.VERSION
//The current version of the game engine.
GA.VERSION = '0.0.1';
//Set `plugins` and `custom` to an intial value of `undefined` to make
//Google Closure Compiler happy
GA.plugins = undefined;
GA.custom = undefined;
//### GA.create
//The entire Ga program exists inside the `Ga.create` method. It
//creates and returns a new instance of Ga, along with all the core
//game engine functions. However, Ga won't actually start until you
//call the `start` method from the applicaiton code, as you can see in
//all the examples (in the `examples` folder).
GA.create = function(width, height, setup, assetsToLoad, load) {
//The `ga` object is returned by this function. All the game
//engine's methods and properties are going to be attached to it.
var ga = {};
/*
### Initialize the game engine
All of Ga's intializtion code happens here.
*/
//Make the canvas element and add it to the DOM.
var dips = 1; //window.devicePixelRatio;
ga.canvas = document.createElement("canvas");
ga.canvas.setAttribute("width", width * dips);
ga.canvas.setAttribute("height", height * dips);
ga.canvas.style.backgroundColor = "black";
document.body.appendChild(ga.canvas);
//Create the context as a property of the canvas.
ga.canvas.ctx = ga.canvas.getContext("2d");
//Make the `stage`. The `stage` is the root container group
//for all the sprites and other groups.
ga.stage = makeStage();
//Initialize the pointer.
ga.pointer = makePointer();
//Make the keyboard keys (arrow keys and space bar.)
ga.key = makeKeys();
//An array to hold all the button sprites.
ga.buttons = [];
//Set `dragAndDrop` to `false` by default
//(Change it to `true` and set the `draggable` property on sprites
//to `true` to enable drag and drop.
ga.dragAndDrop = false;
//An array to store the draggable sprites.
ga.draggableSprites = [];
//An array to store the tweening functions.
ga.tweens = [];
//Set the game `state`.
ga.state = undefined;
//Set the user-defined `load` and `setup` states.
ga.load = load || undefined;
ga.setup = setup || undefined;
//The `setup` function is required, so throw an error if it's
//missing.
if (ga.setup === undefined) {
throw new Error(
"Please supply the setup function in the constructor"
);
}
//Get the user-defined array that lists the assets
//that have to load.
ga.assetFilePaths = assetsToLoad || undefined;
//A Boolean to let us pause the game.
ga.paused = false;
//The upper-limit frames per second that the game should run at.
//Ga defaults to 60 fps.
//Use the `fps` getter/setter to modify this value.
ga._fps = 60;
ga._startTime = Date.now();
ga._frameDuration = 1000 / ga._fps;
ga._lag = 0;
//Set sprite rendering position interpolation to
//`true` by default
ga.interpolate = true;
//An array that stores functions which should be run inside
//Ga's core `update` game loop. Just push any function you write
//into this array, and ga will run it in a continuous loop.
ga.updateFunctions = [];
/*
The canvas's x and y scale. These are set by getters and setter in
the code ahead. The scale is used in the `makeInteractive`
function for correct hit testing between the pointer and sprites
in a scaled canvas. Here's some application code you can use to
scale the Ga canvas to fit into the maximum size of the browser
window.
var scaleX = g.canvas.width / window.innerWidth,
scaleY = g.canvas.height / window.innerHeight,
//Or, scale to the height
//scaleX = window.innerWidth / g.canvas.width,
//scaleY = window.innerHeight / g.canvas.height,
scaleToFit = Math.min(scaleX, scaleY);
g.canvas.style.transformOrigin = "0 0";
g.canvas.style.transform = "scale(" + scaleToFit + ")";
//Set Ga's scale
g.scale = scaleToFit;
*/
//The game's screen's scale.
ga.scale = 1;
/*
### Core game engine methods
This next sections contains all the important methods that the game engine needs to do its work.
*/
//### gameLoop
//The engine's game loop. Ga uses a fixed timestep for logic update
//and rendering. This is mainly for simplicity. I'll probably
//migrate to a "fixed timestep / variable rendering" with
//interpolation in the
//next major update. For a working example, see:
//jsbin.com/tolime/1/edit
//If the `fps` isn't set, the maximum framerate is used.
//Use Ga's `fps` getter/setter (in the code ahead) to change the framerate
//
function gameLoop() {
requestAnimationFrame(gameLoop, ga.canvas);
if (ga._fps === undefined) {
//Run the code for each frame.
update();
ga.render(ga.canvas, 0);
}
//If `fps` has been set, clamp the frame rate to that upper limit.
else {
//Calculate the time that has elapsed since the last frame
var current = Date.now(),
elapsed = current - ga._startTime;
if (elapsed > 1000) elapsed = ga._frameDuration;
//For interpolation:
ga._startTime = current;
//Add the elapsed time to the lag counter
ga._lag += elapsed;
//Update the frame if the lag counter is greater than or
//equal to the frame duration
while (ga._lag >= ga._frameDuration) {
//Capture the sprites' previous positions for rendering
//interpolation
capturePreviousSpritePositions();
//Update the logic
update();
//Reduce the lag counter by the frame duration
ga._lag -= ga._frameDuration;
}
//Calculate the lag offset and use it to render the sprites
var lagOffset = ga._lag / ga._frameDuration;
ga.render(ga.canvas, lagOffset);
}
}
//### capturePreviousSpritePositions
//This function is run in the game loop just before the logic update
//to store all the sprites' previous positions from the last frame.
//It allows the render function to interpolate the sprite positions
//for ultra-smooth sprite rendering at any frame rate
function capturePreviousSpritePositions() {
ga.stage.children.forEach(function(sprite) {
setPosition(sprite);
});
function setPosition(sprite) {
sprite._previousX = sprite.x;
sprite._previousY = sprite.y;
if (sprite.children && sprite.children.length > 0) {
sprite.children.forEach(function(child) {
setPosition(child);
});
}
}
}
//### update
//The things that should happen in the game loop.
function update() {
//Render the canvas.
//ga.render(ga.canvas);
//Update all the buttons in the game.
if (ga.buttons.length > 0) {
ga.canvas.style.cursor = "auto";
for (var i = ga.buttons.length - 1; i >= 0; i--) {
var button = ga.buttons[i];
button.update(ga.pointer, ga.canvas);
if (button.state === "over" || button.state === "down") {
//If the button (or interactive sprite) isn't the actual
//stage itself, change the cursor to a pointer.
if (!button.stage) {
ga.canvas.style.cursor = "pointer";
}
}
}
}
//Update the pointer for drag and drop.
if (ga.dragAndDrop) {
ga.pointer.updateDragAndDrop();
}
//Run the current game `state` function if it's been defined and
//the game isn't `paused`.
if (ga.state && !ga.paused) {
ga.state();
}
/*
Loop through all the functions in the `updateFunctions` array
and run any functions it contains. You can add any of your
own custom functions to this array like this:
var customFunction = function() {console.log("I'm in the game loop!);}
ga.updateFunctions.push(customFunction);
See the see the code in the `particleEffect` and `enableFullscreen`
section of the `plugins.js` file to see typical examples of how code can be
added to the game loop like this.
*/
if (ga.updateFunctions.length !== 0) {
for (var l = 0; l < ga.updateFunctions.length; l++) {
var updateFunction = ga.updateFunctions[l];
updateFunction();
}
}
}
//### start
//The `start` method that gets the whole engine going. This needs to
//be called by the user from the game application code, right after
//Ga is instantiated.
ga.start = function() {
if (ga.assetFilePaths) {
//Use the supplied file paths to load the assets then run
//the user-defined `setup` function.
ga.assets.whenLoaded = function() {
//Clear the game `state` function for now to stop the loop.
ga.state = undefined;
//Call the `setup` function that was supplied by the user in
//Ga's constructor.
ga.setup();
};
ga.assets.load(ga.assetFilePaths);
//While the assets are loading, set the user-defined `load`
//function as the game state. That will make it run in a loop.
//You can use the `load` state to create a loading progress bar.
if (ga.load) {
ga.state = ga.load;
}
}
//If there aren't any assets to load,
//just run the user-defined `setup` function.
else {
ga.setup();
}
//Start the game loop.
gameLoop();
};
//### pause and resume
//Next are a few convenience methods for interacting with the game engine.
//This `pause` and `resume` methods start and stop the game loop to
//allow you to run functions that should only execute once.
ga.pause = function() {
ga.paused = true;
};
ga.resume = function() {
ga.paused = false;
};
//### hidePointer and showPointer
//Use `hidePointer` and `showPointer` to hide and display the
//pointer.
ga.hidePointer = function() {
ga.canvas.style.cursor = "none";
};
ga.showPointer = function() {
ga.canvas.style.cursor = "auto";
};
//Getters and setters for various game engine properties.
Object.defineProperties(ga, {
//### fps
//The `fps` getter/setter. Use it to set the frame rate.
fps: {
get: function() {
return ga._fps;
},
set: function(value) {
ga._fps = value;
ga._startTime = Date.now();
ga._frameDuration = 1000 / ga._fps;
},
enumerable: true,
configurable: true
},
//### backgroundColor
//Set the background color.
backgroundColor: {
set: function(value) {
ga.canvas.style.backgroundColor = value;
},
enumerable: true,
configurable: true
}
});
/*
Chapter 2: Sprites
This chapter contains all the code for Ga's scene graph and sprite system. Ga has 6 built-in sprite types
that have a wide range of applications for making games.
- `circle`: Circles with fill and stroke colors.
- `rectangle`: Rectangles with fill and stroke colors.
- `line`: Lines with a color, width, and start and end points.
- `text`: Single line dynamic text objects.
- `sprite`: A versatile sprite that can be made from a single image, a frame in a texture atlas,
a series of frames in sequence on a tileset or a series of frames in a texture atlas.
- `button`: An interactive button with three states (up, over and down)
and user-definable `press` and `release` actions.
All sprites can be nested inside other sprites with an `addChild` method, and parent
sprites have their own local coordinate system. Compose them together to make really complex game objects.
There are also two special sprites:
- `group`: This is a generic parent container is just used to group related sprites together.
Its `width` and `height` can be assigned manually but, if they aren't, the group's `width`
and `height` will match the area taken up by its children.
- `stage`: this is a special group that is created by the Ga engine when it's initialized. The
`stage` is the root container that contains everything in the game.
Use these building blocks for making most of the kinds of things you'll need in your games.
When sprites are created, they're assigned all of their basic properties with the help of a method called
`makeDisplayObject`. This gives the sprites all their default properties. After `makeDisplayObject` runs,
each sprite type is customized but their own constructor methods.
*/
//### makeDisplayObject
//`makeDisplayObject` assigns properties that are common for all the sprite types.
function makeDisplayObject(o) {
//Initialize the position
o.x = 0;
o.y = 0;
//Initialize the velocity.
o.vx = 0;
o.vy = 0;
//Initialize the `width` and `height`.
o.width = 0;
o.height = 0;
//The sprite's width and height scale factors.
o.scaleX = 1;
o.scaleY = 1;
//The sprite's pivot point, which is its center of rotation.
//This is a percentage between 0.01 and 0.99.
o.pivotX = 0.5;
o.pivotY = 0.5;
//The sprite's rotation and visibility.
o.rotation = 0;
o.visible = true;
//Leave the sprite's `parent` as `undefined` for now.
//(Most will be added as children to the `stage` at a later step.)
o.parent = undefined;
//Is this the `stage` object? This will be `false` for every
//sprite, except the `stage`.
o.stage = false;
//Optional drop shadow properties.
//Set `shadow` to `true` if you want the sprite to display a
//shadow.
o.shadow = false;
o.shadowColor = "rgba(100, 100, 100, 0.5)";
o.shadowOffsetX = 3;
o.shadowOffsetY = 3;
o.shadowBlur = 3;
//Optional blend mode
o.blendMode = undefined;
//The sprite's private properties that are just used for internal
//calculations. All these properties will be changed or accessed through a matching getter/setter
o._alpha = 1;
o._draggable = undefined;
//The sprite's depth layer.
o._layer = 0;
//Is the sprite circular? If it is, it will be given a `radius`
//and `diameter`.
o._circular = false;
//Is the sprite `interactive`? If it is, it can become clickable
//or touchable.
o._interactive = false;
//properties to store the x and y positions from the previous
//frame. Use for rendering interpolation
o._previousX = undefined;
o._previousY = undefined;
//Add the sprite's container properties so that you can have
//a nested parent/child scene graph hierarchy.
//Create a `children` array that contains all the
//in this container.
o.children = [];
//The `addChild` method lets you add sprites to this container.
o.addChild = function(sprite) {
//Remove the sprite from its current parent, if it has one, and
//the parent isn't already this object
if (sprite.parent) {
sprite.parent.removeChild(sprite);
}
//Make this object the sprite's parent and
//add it to this object's `children` array.
sprite.parent = o;
o.children.push(sprite);
//Calculate the sprite's new width and height
//o.calculateSize();
};
//The `removeChild` method lets you remove a sprite from its
//parent container.
o.removeChild = function(sprite) {
if (sprite.parent === o) {
o.children.splice(o.children.indexOf(sprite), 1);
} else {
throw new Error(sprite + "is not a child of " + o);
}
//Calculate the sprite's new width and height
//o.calculateSize();
};
//Dynamically calculate the width and height of the sprite based
//on the size and position of the children it contains
/*
o.calculateSize = function() {
//Calculate the width based on the size of the largest child
//that this sprite contains
if (o.children.length > 0 && o.stage === false) {
for(var i = 0; i < o.children.length - 1; i++) {
var child = o.children[i];
if (child.x + child.width > o.width) {
o.width = child.x + child.width;
}
if (child.y + child.height > o.height) {
o.height = child.y + child.height;
}
}
}
};
*/
//Swap the depth layer positions of two child sprites
o.swapChildren = function(child1, child2) {
var index1 = o.children.indexOf(child1),
index2 = o.children.indexOf(child2);
if (index1 !== -1 && index2 !== -1) {
//Swap the indexes
child1.childIndex = index2;
child2.childIndex = index1;
//Swap the array positions
o.children[index1] = child2;
o.children[index2] = child1;
} else {
throw new Error(child + " Both objects must be a child of the caller " + o);
}
}
//`add` and `remove` convenience methods let you add and remove
//many sprites at the same time.
o.add = function(spritesToAdd) {
var sprites = Array.prototype.slice.call(arguments);
if (sprites.length > 1) {
sprites.forEach(function(sprite) {
o.addChild(sprite);
});
} else {
o.addChild(sprites[0]);
}
};
o.remove = function(spritesToRemove) {
var sprites = Array.prototype.slice.call(arguments);
if (sprites.length > 1) {
sprites.forEach(function(sprite) {
o.removeChild(sprite);
});
} else {
o.removeChild(sprites[0]);
}
};
//A `setPosition` convenience function to let you set the
//x any y position of a sprite with one line of code.
o.setPosition = function(x, y) {
o.x = x;
o.y = y;
};
//The `put` methods are conveniences that help you position a
//another sprite in and around this sprite.
//First, get a short form reference to the sprite to make the code more
//easier to read
var a = o;
//Center a sprite inside this sprite. `xOffset` and `yOffset`
//arguments determine by how much the other sprite's position
//should be offset from the center. These methods use the
//sprites' global coordinates (`gx` and `gy`).
//In all these functions, `b` is the second sprite that is being
//positioned relative to the first sprite (this one), `a`.
//Center `b` inside `a`.
o.putCenter = function(b, xOffset, yOffset) {
xOffset = xOffset || 0;
yOffset = yOffset || 0;
b.x = (a.x + a.halfWidth - b.halfWidth) + xOffset;
b.y = (a.y + a.halfHeight - b.halfHeight) + yOffset;
//Compensate for the parent's position
o.compensateForParentPosition(a, b);
};
//Position `b` above `a`.
o.putTop = function(b, xOffset, yOffset) {
xOffset = xOffset || 0;
yOffset = yOffset || 0;
b.x = (a.x + a.halfWidth - b.halfWidth) + xOffset;
b.y = (a.y - b.height) + yOffset;
//Compensate for the parent's position
o.compensateForParentPosition(a, b);
};
//Position `b` to the right of `a`.
o.putRight = function(b, xOffset, yOffset) {
xOffset = xOffset || 0;
yOffset = yOffset || 0;
b.x = (a.x + a.width) + xOffset;
b.y = (a.y + a.halfHeight - b.halfHeight) + yOffset;
//Compensate for the parent's position
o.compensateForParentPosition(a, b);
};
//Position `b` below `a`.
o.putBottom = function(b, xOffset, yOffset) {
xOffset = xOffset || 0;
yOffset = yOffset || 0;
b.x = (a.x + a.halfWidth - b.halfWidth) + xOffset;
b.y = (a.y + a.height) + yOffset;
//Compensate for the parent's position
o.compensateForParentPosition(a, b);
};
//Position `b` to the left of `a`.
o.putLeft = function(b, xOffset, yOffset) {
xOffset = xOffset || 0;
yOffset = yOffset || 0;
b.x = (a.x - b.width) + xOffset;
b.y = (a.y + a.halfHeight - b.halfHeight) + yOffset;
//Compensate for the parent's position
o.compensateForParentPosition(a, b);
};
//`compensateForParentPosition` is a helper funtion for the above
//`put` methods that subracts the parent's global position from
//the nested child's position.
o.compensateForParentPosition = function(a, b) {
if (b.parent.gx !== 0 || b.parent.gy !== 0) {
b.x -= a.gx;
b.y -= a.gy;
}
}
//Getters and setters for the sprite's internal properties.
Object.defineProperties(o, {
//`gx` and `gy` getters and setters represent the sprite's
//global coordinates.
gx: {
get: function() {
if (this.parent) {
//The sprite's global x position is a combination of
//its local x value and its parent's global x value
return this.x + this.parent.gx;
} else {
return this.x;
}
},
enumerable: true,
configurable: true
},
gy: {
get: function() {
if (this.parent) {
return this.y + this.parent.gy;
} else {
return this.y;
}
},
enumerable: true,
configurable: true
},
//A `position` getter. It's a convenience that lets you get and
//set the sprite's position as an object with x and y values.
position: {
get: function() {
return {
x: o.x,
y: o.y
};
},
enumerable: true,
configurable: true
},
//An `alpha` getter/setter. The sprite's `alpha` (transparency) should match its
//parent's `alpha` value.
alpha: {
get: function() {
//Find out the sprite's alpha relative to its parent's alpha
var relativeAlpha = o.parent._alpha * o._alpha;
return relativeAlpha;
},
set: function(value) {
o._alpha = value;
},
enumerable: true,
configurable: true
},
//The sprite's `halfWidth` and `halfHeight`.
halfWidth: {
get: function() {
return o.width / 2;
},
enumerable: true,
configurable: true
},
halfHeight: {
get: function() {
return o.height / 2;
},
enumerable: true,
configurable: true
},
//The sprite's center point.
centerX: {
get: function() {
return o.x + o.halfWidth;
},
enumerable: true,
configurable: true
},
centerY: {
get: function() {
return o.y + o.halfHeight;
},
enumerable: true,
configurable: true
},
//The sprite's depth layer. All sprites and groups have their depth layer
//set to `0` when their first created. If you want to force a
//sprite to appear above another sprite, set its `layer` to a
//higher number.
layer: {
get: function() {
return o._layer;
},
set: function(value) {
o._layer = value;
o.parent.children.sort(byLayer);
},
enumerable: true,
configurable: true
},
//The `circular` property lets you define whether a sprite
//should be interpreted as a circular object. If you set
//`circular` to `true`, the sprite is sent to the `makeCircular`
//function where its given `radius` and `diameter` properties.
circular: {
get: function() {
return o._circular;
},
set: function(value) {
//Give the sprite `diameter` and `radius` properties
//if `circular` is `true`.
if (value === true && o._circular === false) {
makeCircular(o);
o._circular = true;
}
//Remove the sprite's `diameter` and `radius` properties
//if `circular` is `false`.
if (value === false && o._circular === true) {
delete o.diameter;
delete o.radius;
o._circular = false;
}
},
enumerable: true,
configurable: true
},
//Is the sprite draggable by the pointer? If `draggable` is set
//to `true`, the sprite is added to Ga's `draggableSprites`
//array. All the sprites in `draggableSprites` are updated each
//frame to check whether they're being dragged.
draggable: {
get: function() {
return o._draggable;
},
set: function(value) {
//If it's `true` push the sprite into the `draggableSprites`
//array.
if (value === true) {
ga.draggableSprites.push(o);
o._draggable = true;
//If Ga's `dragAndDrop` property is `false`, set it to
//`true`
if (ga.dragAndDrop === false) ga.dragAndDrop = true;
}
//If it's `false`, remove it from the `draggableSprites` array.
if (value === false) {
ga.draggableSprites.splice(ga.draggableSprites.indexOf(o), 1);
}
},
enumerable: true,
configurable: true
},
//Is the sprite interactive? If `interactive` is set to `true`,
//the sprite is run through the `makeInteractive` method.
//`makeInteractive` makes the sprite sensitive to pointer
//actions. It also adds the sprite to the Ga's `buttons` array,
//which is updated each frame in the `ga.update` method.
interactive: {
get: function() {
return o._interactive;
},
set: function(value) {
if (value === true) {
//Add interactive properties to the sprite
//so that it can act like a button.
makeInteractive(o);
o._interactive = true;
}
if (value === false) {
//Remove the sprite's reference from the game engine's
//`buttons` array so that it it's no longer affected
//by mouse and touch interactivity.
ga.buttons.splice(ga.buttons.indexOf(o), 1);
o._interactive = false;
}
},
enumerable: true,
configurable: true
},
//The `localBounds` and `globalBounds` methods return an object
//with `x`, `y`, `width`, and `height` properties that define
//the dimensions and position of the sprite. This is a convenience
//to help you set or test boundaries without having to know
//these numbers or request them specifically in your code.
localBounds: {
get: function() {
var rectangle = {
x: 0,
y: 0,
width: o.width,
height: o.height
};
return rectangle;
},
enumerable: true,
configurable: true
},
globalBounds: {
get: function() {
rectangle = {
x: o.gx,
y: o.gy,
width: o.gx + o.width,
height: o.gy + o.height
};
return rectangle;
},
enumerable: true,
configurable: true
},
//`empty` is a convenience property that will return `true` or
//`false` depending on whether or not this sprite's `children`
//array is empty.
empty: {
get: function() {
if (o.children.length === 0) {
return true;
} else {
return false;
}
},
enumerable: true,
configurable: true
}
});
};
//### remove
//`remove` is a global convenience method that will
//remove any sprite, or an argument list of sprites, from its parent.
ga.remove = function(spritesToRemove) {
var sprites = Array.prototype.slice.call(arguments);
//Remove sprites that's aren't in an array
if (!(sprites[0] instanceof Array)) {
if (sprites.length > 1) {
sprites.forEach(function(sprite) {
sprite.parent.removeChild(sprite);