【blade04】用物件導向的方法寫javascript坦克大戰

葉小釵發表於2014-08-24

前言

javascript與程式的語言比如C#或者java不一樣,他並沒有“類”的概念,雖然最新的ECMAScript提出了Class的概念,我們卻沒有怎麼用

就單以C#與Java來說,要到真正理解物件導向的程度也是要花一點功夫的,特別是初學的同學往往意識不到物件導向的好處,因為我們編碼的流程是這樣的

① 程式導向

這個時候,我們要思想一個東西,往往就用一個大程式碼段完成了

② 方法重用

我們有時候再也受不了重複的程式碼在一個地方存在了,於是這個時候我們會將相同的邏輯抽象為一個方法

③ 當程式碼達到一定量的時候,我們發現另一個模組似乎實現了相似的功能,當前這種情況越來越多的發生時,我們會將之變成一個“類”

④ 類的出現又帶來了繼承等特性,這個就是我們所謂物件導向了

程式導向VS物件導向

程式導向的程式碼效率高,程式碼清晰,甚至本身不會發生耦合的現象,但這個只是適用於程式碼量較小或者說複雜度低的系統

系統稍微變大,程式導向的程式碼將變得難以維護並且難以擴充套件

物件導向的程式碼自然效率要稍低,至少程式碼複雜度要上升,對初學者來說不太好理解,加上模組劃分後方法東一個西一個

如果沒有好的設計,出來的程式碼會互相影響,系統層次混亂,但是好的物件導向的設計會讓系統程式碼變得優雅並且有所依據

比如沒有物件導向的類,什麼觀察者、工廠模式是玩不轉的

所以程式導向與物件導向的設計沒有明顯的好壞之分,要看使用場景,系統比較複雜的話就不要去搞程式導向了,因為多人維護一個檔案比上面所以問題都要複雜

此文以一個小型的坦克大戰遊戲來介紹“物件導向”的在前端javascript中的一些使用場景,希望對各位理解物件導向程式設計有所幫助

由於本人水平有限,文中想法有誤,請您提出

遊戲簡介

遊戲原始碼

https://github.com/yexiaochai/blade/tree/master(請看其中的tank資料夾)

http://yexiaochai.github.io/blade/tank/index.html(demo地址)

俗話說得好,沒圖你說個JB,我這裡當然是有圖的!

遊戲說明

做這個遊戲的目的其實主要是驗證Blade框架ui.abstract.view的設計是否合理,因為我準備將他用到實際工作中了,於是這裡便花了週末兩天做這個遊戲

PS:遊戲中的圖片全部是“偷”的,到現在連偷的誰的都不知道了,程式碼完全自己寫的,這裡沒有抄襲

這裡說是坦克大戰,其實不然,因為小時候紅白機的坦克大戰實現起來還是要複雜的多,要實現那種程度的話兩天時間怕是妄想了

於是這裡實現的便是簡版了,說是簡版,其實他的原型是我們小時候玩的手柄遊戲機中的坦克大戰,不知各位還記不記得

功能玩法

遊戲玩法便是與NPC坦克不停的廝殺,廝殺過程中英雄坦克會不停的升級,升級後整體效能會提升,但是隨著級數增加NPC坦克的數量會不停增加

所以一般20多級我就掛了,掛了後也未做什麼處理,主要原因是這兩天寫得太累了!!!

遊戲擴充套件

事實上這個遊戲是可以擴充套件的,雖然我這裡未做擴充套件

首先子彈可做擴充套件,比如英雄的子彈可以變成鐳射或者散彈

其次NPC是可擴充套件的,擴充套件時候NPC可以設定為跟著英雄跑

以上都未實現:),這裡這樣說是因為遊戲本身是以物件導向的方式實現,所以就算我要實現以上功能可以十分方便

程式碼缺陷

最初的想法很好,寫物件導向的程式,但是真正程式碼過程中仍然有一些不夠“物件導向”的寫法,如果後面有時間對他進行重構,這是主要要做的事情

另外,程式碼寫了後只經過了簡單測試,有BUG就不要見外了,可以留言嘛

程式碼實現

其實遊戲的實現,首先要有一個全域性的控制器,我這裡全域性的控制器為app

this.app = {
  //英雄升級引數
  levelParameter: [
    { speed: 1, bulletSpeed: 4, maxBulletSize: 1, init: 0 },
    { speed: 1, bulletSpeed: 4, maxBulletSize: 1, init: 0 },
    { speed: 2, bulletSpeed: 6, maxBulletSize: 1, init: 0 },
    { speed: 2, bulletSpeed: 6, maxBulletSize: 2, init: 32 },
    { speed: 2, bulletSpeed: 6, maxBulletSize: 2, init: 32 },
    { speed: 2, bulletSpeed: 6, maxBulletSize: 3, init: 64 },
    { speed: 2, bulletSpeed: 6, maxBulletSize: 4, init: 64 },
    { speed: 2, bulletSpeed: 6, maxBulletSize: 4, init: 96 },
    { speed: 2, bulletSpeed: 7, maxBulletSize: 5, init: 96 }
  ],
  npcObj: {
    NO1: {
      dirObj: {
        init: 32 * 4
      }
    },
    NO2: {
      dirObj: {
        init: 32 * (4 + 2)
      },
      datamodel: {
        speed: 2
      }
    },
    NO3: {
      dirObj: {
        init: 32 * (4 + 4)
      },
      datamodel: {
        bulletSpeed: 6
      }
    },
    NO4: {
      dirObj: {
        init: 32 * (4 + 6)
      },
      HP: 4
    }
  },
  maxNpcSize: 2,
  npcSize: 0,
  gameStatus: false,
  GAMEOBJ: {
    hero: [], //暫時只有一個hero,這裡先不予關注
    npc: [],
    heroBullet: [],
    npcBullet: [],
    boom: []
  },
  tick: $.proxy(function () {
    if (this.me.status == 'destroy') {
      this.app.gameStatus = false;
    }

    $.each(this.app.GAMEOBJ, $.proxy(function (k, v) {
      //首先做篩選
      this.app.GAMEOBJ[k] = _.filter(this.app.GAMEOBJ[k], function (item) {
        return item.status !== 'destroy';
      });
      for (var i = 0, len = this.app.GAMEOBJ[k].length; i < len; i++) {
        this.app.GAMEOBJ[k][i].move();
      }
    }, this));

    this.createNPC();
    this.dataChange();
    this.levelUp();

    if (this.app.gameStatus) {
      rAF($.proxy(this.app.tick, this));
    }

  }, this),
  //建立NPC坦克
  createNPC: $.proxy(function (opts) {
    opts = $.extend({
      gameRule: 'npc',
      wrapper: this.$el.find('#map'),
      app: this.app
    }, opts, true);
    var flag = parseInt(Math.random() * 4);
    if (parseInt(Math.random() * 5) == 4) {
      opts.speciality = 'hp';
    }
    opts = $.extend(opts, this.app.npcObj['NO' + (flag + 1)], true);
    console.log(opts)

    var npc = new NPCTank(opts);
    var i, len, bullet;

    npc.show();

    /*這裡英雄每一步移動會對NPC產生影響,同樣NPC會對影響造成影響
    npc只需要關注英雄和英雄發出的子彈即可,英雄處理要複雜的對多
    */
    this.me.registerObserver(npc);
    npc.registerObserver(this.me);

    //缺陷,npc暫時不關注npc,可互相穿透
    //          $.each(this.app.GAMEOBJ.npc, function (i, item) {
    //            npc.registerObserver(item);
    //          });

    //記錄最後一個npc以便測試
    this.npc = npc;

    this.app.GAMEOBJ.npc.push(npc);

  }, this),
  //建立我方英雄坦克
  createHero: $.proxy(function () {
    this.me = new Tank({
      datamodel: {
        x: 192,
        y: 192
      },
      gameRule: 'hero',
      wrapper: this.$el.find('#map'),
      app: this.app
    });
    this.me.show();
    window.me = this.me;
    this.app.GAMEOBJ.hero.push(this.me);

  }, this),
  createBullet: $.proxy(function (opts) {
    //子彈的建立要區分是hero還是npc
    opts = $.extend({
      wrapper: this.$el.find('#map'),
      app: this.app
    }, opts, true);
    var gameRule = opts.gameRule;
    var bullet = new Bullet(opts);
    bullet.show();

    //這裡根據子彈角色不同,會有不同的觀察物件,npc子彈對應英雄,英雄子彈物件npc!

    //英雄子彈需要被npc坦克以及子彈觀察
    if (gameRule == 'heroBullet') {
      $.each(this.app.GAMEOBJ.npc, function (i, item) {
        bullet.registerObserver(item);
      });

      $.each(this.app.GAMEOBJ.npcBullet, function (i, item) {
        bullet.registerObserver(item);
      });
    } else if (gameRule == 'npcBullet') {
      //npc子彈來了,英雄就要小心了
      $.each(this.app.GAMEOBJ.hero, function (i, item) {
        bullet.registerObserver(item);
      });

      $.each(this.app.GAMEOBJ.heroBullet, function (i, item) {
        bullet.registerObserver(item);
      });
    }

    this.app.GAMEOBJ[gameRule].push(bullet);

    return bullet;
  }, this),
  createBoom: $.proxy(function (opts) {
    opts = $.extend({
      wrapper: this.$el.find('#map'),
      app: this.app
    }, opts, true);
    var boom = new Boom(opts);
    boom.show();
    this.app.GAMEOBJ.boom.push(boom);
    return boom;

  }, this)

};
View Code

這個全域性控制器扮演著“遊戲時鐘”的角色

 1 tick: $.proxy(function () {
 2   if (this.me.status == 'destroy') {
 3     this.app.gameStatus = false;
 4   }
 5 
 6   $.each(this.app.GAMEOBJ, $.proxy(function (k, v) {
 7     //首先做篩選
 8     this.app.GAMEOBJ[k] = _.filter(this.app.GAMEOBJ[k], function (item) {
 9       return item.status !== 'destroy';
10     });
11     for (var i = 0, len = this.app.GAMEOBJ[k].length; i < len; i++) {
12       this.app.GAMEOBJ[k][i].move();
13     }
14   }, this));
15 
16   this.createNPC();
17   this.dataChange();
18   this.levelUp();
19 
20   if (this.app.gameStatus) {
21     rAF($.proxy(this.app.tick, this));
22   }
23 
24 }, this),

他每過一段時間便會通知遊戲物件運動一下,再根據彼此的運動引發遊戲事件,這個是一個典型的釋出訂閱模型

遊戲時鐘變化,然後通知到其它所有物件運動,並且需要做物件銷燬工作

而遊戲物件完全繼承自一個物件“moveOBJ”運動物件,繼承關係為:

結語

本來是想多說幾句的,但是最近兩天編碼有點累,各位自己去看原始碼吧,我這裡扛不住了。。。。。。

相關文章