飞儿乐队 发表于 2018-5-18 15:34:05

jQuery版坦克游戏,缺陷面向对象重构版!

此版本经过精心重构,面向接口设计,模仿类式继承和多继承(掺元类),多处使用闭包优化实现,使用单
体工厂模式降低代码间耦合,添加图片及地图的预加载等等;重构版用新算法替代了大量DOM相关的操作,
大大提高了游戏性能,即使在ie6下玩,效果也差强人意。

源码说明:
1.tank.main.js 定义了基本的接口及其实现,如图:

Javascript代码
// Interfaces.   
var Tank = new Interface('Tank', ['explode','clear','die','fire','isShot','move','stopMoving','isBlocked','attachEvents','init']);   
var Bullet = new Interface('Bullet',['explode','clear','die','fire','init']);   
var Block = new Interface('Block', ['explode','clear','die','isShot','display']);   

// Abstract tank, impl base methods.   
var AbstractTank = function(opt) { // Constructor   
    this.id = opt.id;   
    this.isAlive = true;   
    this.speed = opt.speed;   
    this.top = opt.pos.y;   
    this.left = opt.pos.x;   
    this.movingToward = 'up';   
    this.init();   
    this.attachEvents();   
};   

AbstractTank.prototype = { // Public methods   
    move: function(_d) {   
       ...   
      },   
    stopMoving: function() {   
      var _d = this.movingToward, thisTank = $('div#' + this.id);   
      clearInterval(this.moveTimr);   
      thisTank.removeClass('moving' + _d);   
    },   
    isBlocked: function() {   
            ...   
    },   
    die: function() {   
      this.isAlive = false;   
      this.explode('mapbomb', 11);   
    },   
    fire: function() {   
      TankWarFactory.createBullet(this);   
      return this;   
    },   
    isShot: function() {   
      throw new Error('isShot function is undefined.');   
    },   
    clear: function() {   
      throw new Error('clear function is undefined.');   
    },   
    attachEvents: function() {   
      throw new Error('attachEvents function is undefined.');   
    },   
    init: function() {   
      throw new Error('init function is undefined.');   
    }   
};

2.tank.factory.js 实例化坦克、block、子弹等。
Javascript代码
var TankWarFactory = {   
    createPlayerTank: function() {   
      var tank = new PlayerTank();   
      Interface.ensureImplements(tank, Tank);   
      TankWar.barrier.players.push(tank);   
      TankWarMng.setTankCount(tank.id, --tank.lives);   
      TankWarMng.setScore(tank, 0);   
    },   
    createEnemyTank: (function() {   
      // Private static check type of enemies.   
      function checkType(type) {   
            var types = TankWar.enemies.types.clone();   
            if (!type) type = 'r';   
            if (TankWar.enemies.leftNum > 0) return type;   
            types.remove(type);   
            for (var i = 0, len = types.length; i < len; i++) {   
                if (TankWar.enemies].leftNum === 0) {   
                  types.remove(types);   
                } else {   
                  return types;   
                }   
            }   
            return false;   
      }   
      return function(type) { // return constructor   
            var tank;   
            type = checkType(type);   
            if (!type) throw new Error('No enemies alive.');   
            switch(type) {   
                case 'r': tank = new EnemyRTank(); break;   
                case 'b': tank = new EnemyBTank(); break;   
                case 'y': tank = new EnemyYTank(); break;   
                case 'g': tank = new EnemyGTank(); break;   
            }   
            Interface.ensureImplements(tank, Tank);   
            TankWar.barrier.enemies.push(tank);   
            TankWarMng.setTankCount(tank.id, --TankWar.enemies.leftNum);   
      }   
    })(),   
    createBullet: function(tank) {   
      var bullet;   
      if (tank instanceof PlayerTank) {   
            bullet = new PlayerBullet(tank);   
      } else {   
            bullet = new EnemyBullet(tank);   
      }   
      Interface.ensureImplements(bullet, Bullet);   
    },   
    createBlock: function(param) {   
      var block;   
      switch(param.type) {   
            case 'e': block = new BrickBlock(param); TankWar.barrier.normalBlocks.push(block);break;   
            case 'h': block = new StoneBlock(param); TankWar.barrier.normalBlocks.push(block);break;   
            case 'k': block = new KingBlock(param); TankWar.barrier.normalBlocks.push(block);break;   
            case 'w': block = new WaterBlock(param); TankWar.barrier.waterBlocks.push(block);break;   
            case 'b': block = new BornBlock(param); TankWar.enemies.posBorn.push({x:block.left,y:block.top,avaliable:true});break;   
            case 'l': block = new LawnBlock(param);break;   
      }   
      Interface.ensureImplements(block, Block);   
    }   
};

3.tankwar.js 将页面切换、游戏初始化、事件绑定等封装为TankWarMng对象的方法。
4.tank.progress.js 图片及地图预加载。
Javascript代码
var PreLoad = (function() {   
    // 私有静态方法   
    function obj2array(givenObj) {   
      var urllist = [], patrn = /1-\d{1,2}\.(png|json)$/, level = 0, levelArr = [];   
      if (TankWar.mySite) levelArr = TankWar.mySite;   
      (function(obj) { // 解析对象,将结果填进urllist数组   
            for (var prop in obj) {   
                if (prop === 'urls') {   
                  for (var i = 0, n = obj.length; i < n; i++) {   
                        if (patrn.test(obj)) {   
                            var tmp = obj.split('.').split('-'), suffix = patrn.exec(obj);   
                            for (var j = tmp, m = tmp; j <= m; j++) {   
                              urllist.push(levelArr.join('/') + '/' + j + '.' + suffix);   
                            }   
                        } else {   
                            urllist.push(levelArr.join('/') + '/' + obj);   
                        }   
                  }   
                  levelArr.splice(--level, 1);   
                } else {   
                  levelArr = prop;   
                  arguments.callee(obj);   
                }   
            }   
      })(givenObj);   
      return urllist;   
    };   

飞儿乐队 发表于 2018-5-18 15:34:35

   // 构造器   
    return function(urlObj, callback) {   
      this.callback = callback;   
      if (!TankWar.mySite) { // 如果没有启动预加载,直接进入回调   
            this.progressBar(100);   
            return;   
      }   
      this.urlList = obj2array(urlObj);   
      this.total = this.urlList.length;   
      this.succeedcount = 0;   
      this.errorcount = 0;   
      this.init();   
    }   
})();   

PreLoad.prototype = {   
    loadImg: function(url) {   
      var img = new Image(), that = this;   
      img.onload = function() {   
            that.complete(url, '图片');   
      }   
      img.onerror = function() {   
            that.error(url);   
      }   
      img.src = url;   
    },   
    loadMap: function(url) {   
      var that = this;   
      $.getJSON(url, function(map) {   
            TankWar.maps.push(map);   
            that.complete(url, '地图');   
      });   
    },   
    complete: function(url, type) {   
      this.progressBar(Math.round(++this.succeedcount*100/this.total), url, type);   
    },   
    error: function(url) {   
      throw new Error('load '+ url +' failed.');   
    },   
    progressBar: function(percent, url, type) {   
      if (url && type) {   
            $('#percent span').text(percent);   
            $('#loading span').text(type + ': ' + url.substr(url.lastIndexOf('/') + 1, url.length));   
      }   
      $('#bar').stop().animate({left: 550 - 550*percent/100}, 200);   
      if (percent === 100) this.over();   
    },   
    over: function() {   
      var that = this;   
      setTimeout(function() {   
            that.callback();   
      }, 500);   
    },   
    init: function() {   
      $('#percent, #loading').show();   
      for (var i = 0; i < this.total; i++) {   
            if (/\.json$/.test(this.urlList))   
                this.loadMap(this.urlList);   
            else
                this.loadImg(this.urlList);   
      }   
    }   
};

5.util.js 接口、接口检查、继承等的实现。
6.tank.namespace.js & tank.config.js 命名空间及常用参数。
Javascript代码
var config = {};   
config.my_site = ''; // 如果将此游戏放在您网站上,请配置网址如:config.my_site = 'http://www.mysite.com/tank',将会自动启用预加载技术,以获得更好的游戏体验   

config.develop_model = 'product'; // develop|test|product 如果是product,将不进行接口检查,以提高游戏速度   

config.enemy_number_of_level = [{r:5,b:3,y:2,g:1},{r:10,b:5,y:3,g:2},{r:15,b:5,y:5,g:5}]; // 每一关的敌方坦克数量,目前有三关   

config.default_scene = 'lawn'; // 默认场景   

// 游戏参数   
config.player1_lives = 4;   
config.player1_speed = 2;   
config.player1_move_keys = { 37: 'left', 38: 'up', 39: 'right', 40: 'down'};   
config.player1_fire_key = 32;   

config.player2_lives = 4;   
config.player2_speed = 2;   
config.player2_move_keys = { 65: 'left', 87: 'up', 68: 'right', 83: 'down'};   
config.player2_fire_key = 71;   

config.enemy_red_speed = 1;   
config.enemy_blue_speed = 1.5;   
config.enemy_yellow_speed = 2;   
config.enemy_green_speed = 2.5;   
config.bullet_speed = 10;

7.1-3.json 地图
Javascript代码
[{   
    "type": "b", // b为敌方坦克出生地,你可以添加/减少b的个数,调节敌方坦克数量   
    "y": 10,   
    "x": 9   
}, {   
    "type": "b",   
    "y": 9,   
    "x": 332   
}, {   
    "type": "b",   
    "y": 9,   
    "x": 653   
},   

{   
    "type": "h", // 子弹打不破的石头   
    "y": 172,   
    "x": 10   
},   

{   
    "type": "l", // 草地   
    "y": 212,   
    "x": 43   
}, {   
    "type": "e", // 子弹两次可以打破的砖头   
    "y": 212,   
    "x": 79   
}]


页: [1]
查看完整版本: jQuery版坦克游戏,缺陷面向对象重构版!