前言
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) };
這個全域性控制器扮演著“遊戲時鐘”的角色
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”運動物件,繼承關係為:
結語
本來是想多說幾句的,但是最近兩天編碼有點累,各位自己去看原始碼吧,我這裡扛不住了。。。。。。