现在的JavaScript非常适于当成一种交互式游戏开发语言使用
该引擎将被命名成Quintus。
设计基本的引擎API:
需要在同一页面上运行多个引擎实例,该需求确保引擎作为独立单元出现,不会干扰自身或页面的其他组成部分。在可能的情况下,引擎应提供合理的选项默认值,以此免去搭建运行环境所需的大量配置引擎应足够灵活,对于简单的例子和较复杂的游戏来说都是可用的,且应支持不同的渲染引擎(比如Canvas、CSS、SVG和可能的WebGL等)。 基本的引擎结构:游戏循环必须使用定时器来实现,定时器释放JavaScript代码的控制权,把控制权交回给浏览器,这样浏览器才能更新页面和处理输入事件。
构建更好的游戏循环定时器:使用requestAnimationFrame API
传统的游戏循环使用两大块代码来执行每一帧,它们分别是更新代码和渲染代码。更新部分负责推进游戏逻辑在一小段时间内的发展,处理任何的用户输入、移动和对象间碰撞,以及把每个游戏对象更新成一致的状态。
然后,游戏需要把自身渲染到屏幕上,至于以什么样的步骤进行渲染,这取决于游戏的构建方式。就基于画布的游戏而言,通常你需要清除整块画布,重新在页面上绘制所有必需的精灵。而对于CSS和SVG游戏来说,只要正确更新了页面上的对象的属性,那么工作实际上就算完成了---浏览器会负责对象的移动和更新。
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();这便于避免引擎的不同部分过度紧密耦合。这意味着,在不必了解和游戏进行通信的对象的任何信息的情况下,游戏的一部分和其他部分之间能够就事件和行为进行沟通。
在把一些组件添加到这一混搭环境中后,它甚至允许精灵在不必了解构成自身的所有组件的情况下与自身通信。精灵的某个物理组件可能会触发一个碰撞事件,而两个负责监听该事件的组件则可以分别处理适当音响效果和动画效果的触发过程。
设计事件API:使用了一个名为Evented的基类,这是任何需要订阅和触发事件的对象的起点。
组件简化了小块可重用功能的创建,这些可重用功能可经组合和匹配来满足各种精灵和对象的需要。
精灵组件的增加和删除操作必须简捷,它们应是可通过对象访问的,但又不能过分污染对象的名称空间。
需要注册组件、添加组件、删除组件,还允许使用其他一些方法来扩充基本精灵。
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游戏引擎的构建块,你创建了最初的游戏容器对象、游戏循环以及一些使用事件和组件的基类。