JSGF游戏开发课程(三):开始一个2D游戏示例

上一篇   下一篇

在正式开始前,我们需要先解释一下JSGF坐标系与方向表示。

JSGF中物体的坐标一般以直角坐标来表示。其坐标系以左上方的顶点为原点,右方向为X轴正方向,下方向为Y轴正方向,坐标单位均为像素(px)。


常用的JSGF直角坐标系有两种:一种为屏幕坐标系,以浏览器屏幕内的左上顶点为原点建立; 另一种为画布坐标系,是以当前画布的左上顶点为原点建立。在JSGF中,物体的坐标是基于所在“容器”的坐标系表示的。如下图:画布是绘制在document对象上的,所以画布的坐标是基于屏幕坐标系的。 精灵是绘制在画布中的,所以精灵的坐标是基于画布坐标系的。


再举一个例子。假设有a,b两个精灵分别绘制在A,B两个画布上。如果想比较a,b的坐标,最好的方式将a,b的坐标都转换到 同一个坐标系下(如:屏幕坐标系)后再比较。

一个点除了可以用( x , y )方式的直角坐标表示外,还可以用极坐标来表示:(与原点的距离,与原点及X轴正向夹角的弧度)。 有些情况下用直角坐标来运算非常复杂,而转换成极坐标来运算则极为简便。


JSGF游戏中方向以弧度来表示,该值可以看作该方向与X轴正方向的夹角的弧度值。 需要注意的是,弧度是一个有方向的量,JSGF规定其正方向为顺时针方向。



接下来,我们开始用JSGF写一个魔兽争霸2的游戏示例。


1. 创建游戏主类


定义一个游戏主类,继承自js.game.Game类。

				    var SYS = js.lang.System,
						D = js.core.Dom,
						$ = js.core.Dom.$,
						MT = js.math.MathTool,
						F = js.phys.Formulas;	
					SYS.namespace('js.game.demo.warcraft2');
					
					js.game.demo.warcraft2.Game = function(config){
						js.game.demo.warcraft2.Game.superclass.constructor.apply(this, arguments);
					};					
					SYS.extend(js.game.demo.warcraft2.Game, js.game.Game, {
						......
					});
				

2. 创建精灵类


定义一个魔兽战士Warrior类,继承自js.game.Sprite类。

				js.game.demo.warcraft2.Warrior = function(config){
					js.game.demo.warcraft2.Warrior.superclass.constructor.apply(this, arguments);
				};
				
				SYS.extend(js.game.demo.warcraft2.Warrior, js.game.Sprite, {
				    ......
				});
				

3. 精灵的帧序列


我们先定义魔兽战士的状态有:站立、行走、攻击、死亡。 再定义八个方向的编号(0-7):从弧度0开始,每隔四分之一PI的弧度定义一个方向。

精灵某个时刻的帧画面取决于它的状态与方向。于是,我们按照状态+方位的组合定义一个帧序列。帧序列的名称即:状态名 + 方位编号。 比如,站立状态的八个帧序列名为stand0 ~ stand7:



每个帧序列的数据是一个数组,包含了相关联的数个帧的数据。而每个帧的数据也是一个数组:第1项为该帧在图像资源中的X轴偏移量;第2项为该帧在图像资源中的Y轴偏移量。 站立状态的帧序列比较特殊,都只包含一个帧。站立状态的八个帧序列数据如下:

				frameSeqs:{
					stand6:[[1,1]]
			        ,stand7:[[66,1]]
			        ,stand0:[[131,1]]
			        ,stand1:[[196,1]]
			        ,stand2:[[261,1]]
			        ,stand3:[[326,1]]
			        ,stand4:[[391,1]]
			        ,stand5:[[456,1]]
				}
				

更多状态的帧序列数据,你可以右键点击页面查看示例的源代码。


4. 创建精灵实例


我们给Game类新增一个方法,用于创建一个魔兽战士实例。

				_newWarrior: function(config){
					return new js.game.demo.warcraft2.Warrior({
						side:config['side'],state:config['state']||'stand'
					    ,imageSrc:'../../images/warcraft2/'+config['side']+'/'+config['name']+'.png'
					    ,x:config['x'],y:config['y'],dir:config['dir'],width:72,height:72
					    ,frameSeqs:{
						    .......
					});
				}
				

战士类的基本属性:
side:  所属阵营。人族(human)或兽族(orc)。
state:  初始状态。取值范围是:"stand" | "walk" | "fight" | "die",缺省值为"stand"。
x,y:  初始坐标。
z:  对应的DOM元素的z-index值。
width,height:  图像的宽,高度。
dir:  方向(弧度值)。


5. 绘制精灵的全部方位


为Game类添加一个函数,绘制兽人的某状态的八个方位图像:

					_paint8Orc: function(center, r, state){
						for(var i=0;i<8;i++){
							var xy = F.round(i, [center[0]+r,center[1]], center, r*MT.RADIAN_2),
							player = this._newWarrior({
								side:'orc',name:'axethrower'
								,x:xy[0],y:xy[1],dir:i*MT.RADIAN_2,state:state
							});
							player.init(this);
							this._players.push(player);
						}
					}
				

6. 运行游戏实例


编写一个GameApp类,在main方法中创建一个游戏实例:

					game = new js.game.demo.warcraft2.Game({
						id:'warcraft2',x:200,y:900,background:'black',width:500,height:700,fpsMax:12
					});
					game.watchFPS(true);
					game.init();
				

我们设定这个游戏实例的最大帧速为12,以免精灵动作看起来过快。
同时在Game类的init方法中添加“ended”事件监听代码:在游戏结束时擦除画布。

					this.subscribe('ended', function(){
						this.getCanvas().destory();			
					})