使用JavaScript和Canvas开发游戏(四)
原文作者:Matthew Casperson
原文链接: Game Development with JavaScript and the Canvas element
1、认识一下Canvas
2、在Canvas上绘图
3、通过Canvas元素实现高级图像操作
4、写一个游戏框架(一)
5、写一个游戏框架(二)
6、通过Canvas实现视差滚动
7、动画
8、JavaScript键盘输入
9、综合运用
10、定义级别
11、跳跃与坠落
12、添加道具
13、加载资源
14、添加主菜单
4、写一个游戏框架(二)
在这篇文章里,我们继续介绍构成这个基本JavaScript游戏框架的其他必要的类。
前一篇文章介绍了GameObjectManager类,该类负责画布的渲染并允许GameObject类更新和删除它们自己。下面就来看一看GameObject类。
GameObject.js
/** 游戏中出现的所有元素的基类 @class */ function GameObject() { /** 显示的深度次序。较小的zOrder值表示先渲染,因而会在背景中。 @type Number */ this.zOrder = 0; /** x轴的坐标 @type Number */ this.x = 0; /** y轴的坐标 @type Number */ this.y = 0; /** 初始化游戏对象,并将其添加到GameObjectManager维护的对象列表中 @param x x轴的坐标 @param y y轴的坐标 @param z 元素的z次序(背景元素的z值较小) */ this.startupGameObject = function(/**Number*/ x, /**Number*/ y, /**Number*/ z) { this.zOrder = z; this.x = x; this.y = y; g_GameObjectManager.addGameObject(this); return this; } /** 清理当前对象,将其从GameObjectManager维护的对象列表中删除 */ this.shutdownGameObject = function() { g_GameObjectManager.removeGameObject(this); } }
这个GameObject类(是一个引擎类)的目的,是为游戏中将会出现的所有对象定义一些共有的属性,包括它们的位置(x和y)和深度(z)。需要注意的是,我们不会直接创建GameObject类的实例,而是会再创建一个类来扩展它。
这个类的x和y坐标值没有什么好说的——就是相应对象左上角位置在画布上的坐标。关键是GameObject中的z值,这个值定义的是对象的深度。理解这个值很重要,这个值较小的GameObject会先绘制到画布上。换句话说,z值较大的GameObject将被绘制到z值较小的GameObject上面。
上一篇文章里介绍过,所有类都是通过一个类似startupClassName的函数完成自身初始化的。因此,GameObject类就有一个名为startupGameObject的函数。在这个函数里,除了初始化所有变量外,还会通过addGameObject函数把当前的GameObject添加到由GameObjectManager维护的GameObject列表中。
/** 初始化游戏对象,并将其添加到GameObjectManager维护的对象列表中 @param x x轴的坐标 @param y y轴的坐标 @param z 元素的z次序(背景元素的z值较小) */ this.startupGameObject = function(/**Number*/ x, /**Number*/ y, /**Number*/ z) { this.zOrder = z; this.x = x; this.y = y; g_GameObjectManager.addGameObject(this); return this; }
函数shutdownGameObject用于清除GameObject。这里所谓的清除,是指GameObject通过removeGameObject函数将自身从GameObjectManager中删除。
/** 清理当前对象,将其从GameObjectManager维护的对象列表中删除 */ this.shutdownGameObject = function() { g_GameObjectManager.removeGameObject(this); }
VisualGameObject.js
/** 出现在游戏中的所有元素的基类 @class */ function VisualGameObject() { /** 由当前对象显示的图像 @type Image */ this.image = null; /** 将当前元素绘制到后台缓冲 @param dt 自上一帧绘制起经过的秒数 */ this.draw = function(/**Number*/ dt, /**CanvasRenderingContext2D*/ context, /**Number*/ xScroll, /**Number*/ yScroll) { context.drawImage(this.image, this.x - xScroll, this.y - yScroll); } /** 初始化当前对象 @param image 要显示的图像 */ this.startupVisualGameObject = function(/**Image*/ image, /**Number*/ x, /**Number*/ y, /**Number*/ z) { this.startupGameObject(x, y, z); this.image = image; return this; } /** 清理当前对象 */ this.shutdownVisualGameObject = function() { this.shutdownGameObject(); } } VisualGameObject.prototype = new GameObject;
VisualGameObject也是一个引擎类,它扩展了GameObject类,为将在屏幕上绘制的对象定义了更具体的属性和函数。顾名思义,可见对象显然是需要绘制的对象,因此VisualGameObject定义了一个image属性,当把当前对象绘制到后台缓冲时,将以这个属性作为图形的来源。
/** 由当前对象显示的图像 @type Image */ this.image = null;
此外,还需要写几行代码,以便把这个对象实际地绘制到后台缓冲——这就是draw函数了,它接受图像并基于GameObject类中定义的x和y值将其复制到后台缓冲。
/** 将当前元素绘制到后台缓冲 @param dt 自上一帧绘制起经过的秒数 */ this.draw = function(/**Number*/ dt, /**CanvasRenderingContext2D*/ context, /**Number*/ xScroll, /**Number*/ yScroll) { context.drawImage(this.image, this.x - xScroll, this.y - yScroll); }
ApplicationManager.js
/** ApplicationManager用于管理应用 @class */ function ApplicationManager() { /** 初始化对象 @return 对初始化对象的引用 */ this.startupApplicationManager = function() { this.bounce = new Bounce().startupBounce(g_image); return this; } }
ApplicationManager是第一个应用类,之所以将其归为应用类,是因为它用来定义应用的运行方式,而不是定义与浏览器的底层交互。这个类非常简单,只用来创建并初始化Bounce类的一个新实例。表面上看,创建一个类仅仅是为了创建一个对象有点多此一举。但在更复杂的应用中,把创建和管理游戏对象的逻辑放到一起是很有必要的。
Bounce.js
/** 测试类,用于演示VisualGameObject类的用法 @class */ function Bounce() { /** x轴的运动方向 @type Number */ this.xDirection = 1; /** y轴的运动方向 @type Number */ this.yDirection = 1; /** 运动速度 @type Number */ this.speed = 10; /** 初始化对象 @return 对初始化对象的引用 */ this.startupBounce = function(image) { this.startupVisualGameObject(image, 0, 0, 0); return this; } /** 更新对象 @param dt 自上一帧绘制起经过的秒数 @param context 绘制上下文 @param xScroll x轴的全局滚动值 @param yScroll y轴的全局滚动值 */ this.update = function (/**Number*/ dt, /**CanvasRenderingContext2D*/context, /**Number*/ xScroll, /**Number*/ yScroll) { this.x += dt * this.speed * this.xDirection; this.y += dt * this.speed * this.yDirection; if (this.x >= 450) { this.x = 450; this.xDirection = -1; } else if (this.x <= 0) { this.x = 0; this.xDirection = 1; } if (this.y >= 250) { this.y = 250; this.yDirection = -1; } else if (this.y <= 0) { this.y = 0; this.yDirection = 1; } } } Bounce.prototype = new VisualGameObject;
Bounce是第二个应用类,它扩展了VisualGameObject类,并将把自己绘制到屏幕上。Bounce类会显示一幅在屏幕上反弹的图像,效果非常类似第一篇文章中举的例子。这个类是在前面所有类的基础上实现最终动画的关键。
startupBounce函数接受一幅图像,通过调用startupVisualGameObject来初始化这个基本的类。
/** 初始化对象 @return 对初始化对象的引用 */ this.startupBounce = function(image) { this.startupVisualGameObject(image, 0, 0, 0); return this; }
而update函数(将被GameObjectManager在渲染期间调用)会更新图像的位置,在图像到达画布边缘时反转方向。
/** 更新对象 @param dt 自上一帧绘制起经过的秒数 @param context 绘制上下文 @param xScroll x轴的全局滚动值 @param yScroll y轴的全局滚动值 */ this.update = function (/**Number*/ dt, /**CanvasRenderingContext2D*/context, /**Number*/ xScroll, /**Number*/ yScroll) { this.x += dt * this.speed * this.xDirection; this.y += dt * this.speed * this.yDirection; if (this.x >= 450) { this.x = 450; this.xDirection = -1; } else if (this.x <= 0) { this.x = 0; this.xDirection = 1; } if (this.y >= 250) { this.y = 250; this.yDirection = -1; } else if (this.y <= 0) { this.y = 0; this.yDirection = 1; } } }
就这些了。你可能会想,怎么没有与绘制这个对象有关的代码呢?相应的代码都在VisualGameObject类的draw函数中了。而且,由于VisualGameObject类扩展了GameObject类,所以我们知道每渲染一帧都会调用一次update和draw函数。Bounce类中的所有代码只跟让图像反弹有关,也就是修改变量x和y。
好啦,我们已经创建了一批类,基于这些类也实现了与第一个示例相同的效果。而有了这个框架,再创建游戏就不必因为绘制画布等底层逻辑以及管理游戏对象等问题而重复编码了。
看看示例Demo吧。http://webdemos.sourceforge.net/jsplatformer3/jsplatformer3.html
转载地址:为之漫谈技术博客