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