1 /*
  2  ___               
  3 | _ \_  _ _ _ __ __
  4 |  _/ || | ' \\ \ /
  5 |_|  \_,_|_||_/_\_\
  6                    
  7 */
  8 /**
  9  * @author magicmarkers
 10  * @description A derpy HTML5/JS engine for side-scrolling brawler / beat 'em up / punchy-fighty games.
 11  * @see https://github.com/magicmarkers/Punx
 12  */
 13 
 14 // TODO: change collision engine, make pixel perfect. keep depth in mind.
 15 // TODO: get audio stuff working
 16 // TODO: parallax backgrounds
 17 // TODO: boilerplate baddy AI
 18 // TODO: boilerplate post-hit temporary invulnerability/blinkage for player
 19 // TODO: boilerplate talk bubbles
 20 
 21 /**
 22  * @namespace Punx
 23  */
 24 var Punx = {};
 25 /*
 26  ___                      ___     _    _ 
 27 | _ \_  _ _ _ __ __      / __|_ _(_)__| |
 28 |  _/ || | ' \\ \ /  _  | (_ | '_| / _` |
 29 |_|  \_,_|_||_/_\_\ (_)  \___|_| |_\__,_|
 30                                          
 31 */
 32 /**
 33  * @class Represents the game engine. Houses the main loop and canvas DOM node reference.
 34  * @param {object} args Public field construction values.
 35  */
 36 var Punx.Grid = function(args) {
 37 
 38     /**
 39      * Required: Object holding all screens, keyed by name.
 40      * @type object.<string,Punx.Screen>
 41      * @public
 42      */
 43     this.screens = args.screens;
 44 
 45     /**
 46      * Required: Key of the initial screen.
 47      * @public
 48      * @type string
 49      */
 50     this.screen = args.screen
 51 
 52     /**
 53      * Integer canvas width.
 54      * @public
 55      * @type number
 56      * @default 960
 57      */
 58     this.w = args.w || 960;
 59 
 60     /**
 61      * Integer canvas height.
 62      * @public
 63      * @type number
 64      * @default 540
 65      */
 66     this.h = args.h || 540;
 67 
 68     /**
 69      * Integer frames-per-second.
 70      * @public
 71      * @type number
 72      * @default 60
 73      */
 74     this.fps = args.fps || 60;
 75 
 76     /**
 77      * For debugging, causes the main loop to idle when true.
 78      * @public
 79      * @type boolean
 80      * @default false
 81      */
 82     this.idle = false;
 83 
 84     /**
 85      * Internal frame counter, increments once after every update cycle, wraps at fps. This should be treated as read-only.
 86      * @public
 87      * @type number
 88      * @default 0
 89      */
 90     this.frame = 0;
 91 
 92     /**
 93      * Viewport canvas context.
 94      * @public
 95      */
 96     this.ctx = null;
 97 
 98     /**
 99      * Imaginary canvas context.
100      * @public
101      */
102     this.ictx = null;
103 
104     /**
105      * Viewport canvas DOM element. This is displayed.
106      * @private
107      */
108     this.canvas = null;
109 
110     /**
111      * Imaginary canvas DOM element used as a frame-buffer for collision detection and other graphics analysis. Scratch-space. Never shown.
112      * @private
113      */
114     this.icanvas = null;
115 
116     /**
117      * The game loop, an interval that calls update() on the current screen, timed according to fps.
118      * @private
119      */
120     this.loop = null;
121 
122     /**
123      * Creates the canvases and injects the main canvas into the viewport.
124      * @public
125      * @param [parentElement="document.body"] DOM element to make the canvas a child of.
126      */
127     this.create = function(parentElement) {
128         // viewport canvas
129         this.canvas = document.createElement('canvas');
130         this.canvas.setAttribute('width',this.w);
131         this.canvas.setAttribute('height',this.h);
132         this.canvas.setAttribute('tabindex',0);
133         this.canvas.addEventListener('keydown',Punx.Key.onkeydown,true);
134         this.canvas.addEventListener('keyup',Punx.Key.onkeyup,true);
135         this.ctx = this.canvas.getContext('2d');
136         this.ctx.imageSmoothingEnabled = false;
137         this.ctx.mozImageSmoothingEnabled = false;
138         this.ctx.webkitImageSmoothingEnabled = false;
139         (parentElement||document.body).appendChild(this.canvas);
140         this.canvas.focus();
141 
142         // imaginary canvas
143         this.icanvas = document.createElement('canvas');
144         this.icanvas.setAttribute('width',this.w);
145         this.icanvas.setAttribute('height',this.h);
146         this.ictx = this.icanvas.getContext('2d');
147         this.ictx.imageSmoothingEnabled = false;
148         this.ictx.mozImageSmoothingEnabled = false;
149         this.ictx.webkitImageSmoothingEnabled = false;
150     };
151 
152     /**
153      * Starts the main loop.
154      * @public
155      */
156     this.start = function() {
157         this.loop = window.setInterval(this.update.bind(this),1000/this.fps);
158     };
159 
160     /**
161      * The main loop calls this once every fps. The loop is killed if any errors occur. Does nothing if idle is true.
162      * @private
163      */
164     // the main loop calls this once every FPS.
165     // kills the loop upon error. does nothing if idle is true
166     this._update = function() {
167         if (this.idle) return;
168         try {
169             this.screen.update();
170             this.frame = ++this.frame % this.fps;
171         }
172         catch (e) {
173             console.log(e.stack);
174             window.clearInterval(this._loop);
175         }
176     };
177 
178 };
179 
180 
181 /*
182  ___                     _   _ _   _ _ 
183 | _ \_  _ _ _ __ __     | | | | |_(_) |
184 |  _/ || | ' \\ \ /  _  | |_| |  _| | |
185 |_|  \_,_|_||_/_\_\ (_)  \___/ \__|_|_|
186                                        
187 */
188 
189 Punx.Util = new function() {
190 
191     this.PHI = (1+Math.sqrt(5))/2; // golden ratio
192     this.PHI_A = 1 - this.PHI;
193     this.PHI_B = 1 - this.PHI_A;
194 
195     // determines if two boxes, a and b, are intersecting
196     this.AABB = function(a,b) {
197         return (!(
198             a.x + a.w < b.x || // a too left
199             a.x > b.x + b.w || // a too right
200             a.y + a.h < b.y || // a too bg (y)
201             a.y > b.y + b.h    // a too fg (y)
202         ));
203     };
204 
205     // returns a box for the intersection of boxes a and b
206     // this may return strange things if they aren't actually intersecting. use AABB first.
207     this.AABBC = function(a,b) {
208         return new function() {
209             this.x = a.x < b.x ? b.x : a.x;
210             this.y = a.y < b.y ? b.y : a.y;
211             this.w = (a.x + a.w < b.x + b.w ? a.x + a.w : b.x + b.w) - this.x;
212             this.h = (a.y + a.h < b.y + b.h ? a.y + a.h : b.y + b.h) - this.y;
213         };
214     };
215 
216     // imagines a box and returns a list of sprites it AABB intersects.
217     // use this for instantaneous projectiles or other things from afar.
218     // sprites is a list of sprite instances, and box is an object with x,y,w,h
219     this.scan = function(box,sprites) {
220         var intersections = [];
221         for (var i in sprites) {
222             if (Punx.Util.AABB(box,sprites[i])) intersections.append(sprites[i]);
223         }
224         return intersections;
225     };
226 
227     // returns a sorted list of sprites according to their proximity to the origin x
228     this.xSort = function(sprites,x) {
229         x=x||0;
230         return sprites.sort(function(a,b) {
231             if (a.x + a.w < x) { // a is left
232                 if (b.x + b.w < x) { // b is left
233                     return (a.x + a.w > b.x + b.w ? -1 : 1);
234                 }
235                 else { // b is right
236                     return (x - (a.x + a.w) < b.x - x ? -1 : 1);
237                 }
238             }
239             else { // a is right
240                 if (b.x > x) { // b is right
241                     return (a.x < b.x ? -1 : 1);
242                 }
243                 else { // b is left
244                     return (a.x - x < x - (b.x + b.w) ? -1 : 1);
245                 }
246             }
247         });
248     };
249 
250     // returns a sorted list of sprites according to their proximity to the origin y
251     this.ySort = function(sprites,y) {
252         y=y||0;
253         return sprites.sort(function(a,b) {
254             if (a.y + a.h < y) { // a is above
255                 if (b.y + b.h < y) { // b is above
256                     return (a.y + a.h > b.y + b.h ? -1 : 1);
257                 }
258                 else { // b is below
259                     return (y - (a.y + a.h) < b.y - y ? -1 : 1);
260                 }
261             }
262             else { // a is below
263                 if (b.y > y) { // b is below
264                     return (a.y < b.y ? -1 : 1);
265                 }
266                 else { // b is above
267                     return (a.y - y < y - (b.y + b.h) ? -1 : 1);
268                 }
269             }
270         });
271     };
272 
273 };
274 
275 
276 /*
277   ___     _    _       _  __         
278  / __|_ _(_)__| |     | |/ /___ _  _ 
279 | (_ | '_| / _` |  _  | ' </ -_) || |
280  \___|_| |_\__,_| (_) |_|\_\___|\_, |
281                                 |__/ 
282 */
283 // keyboard stuff
284 
285 Punx.Key = new function() {
286 
287     // key name => bool state
288     // sprites should poll this during update() and act accordingly.
289     // sprites should use internal flags to determine if they can be interrupted.
290     // example: if (Punx.Key.state['left'] && !this.busy) ...
291     this.state = {};
292 
293     // keyCode => key name
294     this._keys = {
295         '9':'tab',
296         '13':'enter',
297         '27':'escape',
298         '32':'space',
299         '37':'left',
300         '38':'up',
301         '39':'right',
302         '40':'down'
303     };
304     for (var i=48;i<=57;i++) { this._keys[i] = String.fromCharCode(i); } // numbers
305     for (var i=65;i<=90;i++) { this._keys[i] = String.fromCharCode(i).toLowerCase(); } // letters
306 
307     // document's keydown listener
308     this._keydown = function(e) {
309         e=e||window.event;
310         if (e.altGraphKey || e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) return;
311         var key = this._keys[e.keyCode];
312         if (!key) return;
313         e.preventDefault();
314         e.stopPropagation();
315         this.state[key] = true;
316     }.bind(this); // event handlers are wacky and need "this" explicitly bound
317 
318     // document's keyup listener
319     this._keyup = function(e) {
320         e=e||window.event;
321         var key = this._keys[e.keyCode];
322         if (!key) return;
323         e.preventDefault();
324         e.stopPropagation();
325         this.state[key] = false;
326     }.bind(this); // event handlers are wacky and need "this" explicitly bound
327 
328 };
329 
330 
331 /*
332   ___     _    _       ___                   _ 
333  / __|_ _(_)__| |     / __| ___ _  _ _ _  __| |
334 | (_ | '_| / _` |  _  \__ \/ _ \ || | ' \/ _` |
335  \___|_| |_\__,_| (_) |___/\___/\_,_|_||_\__,_|
336                                                
337 */
338 // in progress
339 Audio.prototype.fadeTo = function(fadeTo,callback) {
340     if (fadeTo < 0) fadeTo = 0;
341     else if (fadeTo > 1) fadeTo = 1;
342     var that = this;
343     var setVolume = function(setTo) {
344         return function() {
345             that.volume = setTo;
346             if (setTo == fadeTo && callback) callback();
347         };
348     };
349     var delay = 0;
350     if (this.volume < fadeTo) {
351         for (var v=this.volume; v<=fadeTo; v+=0.1) {
352             setTimeout(setVolume(v),delay);
353             delay+=100;
354         }
355     }
356     else if (volume > fadeTo) {
357         for (var v=this.volume; v>=fadeTo; v-=0.1) {
358             setTimeout(setVolume(v),delay);
359             delay+=100;
360         }
361     }
362     else if (callback) callback();
363 };
364 
365 Punx.Sound = new function() {
366 
367     this.music = new Audio(); // swap out for one in Library
368 
369     this.muted = false;
370     this.mute = function() {
371         this.muted ^= 1;
372         this.music.muted = this.muted;
373     };
374 
375 };
376 
377 
378 /*
379   ___     _    _       ___                      
380  / __|_ _(_)__| |     / __| __ _ _ ___ ___ _ _  
381 | (_ | '_| / _` |  _  \__ \/ _| '_/ -_) -_) ' \ 
382  \___|_| |_\__,_| (_) |___/\__|_| \___\___|_||_|
383                                                 
384 */
385 // a screen is a collection of sprites, such as a menu. levels are stages and extend this.
386 // only one screen is drawn to the canvas.
387 
388 Punx.Screen = function(args) {
389 
390     // required
391     this.grid = args['grid']; // grid reference
392 
393     // optional
394     this.sprites = args['sprites']||[]; // list of Punx.Sprite instances
395     this.bg = args['bg']; // Image
396 
397     // called by the grid. screens should draw themselves to the grid canvas context if they need to.
398     // this notifies sprites that they should "step" or otherwise incrementally change themselves if needed.
399     // sprites should return boolean of whether or not they changed
400     this.update = function() {
401         var updated = false;
402         for (var i in this.sprites) {
403             updated |= this.sprites[i].update();
404         }
405         if (updated) this._draw();
406     };
407 
408     // draw the screen
409     // sprites are drawn from "back to front", that is, sprites with more "depth" are drawn first as to mimic layering.
410     this._draw = function() {
411         this.grid.ctx.clearRect(0,0,this.grid.w,this.grid.h);
412         if (this.bg) this.grix.ctx.drawImage(this.bg,0,0);
413         for (var i in this.sprites.sort(Punx.Sprite.sort)) {
414             this.sprites[i].draw(this.grid.ctx);
415         }
416     };
417 };
418 
419 
420 /*
421   ___     _    _       ___                            ___ _                 
422  / __|_ _(_)__| |     / __| __ _ _ ___ ___ _ _       / __| |_ __ _ __ _ ___ 
423 | (_ | '_| / _` |  _  \__ \/ _| '_/ -_) -_) ' \   _  \__ \  _/ _` / _` / -_)
424  \___|_| |_\__,_| (_) |___/\__|_| \___\___|_||_| (_) |___/\__\__,_\__, \___|
425                                                                   |___/     
426 */
427 // stages are screens that are game levels. they have "curtains" and a "floor".
428 // imagine looking upon a theater stage from an ideal balcony seat.
429 // curtains are used for backgrounds and parallax.
430 // the floor is the "depth" where actors move away from and toward the "front" of the stage.
431 // the defaults define the curtains and floor in terms of the golden ratio:
432 // http://en.wikipedia.org/wiki/Golden_ratio
433 // where the longer portion represents the curtains and the shorter portion is the floor.
434 
435 Punx.Screen.Stage = function(args) {
436     Punx.Screen.call(this,args);
437 
438     // required
439     this.player = args['player']; // Punx.Sprite.Actor.Player instance
440 
441     // optional
442     this.w = args['w'] || 960; // width of the stage (can go beyond viewport for panning)
443     this.y = args['y'] || 334; // height of curtains
444     this.h = args['h'] || 206; // height ("depth") of floor.
445     this.x = args['x'] || 0; // starting pan amount from stage left
446     this.panMargin = 200; // player distance from frame edge to start panning
447     this.pannable = args['pannable']||true; // whether to pan
448 
449     // private stuff temporary to update()
450     this._sprites = []; // all in-frame sprites
451     this._actors = []; // in-frame actors
452     this._walls = []; // in-frame walls
453 
454     this.update = function() {
455         var updated = false;
456         this._sprites = [];
457         this._walls = [];
458         this._actors = [];
459 
460         // pan
461         if (this.pannable) with (this) {
462             // pan left if: frame left > stage left && player left < frame left margin
463             if (x > 0 && player.x < x + panMargin) {
464                 // amount: margin - (player left - frame left)
465                 x -= panMargin - (player.x - x);
466                 if (x < 0) x = 0; // stop at min left
467             }
468             // pan right if: frame right < stage right && player right > frame right margin
469             else if (x + grid.w < w && player.x + player.w > x + grid.w - panMargin) {
470                 // amount: margin - ((frame right) - (player right))
471                 x += panMargin - ((x + grid.w) - (player.x + player.w));
472                 if (x > w - grid.w) x = w - grid.w; // stop at max right
473             }
474         }
475 
476         // determine active sprites, in-frame and frame-edged
477         for (var i in this.sprites) {
478             var s = this.sprites[i];
479             if (s.x + s.w < this.x || s.x > this.x + this.grid.w) continue; // sprite is out of frame
480             this._sprites.push(s);
481             if (s instanceof Punx.Sprite.Wall) this._walls.push(s);
482             else if (s instanceof Punx.Sprite.Actor) this._actors.push(s);
483         }
484 
485         // update active sprites. sprites should return boolean of whether they updated
486         for (var i in this._sprites.sort(Punx.Sprite.sort)) {
487             updated |= this._sprites[i].update();
488         }
489 
490         if (updated) {
491             this._draw(); // draw the screen
492             this._collisions(); // detect collisions and notify sprites after drawing so "reactions" occur next update cycle
493         }
494 
495     };
496 
497     // returns boolean. actors should call this during their update() to determine if they can move to a given position
498     this.clipping = function(actor,x,y) {
499         // ensure actor stays on stage floor vertically. their "depth" is determined by the PHI_B of their height
500         if (y < this.y - (actor.h * Punx.Const.PHI_B) || y + actor.h > this.y + this.h) return true;
501 
502         // ensure actor can only walk into frame horizontally, not out of frame
503         if (x < this.x || x + actor.w > this.x + this.grid.w) return true;
504 
505         // ensure actor doesn't walk through walls
506         for (var i in this._walls) {
507             if (Punx.Util.AABB(this._walls[i],actor.w,actor.h)) return true;
508         }
509     };
510 
511     // draws the screen
512     this._draw = function() {
513         this.grid.ctx.clearRect(0,0,this.grid.w,this.grid.h);
514         for (var i in this._sprites.sort(Punx.Sprite.sort)) {
515             this._sprites[i].draw(this.grid.ctx);
516         }
517     };
518 
519     this._collisions = function() {
520         // check in-frame actors for collision
521         // this performs n(n-1)/2 AABB checks, subchecking pixels in overlapping areas
522         // a pixel is only considered collidable if its opacity is >=127
523         // when two actors collide, both are notified via actor.collide(stage,other actor)
524         // the order of notification is undefined. actors should only "be collided", not do things to effect the other actor as it might not be notified yet.
525         // TODO "z" axis
526         var _actorsLength = this._actors.length;
527         for (var i=0;i<_actorsLength;i++) {
528             var a = this._actors[i];
529             for (var j=i+1;j<_actorsLength;j++) {
530                 var b = this._actors[j];
531                 if (!Punx.Util.AABB(a,b)) continue; // no box collision, move on.
532 
533                 // triggers don't need pixel detection, bounding box is enough
534                 if (a instanceof Punx.Sprite.Actor.Trigger || b instanceof Punx.Sprite.Actor.Trigger) {
535                     a.collide(b);
536                     b.collide(a);
537                     continue;
538                 }
539 
540                 // moving on to pixel detection,
541                 // determine intersection
542                 var c = Punx.Util.AABBC(a,b);
543 
544                 // get separate slices of a and b at the intersection using the scratch canvas
545                 this.grid.ictx.clearRect(a.x-this.x,a.y,a.w,a.h); // clear scratch space
546                 this.grid.draw(a,this.grid.ictx); // draw scratch a
547                 c.a = this.grid.ictx.getImageData(c.x-this.x,c.y,c.w,c.h); // slice of a @ intersect
548                 this.grid.ictx.clearRect(b.x-this.x,b.y,b.w,b.h); // clear scratch space
549                 this.grid.draw(b,this.grid.ictx); // draw scratch b
550                 c.b = this.grid.ictx.getImageData(c.x-this.x,c.y,c.w,c.h); // slice of b @ intersect
551 
552                 // compare slices on a pixel-by-pixel basis according to a resolution
553                 var resolution = 5; // jump this many pixels for each check in order to speed it up.
554                                     // if set to 1, all pixels are checked.
555                 resolution *= 4;    // each pixel actually takes up 4 elements in the data.
556 
557                 var sliceLength = c.a.data.length;
558                 for (var p=0;p<sliceLength;p+=resolution) {
559                     // notify and break when two pixels collide
560                     // pixels are collidable when their opacity is >= 127
561                     if (c.a.data[p+3] >= 127 && c.b.data[p+3] >= 127) {
562                         a.collide(b);
563                         b.collide(a);
564                         break;
565                     }
566                 }
567             }
568         }
569     };
570 
571 }; Punx.Screen.Stage.prototype = new Punx.Screen();
572 
573 /*
574   ___     _    _       ___          _ _       
575  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___ 
576 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)
577  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___|
578                           |_|                 
579 */
580 // sprites are stage elements that have a drawable face
581 
582 Punx.Sprite = function(args) {
583 
584     // required
585     this.screen = args['screen']; // Punx.Screen reference
586     this.sheet = new Image();
587     this.sheet.src = args['sheet']; // sprite sheet url relative to html
588     this.frames = args['frames']; // { group name : [ [ x,y,w,h ] , ... ] , ... } where x,y,w,h is sheet cropping origin point
589     this.group = args['group']; // current frame group name
590 
591     // optional
592     this.rate = v['rate']||10; // updates per animation step
593     this.name = args['name']||'Sprite';
594     this.x = args['x']||0;
595     this.y = args['y']||0;
596     this.z = args['z']||1; // 0 rug, 1 standing, 2 in air, 3 sticky
597     this.w = args['w']||64;
598     this.h = args['h']||64;
599 
600     this._step = 0; // animation step index, increments once per rated update prior to step() being called, wraps at length of current group
601 
602     this.setGroup = function(name) {
603         this.group = name;
604         this._step %= this.frames[this.group].length; // wrap animation step index
605     };
606 
607     this.update = function() {
608         var updated = false;
609 
610         // issue a tick. this is the sprite's custom logic every grid fps.
611         updated |= this.tick();
612 
613         // rate-limited steps
614         if (this.screen.grid.frame % this.rate == 0) {
615             if (this.frames[this.group].length > 1) {
616                 updated = true;
617                 this._step = ++this._step % this.frames[this.group].length; // increment and wrap animation step index
618             }
619             updated |= this.step();
620         }
621 
622         return updated;
623     };
624 
625     // called whenever the screen calls update() on the sprite.
626     // use this to perform logic per grid frame, return boolean if the sprite is updated
627     this.tick = function() {
628     };
629 
630     // called every rated amount of grid frames
631     // use this to perform rated logic. it may be called whether or not an animation step was made, as groups can have a single frame.
632     // return boolean of whether the sprite is updated
633     this.step = function() {
634     };
635 
636     this.draw = function(ctx) {
637         var frame = this.frames[this.group][this._step];
638         ctx.drawImage(this.sheet,this.frame[0],this.frame[1],this.frame[2],this.frame[3],this.x - (this.screen.x||0),this.y,this.w,this.h);
639     }
640 };
641 
642 Punx.Sprite.sort = function(a,b) {
643     // sorting function that prioritizes sprites with more depth
644     // this is mainly used when figuring out the drawing order.
645     if (a instanceof Punx.Sprite.Wall ^ b instanceof Punx.Sprite.Wall) return (a instanceof Punx.Sprite.Wall ? -1 : 1); // walls always behind
646     else if (a.z != b.z) return (a.z < b.z ? -1 : 1);
647     else return (a.y + a.h < b.y + b.h ? -1 : 1); // sprite's bottom is further away
648 };
649 
650 
651 /*
652   ___     _    _       ___          _ _            __      __    _ _ 
653  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___      \ \    / /_ _| | |
654 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   \ \/\/ / _` | | |
655  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_)   \_/\_/\__,_|_|_|
656                           |_|                                        
657 */
658 // a wall is a stage element that actors cannot clip.
659 // walls are not actors, they are inert.
660 
661 Punx.Sprite.Wall = function(args) {
662     Punx.Sprite.call(this,args);
663     
664 }; Punx.Sprite.Wall.prototype = new Punx.Sprite();
665 
666 
667 /*
668   ___     _    _       ___          _ _               _      _           
669  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _ 
670 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|
671  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|  
672                           |_|                                            
673 */
674 // actors are any non-inert stage element. these include baddies, powerups, and props.
675 // actors interact, such as via collision.
676 
677 Punx.Sprite.Actor = function(args) {
678     Punx.Sprite.call(this,args);
679 
680     this.name = args['name']||'Actor';
681 
682     this.busy = false; // set this to disable user input or to denote that a multi-cycle process is taking place and you dont want to be interrupted
683 
684     // get collided by another actor, react to it.
685     // this is meant to be overloaded.
686     // dont modify the other actor, as notification order is undefined. it's only provided for context so you can react appropriately.
687     // chances are if its the initial collision then there will be a large swath of calls to this function within a short period of time, as
688     // collisions are checked every screen update.
689     // set flags etc accordingly to "debounce" collision notifications.
690     // for example, when the player is collided by a baddy, inflict self-damage and set a flag and timeout for recover-time invulnerability.
691     this.collide = function(actor) {
692     };
693 
694     this.move = function(stage,direction,amount) {
695         amount = amount || 5;
696         this.setGroup(direction);
697         var x = this.x;
698         var y = this.y;
699         switch (direction) {
700             case 'left' : { x -= amount; break; }
701             case 'right' : { x += amount; break; }
702             case 'up' : { y -= amount; break; }
703             case 'down' : { y += amount; break; }
704         }
705         if (!stage.clipping(this,x,y)) {
706             this.x = x;
707             this.y = y;
708         }
709         return true;
710     }
711 
712 }; Punx.Sprite.Actor.prototype = new Punx.Sprite();
713 
714 
715 /*
716   ___     _    _       ___          _ _               _      _                 _____    _                   
717  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _      |_   _| _(_)__ _ __ _ ___ _ _ 
718 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|  _    | || '_| / _` / _` / -_) '_|
719  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|   (_)   |_||_| |_\__, \__, \___|_|  
720                           |_|                                                            |___/|___/         
721 */
722 // triggers are actors that collide with just bounding box
723 
724 Punx.Sprite.Actor.Trigger = function(v) {
725     Punx.Sprite.Actor.call(this,v=v||{});
726     this.name = 'Trigger';
727 }; Punx.Sprite.Actor.Trigger.prototype = new Punx.Sprite.Actor();
728 
729 /*
730   ___     _    _       ___          _ _               _      _                 ___ _                   
731  / __|_ _(_)__| |     / __|_ __ _ _(_) |_ ___        /_\  __| |_ ___ _ _      | _ \ |__ _ _  _ ___ _ _ 
732 | (_ | '_| / _` |  _  \__ \ '_ \ '_| |  _/ -_)  _   / _ \/ _|  _/ _ \ '_|  _  |  _/ / _` | || / -_) '_|
733  \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_|   (_) |_| |_\__,_|\_, \___|_|  
734                           |_|                                                             |__/         
735 */
736 // the user
737 
738 Punx.Sprite.Actor.Player = function(v) { 
739     Punx.Sprite.Actor.call(this,v=v||{});
740     this.name = 'Player';
741     this.update = function(stage) {
742         if (!this.busy) {
743             with (Punx.Key) {
744                 if (key['left'] || key['a']) this.move(stage,'left');
745                 else if (key['right'] || key['d']) this.move(stage,'right');
746                 if (key['down'] || key['s']) this.move(stage,'down');
747                 else if (key['up'] || key['w']) this.move(stage,'up');
748             }
749         }
750         Punx.Sprite.Actor.Player.prototype.update.call(this,stage);
751     };
752 }; Punx.Sprite.Actor.Player.prototype = new Punx.Sprite.Actor();
753 
754