topical media & game development

talk show tell print

mobile-game-ch13-rpg.js / js



  $(function() {
    var Q = window.Q = Quintus()
                       .include('Input,Sprites,Scenes,DOM')
                       .domOnly()
                       .setup('quintus',{ maximize: true });
    var tileSize = 32;
    var TILE = {
      WALL: 10,
      FLOOR: 30,
      STAIRS: 45
    };
    var impassableTiles = {
      10: true
    };
    Q.input.keyboardControls();
    Q.input.joypadControls({zone: Q.width});
  
    Q.Level = Q.DOMTileMap.extend({
      legend: {
        "X": "wall",
        ".": "floor",
        "m": "monster",
        "f": "fountain",
        "d": "door",
        "g": "gold",
        "s": "stairs"
      },
      init:function(asset,stage) {
        this.stage = stage;
        this.level = [];
        this.sprites = [];
        var data = Q.asset(asset);
        this.extra = [];
        _.each(data.split("\n"),function(row) { 
          var columns = row.split("");
          if(columns.length > 1) {
            this.level.push(columns);
            this.sprites.push([]);
          }
        },this);
  
        this._super({
          cols:this.level[0].length,
          rows:this.level.length,
          sheet: 'tiles'
        })
  
        var tiles =[];
        for(var y=0;y<this.level.length;y++) {
          tiles[y] = [];
          for(var x =0;x<this.level[0].length;x++) {
            var square = this.level[y][x],
                frame = null,
                method = this.legend[square] || "wall";
            
            frame = this[method](x*tileSize,y*tileSize);
            tiles[y].push(frame);
          }
        }
        this.setup(tiles,true);
      },
  
      insert: function(sprite) {
        this.stage.insert(sprite);
        this.sprites[sprite.p.tileY][sprite.p.tileX] = sprite;
        return sprite;
      },
    
      unfog: function(x,y) {
        for(var sx=x-2,ex=x+2;sx<=ex;sx++) {
          for(var sy=y-2,ey=y+2;sy<=ey;sy++) {
            this.show(sx,sy);
            if(this.validTile(sx,sy) && this.sprites[sy][sx]) {
              this.sprites[sy][sx].show();
            }
          }
        }
      },
      wall: function(x,y) { return TILE.WALL; },
   
      floor: function(x,y) { return TILE.FLOOR; },
   
      stairs: function(x,y) {
        this.startX = x;
        this.startY = y;
        return TILE.STAIRS;
      },
   
      gold: function(x,y) {
        this.insert(new Q.Loot({ x:x, y:y }));
        return TILE.FLOOR;
      },
   
      fountain: function(x,y) {
        this.insert(new Q.Fountain({ x:x, y:y }));
        return TILE.FLOOR;
      },
  
      monster: function(x,y) {
        var frame = Math.floor(Math.random()*64);
        this.insert(new Q.Enemy({ x:x, y:y, frame:frame }));
        return TILE.FLOOR;
      }
     
    });
  
    Q.register('tiled', {
      added:function() {
        var p = this.entity.p;
        _(p).extend({
          wait: 0,
          delay: 0.15,
          tileX: Math.floor(p.x / tileSize),
          tileY: Math.floor(p.y / tileSize),
          dx: 0,
          dy: 0
        });
        this.direction = {};
        this.entity.bind('step',this,'move');
        this.entity.bind('removed',this,'removed');
      },
  
      move: function(dt) {
        var p =this.entity.p,
            stage = this.entity.parent;
  
        if(p.wait <= 0) {
          var destX = p.tileX, destY = p.tileY;
  
          if(p.attacking) {
              this.entity.trigger('attack',this.direction);
          } else if(p.dx || p.dy) {
            if(p.dx > 0) { destX += 1; }
            else if(p.dx < 0) { destX -= 1 };
            if(p.dy > 0) { destY += 1; }
            else if(p.dy < 0) { destY -= 1; }
  
            if(!impassableTiles[stage.level.get(destX,destY)]) {
              var sprite = stage.level.sprites[destY][destX];
              this.direction.dx = destX - p.tileX;
              this.direction.dy = destY - p.tileY;
              this.direction.sprite = sprite;
              if(!sprite) {
                this.moved(destX,destY);
                this.setPosition();
                p.wait = p.delay;
              } else {
                p.wait = p.delay * 2;
              }
              this.entity.trigger(sprite ? 'hit' : 'moved',
                                  this.direction);
            }
          } 
        } else {
          p.wait -= dt;
        }
      },
      setPosition: function() {
        var p =this.entity.p;
        p.x = p.tileX * tileSize;
        p.y = p.tileY * tileSize;
      },
      moved: function(destX,destY) {
        var stage = this.entity.parent;
        var p =this.entity.p;
        stage.level.sprites[p.tileY][p.tileX] = null;
        p.tileX = destX;
        p.tileY = destY;
        stage.level.sprites[p.tileY][p.tileX] = this.entity;
      },
      removed: function() {
        var stage = this.entity.parent;
        var p =this.entity.p;
        stage.level.sprites[p.tileY][p.tileX] = null;
      }
    });
  
    Q.register('transition', {
      added: function() {
        Q.transitionDOM(this.entity.dom,'transform','0.25s');
      }
    });
  
    Q.register('camera', {
      added: function() {
        this.entity.bind('moved',this,'track');
      },
      track: function() {
        var p = this.entity.p,
            stage = this.entity.parent;
        stage.centerOn(p.x, p.y);
        stage.level.unfog(p.tileX,p.tileY);
      }
    });
  
    Q.register('player_input', {
      added: function() {
        this.entity.bind('step',this,'input');
      },
      input: function() {
        var p = this.entity.p;
        if(Q.inputs['left']) { p.dx = -1 }
        else if(Q.inputs['right']) { p.dx = 1;}
        else { p.dx = 0;}
        if(Q.inputs['up']) { p.dy = -1 }
        else if(Q.inputs['down']) { p.dy = 1;}
        else { p.dy = 0;}
      }
    });
  
    Q.register('healthbar', {
      added: function() {
        this.entity.bind('health',this,'update');
  
        this.bg = $("<div>").appendTo(this.entity.dom).css({
          width: "100%",
          height: 5,
          position: 'absolute',
          bottom: -6,
          left: 0,
          backgroundColor: "#000",
          border: "1px solid #999"
        }).hide();
  
        this.bar = $("<div>").appendTo(this.entity.dom).css({
          width: "100%",
          height: 5,
          position: 'absolute',
          bottom: -5,
          left: 1,
          backgroundColor: "#F00"
        }).hide();
  
        Q.transitionDOM(this.bar[0],'width');
  
      },
  
      large: function() {
        this.bg.css({ height: 20, bottom: -1 }).show();
        this.bar.css({ height: 20, bottom: 0 }).show();
        return this;
      },
  
      update: function(sprite) {
        this.bar.show();
        this.bg.show();
        var p = sprite.p;
        var width = Math.round(p.health / p.maxHealth * 100);
        this.bar.css('width',width + "%");
      }
    });
  
    Q.Player = Q.Sprite.extend({
      init: function(props) {
        this._super(_({
          sheet: 'characters',
          frame: 65,
          wait: 0,
          z: 10,
          attack: 5,
          health: 40,
          maxHealth: 40,
          gold: 0,
          xp: 0
        }).extend(props));
        this.add('player_input, tiled, camera, transition');
        this.bind('hit',this,'collision');
        this.bind('attack',this,'attack');
        this.bind('interact',this,'interact');
        this.bind('heal',this,'heal');
      },
  
      collision: function(data) {
        this.p.x += data.dx * tileSize/2; 
        this.p.y += data.dy * tileSize/2; 
        this.p.attacking = true;
      },
  
      attack: function(data) {
        var damage = Math.round(Math.random() * this.p.attack);
        data.sprite.trigger('interact',
                            { source: this, damage: damage });
        this.p.attacking = false;
        this.tiled.setPosition();
      },
  
      interact: function(data) {
        this.p.health -= data.damage;
        if(this.p.health <= 0) {
          this.destroy();
        }
        this.trigger('health');
      },
  
      heal: function(data) {
        this.p.health += data.amount;
        if(this.p.health > this.p.maxHealth) {
          this.p.health = this.p.maxHealth;
        }
        this.trigger('health');
      }
    });
  
    Q.Enemy = Q.Sprite.extend({
      init: function(props) {
        this._super(_({
          sheet: 'characters',
          z: 10,
          health: 10,
          maxHealth: 10,
          damage: 5,
          xp: 100
        }).extend(props));
        this.add('tiled, transition, healthbar');
        this.bind('interact',this,'interact');
        this.hide();
      },
  
      interact: function(data) {
        this.p.health -= data.damage;
        if(this.p.health <= 0) {
          this.destroy();
          data.source.trigger('xp',this.p.xp);
        } else {
          var damage = Math.round(Math.random() * this.p.damage);
          data.source.trigger('interact',
                              { source: this, damage: damage });
        }
        this.trigger('health',this);
      }
    });
  
    Q.Fountain= Q.Sprite.extend({
      init: function(props) {
        this._super(_({
          sheet: 'tiles',
          frame: 71,
          z: 10,
          power: 10
        }).extend(props));
        this.add('tiled');
        this.bind('interact',this,'interact');
        this.hide();
      },
  
      interact: function(data) {
        data.source.trigger('heal',{ amount: this.p.power });
      }
    });
  
    Q.Loot = Q.Sprite.extend({
      init: function(props) {
        this._super(_({
          sheet: 'items',
          frame: Math.floor(Math.random() * 30 * 9) + 150, 
          z: 10,
          gold: Math.floor(Math.random() * 100)
        }).extend(props));
        this.add('tiled');
        this.bind('interact',this,'interact');
        this.hide();
      },
      interact: function(data) {
        data.source.trigger('gold',this.p.gold);
        this.destroy();
      }
    });
  
    Q.Stat = Q.Sprite.extend({
      init: function(props) {
        this._super(_(props).extend({
          w: 100, h: 20, z: 100
        }));
        
        this.el.css({color: 'white', fontFamily: 'arial' })
               .text(this.p.text + ": 0");
      },
  
      update: function(data) {
        this.el.text(this.p.text + ": " + data.amount);
      }
    });
  
    Q.PlayerHealth = Q.Sprite.extend({
      init: function(props) {
        this._super(_(props).extend({
          w: Q.width / 4, h: 20, z: 100
        }));
        this.add('healthbar');
        this.healthbar.large();
      },
      update: function(sprite) {
        this.trigger('health',sprite);
      }
    });
  
    
  
    Q.load(['characters.png',
            'dungeon.png',
            'items.png',
            'level1.txt'], function() {
      
      Q.sheet('characters', 'characters.png', 
              { tilew: tileSize, tileh: tileSize });
      
      Q.sheet('tiles', 'dungeon.png', 
              { tilew: tileSize, tileh: tileSize });
      
      Q.sheet('items', 'items.png',
              { tilew: tileSize, tileh: tileSize });
  
      Q.scene('hud',new Q.Scene(function(stage) {
        var health, gold, xp;
        health = stage.insert(new Q.PlayerHealth({ x: 0, y: 10 }));
        stage.bind('health',health,'update');
        gold = stage.insert(new Q.Stat({ 
                                    text: "gold", x: Q.width-100, y: 10 
                                }));
  
        stage.bind('gold',gold,'update');
        xp = stage.insert(new Q.Stat({ text: "xp", x: Q.width-200, y: 10 }));
        stage.bind('xp',xp,'update');
      }));
  
      
      Q.scene('level1',new Q.Scene(function(stage) {
        Q.stageScene('hud',1);
        if(Q.width > 600 || Q.height > 600) {
          stage.rescale(2);
        }
  
        stage.level = stage.insert(
                        new Q.Level("level1.txt",stage)
                      );
  
        stage.add('transition');
        var player = stage.insert(new Q.Player({ x: stage.level.startX ,
                                                 y: stage.level.startY }));
  
        player.camera.track();
        player.bind('removed',stage,function() {
         Q.stageScene('level1');
        });
  
        player.bind('health',stage,function() {
          Q.stage(1).trigger('health',player);
        });
  
        Q.stage(1).trigger('health',player);
        player.bind('gold',stage,function(amount) {
          player.p.gold += amount;
          Q.stage(1).trigger('gold',{ amount: player.p.gold });
        });
  
        player.bind('xp',stage,function(amount) {
          player.p.xp += amount;
          Q.stage(1).trigger('xp',{ amount: player.p.xp });
        });
      
  
     
      }));
      Q.stageScene('level1');
    });
  });
  
  


(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.