HTML5 canvas 让程序员自由的绘制想要的图形和动画,使得纯粹基于HTML/Javascript/CSS的游戏铺平了道路。只不过canvas最初并非为游戏而设计,而且除了绘图以外,做游戏还有不少其他事情要考虑,例如鼠标键盘事件、图层、动画等。为了方便程序员开发游戏,游戏框架/引擎陆续被开发出来,gin 就是其中之一。
Project home: https://github.com/huandu/gin
Author: Huan Du (blog , twitter )
Samples: https://github.com/huandu/gin-samples
Live demo: simple sample mouse tracer shape breaker
gin是什么
gin是一个开源轻量级的HTML5游戏引擎,只专注于搭建一个简单可靠的游戏基础设施,让游戏能在网页中高效流畅的运转起来。
gin能做什么
gin提供了游戏开发中各种必须的基础设施,例如固定帧率渲染、鼠标键盘事件捕捉、图层、用户数据等,这些工具都是以最简单直接的方式提供出来,不要求OOP,没有预编译。
gin在设计之初就定位为一个“引擎”,而不是框架。就像汽车引擎不负责提供动力以外的事情一样,gin只专注于驱动游戏运转,不提供表现层的任何工具。
gin的特点
gin的特点是:简单、高效 。
使用gin只需要写如下的代码,十分简单:
- $G( 'your-game-container-id' , {}, {
- render: function (e) {
- // draw canvas with e.context
- }
- });
$G('your-game-container-id', {}, {
render: function(e) {
// draw canvas with e.context
}
});
点击这里 可以看到一个完整而简单的使用gin的例子,源码看这里 。
相比其他现有的js游戏/绘图框架的设计思路,gin摈弃了传统的事件驱动模型,只提供固定帧率的回调接口,所有的鼠标键盘事件都由gin负责接收和缓存。gin的使用者可以在beforerender或render回调中集中处理所有缓存的事件,这样能最大化游戏性能,并提高整体游戏响应速度。
根据google chrome 8.0的profiling结果,在canvas绘图函数中数清除画布数据的clearRect()消耗CPU时间最多,画布越大性能消耗越明显,约是 stroke()一个相同大小的圆或长方形耗时的100倍甚至更多。如果采用传统的事件驱动模型,游戏会立即处理接收到的事件,执行绘图、逻辑判断等等,这样会不断的清除画布,浪费大量的CPU,而实际上只要达到30帧/s就能有流畅体验,在真正需要绘图的时候再绘图才更合理。
而且由于现在所有javascript引擎都是单线程的,脚本执行时无法响应任何DOM事件,浏览器也不会缓存这些事件,如果脚本较长时间占用CPU,还会造成事件丢失,最终影响用户体验。
由gin来缓存事件还有一个好处,这可以让键盘鼠标状态检测变得更简单。gin在beforerender和render回调中传入的事件对象带有 keyStates和buttonStates数组,分别对应键盘和鼠标的按键状态,可以支持多个键盘/鼠标按键同时按下的状态检测。
- $G( 'your-game-container-id' , {}, {
- render: function (e) {
- // check if 'blank' key pressed
- if (e.keyStates[0x20]) {
- // do something
- }
- // check if mouse L button pressed
- if (e.buttonStates[0]) {
- // do something
- }
- }
- });
$G('your-game-container-id', {}, {
render: function(e) {
// check if 'blank' key pressed
if (e.keyStates[0x20]) {
// do something
}
// check if mouse L button pressed
if (e.buttonStates[0]) {
// do something
}
}
});
beforerender和render
gin将帧的回调函数分为两个,beforerender和render。这两个回调的唯一区别是beforerender参数e里面没有 canvas context,不能用于画图。这样做的好处是鼓励使用者将与绘图无关的逻辑放入beforerender,让每次脚本运行的时间更短,降低丢失消息的可能性。
遍历鼠标事件
gin缓存的鼠标事件并不能直接暴露出来,这是因为gin支持图层,在不同图层里面,鼠标事件的clientX和clientY都会因为图层的偏移量不同而不一样。如果为每一个图层事先计算好正确的clientX和clientY并且缓存起来,那会造成很大的性能损耗。gin的做法是提供遍历鼠标事件的接口,并在遍历中计算正确的坐标。
- $G( 'your-game-container-id' , {}, {
- render: function (e) {
- if (e.buttonStates[0]) {
- // draw mouse move path
- var ctx = e.context;
- ctx.strokeStyle = 'rgb(0,0,0)' ;
- ctx.beginPath();
- e.traverseHistory(function (cur, prev) {
- // the first point
- if (!prev) {
- ctx.moveTo(cur.clientX, cur.clientY);
- } else {
- ctx.lineTo(cur.clientX, cur.clientY);
- }
- });
- ctx.stroke();
- }
- // cached mouse history must be cleared explicitly
- // history will not be really cleared until next beforerender/render is ready to call
- // so it's safe to clear history many times in one function or in other layer
- e.clearHistory();
- }
- });
$G('your-game-container-id', {}, {
render: function(e) {
if (e.buttonStates[0]) {
// draw mouse move path
var ctx = e.context;
ctx.strokeStyle = 'rgb(0,0,0)';
ctx.beginPath();
e.traverseHistory(function(cur, prev) {
// the first point
if (!prev) {
ctx.moveTo(cur.clientX, cur.clientY);
} else {
ctx.lineTo(cur.clientX, cur.clientY);
}
});
ctx.stroke();
}
// cached mouse history must be cleared explicitly
// history will not be really cleared until next beforerender/render is ready to call
// so it's safe to clear history many times in one function or in other layer
e.clearHistory();
}
});
图层
gin虽然并没有把图层的内部类(GinLayer)暴露出来,但它无处不在,例如,所有的回调函数的this都是当前layer的实例。
gin默认会创建一个图层作为所有图层的基础,使用者可以在图层上创建无数的子图层,子图层可以继续嵌套更多子图层。每个子图层有自己的beforerender和render,可以独立的渲染。
图层可以用stop()方法来停止渲染,stop的图层和它的子图层的beforerender/render都不会被调用,直到调用play()让它们继续渲染。
- $G( 'your-game-container-id' , {}, {
- start: function () {
- this .layers( 'sample' , {
- left: 0,
- top: 20,
- width: 100,
- height: 200
- }, {
- render: function (e) {
- // your code
- }
- })
- .layers('sample_again' , {}, {
- play: function () {
- // this function will be called once the layer starts to play
- this .layers( 'sub_sample' , {});
- }
- });
- },
- render: function (e) {
- // it's how to find layer, or even sub-layer
- var sample = this .layers('sample '),
- sub = this.layers([' sample_again ', ' sub_sample']);
- // do something
- }
- });
$G('your-game-container-id', {}, {
start: function() {
this.layers('sample', {
left: 0,
top: 20,
width: 100,
height: 200
}, {
render: function(e) {
// your code
}
})
.layers('sample_again', {}, {
play: function() {
// this function will be called once the layer starts to play
this.layers('sub_sample', {});
}
});
},
render: function(e) {
// it's how to find layer, or even sub-layer
var sample = this.layers('sample'),
sub = this.layers(['sample_again', 'sub_sample']);
// do something
}
});
gin的未来
gin刚刚发布了1.0.0版 ,是一个just work的版本,并且发布了三个例子,放在gin-samples 项目中。
gin未来将首先考虑支持更多的浏览器和平台,特别是移动设备上的支持。对于简单高效的引擎而言,最适合应用的环境就是性能相对较差的移动设备。
其次需要考虑的是实现更强大的图层(layers),现在的图层已经实现了类似于flash MovieClip的各种基本功能,还差key frame没有实现,未来会增加支持。
FAQ
Q: gin的api文档呢?
A: 暂时还没有,有问题请直接联系我(github ,twitter )
Q: gin支持哪些浏览器?
A: 测试过的是Firefox 3.6、Chrome 8.0/10.0,理论上Opera、Safari也会支持。IE就别想了,等IE9支持了canvas再说吧。
Q: gin-samples里面的例子不能正常运行是什么原因?
A: 如果是下载源码运行,需要手动将gin.js拷贝到sample目录里面才能运行。此外,需要保证电脑可以访问网络,因为有些例子使用了jQuery,用的是google dns。如果还不行,请检查一下浏览器是否是gin所支持的浏览器,gin自己没有做任何浏览器检测。
附录
我在开发过程中了解过的游戏/绘图框架包括The Render Engine 、GameJS 、cakejs 、cocos2d ,它们都是非常好的框架,gin从某种程度上说是针对它们的不足而设计的。 :P
gin最开始是我心血来潮写的一个小框架,还未完成就在“给力HTML5 —— 2011 Google HTML5训练营” 第一期活动 中使用,非常意外且幸运的用它实现了第一个可玩的HTML5游戏Raiden 5 (Chrome only) ,在这里 可以围观最原始的gin。