久久久久久久av_日韩在线中文_看一级毛片视频_日本精品二区_成人深夜福利视频_武道仙尊动漫在线观看

JavaScript 游戲中的面向?qū)ο蟮脑O(shè)計

簡介 在本文中,您將了解 JavaScript 中的 OOP,來探索原型繼承模型和經(jīng)典繼承模型。舉例說明游戲中能夠從 OOP 設(shè)計的結(jié)構(gòu)和可維護性中獲得極大利益的模式。我們的最終目標是讓每一塊
       簡介

       在本文中,您將了解 JavaScript 中的 OOP,來探索原型繼承模型和經(jīng)典繼承模型。舉例說明游戲中能夠從 OOP 設(shè)計的結(jié)構(gòu)和可維護性中獲得極大利益的模式。我們的最終目標是讓每一塊代碼都成為人類可讀的代碼,并代表一種想法和一個目的,這些代碼的結(jié)合超越了指令和算法的集合,成為一個精致的藝術(shù)品。

       JavaScript 中的 OPP 的概述

       OOP 的目標就是提供數(shù)據(jù)抽象、模塊化、封裝、多態(tài)性和繼承。通過 OOP,您可以在代碼編寫中抽象化代碼的理念,從而提供優(yōu)雅、可重用和可讀的代碼,但這會消耗文件計數(shù)、行計數(shù)和性能(如果管理不善)。

       過去,游戲開發(fā)人員往往會避開純 OOP 方式,以便充分利用 CPU 周期的性能。很多 JavaScript 游戲教程采用的都是非 OOP 方式,希望能夠提供一個快速演示,而不是提供一種堅實的基礎(chǔ)。與其他游戲的開發(fā)人員相比,JavaScript 開發(fā)人員面臨不同的問題:內(nèi)存是非手動管理的,且 JavaScript 文件在全局的上下文環(huán)境中執(zhí)行,這樣一來,無頭緒的代碼、命名空間的沖突和迷宮式的 if/else 語句可能會導(dǎo)致可維護性的噩夢。為了從 JavaScript 游戲的開發(fā)中獲得最大的益處,請遵循 OOP 的最佳實踐,顯著提高未來的可維護性、開發(fā)進度和游戲的表現(xiàn)能力。

       原型繼承

       與使用經(jīng)典繼承的語言不同,在 JavaScript 中,沒有內(nèi)置的類結(jié)構(gòu)。函數(shù)是 JavaScript 世界的一級公民,并且,與所有用戶定義的對象類似,它們也有原型。用 new 關(guān)鍵字調(diào)用函數(shù)實際上會創(chuàng)建該函數(shù)的一個原型對象副本,并使用該對象作為該函數(shù)中的關(guān)鍵字 this 的上下文。清單 1 給出了一個例子。

       清單 1. 用原型構(gòu)建一個對象


// constructor function
function MyExample() {
// property of an instance when used with the 'new' keyword
this.isTrue = true;
};

MyExample.prototype.getTrue = function() {
return this.isTrue;
}

MyExample();
// here, MyExample was called in the global context,
// so the window object now has an isTrue property—this is NOT a good practice

MyExample.getTrue;
// this is undefined—the getTrue method is a part of the MyExample prototype,
// not the function itself

var example = new MyExample();
// example is now an object whose prototype is MyExample.prototype

example.getTrue; // evaluates to a function
example.getTrue(); // evaluates to true because isTrue is a property of the
// example instance

       依照慣例,代表某個類的函數(shù)應(yīng)該以大寫字母開頭,這表示它是一個構(gòu)造函數(shù)。該名稱應(yīng)該能夠代表它所創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)。

       創(chuàng)建類實例的秘訣在于綜合新的關(guān)鍵字和原型對象。原型對象可以同時擁有方法和屬性,如 清單 2 所示。

       清單 2. 通過原型化的簡單繼承

// Base class
function Character() {};

Character.prototype.health = 100;

Character.prototype.getHealth = function() {
return this.health;
}

// Inherited classes

function Player() {
this.health = 200;
}

Player.prototype = new Character;

function Monster() {}

Monster.prototype = new Character;

var player1 = new Player();

var monster1 = new Monster();

player1.getHealth(); // 200- assigned in constructor

monster1.getHealth(); // 100- inherited from the prototype object

       為一個子類分配一個父類需要調(diào)用 new 并將結(jié)果分配給子類的 prototype 屬性,如 清單 3 所示。因此,明智的做法是保持構(gòu)造函數(shù)盡可能的簡潔和無副作用,除非您想要傳遞類定義中的默認值。

       如果您已經(jīng)開始嘗試在 JavaScript 中定義類和繼承,那么您可能已經(jīng)意識到該語言與經(jīng)典 OOP 語言的一個重要區(qū)別:如果已經(jīng)覆蓋這些方法,那么沒有 super 或 parent 屬性可用來訪問父對象的方法。對此有一個簡單的解決方案,但該解決方案違背了 “不要重復(fù)自己 (DRY)” 原則,而且很有可能是如今有很多庫試圖模仿經(jīng)典繼承的最重要的原因。

       清單 3. 從子類調(diào)用父方法

function ParentClass() {
this.color = 'red';
this.shape = 'square';
}

function ChildClass() {
ParentClass.call(this); // use 'call' or 'apply' and pass in the child
// class's context
this.shape = 'circle';
}

ChildClass.prototype = new ParentClass(); // ChildClass inherits from ParentClass

ChildClass.prototype.getColor = function() {
return this.color; // returns "red" from the inherited property
};

       在 清單 3 中, color 和 shape 屬性值都不在原型中,它們在 ParentClass 構(gòu)造函數(shù)中賦值。ChildClass 的新實例將會為其形狀屬性賦值兩次:一次作為 ParentClass 構(gòu)造函數(shù)中的 "squre",一次作為 ChildClass 構(gòu)造函數(shù)中的 "circle"。將類似這些賦值的邏輯移動到原型將會減少副作用,讓代碼變得更容易維護。

       在原型繼承模型中,可以使用 JavaScript 的 call 或 apply 方法來運行具有不同上下文的函數(shù)。雖然這種做法十分有效,可以替代其他語言的 super 或 parent,但它帶來了新的問題。如果需要通過更改某個類的名稱、它的父類或父類的名稱來重構(gòu)這個類,那么現(xiàn)在您的文本文件中的很多地方都有了這個 ParentClass 。隨著您的類越來越復(fù)雜,這類問題也會不斷增長。更好的一個解決方案是讓您的類擴展一個基類,使代碼減少重復(fù),尤其在重新創(chuàng)建經(jīng)典繼承時。

       經(jīng)典繼承

       雖然原型繼承對于 OOP 是完全可行的,但它無法滿足優(yōu)秀編程的某些目標。比如如下這些問題:

  • 它不是 DRY 的。類名稱和原型隨處重復(fù),讓讀和重構(gòu)變得更為困難。
  • 構(gòu)造函數(shù)在原型化期間調(diào)用。一旦開始子類化,就將不能使用構(gòu)造函數(shù)中的一些邏輯。
  • 沒有為強封裝提供真正的支持。
  • 沒有為靜態(tài)類成員提供真正的支持。

       很多 JavaScript 庫試圖實現(xiàn)更經(jīng)典的 OOP 語法來解決上述問題。其中一個更容易使用的庫是 Dean Edward 的 Base.js,它提供了下列有用特性:

  • 所有原型化都是用對象組合(可以在一條語句中定義類和子類)完成的。
  • 用一個特殊的構(gòu)造函數(shù)為將在創(chuàng)建新的類實例時運行的邏輯提供一個安全之所。
  • 它提供了靜態(tài)類成員支持。
  • 它對強封裝的貢獻止步于讓類定義保持在一條語句內(nèi)(精神封裝,而非代碼封裝)。

       其他庫可以提供對公共和私有方法和屬性(封裝)的更嚴格支持,Base.js 提供了一個簡潔、易用、易記的語法。

       清單 4 給出了對 Base.js 和經(jīng)典繼承的簡介。該示例用一個更為具體的 RobotEnemy 類擴展了抽象 Enemy 類的特性。

       清單 4. 對 Base.js 和經(jīng)典繼承的簡介

// create an abstract, basic class for all enemies
// the object used in the .extend() method is the prototype
var Enemy = Base.extend({
health: 0,
damage: 0,
isEnemy: true,

constructor: function() {
// this is called every time you use "new"
},

attack: function(player) {
player.hit(this.damage); // "this" is your enemy!
}
});

// create a robot class that uses Enemy as its parent
//
var RobotEnemy = Enemy.extend({
health: 100,
damage: 10,

// because a constructor isn't listed here,
// Base.js automatically uses the Enemy constructor for us

attack: function(player) {
// you can call methods from the parent class using this.base
// by not having to refer to the parent class
// or use call / apply, refactoring is easier
// in this example, the player will be hit
this.base(player);

// even though you used the parent class's "attack"
// method, you can still have logic specific to your robot class
this.health += 10;
}
});

       游戲設(shè)計中的 OOP 模式

       基本的游戲引擎不可避免地依賴于兩個函數(shù):update 和 render。render 方法通常會根據(jù) setInterval 或 polyfill 進行 requestAnimationFrame,比如 Paul Irish 使用的這個(請參閱 參考資料)。使用 requestAnimationFrame 的好處是僅在需要的時候調(diào)用它。它按照客戶監(jiān)視器的刷新頻率運行(對于臺式機,通常是一秒 60 次),此外,在大多數(shù)瀏覽器中,通常根本不會運行它,除非游戲所在的選項卡是活動的。它的優(yōu)勢包括:

  • 在用戶沒有盯著游戲時減少客戶機上的工作量
  • 節(jié)省移動設(shè)備上的用電。
  • 如果更新循環(huán)與呈現(xiàn)循環(huán)有關(guān)聯(lián),那么可以有效地暫停游戲。

       出于這些原因,與 setInterval 相比,requestAnimationFrame 一直被認為是 “客戶友好” 的 “好公民”。

       將 update 循環(huán)與 render 循環(huán)捆綁在一起會帶來新的問題:要保持游戲動作和動畫的速度相同,而不管呈現(xiàn)循環(huán)的運行速度是每秒 15 幀還是 60 幀。這里要掌握的技巧是在游戲中建立一個時間單位,稱為滴答 (tick),并傳遞自上次更新后過去的時間量。然后,就可以將這個時間量轉(zhuǎn)換成滴答數(shù)量,而模型、物理引擎和其他依賴于時間的游戲邏輯可以做出相應(yīng)的調(diào)整。比如,一個中毒的玩家可能會在每個滴答接受 10 次損害,共持續(xù) 10 個滴答。如果呈現(xiàn)循環(huán)運行太快,那么玩家在某個更新調(diào)用上可能不會接受損害。但是,如果垃圾回收在最后一個導(dǎo)致過去 1 個半滴答的呈現(xiàn)循環(huán)上生效,那么您的邏輯可能會導(dǎo)致 15 次損害。

       另一個方式是將模型更新從視圖循環(huán)中分離出來。在包含很多動畫或?qū)ο蠡蚴抢L制占用了大量資源的游戲中,更新循環(huán)與 render 循環(huán)的耦合會導(dǎo)致游戲完全慢下來。在這種情況下,update 方法能夠以設(shè)置好的間隔運行(使用 setInterval),而不管 requestAnimationFrame 處理程序何時會觸發(fā),以及多久會觸發(fā)一次。在這些循環(huán)中花費的時間實際上都花費在了呈現(xiàn)步驟中,所以,如果只有 25 幀被繪制到屏幕上,那么游戲會繼續(xù)以設(shè)置好的速度運行。在這兩種情況下,您可能都會想要計算更新周期之間的時間差;如果一秒更新 60 次,那么完成函數(shù)更新最多有 16ms 的時間。如果運行此操作的時間更長(或如果運行了瀏覽器的垃圾回收),那么游戲還是會慢下來。 清單 5 顯示了一個示例。

       清單 5. 帶有 render 和 update 循環(huán)的基本應(yīng)用程序類

// requestAnim shim layer by Paul Irish
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();

var Engine = Base.extend({
stateMachine: null, // state machine that handles state transitions
viewStack: null, // array collection of view layers,
// perhaps including sub-view classes
entities: null, // array collection of active entities within the system
// characters,
constructor: function() {
this.viewStack = []; // don't forget that arrays shouldn't be prototype
// properties as they're copied by reference
this.entities = [];

// set up your state machine here, along with the current state
// this will be expanded upon in the next section

// start rendering your views
this.render();
// start updating any entities that may exist
setInterval(this.update.bind(this), Engine.UPDATE_INTERVAL);
},

render: function() {
requestAnimFrame(this.render.bind(this));
for (var i = 0, len = this.viewStack.length; i < len; i++) {
// delegate rendering logic to each view layer
(this.viewStack[i]).render();
}
},

update: function() {
for (var i = 0, len = this.entities.length; i < len; i++) {
// delegate update logic to each entity
(this.entities[i]).update();
}
}
},

// Syntax for Class "Static" properties in Base.js. Pass in as an optional
// second argument to.extend()
{
UPDATE_INTERVAL: 1000 / 16
});

       如果您對 JavaScript 中 this 的上下文不是很熟悉,請注意 .bind(this) 被使用了兩次:一次是在 setInterval 調(diào)用中的匿名函數(shù)上,另一次是在 requestAnimFrame 調(diào)用中的 this.render.bind() 上。setInterval 和 requestAnimFrame 都是函數(shù),而非方法;它們屬于這個全局窗口對象,不屬于某個類或身份。因此,為了讓此引擎的呈現(xiàn)和更新方法的 this 引用我們的 Engine 類的實例,調(diào)用 .bind(object) 會迫使此函數(shù)中的 this 與正常情況表現(xiàn)不同。如果您支持的是 Internet Explorer 8 或其更早版本,則需要添加一個 polyfill,將它用于綁定。


【網(wǎng)站聲明】本站除付費源碼經(jīng)過測試外,其他素材未做測試,不保證完整性,網(wǎng)站上部分源碼僅限學(xué)習(xí)交流,請勿用于商業(yè)用途。如損害你的權(quán)益請聯(lián)系客服QQ:2655101040 給予處理,謝謝支持。

相關(guān)文檔推薦

由于實際運行環(huán)境是在瀏覽器中,因此性能還取決于JavaScript解釋器的效率,指定的FPS幀速在低性能解釋器中可能不會達到,所以這部分不是開發(fā)者能夠決定的,開發(fā)者能作的是盡可能通
本文將使用HTML5提供的VideoAPI做一個自定義的視頻播放器,需要用到HTML5提供的video標簽、以及HTML5提供的對JavascriptAPI的擴展。,HTML5中國,中國最大的HTML5中文門戶。
隨著 Hybrid 應(yīng)用的豐富,HTML5 工程師們已經(jīng)不滿足于把桌面端體驗簡單移植到移動端,他們覬覦移動原生應(yīng)用人性化的操作體驗,特別是原生應(yīng)用與生俱來的豐富的手勢系統(tǒng)。HTML5 沒有提
你想要在自己網(wǎng)站上分享一個產(chǎn)品,或者是一個作品集,又或者僅僅只是一個靈感。在你發(fā)布到網(wǎng)上之前,你想讓它看起來有吸引力,專業(yè),或者至少得看起來像那么回事。那么你接下
H5廣告,包括H5廣告的設(shè)計流程,究竟有什么講究,和階段。為了能幫助更多的人了解H5廣告,我專門做了一個講義。同時,也讓我意外的收到了非常好反饋和認!這是對我的極大鼓勵!我的
本文主要內(nèi)容有:框架與組件、構(gòu)建生態(tài)、開發(fā)技巧與調(diào)試、html、css與重構(gòu)、native/hybrid/桌面開發(fā)、前端/H5優(yōu)化、全棧/全端開發(fā)、研究實驗、數(shù)據(jù)分析與監(jiān)控、其它軟技能、前端技術(shù)網(wǎng)
主站蜘蛛池模板: 久久久久亚洲精品 | 日韩在线免费播放 | 国产一区二区欧美 | 亚洲国产情侣 | 日韩精品在线网站 | 国产成人精品久久久 | 97成人免费 | 成年人黄色免费视频 | 天久久 | 日韩视频一级 | 久久久性色精品国产免费观看 | 涩爱av一区二区三区 | 欧美一级欧美三级在线观看 | 在线不卡视频 | 久久机热 | 成人国内精品久久久久一区 | 国产精品一区二区免费 | 999热在线视频 | 精品国产一区二区三区久久久蜜月 | 亚洲精品性视频 | 色婷婷av一区二区三区软件 | 黄视频网址 | 日韩欧美大片在线观看 | 浮生影院免费观看中文版 | 一区二区三区视频在线 | 久久曰视频 | 中文字幕一区二区三区在线观看 | 免费在线一区二区 | 亚洲色视频 | tube国产| 欧美日韩久久 | 精品1区2区3区4区 | 日韩一区二区三区在线观看 | 国产精品91网站 | 亚洲人成人一区二区在线观看 | 中文字幕亚洲无线 | 亚洲人成网亚洲欧洲无码 | 久草视频网站 | 欧美日一区 | 欧美午夜精品 | 激情a |