HTML5移動遊戲開發高階程式設計 9:自建Quintus引擎(1)
9.1 引言
現在的JavaScript非常適於當成一種互動式遊戲開發語言使用
9.2 建立可重用HTML5引擎的框架
該引擎將被命名成Quintus。
設計基本的引擎API:
- 需要在同一頁面上執行多個引擎例項,該需求確保引擎作為獨立單元出現,不會干擾自身或頁面的其他組成部分。
- 在可能的情況下,引擎應提供合理的選項預設值,以此免去搭建執行環境所需的大量配置
- 引擎應足夠靈活,對於簡單的例子和較複雜的遊戲來說都是可用的,且應支援不同的渲染引擎(比如Canvas、CSS、SVG和可能的WebGL等)。
9.3 新增遊戲迴圈
遊戲迴圈必須使用定時器來實現,定時器釋放JavaScript程式碼的控制權,把控制權交回給瀏覽器,這樣瀏覽器才能更新頁面和處理輸入事件。
構建更好的遊戲迴圈定時器:使用requestAnimationFrame API
傳統的遊戲迴圈使用兩大塊程式碼來執行每一幀,它們分別是更新程式碼和渲染程式碼。更新部分負責推進遊戲邏輯在一小段時間內的發展,處理任何的使用者輸入、移動和物件間碰撞,以及把每個遊戲物件更新成一致的狀態。
然後,遊戲需要把自身渲染到螢幕上,至於以什麼樣的步驟進行渲染,這取決於遊戲的構建方式。就基於畫布的遊戲而言,通常你需要清除整塊畫布,重新在頁面上繪製所有必需的精靈。而對於CSS和SVG遊戲來說,只要正確更新了頁面上的物件的屬性,那麼工作實際上就算完成了---瀏覽器會負責物件的移動和更新。
9.4 新增繼承
Quintus同時支援繼承和元件,採取了一種介於這兩者之間的折中做法,這使得你既能在一些地方合理使用繼承,又可在需要更多靈活性時使用元件。為了支援前者,引擎需要把一個傳統繼承模型新增到JavaScript中;為了支援後者,它需要增加一個元件系統,以及要擁有對事件的底層支援,這樣才能做到儘可能支援元件之間的解耦。
一個最受歡迎的類層次結構是jQuery的建立者John Resig的Simple JavaScript繼承,其靈感來自base2和另一個名為Prototype.js的JavaScript庫。
var Person = Class.extend({
init: function() {
console.log('Created Person');
},
speak: function() {
console.log('Person Speaking:');
},
});
var p = new Person();
p.speak();
9.5 支援事件
這便於避免引擎的不同部分過度緊密耦合。這意味著,在不必瞭解和遊戲進行通訊的物件的任何資訊的情況下,遊戲的一部分和其他部分之間能夠就事件和行為進行溝通。
在把一些元件新增到這一混搭環境中後,它甚至允許精靈在不必瞭解構成自身的所有元件的情況下與自身通訊。精靈的某個物理元件可能會觸發一個碰撞事件,而兩個負責監聽該事件的元件則可以分別處理適當音響效果和動畫效果的觸發過程。
設計事件API:使用了一個名為Evented的基類,這是任何需要訂閱和觸發事件的物件的起點。
9.6 支援元件
元件簡化了小塊可重用功能的建立,這些可重用功能可經組合和匹配來滿足各種精靈和物件的需要。
精靈元件的增加和刪除操作必須簡捷,它們應是可通過物件訪問的,但又不能過分汙染物件的名稱空間。
需要註冊元件、新增元件、刪除元件,還允許使用其他一些方法來擴充基本精靈。
gameloop_test.html
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src='jquery.min.js'></script>
<script src='underscore.js'></script>
<script src='quintus.js'></script>
</head>
<body>
<div id='timer'>0</div>
<div id='fps'>0</div>
<button id='pause'>Pause</button>
<button id='unpause'>Unpause</button>
<script>
var TimerTest = Quintus();
var totalFrames = 0;
var totalTime = 0.0;
TimerTest.gameLoop(function(dt) {
totalTime += dt;
totalFrames += 1;
//console.log(totalTime);
//console.log(totalFrames);
$("#timer").text(Math.round(totalTime * 1000) + " MS");
$("#fps").text(Math.round(totalFrames / totalTime) + " FPS");
});
$("#pause").on('click', TimerTest.pauseGame);
$("#unpause").on('click', TimerTest.unpauseGame);
</script>
</body>
</html>
quintus.js
// 繼承
(function(){
var initializing = false;
var fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
this.Class = function(){};
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
// 是否已存在於超類中,若是,則建立一個函式,在再次呼叫新方法之前
// 該函式會先對this._super執行一個臨時的重新賦值。
// 若該方法不存在,程式碼僅對屬性賦值,不會加入任何額外開銷。
for (var name in prop) {
// Check if we're
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" &&
fnTest.test(prop[name]) ?
(function(name, fn) {
return function() {
var tmp = this._super;
// Add a new ._super()
this._super = _super[name];
//
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) : prop[name];
}
// 建構函式
function Class() {
// All construction is actually done in the init method
if (!initializing && this.init) {
this.init.apply(this, arguments);
}
}
// Populate
Class.prototype = prototype;
// Enforce
Class.prototype.constructor = Class;
// make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();
// 新增遊戲迴圈
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for (var x=0; x<vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
window[vendors[x] + 'CancelRequestAnimationFrame'];
};
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
}());
// 基本的引擎結構
var Quintus = function(opts) {
var Q = {};
//
Q.options = {
// TODO:
};
if (opts) {
_(Q.options).extend(opts);
}
Q._normalizeArg = function(arg) {
if (_.isString(arg)) {
arg = arg.replace(/\s+/g,'').split(",");
}
if (!_.isArray(arg)) {
arg = [ arg ];
}
return arg;
};
// Shortcut to extend Quintus with new functionality
// binding the methods to Q
Q.extend = function(obj) {
_(Q).extend(obj);
return Q;
};
// Syntax for including other modules into quintus
Q.include = function(mod) {
_.each(Q._normalizeArg(mod), function(m) {
m = Quintus[m] || m;
m(Q);
});
return Q;
};
// 遊戲迴圈
Q.gameLoop = function(callback) {
Q.lastGameLoopFrame = new Date().getTime();
Q.gameLoopCallbackWrapper = function(now) {
Q.loop = requestAnimationFrame(Q.gameLoopCallbackWrapper);
var dt = now - Q.lastGameLoopFrame;
if (dt > 100) {
dt = 100;
}
callback.apply(Q, [dt/1000]);
Q.lastGameLoopFrame = now;
};
requestAnimationFrame(Q.gameLoopCallbackWrapper);
};
// 暫停遊戲
Q.pauseGame = function() {
if (Q.loop) {
cancelAnimationFrame(Q.loop);
}
Q.loop = null;
};
// 取消暫停
Q.unpauseGame = function() {
if (!Q.loop) {
Q.lastGameLoopFrame = new Date().getTime();
Q.loop = requestAnimationFrame(Q.gameLoopCallbackWrapper);
}
}
// 事件類
Q.Evented = Class.extend({
bind: function(event, target, callback) {
if (!callback) {
callback = target;
target = null;
}
if (_.isString(classback)) {
callback = target[callback];
}
this.listeners = this.listeners || {};
this.listeners[event] = this.listeners[event] || [];
this.listeners[event].push([target || this, callback]);
if (target) {
if (!target.binds) {
target.binds = [];
}
target.binds.push([this, event, callback]);
}
},
// 觸發事件
trigger: function(event, data) {
if (this.listeners && this.listeners[event]) {
for (var i=0,len=this.listeners[event].length; i<len; i++) {
var listener = this.listeners[event][i];
listener[1].call(listener[0], data);
}
}
},
unbind: function(event, target, callback) {
if (!target) {
if (this.listeners[event]) {
delete this.listeners[event];
}
} else {
var l = this.listeners && this.listeners[event];
if (l) {
for (var i=l.length-1; i>=0; i--) {
if (l[i][0] == target) {
if (!callback || callback == l[i][1]) {
this.listeners[event].splice(i,1);
}
}
}
}
}
},
// 銷燬物件時刪除其所有的監聽器
debind: function() {
if (this.binds) {
for (var i=0,len=this.binds.length; i<len; i++) {
var boundEvent = this.binds[i];
var source = boundEvent[0];
var event = boundEvent[1];
source.unbind(event, this);
}
}
},
});
// 元件
Q.components = {};
Q.register = function(name, methods) {
methods.name = name;
Q.components[name] = Q.Component.extend(methods);
};
Q.Component = Q.Evented.extend({
init: function(entity) {
this.entity = entity;
if (this.extend) {
_.extend(entity, this.extend);
}
entity[this.name] = this;
entity.activeComponents.push(this.name);
if (this.added)
this.added();
},
destroy: function() {
if (this.extend) {
var extensions = _.keys(this.extend);
for (var i=0,len=extensions.length; i<len; i++) {
delete this.entity[extensions[i]];
}
}
delete this.entity[this.name];
var idx = this.entity.activeComponents.indexOf(this.name);
if (idx != -1) {
this.entity.activeComponents.splice(idx, 1);
}
this.debind();
if (this.destroyed) this.destroyed();
}
});
// 所有活動的遊戲物件的基類
Q.GameObject = Q.Evented.extend({
has: function(component) {
return this[component] ? true : false;
},
add: function(components) {
components = Q._normalizeArg(components);
if (!this.activeComponents) {
this.activeComponents = [];
}
for (var i=0,len=components.length; i<len; i++) {
var name = components[i];
var comp = Q.components[name];
if (!this.has(name) && comp) {
var c = new comp(this);
this.trigger('addComponent', c);
}
}
return this;
},
del: function(components) {
components = Q._normalizeArg(components);
for (var i=0,len=components.length; i<len; i++) {
var name = components[i];
if (name && this.has(name)) {
this.trigger('delComponent', this[name]);
this[name].destroy();
}
}
return this;
},
destroy: function() {
if (this.destroyed) {
return;
}
this.debind();
if (this.parent && this.parent.remove) {
this.parent.remove(this);
}
this.trigger('removed');
this.destroyed = true;
}
});
return Q;
}
你已經擁有了一些用來建立可重用的HTML5遊戲引擎的構建塊,你建立了最初的遊戲容器物件、遊戲迴圈以及一些使用事件和元件的基類。相關文章
- 【程式設計師的遊戲開發之路】 遊戲架構程式設計師遊戲開發架構
- Rust 程式設計影片教程(進階)——026_1 高階 trait1Rust程式設計AI
- Rust 程式設計影片教程(進階)——027_1 高階特性Rust程式設計
- 【譯】闖入遊戲開發 #3:程式設計遊戲開發程式設計
- 3D遊戲程式設計作業93D遊戲程式設計
- Rust 程式設計視訊教程(進階)——026_1 高階 trait1Rust程式設計AI
- Rust 程式設計視訊教程(進階)——027_1 高階特性Rust程式設計
- 遊戲開發與設計遊戲開發
- 遊戲陪玩app開發,高併發系統如何設計?遊戲APP
- 《Unity移動遊戲開發》讀後感Unity遊戲開發
- Python 高階程式設計:深入探索高階程式碼實踐Python程式設計
- HTML5遊戲開發(二):使用TypeScript編寫程式碼HTML遊戲開發TypeScript
- 室內設計遊戲為何風靡移動遊戲市場?遊戲
- 遊戲開發—協議設計遊戲開發協議
- 高階語言程式設計課程第9次個人作業程式設計
- Javascript高階程式設計 備忘JavaScript程式設計
- C++高階程式設計pdfC++程式設計
- windows核心程式設計--DLL高階Windows程式設計
- 重讀《JavaScript高階程式設計》JavaScript程式設計
- Flink(1.11)高階程式設計——FlinkSQL程式設計SQL
- JavaScript高階程式設計筆記JavaScript程式設計筆記
- 遊戲發展勢頭強勁:2021 年移動遊戲使用者支出高達 1,160 億美元遊戲
- 鴨鴨星球遊戲系統程式設計開發丨NFT鏈遊遊戲開發技術語言程式設計遊戲開發
- GameFi+NFT鏈遊開發技術/NFT鏈遊遊戲系統程式設計開發程式碼示例GAM遊戲程式設計
- 來黑馬程式設計師從零學前端與移動開發----移動web開發----伸縮佈局程式設計師前端移動開發Web
- HTML5與WebGL程式設計(1):介紹HTMLWeb程式設計
- 7個HTML5移動開發框架,初學HTML5必看HTML移動開發框架
- 高階前端程式設計師面試問題與答案【精選9道】前端程式設計師面試
- 從初級到高階,如何設計出好的遊戲掩體遊戲
- java遊戲開發雜談 - java程式設計怎麼學Java遊戲開發程式設計
- 人人都能學會的python程式設計教程14:高階特性1Python程式設計
- 全景探祕遊戲設計藝術(1):遊戲設計師遊戲設計師
- 學習程式設計從遊戲開始程式設計遊戲
- 如何設計高難度遊戲遊戲
- shell程式設計,實戰高階進階教學程式設計
- unix環境高階程式設計(中)-程式篇程式設計
- GameFi/NFT鏈遊合成遊戲系統技術程式設計開發程式碼示例GAM遊戲程式設計
- Pygame - Python 遊戲程式設計入門 class1GAMPython遊戲程式設計
- 遊戲大地圖開發指南:遊戲外部空間設計遊戲地圖