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

一款jQuery版tank游戏,画面美观、逼真,可双人游戏,其效果堪与flash媲美。支持浏览器:ie6+,firefox,opera,safari,在ie9或其他非ie浏览器,可获得更加流畅的游戏速度。

下载地址:http://jtankwar.googlecode.com/files/tank%20war%203.0.zip

诚邀各位志士组团(HTML5+ANDROID):38155026(欢迎入群)

此文精辟点在第七页“fch415”大哥的回复里面!

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

源码说明:

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

Javascript代码

  1. // Interfaces.
  2. var Tank = new Interface('Tank', ['explode','clear','die','fire','isShot','move','stopMoving','isBlocked','attachEvents','init']);
  3. var Bullet = new Interface('Bullet',['explode','clear','die','fire','init']);
  4. var Block = new Interface('Block', ['explode','clear','die','isShot','display']);
  5. // Abstract tank, impl base methods.
  6. var AbstractTank = function(opt) { // Constructor
  7. this.id = opt.id;
  8. this.isAlive = true;
  9. this.speed = opt.speed;
  10. this.top = opt.pos.y;
  11. this.left = opt.pos.x;
  12. this.movingToward = 'up';
  13. this.init();
  14. this.attachEvents();
  15. };
  16. AbstractTank.prototype = { // Public methods
  17. move: function(_d) {
  18. ...
  19. },
  20. stopMoving: function() {
  21. var _d = this.movingToward, thisTank = $('div#' + this.id);
  22. clearInterval(this.moveTimr);
  23. thisTank.removeClass('moving' + _d);
  24. },
  25. isBlocked: function() {
  26. ...
  27. },
  28. die: function() {
  29. this.isAlive = false;
  30. this.explode('mapbomb', 11);
  31. },
  32. fire: function() {
  33. TankWarFactory.createBullet(this);
  34. return this;
  35. },
  36. isShot: function() {
  37. throw new Error('isShot function is undefined.');
  38. },
  39. clear: function() {
  40. throw new Error('clear function is undefined.');
  41. },
  42. attachEvents: function() {
  43. throw new Error('attachEvents function is undefined.');
  44. },
  45. init: function() {
  46. throw new Error('init function is undefined.');
  47. }
  48. };
// 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代码

  1. var TankWarFactory = {
  2. createPlayerTank: function() {
  3. var tank = new PlayerTank();
  4. Interface.ensureImplements(tank, Tank);
  5. TankWar.barrier.players.push(tank);
  6. TankWarMng.setTankCount(tank.id, --tank.lives);
  7. TankWarMng.setScore(tank, 0);
  8. },
  9. createEnemyTank: (function() {
  10. // Private static check type of enemies.
  11. function checkType(type) {
  12. var types = TankWar.enemies.types.clone();
  13. if (!type) type = 'r';
  14. if (TankWar.enemies[type].leftNum > 0) return type;
  15. types.remove(type);
  16. for (var i = 0, len = types.length; i < len; i++) {
  17. if (TankWar.enemies[types[0]].leftNum === 0) {
  18. types.remove(types[0]);
  19. } else {
  20. return types[0];
  21. }
  22. }
  23. return false;
  24. }
  25. return function(type) { // return constructor
  26. var tank;
  27. type = checkType(type);
  28. if (!type) throw new Error('No enemies alive.');
  29. switch(type) {
  30. case 'r': tank = new EnemyRTank(); break;
  31. case 'b': tank = new EnemyBTank(); break;
  32. case 'y': tank = new EnemyYTank(); break;
  33. case 'g': tank = new EnemyGTank(); break;
  34. }
  35. Interface.ensureImplements(tank, Tank);
  36. TankWar.barrier.enemies.push(tank);
  37. TankWarMng.setTankCount(tank.id, --TankWar.enemies[type].leftNum);
  38. }
  39. })(),
  40. createBullet: function(tank) {
  41. var bullet;
  42. if (tank instanceof PlayerTank) {
  43. bullet = new PlayerBullet(tank);
  44. } else {
  45. bullet = new EnemyBullet(tank);
  46. }
  47. Interface.ensureImplements(bullet, Bullet);
  48. },
  49. createBlock: function(param) {
  50. var block;
  51. switch(param.type) {
  52. case 'e': block = new BrickBlock(param); TankWar.barrier.normalBlocks.push(block);break;
  53. case 'h': block = new StoneBlock(param); TankWar.barrier.normalBlocks.push(block);break;
  54. case 'k': block = new KingBlock(param); TankWar.barrier.normalBlocks.push(block);break;
  55. case 'w': block = new WaterBlock(param); TankWar.barrier.waterBlocks.push(block);break;
  56. case 'b': block = new BornBlock(param); TankWar.enemies.posBorn.push({x:block.left,y:block.top,avaliable:true});break;
  57. case 'l': block = new LawnBlock(param);break;
  58. }
  59. Interface.ensureImplements(block, Block);
  60. }
  61. };
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[type].leftNum > 0) return type;
                        types.remove(type);
                        for (var i = 0, len = types.length; i < len; i++) {
                                if (TankWar.enemies[types[0]].leftNum === 0) {
                                        types.remove(types[0]);
                                } else {
                                        return types[0];
                                }
                        }
                        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[type].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代码

  1. var PreLoad = (function() {
  2. // 私有静态方法
  3. function obj2array(givenObj) {
  4. var urllist = [], patrn = /1-\d{1,2}\.(png|json)$/, level = 0, levelArr = [];
  5. if (TankWar.mySite) levelArr[level++] = TankWar.mySite;
  6. (function(obj) { // 解析对象,将结果填进urllist数组
  7. for (var prop in obj) {
  8. if (prop === 'urls') {
  9. for (var i = 0, n = obj[prop].length; i < n; i++) {
  10. if (patrn.test(obj[prop][i])) {
  11. var tmp = obj[prop][i].split('.')[0].split('-'), suffix = patrn.exec(obj[prop][i])[1];
  12. for (var j = tmp[0], m = tmp[1]; j <= m; j++) {
  13. urllist.push(levelArr.join('/') + '/' + j + '.' + suffix);
  14. }
  15. } else {
  16. urllist.push(levelArr.join('/') + '/' + obj[prop][i]);
  17. }
  18. }
  19. levelArr.splice(--level, 1);
  20. } else {
  21. levelArr[level++] = prop;
  22. arguments.callee(obj[prop]);
  23. }
  24. }
  25. })(givenObj);
  26. return urllist;
  27. };
  28. // 构造器
  29. return function(urlObj, callback) {
  30. this.callback = callback;
  31. if (!TankWar.mySite) { // 如果没有启动预加载,直接进入回调
  32. this.progressBar(100);
  33. return;
  34. }
  35. this.urlList = obj2array(urlObj);
  36. this.total = this.urlList.length;
  37. this.succeedcount = 0;
  38. this.errorcount = 0;
  39. this.init();
  40. }
  41. })();
  42. PreLoad.prototype = {
  43. loadImg: function(url) {
  44. var img = new Image(), that = this;
  45. img.onload = function() {
  46. that.complete(url, '图片');
  47. }
  48. img.onerror = function() {
  49. that.error(url);
  50. }
  51. img.src = url;
  52. },
  53. loadMap: function(url) {
  54. var that = this;
  55. $.getJSON(url, function(map) {
  56. TankWar.maps.push(map);
  57. that.complete(url, '地图');
  58. });
  59. },
  60. complete: function(url, type) {
  61. this.progressBar(Math.round(++this.succeedcount*100/this.total), url, type);
  62. },
  63. error: function(url) {
  64. throw new Error('load '+ url +' failed.');
  65. },
  66. progressBar: function(percent, url, type) {
  67. if (url && type) {
  68. $('#percent span').text(percent);
  69. $('#loading span').text(type + ': ' + url.substr(url.lastIndexOf('/') + 1, url.length));
  70. }
  71. $('#bar').stop().animate({left: 550 - 550*percent/100}, 200);
  72. if (percent === 100) this.over();
  73. },
  74. over: function() {
  75. var that = this;
  76. setTimeout(function() {
  77. that.callback();
  78. }, 500);
  79. },
  80. init: function() {
  81. $('#percent, #loading').show();
  82. for (var i = 0; i < this.total; i++) {
  83. if (/\.json$/.test(this.urlList[i]))
  84. this.loadMap(this.urlList[i]);
  85. else
  86. this.loadImg(this.urlList[i]);
  87. }
  88. }
  89. };
var PreLoad = (function() {
        // 私有静态方法
        function obj2array(givenObj) {
                var urllist = [], patrn = /1-\d{1,2}\.(png|json)$/, level = 0, levelArr = [];
                if (TankWar.mySite) levelArr[level++] = TankWar.mySite;
                (function(obj) { // 解析对象,将结果填进urllist数组
                        for (var prop in obj) {
                                if (prop === 'urls') {
                                        for (var i = 0, n = obj[prop].length; i < n; i++) {
                                                if (patrn.test(obj[prop][i])) {
                                                        var tmp = obj[prop][i].split('.')[0].split('-'), suffix = patrn.exec(obj[prop][i])[1];
                                                        for (var j = tmp[0], m = tmp[1]; j <= m; j++) {
                                                                urllist.push(levelArr.join('/') + '/' + j + '.' + suffix);
                                                        }
                                                } else {
                                                        urllist.push(levelArr.join('/') + '/' + obj[prop][i]);
                                                }
                                        }
                                        levelArr.splice(--level, 1);
                                } else {
                                        levelArr[level++] = prop;
                                        arguments.callee(obj[prop]);
                                }
                        }
                })(givenObj);
                return urllist;
        };
        // 构造器
        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[i]))
                                this.loadMap(this.urlList[i]);
                        else
                                this.loadImg(this.urlList[i]);
                }
        }
};

5.util.js 接口、接口检查、继承等的实现。

6.tank.namespace.js & tank.config.js 命名空间及常用参数。

Javascript代码

  1. var config = {};
  2. config.my_site = ''; // 如果将此游戏放在您网站上,请配置网址如:config.my_site = 'http://www.mysite.com/tank',将会自动启用预加载技术,以获得更好的游戏体验
  3. config.develop_model = 'product'; // develop|test|product 如果是product,将不进行接口检查,以提高游戏速度
  4. 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}]; // 每一关的敌方坦克数量,目前有三关
  5. config.default_scene = 'lawn'; // 默认场景
  6. // 游戏参数
  7. config.player1_lives = 4;
  8. config.player1_speed = 2;
  9. config.player1_move_keys = { 37: 'left', 38: 'up', 39: 'right', 40: 'down'};
  10. config.player1_fire_key = 32;
  11. config.player2_lives = 4;
  12. config.player2_speed = 2;
  13. config.player2_move_keys = { 65: 'left', 87: 'up', 68: 'right', 83: 'down'};
  14. config.player2_fire_key = 71;
  15. config.enemy_red_speed = 1;
  16. config.enemy_blue_speed = 1.5;
  17. config.enemy_yellow_speed = 2;
  18. config.enemy_green_speed = 2.5;
  19. config.bullet_speed = 10;
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代码

  1. [{
  2. "type": "b", // b为敌方坦克出生地,你可以添加/减少b的个数,调节敌方坦克数量
  3. "y": 10,
  4. "x": 9
  5. }, {
  6. "type": "b",
  7. "y": 9,
  8. "x": 332
  9. }, {
  10. "type": "b",
  11. "y": 9,
  12. "x": 653
  13. },
  14. {
  15. "type": "h", // 子弹打不破的石头
  16. "y": 172,
  17. "x": 10
  18. },
  19. {
  20. "type": "l", // 草地
  21. "y": 212,
  22. "x": 43
  23. }, {
  24. "type": "e", // 子弹两次可以打破的砖头
  25. "y": 212,
  26. "x": 79
  27. }]
[{
        "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
}]
更多精彩,请下载源码:http://jtankwar.googlecode.com/files/tank%20war%203.0.zip

游戏效果截图:

主页--游戏设置

游戏中..

暂停/退出

任务结束

此文供大家交流,共同学习,可以随便转载!