HTML5移动游戏开发高级编程 9:自建Quintus引擎(1)

9.1 引言

     现在的JavaScript非常适于当成一种交互式游戏开发语言使用

9.2 创建可重用HTML5引擎的框架

     该引擎将被命名成Quintus。

     设计基本的引擎API:

  1. 需要在同一页面上运行多个引擎实例,该需求确保引擎作为独立单元出现,不会干扰自身或页面的其他组成部分。
  2. 在可能的情况下,引擎应提供合理的选项默认值,以此免去搭建运行环境所需的大量配置
  3. 引擎应足够灵活,对于简单的例子和较复杂的游戏来说都是可用的,且应支持不同的渲染引擎(比如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游戏引擎的构建块,你创建了最初的游戏容器对象、游戏循环以及一些使用事件和组件的基类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值