废话不多说,直接进入正题,这篇文章主要为大家讲解一下一个类似【Z-Type】的html5小游戏的开发思路。
【Z-Type】不知大家是否有玩过,Impactjs 的一个演示demo。一个需要99$的html5游戏框架。咱们暂且先不管他实现的思路,以下我们按自己的思路来一步步实现。
以下实例基于AlloyTeam团队游戏底层库【Laro】 实现.
【演示Demo】 (Firefox3.4+,Chrome10+,safari10+ 测试通过)
=======================================================
游戏初始化的时候,我们先做两件事:
1
|
this.render =newLa.CanvasRender(canvas, 1,false);
|
接下来,主循环开始,我们在主循环里可以先做两件事,一个update 用于处理每次循环数据方面的更新。 一个 draw ,用来做每帧的重绘。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/**
* looper
*/
Laro.register('TypeShot.$loop',function(La) {
...
this.init =function() {
this.$ =newLa.Loop(this.looper,this);
}
this.looper =function(dt) {
this.update(dt);
this.draw();
}
this.update =function(dt) {
...
};
this.draw =function() {
$TS.render.clear();
...
};
})
|
接下来,开始考虑游戏主流程的逻辑编码,这里我们使用有限状态机FSM来处理游戏主流程。
关于有限状态机,简单的理解可以理解为一种switch case 的升级版,基于事件驱动的状态分支管理方式。适用于一些典型的基于事件驱动的模型,比如说游戏,或者是一些富操作的app。详细可以google或者百科一下。
状态与状态之间尽量解耦,状态之间的消息传递和转换通过他们的宿主来处理。
于是,我们暂且先把这个游戏流程分为3块。
关于状态机的使用方式,我这里有一个简单的Demo ,对于每个状态都抛出了enter,leave,update,transition等事件,状态宿主有onStateChange状态改变的监听。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Laro.register('TypeShot.$fsm',function(La) {
...
varstatesList = [
states.loading, $sClass.Loading, states.inGame, $sClass.InGame, states.gameOver, $sClass.GameOver];
...
this.init =function() {
this.$ =newLa.AppFSM(this, statesList);
this.setState(this.states.loading);//进入loading
}
this.setState =function(state, msg, suspendCurrent) {
...
this.$.setState(state, msg, suspendCurrent);
...
}
});
|
我们状态机主流程初始化的时候,进入第一个资源Loading 状态,这时候,我们可以在loading 的state class 里面来处理资源加载状态的一些情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
this.Loading = La.BaseState.extend(function() {
...
}).methods({
enter:function(msg, fromState) {
...
varimages = [
// images
'images/backdrop.png',
//music
'music/endure.ogg',
...
];
$TS.loader.preload(images, La.curry(this.resourceLoadCallback,this));//加载资源
}, leave:function() {
}, update:function(dt) {
...
}, draw:function(render) {
...
// 绘制背景,进度条,文案等...
}, transition:function() {
//加载完毕,自动跳到下一状态
if(this.loadAll) {
this.host.setState(this.host.states.inGame);
}
}, resourceLoadCallback:function(p) {
// 资源加载进度回调
this.progress = p;
if(p & gt; = 1) {
this.loadAll =true;
}
}, drawProgressBar:function(render) {
// 绘制进度条
}
});
|
上面抛出了
还有一些别的事件,详细请参考源码和文档。 每个状态的update和draw方法会有他们的状态宿主统一派发。
所以只需要在主循环里面的update和draw里面加上这个fsm宿主的update和draw的调用即可。
进入游戏主场景状态之后,我们可以看到,主场景绘制也可以分为下面几个方面:
动态网格背景很简单,让背景绘制的坐标不断向上移动就可以了
1
2
3
4
5
|
drawGrid:function(render) {
for(vari = -2; i & lt; 11; i++) {
render.drawImage($TS.textures['grid'], 0, i * $TS.textures['grid'].height +this.bgPos, 0,false, 0.5,false,false);
}
}
|
飞船,因为需要处理的逻辑可能比较多,我们用一个新建的类来处理。同时,飞船必定也会有多种状态间的切换,比如:普通静止的状态,射击的状态,被撞毁的状态等等。
所以,飞船这里我们又可以利用一个状态机来处理飞船自身的一些状态切换管理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
this.Ship = La.Class(function(x, y) {
...
}).methods({
update:function(dt) {
this.fsm.update(dt);
this.check();
...
}, draw:function(render) {
this.fsm.draw(render);
...
}, setState:function(state, msg) {
this.fsm.setState(state, msg);
}, check:function() {
...
}
});
|
然后,在游戏主场景状态里面的update和draw里面去派发调用这个Ship的update和draw即可。
敌人的绘制也一样,可以独立一个 负责 敌人的类, 只不过可能需要多个敌人在同一个场景里面,那么按照不同的条件生成多个实例即可。基本思路一致。这里就不细说。
键盘事件,很常见,这里就不详述,监听键盘事件,判断当前的按键符不符合当前射击条件,这里的条件跟游戏规则挂钩。
我这里设计的游戏规则是这样的。
我们按照这个规则处理是否射击的判断,也就简单了,假如有符合条件的判断,那么我们添加一个“激光”类的实例到当前需要渲染的激光列表里面。
在游戏主场景 的update 和draw 里面对这个激光 列表进行遍历 update 和draw即可。
激光击中之后,消失,移除这个激光渲染列表。
由于我们是打字游戏,所以只要激光射出,就一定会中,至于激光在什么时候消失,我们可以做个粗略的 碰撞检测 即可,比如激光当前 位置 和 所射击敌人的位置 距离 的绝对值 小于多少 就判定 为击中…
因为并不需要判定 精确击中,所以这种逻辑基本也能满足需求。
那么判定激光击中之后,将当前激光移除渲染列表,同时可以在击中敌人周围加上一些 击中的效果, 比如说一些 飞舞的光圈等等。
当然,这些都是增加体验的效果,不是必需。
当然,想要更好的体验,可以考虑在射击可击中的时候加上音效的播放,这一点可以直接使用html5 的audio来处理。需要注意的几个点是:
============================
好吧,大致的思路就写到这里,感兴趣的同学可以直接查看demo源码,当然,代码里面有一些处理的不够好的地方,因为仅作演示而已。