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: implement elevation 15 // TODO: get audio stuff working 16 // TODO: multi-layered background, all panning according to their width vs screen/stage width 17 // this accounts for mutliple parallaxes and a 1:1 "immediate bg" that is synced with panning. 18 // TODO: boilerplate baddy AI 19 // TODO: boilerplate post-hit temporary invulnerability/blinkage for player 20 // TODO: boilerplate talk bubbles 21 22 /** 23 * @namespace Punx 24 */ 25 var Punx = {}; 26 /* 27 ___ _ _ _ _ _ 28 | _ \_ _ _ _ __ __ | | | | |_(_) | 29 | _/ || | ' \\ \ / _ | |_| | _| | | 30 |_| \_,_|_||_/_\_\ (_) \___/ \__|_|_| 31 32 */ 33 34 Punx.Util = new function() { 35 36 this.PHI = (1+Math.sqrt(5))/2; // golden ratio 37 this.PHI_A = 1 - this.PHI; 38 this.PHI_B = 1 - this.PHI_A; 39 40 /** 41 * Determines if two boxes intersect. 42 * @public 43 * @param {Punx.Box} a 44 * @param {Punx.Box} b 45 * @returns boolean 46 */ 47 this.AABB = function(a,b) { 48 return (!( 49 a.x + a.w < b.x || // a too left 50 a.x > b.x + b.w || // a too right 51 a.y + a.h < b.y || // a too bg (y) 52 a.y > b.y + b.h // a too fg (y) 53 )); 54 }; 55 56 /** 57 * Returns a box describing the intersection of two boxes. 58 * This may return strange things if they aren't actually intersecting. Use AABB() first. 59 * @public 60 * @param {Punx.Box} a 61 * @param {Punx.Box} b 62 * @returns Punx.Box 63 */ 64 this.AABBC = function(a,b) { 65 var c = { 66 x : (a.x < b.x ? b.x : a.x), 67 y : (a.y < b.y ? b.y : a.y) 68 }; 69 c.w = (a.x + a.w < b.x + b.w ? a.x + a.w : b.x + b.w) - c.x; 70 c.h = (a.y + a.h < b.y + b.h ? a.y + a.h : b.y + b.h) - c.y; 71 return new Punx.Box(c); 72 }; 73 74 /** 75 * Scans a box for all AABB intersecting sprites. 76 * @public 77 * @param {Punx.Sprite[]} sprites Needles 78 * @param {Punx.Box} box Haystack 79 * @returns {Punx.Sprite[]} An array of sprites that collide with the box 80 */ 81 this.scan = function(sprites,box) { 82 var intersections = []; 83 for (var i in sprites) { 84 if (this.AABB(box,sprites[i])) intersections.push(sprites[i]); 85 } 86 return intersections; 87 }; 88 89 /** 90 * Returns an array of sprites, sorted according to their proximity to the origin x. 91 * @param {number} x Integer x origin point 92 * @param {Punx.Sprite[]} sprites Sprites to sort 93 * @returns {Punx.Sprite[]} Sorted array 94 */ 95 this.xSort = function(x,sprites) { 96 return sprites.sort(function(a,b) { 97 var A = (a.x + a.w < x ? x - (a.x + a.w) : a.x - x); 98 var B = (b.x + b.w < x ? x - (b.x + b.w) : b.x - x); 99 return (A < B ? -1 : (A > B ? 1 : 0)); 100 }); 101 }; 102 103 /** 104 * Returns an array of sprites, sorted according to their proximity to the origin y. 105 * @param {number} y Integer y origin point 106 * @param {Punx.Sprite[]} sprites Sprites to sort 107 * @returns {Punx.Sprite[]} Sorted array 108 */ 109 this.ySort = function(y,sprites) { 110 return sprites.sort(function(a,b) { 111 var A = (a.y + a.h < y ? y - (a.y + a.h) : a.y - y); 112 var B = (b.y + b.h < y ? y - (b.y + b.h) : b.y - y); 113 return (A < B ? -1 : (A > B ? 1 : 0)); 114 }); 115 }; 116 117 }; 118 119 /* 120 ___ ___ _ _ 121 | _ \_ _ _ _ __ __ / __|_ __ _ _(_) |_ ___ 122 | _/ || | ' \\ \ / _ \__ \ '_ \ '_| | _/ -_) 123 |_| \_,_|_||_/_\_\ (_) |___/ .__/_| |_|\__\___| 124 |_| 125 */ 126 127 /** 128 * A screen object. 129 * @class 130 * @param {object} args Construction values for public properties. 131 */ 132 133 Punx.Sprite = function(args) { 134 135 args = args||{}; 136 137 /** 138 * Required: Screen reference. 139 * @public 140 * @type Punx.Screen 141 */ 142 this.screen = args.screen; 143 144 /** 145 * Sprite sheet to draw from. 146 * @public 147 * @type Image 148 */ 149 this.sheet = args.sheet; 150 151 /** 152 * Keys animation frame group names to an array of integer arrays [x,y,w,h], the cropping information of the sheet for each animation frame. 153 * Required if sheet is set. 154 * @public 155 * @type object<string,int[][]> 156 */ 157 this.frames = args.frames; 158 159 /** 160 * Current/initial animation frame group name. 161 * Required if frames is set. Automatically wraps the animation frame index when set. 162 * @public 163 * @type string 164 */ 165 this.group = args.group; 166 167 set group(group) { 168 this.group = group; 169 this.frame %= this.frames[this.group].length; 170 } 171 172 /** 173 * Sprite's framerate. 174 * @public 175 * @type int 176 * @defaults 10 177 */ 178 this.fps = args.fps || 10; 179 180 /** 181 * Sprite's X offset. 182 * @public 183 * @type int 184 * @defaults 0 185 */ 186 this.x = args.x || 0; 187 188 /** 189 * Sprite's Y offset. 190 * @public 191 * @type int 192 * @defaults 0 193 */ 194 this.y = args.y || 0; 195 196 /** 197 * Sprite's "elevation". 198 * 0 = floor tile, 1 = standing on ground, 2 = in air, 3 = sticky (health bar, etc) 199 * @public 200 * @type int 201 * @defaults 1 202 */ 203 this.z = args.z || 1; 204 205 /** 206 * Sprite's width. 207 * @public 208 * @type int 209 * @defaults 64 210 */ 211 this.w = args.w || 64; 212 213 /** 214 * Sprite's height. 215 * @public 216 * @type int 217 * @defaults 64 218 */ 219 this.h = args.h || 64; 220 221 /** 222 * Animation frame index. 223 * Increments once per update, prior to step(). Automatically wraps at the length of the current group. 224 * @public 225 * @type int 226 */ 227 this.frame = 0; 228 229 set frame(frame) { 230 if (this.group) this.frame = frame % this.frames[this.group].length; 231 } 232 233 /** 234 * Called by the screen. The sprite should prepare itself to be drawn. 235 * Returns boolean of whether it was updated. 236 * Every update, tick() is called. Every frame-rated update, step() is called after the animation frame increments. 237 * @public 238 * @returns bool 239 */ 240 this.update = function() { 241 var updated = false; 242 updated |= this._tick(); 243 if (this.screen.grid.frame % this.fps == 0) { 244 updated = true; 245 this.frame++; 246 this._step(); 247 } 248 return updated; 249 }; 250 251 /** 252 * Called upon every update. 253 * Returns boolean of whether the tick caused an update. 254 * This is a stub. 255 * @private 256 * @returns ?boolean 257 */ 258 this._tick = function() { 259 }; 260 261 /** 262 * Called every frame-rated update. 263 * Unlike tick(), the return value is ignored as the sprite is considered updated on every frame-rated update. 264 * This is a stub. 265 * @private 266 */ 267 // called every rated amount of grid frames 268 // 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. 269 // return boolean of whether the sprite is updated 270 this._step = function() { 271 }; 272 273 /** 274 * Sprite draws itself to the canvas context provided. 275 * This is called by the screen once all sprites are updated and accounted for, such that when the sprite draws itself it will automatically layer properly. 276 * Automatically adjusts for Punx.Screen.Stage.x (panning). 277 * Does nothing if group isn't set. 278 * The sprite may be drawn multiple times during graphics analysis. Drawing should not change the sprite in any way. 279 * @public 280 * @param ctx Canvas context. 281 */ 282 this.draw = function(ctx) { 283 if (!this.group) return; 284 var frame = this.frames[this.group][this.frame]; 285 ctx.drawImage(this.sheet,frame[0],frame[1],frame[2],frame[3],this.x - (this.screen.x||0),this.y,this.w,this.h); 286 }; 287 }; 288 289 Punx.Sprite.sort = function(a,b) { 290 // sorting function that prioritizes sprites with more depth 291 // this is mainly used when figuring out the drawing order. 292 if (a instanceof Punx.Sprite.Wall ^ b instanceof Punx.Sprite.Wall) return (a instanceof Punx.Sprite.Wall ? -1 : 1); // walls always behind 293 else if (a.z != b.z) return (a.z < b.z ? -1 : 1); 294 else return (a.y + a.h < b.y + b.h ? -1 : 1); // sprite's bottom is further away 295 }; 296 297 298 /* 299 ___ _ _ ___ _ _ __ __ _ _ 300 / __|_ _(_)__| | / __|_ __ _ _(_) |_ ___ \ \ / /_ _| | | 301 | (_ | '_| / _` | _ \__ \ '_ \ '_| | _/ -_) _ \ \/\/ / _` | | | 302 \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) \_/\_/\__,_|_|_| 303 |_| 304 */ 305 // a wall is a stage element that actors cannot clip. 306 // walls are not actors, they are inert. 307 308 Punx.Sprite.Wall = function(args) { 309 Punx.Sprite.call(this,args); 310 311 }; Punx.Sprite.Wall.prototype = new Punx.Sprite(); 312 313 314 /* 315 ___ _ _ ___ _ _ _ _ 316 / __|_ _(_)__| | / __|_ __ _ _(_) |_ ___ /_\ __| |_ ___ _ _ 317 | (_ | '_| / _` | _ \__ \ '_ \ '_| | _/ -_) _ / _ \/ _| _/ _ \ '_| 318 \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_| 319 |_| 320 */ 321 // actors are any non-inert stage element. these include baddies, powerups, and props. 322 // actors interact, such as via collision. 323 324 Punx.Sprite.Actor = function(args) { 325 Punx.Sprite.call(this,args); 326 327 this.name = args['name']||'Actor'; 328 329 this.collidable = true; 330 331 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 332 333 // get collided by another actor, react to it. 334 // this is meant to be overloaded. 335 // dont modify the other actor, as notification order is undefined. it's only provided for context so you can react appropriately. 336 // 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 337 // collisions are checked every screen update. 338 // set flags etc accordingly to "debounce" collision notifications. 339 // for example, when the player is collided by a baddy, inflict self-damage and set a flag and timeout for recover-time invulnerability. 340 this.collide = function(actor) { 341 }; 342 343 this.move = function(stage,direction,amount) { 344 amount = amount || 5; 345 this.setGroup(direction); 346 var x = this.x; 347 var y = this.y; 348 switch (direction) { 349 case 'left' : { x -= amount; break; } 350 case 'right' : { x += amount; break; } 351 case 'up' : { y -= amount; break; } 352 case 'down' : { y += amount; break; } 353 } 354 if (!stage.clipping(this,x,y)) { 355 this.x = x; 356 this.y = y; 357 } 358 return true; 359 } 360 361 }; Punx.Sprite.Actor.prototype = new Punx.Sprite(); 362 363 364 /* 365 ___ _ _ ___ _ _ _ _ _____ _ 366 / __|_ _(_)__| | / __|_ __ _ _(_) |_ ___ /_\ __| |_ ___ _ _ |_ _| _(_)__ _ __ _ ___ _ _ 367 | (_ | '_| / _` | _ \__ \ '_ \ '_| | _/ -_) _ / _ \/ _| _/ _ \ '_| _ | || '_| / _` / _` / -_) '_| 368 \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_| (_) |_||_| |_\__, \__, \___|_| 369 |_| |___/|___/ 370 */ 371 // triggers are actors that collide with just bounding box 372 373 Punx.Sprite.Actor.Trigger = function(v) { 374 Punx.Sprite.Actor.call(this,v=v||{}); 375 this.name = 'Trigger'; 376 }; Punx.Sprite.Actor.Trigger.prototype = new Punx.Sprite.Actor(); 377 378 /* 379 ___ _ _ ___ _ _ _ _ ___ _ 380 / __|_ _(_)__| | / __|_ __ _ _(_) |_ ___ /_\ __| |_ ___ _ _ | _ \ |__ _ _ _ ___ _ _ 381 | (_ | '_| / _` | _ \__ \ '_ \ '_| | _/ -_) _ / _ \/ _| _/ _ \ '_| _ | _/ / _` | || / -_) '_| 382 \___|_| |_\__,_| (_) |___/ .__/_| |_|\__\___| (_) /_/ \_\__|\__\___/_| (_) |_| |_\__,_|\_, \___|_| 383 |_| |__/ 384 */ 385 // the user 386 387 Punx.Sprite.Actor.Player = function(v) { 388 Punx.Sprite.Actor.call(this,v=v||{}); 389 this.name = 'Player'; 390 this.update = function(stage) { 391 if (!this.busy) { 392 with (Punx.Key) { 393 if (key['left'] || key['a']) this.move(stage,'left'); 394 else if (key['right'] || key['d']) this.move(stage,'right'); 395 if (key['down'] || key['s']) this.move(stage,'down'); 396 else if (key['up'] || key['w']) this.move(stage,'up'); 397 } 398 } 399 Punx.Sprite.Actor.Player.prototype.update.call(this,stage); 400 }; 401 }; Punx.Sprite.Actor.Player.prototype = new Punx.Sprite.Actor(); 402 403 /* 404 ___ ___ _ 405 | _ \_ _ _ _ __ __ / __| ___ _ _ _ _ __| | 406 | _/ || | ' \\ \ / _ \__ \/ _ \ || | ' \/ _` | 407 |_| \_,_|_||_/_\_\ (_) |___/\___/\_,_|_||_\__,_| 408 409 */ 410 // under construction 411 Audio.prototype.fadeTo = function(fadeTo,callback) { 412 if (fadeTo < 0) fadeTo = 0; 413 else if (fadeTo > 1) fadeTo = 1; 414 var that = this; 415 var setVolume = function(setTo) { 416 return function() { 417 that.volume = setTo; 418 if (setTo == fadeTo && callback) callback(); 419 }; 420 }; 421 var delay = 0; 422 if (this.volume < fadeTo) { 423 for (var v=this.volume; v<=fadeTo; v+=0.1) { 424 setTimeout(setVolume(v),delay); 425 delay+=100; 426 } 427 } 428 else if (volume > fadeTo) { 429 for (var v=this.volume; v>=fadeTo; v-=0.1) { 430 setTimeout(setVolume(v),delay); 431 delay+=100; 432 } 433 } 434 else if (callback) callback(); 435 }; 436 437 Punx.Sound = new function() { 438 439 this.music = new Audio(); // swap out for one in Library 440 441 this.muted = false; 442 this.mute = function() { 443 this.muted ^= 1; 444 this.music.muted = this.muted; 445 }; 446 447 }; 448 449 /* 450 ___ ___ 451 | _ \_ _ _ _ __ __ / __| __ _ _ ___ ___ _ _ 452 | _/ || | ' \\ \ / _ \__ \/ _| '_/ -_) -_) ' \ 453 |_| \_,_|_||_/_\_\ (_) |___/\__|_| \___\___|_||_| 454 455 */ 456 /** 457 * Represents a collection of sprites to be drawn to the canvas. Only the active screen is drawn. 458 * @class 459 * @param {object} args Construction values for public properties. 460 */ 461 462 Punx.Screen = function(args) { 463 464 /** 465 * Required: {Punx.Grid} reference 466 * @type Punx.Grid 467 */ 468 this.grid = args.grid; 469 470 /** 471 * List of {Punx.Sprite} instances 472 * @type Punx.Sprites[] 473 * @default [] 474 */ 475 this.sprites = args.sprites || []; 476 477 /** 478 * Optional background image 479 * @type Image 480 */ 481 this.bg = args.bg; 482 483 /** 484 * Called by the game loop. Update all sprites and redraw if needed. 485 */ 486 this.update = function() { 487 var updated = false; 488 for (var i in this.sprites) { 489 updated |= this.sprites[i].update(); 490 } 491 if (updated) this._draw(); 492 }; 493 494 /** 495 * Called by update() if needed. 496 * @private 497 */ 498 this._draw = function() { 499 this.grid.ctx.clearRect(0,0,this.grid.w,this.grid.h); 500 if (this.bg) this.grix.ctx.drawImage(this.bg,0,0); 501 for (var i in this.sprites.sort(Punx.Sprite.sort)) { 502 this.sprites[i].draw(this.grid.ctx); 503 } 504 }; 505 506 }; 507 508 /* 509 ___ _ _ ___ ___ _ 510 / __|_ _(_)__| | / __| __ _ _ ___ ___ _ _ / __| |_ __ _ __ _ ___ 511 | (_ | '_| / _` | _ \__ \/ _| '_/ -_) -_) ' \ _ \__ \ _/ _` / _` / -_) 512 \___|_| |_\__,_| (_) |___/\__|_| \___\___|_||_| (_) |___/\__\__,_\__, \___| 513 |___/ 514 */ 515 516 /** 517 * stages are screens that are game levels. they have "curtains" and a "floor". 518 * imagine looking upon a theater stage from an ideal balcony seat. 519 * curtains are used for backgrounds and parallax. 520 * the floor is the "depth" where actors move away from and toward the "front" of the stage. 521 * the defaults define the curtains and floor in terms of the golden ratio: 522 * http://en.wikipedia.org/wiki/Golden_ratio 523 * where the longer portion represents the curtains and the shorter portion is the floor. 524 * @class 525 */ 526 Punx.Screen.Stage = function(args) { 527 Punx.Screen.call(this,args); 528 529 /** 530 * Required. This is the actor which the camera pans for. 531 * @public 532 * @type Punx.Sprite.Actor.Player 533 */ 534 this.player = args.player; 535 536 /** 537 * Width of the stage. This can go beyond the viewport, the camera will pan with the player. 538 * @public 539 * @type int 540 * @default 960 541 */ 542 this.w = args.w || 960; 543 544 /** 545 * Stage floor Y offset. This is the length of the "curtains", the bottom of which is up-stage. 546 * This is the height of the background area inaccessible to actors. 547 * This defaults to Phi-A of the default canvas height (540). 548 * @public 549 * @type int 550 * @default 334 551 */ 552 this.y = args.y || 334; 553 554 /** 555 * Stage floor "depth". The amount of freedom to move along the Y axis, relative to the bottom of the curtains, that actors have. 556 * This defaults to the remainder of the canvas height, but can be made shorter to prevent actors from moving too down-stage. 557 * @public 558 * @type int 559 * default 206 560 */ 561 this.h = args.h || (this.grid.h - this.y); 562 563 /** 564 * Pan offset from house-left. 565 * @public 566 * @type int 567 * @default 0 568 */ 569 this.x = args.x || 0; 570 571 /** 572 * How close the player can get to the edge of the frame before the camera pans. 573 * @public 574 * @type int 575 * @default 200 576 */ 577 this.panMargin = 200; 578 579 /** 580 * All in-frame and frame-edge sprites, populated at the beginning of each update(). 581 * @private 582 * @type Punx.Sprite[] 583 */ 584 this._sprites = []; 585 586 /** 587 * All in-frame and frame-edge actors, populated at the beginning of each update(). 588 * @private 589 * @type Punx.Sprite.Actor[] 590 */ 591 this._actors = []; 592 593 /** 594 * All in-frame and frame-edge walls, populated at the beginning of each update(). 595 * @private 596 * @type Punx.Sprite.Wall[] 597 */ 598 this._walls = []; 599 600 /** 601 * Determines in-frame and frame-edged sprites, pans, and draws and checks for collisions if needed. 602 * @public 603 */ 604 this.update = function() { 605 var updated = false; 606 this._sprites = []; 607 this._walls = []; 608 this._actors = []; 609 this._collidable = []; 610 611 // pan 612 with (this) { 613 // pan left 614 if (x > 0 && player.x < x + panMargin) { 615 x -= panMargin - (player.x - x); 616 if (x < 0) x = 0; 617 } 618 // pan right 619 else if (x + grid.w < w && player.x + player.w > x + grid.w - panMargin) { 620 x += (player.x + player.w) - (x + grid.w - panMargin); 621 if (x > w - grid.w) x = w - grid.w; 622 } 623 } 624 625 // determine active sprites, in-frame and frame-edged 626 for (var i in this.sprites) { 627 var sprite = this.sprites[i]; 628 if (sprite.x + sprite.w < this.x || sprite.x > this.x + this.grid.w) continue; // sprite is out of frame 629 this._sprites.push(sprite); 630 if (sprite instanceof Punx.Sprite.Wall) this._walls.push(sprite); 631 else if (sprite instanceof Punx.Sprite.Actor) this._actors.push(sprite); 632 } 633 634 // update active sprites. sprites should return boolean of whether they updated 635 for (var i in this._sprites.sort(Punx.Sprite.sort)) { 636 updated |= this._sprites[i].update(); 637 } 638 639 if (updated) { 640 this._draw(); // draw the screen 641 this._collisions(); // detect collisions and notify sprites after drawing so "reactions" occur next update cycle 642 } 643 644 }; 645 646 /** 647 * Actors should call this to determine if they can move to a given position. 648 * This will ensure that actors stay on the stage floor and don't hit any walls. 649 * An actor can move close to the back of the stage, according to the Phi-B of their height. This is used as the actor's "depth". 650 * @public 651 * @param {Punx.Sprite.Actor} actor 652 * @param {int} x Destination X coord 653 * @param {int} y Destination Y coord 654 * @returns bool 655 */ 656 this.clips = function(actor,x,y) { 657 // stay on stage floor vertically 658 if (y + (actor.h * Punx.Util.PHI_B) < this.y || y + actor.h > this.y + this.h) return true; 659 660 // stay in frame horizontally 661 if (x < this.x || x + actor.w > this.x + this.grid.w) return true; 662 663 // don't clip walls 664 for (var i in this._walls) { 665 if (Punx.Util.AABB(this._walls[i],{x:x,y:y,w:actor.w,h:actor.h})) return true; 666 } 667 668 // nothing clipped 669 return false; 670 }; 671 672 /** 673 * Draws the screen. 674 * @private 675 */ 676 this._draw = function() { 677 this.grid.ctx.clearRect(0,0,this.grid.w,this.grid.h); 678 for (var i in this._sprites.sort(Punx.Sprite.sort)) { 679 this._sprites[i].draw(this.grid.ctx); 680 } 681 }; 682 683 /** 684 * Checks in-frame actors for collision and notifies them. 685 * This performs n(n-2)/2 AABB checks, subchecking pixels in intersecting areas to a semi-fine resolution (5 pixels), using the imaginary canvas. 686 * An actor is only collidable if its collidable flag is true. 687 * If an actor is collided and sets its collidable flag to false, any further checks on it are skipped. 688 * A pixel is only considered collidable if its opacity is >= 127, however, triggers (Punx.Sprite.Actor.Trigger) collide with just AABB. 689 * When two actors collide, both are notified via their collide() methods and given a reference to each other. 690 * The order of notification is undefined, so actors should only "be collided" and treat each other as read-only. 691 * @private 692 * @param {number} [resolution=5] Integer pixel collision resolution, the number of pixels to skip per pixel check. 693 */ 694 this._collisions = function(resolution) { 695 resolution = (resolution || 5) * 4; 696 var n = this._actors.length; 697 for (var i=0;i<n;i++) { 698 var a = this._actors[i]; 699 for (var j=i+1;j<n;j++) { 700 var b = this._actors[j]; 701 if (!a.collidable) break; 702 if (!b.collidable) continue; 703 if (!Punx.Util.AABB(a,b)) continue; // no box collision, move on. 704 705 // triggers don't need pixel detection, bounding box is enough 706 if (a instanceof Punx.Sprite.Actor.Trigger || b instanceof Punx.Sprite.Actor.Trigger) { 707 a.collide(b); 708 b.collide(a); 709 continue; 710 } 711 712 // proceed with pixel detection 713 714 var c = Punx.Util.AABBC(a,b); // determine intersection 715 716 // get separate slices of a and b at the intersection using the imaginary canvas 717 718 // imagine a 719 this.grid.ictx.clearRect(0,0,this.grid.w,this.grid.h); 720 a.draw(this.grid.ictx); 721 c.a = this.grid.ictx.getImageData(c.x-this.x,c.y,c.w,c.h); // slice a @ intersect 722 723 // imagine b 724 this.grid.ictx.clearRect(0,0,this.grid.w,this.grid.h); 725 b.draw(this.grid.ictx); 726 c.b = this.grid.ictx.getImageData(c.x-this.x,c.y,c.w,c.h); // slice b @ intersect 727 728 // compare slice pixels according to resolution 729 var m = c.a.data.length; // since the slices are the same size we just use a here 730 for (var p=0;p<m;p+=resolution) { 731 if (c.a.data[p+3] >= 127 && c.b.data[p+3] >= 127) { // two pixels occupy the same space and are opaque enough 732 a.collide(b); 733 b.collide(a); 734 break; 735 } 736 } 737 } 738 } 739 }; 740 741 }; Punx.Screen.Stage.prototype = new Punx.Screen(); 742 /* 743 ___ _ __ 744 | _ \_ _ _ _ __ __ | |/ /___ _ _ 745 | _/ || | ' \\ \ / _ | ' </ -_) || | 746 |_| \_,_|_||_/_\_\ (_) |_|\_\___|\_, | 747 |__/ 748 */ 749 /** 750 * Keyboard interface. 751 */ 752 Punx.Key = new function() { 753 754 /** 755 * Object keyed by button name and its current press-state, so that sprites et al. can poll the keyboard. 756 * @public 757 * @type object<string,boolean> 758 */ 759 this.state = {}; 760 761 /** 762 * Internal map between keyCode and button name. 763 * @private 764 * @type object<integer,string> 765 */ 766 this._map = { 767 9:'tab', 768 13:'enter', 769 27:'escape', 770 32:'space', 771 37:'left', 772 38:'up', 773 39:'right', 774 40:'down' 775 }; 776 for (var i=48;i<=57;i++) { this._map[i] = String.fromCharCode(i); } // numbers 777 for (var i=65;i<=90;i++) { this._map[i] = String.fromCharCode(i).toLowerCase(); } // letters 778 779 /** 780 * Document's onkeydown listener, attached to the document by the grid during create(). 781 * Sets the key's state to true. Keypresses that are combined with special keys like CTRL, ALT, Shift, Meta/Windows, Menu, etc are ignored. 782 * @public 783 * @param {event} [e=window.event] 784 */ 785 this.onkeydown = function(e) { 786 e=e||window.event; 787 if (e.altGraphKey || e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) return; 788 var key = this._map[e.keyCode]; 789 if (!key) return; 790 e.preventDefault(); 791 e.stopPropagation(); 792 this.state[key] = true; 793 }.bind(this); 794 795 /** 796 * Document's keyup listener, attached to the document by the grid during create(). 797 * Sets the key's state to false. 798 * @public 799 * @param {event} [e=window.event] 800 */ 801 this.onkeyup = function(e) { 802 e=e||window.event; 803 var key = this._map[e.keyCode]; 804 if (!key) return; 805 e.preventDefault(); 806 e.stopPropagation(); 807 this.state[key] = false; 808 }.bind(this); 809 810 }; 811 812 /* 813 ___ ___ _ _ 814 | _ \_ _ _ _ __ __ / __|_ _(_)__| | 815 | _/ || | ' \\ \ / _ | (_ | '_| / _` | 816 |_| \_,_|_||_/_\_\ (_) \___|_| |_\__,_| 817 818 */ 819 /** 820 * @class Represents the game engine. Houses the main loop and canvas DOM node reference. 821 * @param {object} args Public field construction values. 822 */ 823 var Punx.Grid = function(args) { 824 825 /** 826 * Required: Object holding all screens, keyed by name. 827 * @type object<string,Punx.Screen> 828 * @public 829 */ 830 this.screens = args.screens; 831 832 /** 833 * Required: Key of the initial screen. 834 * @public 835 * @type string 836 */ 837 this.screen = args.screen 838 839 /** 840 * Integer canvas width. 841 * @public 842 * @type number 843 * @default 960 844 */ 845 this.w = args.w || 960; 846 847 /** 848 * Integer canvas height. 849 * @public 850 * @type number 851 * @default 540 852 */ 853 this.h = args.h || 540; 854 855 /** 856 * Integer frames-per-second. 857 * @public 858 * @type number 859 * @default 60 860 */ 861 this.fps = args.fps || 60; 862 863 /** 864 * For debugging, causes the main loop to idle when true. 865 * @public 866 * @type boolean 867 * @default false 868 */ 869 this.idle = false; 870 871 /** 872 * Internal frame counter, increments once after every update cycle, wraps at fps. This should be treated as read-only. 873 * @public 874 * @type number 875 * @default 0 876 */ 877 this.frame = 0; 878 879 /** 880 * Viewport canvas context. 881 * @public 882 */ 883 this.ctx = null; 884 885 /** 886 * Imaginary canvas context. 887 * @public 888 */ 889 this.ictx = null; 890 891 /** 892 * Viewport canvas DOM element. This is displayed. 893 * @private 894 */ 895 this._canvas = null; 896 897 /** 898 * Imaginary canvas DOM element used as a frame-buffer for collision detection and other graphics analysis. Scratch-space. Never shown. 899 * @private 900 */ 901 this._icanvas = null; 902 903 /** 904 * The game loop, an interval that calls update() on the current screen, timed according to fps. 905 * @private 906 */ 907 this._loop = null; 908 909 /** 910 * Creates the canvases and injects the main canvas into the viewport. 911 * @public 912 * @param [parentElement=document.body] DOM element to make the canvas a child of. 913 */ 914 this.create = function(parentElement) { 915 // viewport canvas 916 this._canvas = document.createElement('canvas'); 917 this._canvas.setAttribute('width',this.w); 918 this._canvas.setAttribute('height',this.h); 919 this._canvas.setAttribute('tabindex',0); 920 this._canvas.addEventListener('keydown',Punx.Key.onkeydown,true); 921 this._canvas.addEventListener('keyup',Punx.Key.onkeyup,true); 922 this.ctx = this._canvas.getContext('2d'); 923 this.ctx.imageSmoothingEnabled = false; 924 this.ctx.mozImageSmoothingEnabled = false; 925 this.ctx.webkitImageSmoothingEnabled = false; 926 (parentElement||document.body).appendChild(this._canvas); 927 this._canvas.focus(); 928 929 // imaginary canvas 930 this._icanvas = document.createElement('canvas'); 931 this._icanvas.setAttribute('width',this.w); 932 this._icanvas.setAttribute('height',this.h); 933 this.ictx = this._icanvas.getContext('2d'); 934 this.ictx.imageSmoothingEnabled = false; 935 this.ictx.mozImageSmoothingEnabled = false; 936 this.ictx.webkitImageSmoothingEnabled = false; 937 }; 938 939 /** 940 * Starts the main loop. 941 * @public 942 */ 943 this.start = function() { 944 this._loop = window.setInterval(this._update.bind(this),1000/this.fps); 945 }; 946 947 /** 948 * The main loop calls this once every fps. The loop is killed if any errors occur. Does nothing if idle is true. 949 * @private 950 */ 951 this._update = function() { 952 if (this.idle) return; 953 try { 954 this.screen.update(); 955 this.frame = ++this.frame % this.fps; 956 } 957 catch (e) { 958 console.log(e.stack); 959 window.clearInterval(this._loop); 960 } 961 }; 962 963 }; 964 /* 965 ___ ___ _ _ 966 | _ \_ _ _ _ __ __ | __|_ ____ ___ _ __| |_(_)___ _ _ 967 | _/ || | ' \\ \ /_| _|\ \ / _/ -_) '_ \ _| / _ \ ' \ 968 |_| \_,_|_||_/_\_(_)___/_\_\__\___| .__/\__|_\___/_||_| 969 |_| 970 */ 971 972 /** 973 * Generic exception. 974 * @class 975 * @param {string} message Exception message. 976 */ 977 978 Punx.Exception = function(message) { 979 980 this.message = message; 981 982 } 983 /* 984 ___ ___ 985 | _ \_ _ _ _ __ __ | _ ) _____ __ 986 | _/ || | ' \\ \ /_| _ \/ _ \ \ / 987 |_| \_,_|_||_/_\_(_)___/\___/_\_\ 988 989 */ 990 991 Punx.Box = function(x,y,w,h) { 992 993 this.x = x; 994 this.y = y; 995 this.w = w; 996 this.h = h; 997 998 this.__defineGetter__('X',function(){ 999 return this.x+this.w; 1000 }); 1001 1002 this.__defineGetter__('Y',function(){ 1003 return this.y+this.h; 1004 }); 1005 1006 } 1007