用 CraftyJS 库开发游戏 - 简介
PUBLISHED
简介
这篇文章的目的是使开发人员熟悉 CraftyJS 框架,并描述如何在 Tizen 应用程序中使用它。 这些主题都通过一个示例应用程序Quiz游戏进行描述。 它是系列文章中的第一篇,涵盖了 CraftyJS-s 基础知识。 文章将对框架的结构、场景和实体/组件系统进行解释。 然后我们将展示如何使用 sprite 和创建简单实体。 在文章末尾,将介绍一些基本动画。
Crafty
CraftyJS 是一个年轻的、 积极发展的 JavaScript HTML5 游戏引擎。 它是开源的,根据MIT或 GPL 授权发布。 它相对来说比较容易使用。 您只需要包括一个小的库文件到您的项目。 它能兼容不同的浏览器,并能在 Tizen 上有效运作。 Crafty 基于实体-组件系统而不是经典的继承系统。 它使您能够轻松创建可重用的组件。 可以使用 DOM 或画布进行渲染。 Crafty 还提供了工具来处理 sprite、 资产加载和音频。
在这篇文章中,我们将解释如何使用所有这些功能使在 Tizen 中创建 JavaScript 游戏变得快速和容易。 这篇文章的大篇幅内容将专门解释如何使用 sprite sheet 和动画创建一个漂亮的用户界面。
要了解更多信息,可以参阅此演示文稿、用 JavaScript 和 Crafty 制作游戏教程,或官方教程。 可在 http://craftyjs.com/api/ 找到官方文档。 库代码非常容易阅读并有良好的注释,因此也应该仔细看看。
示例应用程序和先决条件
我们提供了一个名叫“Quiz”的示例游戏以演示对 CraftyJS 框架的使用。 游戏的目的是在有限的时间内正确回答所有问题。 游戏以横屏模式进行。 示例应用程序基于Query Mobile 1.2.0 框架,该框架使您能够创建具有直观且一致的用户界面的高度可扩展的 web 应用程序。 为能使用 jQuery Mobile,您的应用程序必须也包含 jQuery 库。
图 1 Quiz游戏
代码示例使用自定义函数将日志消息发送到控制台。 它们位于应用程序的 js/lib/tlib 文件夹中。
将 Crafty 添加到您的工程
您可以从以下网址下载最新的 Crafty 库:
- 官方网站 (http://craftyjs.com),
- Github 上的 Crafty 源 (https://github.com/craftyjs/Crafty)。
要将 Crafty 添加到您的 Tizen 工程,您只需要将 crafty.js 文件复制到资源中并将其添加到 index.html 文件。
<script src="js/lib/external/crafty.js">
然后您必须用 Crafty.init() 函数初始化 Crafty:
// init Crafty Crafty.init(this.config.width, this.config.height);
Crafty.canvas.init();
场景
场景是一种组织对象的方式。 它们代表游戏的不同部分。 这些场景可以是不同的屏幕或级别。 在我们的Quiz游戏中,我们有像菜单 (显示主菜单)、 选项(设置游戏选项)、关于、帮助和有实际游戏的主要游戏场景。
您运行一个场景所要做的工作就是从您的代码的任何部分调用 Crafty.scene(scene) 函数。 该函数只有一个参数: 场景的名称。
Crafty.scene("menu");
请记住调用场景会销毁当前显示的所有实体。
您可以按以下方式定义一个场景:
Crafty.scene("menu", function() { // define game objects here });
在单独的文件中定义每个场景是一个好做法。 这将使您的应用程序模块化并且更容易理解。
您可以定义应用程序的背景,该背景适用于所有的场景:
// set background Crafty.background("url('images/game_bg.png')");
如果您需要为每个场景定义单独的背景,您必须在每个场景的初始化中使用 Carfty.background() 函数。
加载资产
Crafty 库中有一个用于所提供资产的预载器函数。 它使用url列表为参数,并将这些 url 添加到 afty.assets 对象中。
Crafty.load([ "images/quiz_sprite.png", "images/quiz_sprite_help.png", "sounds/game_correct.mp3" ], // on load finish function() { Crafty.scene("menu"); // go to menu scene }, // on load progress function(e) { // do sth }, // on error function(e) { // do sth });
Crafty.load() 方法有四个参数:
- 资产 - 要加载的资产数组。 这些资产可以是声音和图像。
- onLoad - 所有资产被加载时的回调函数。
- onProgress - 某项资产被加载时的回调函数。 它传递了一个带有加载进度信息的对象:
- { loaded: j, total: total, percent: (j / total * 100), src:src}
- onError - 资产加载失败时的回调函数。 此函数传递了无法加载的资产。 如果您不提供此回调,onLoad 方法将被调用,不管资产是否已加载成功。
实体/组件系统
- 实体 - 游戏对象,如播放器、 球或动画式 UI 项。 它是此对象的单个实例。
- 组件 - 抽象对象或函数/属性的集合。 它可以被应用到实体 (由这些实体“继承”)。 它们是可重用的,一个单一的实体可以由许多不同的组件组成。
var answerText = Crafty.e("2D, DOM, Text, Mouse");
answerText 是一个示例实体,在屏幕上显示文本。 它包括 (“继承于”) 四个不同组件:“2D”、“DOM”、“文本”和“鼠标”。 所有这些组件都在 Crafty 引擎中被预定义。 在声明中,用逗号把它们隔开。 使用实体并创建您自己的自定义组件将在下文中解释。
使用 sprite
单独加载图片会严重损害应用程序的性能和代码的可读性。 如果您有许多图像资源,特别是有很多代表单个对象动画不同阶段的资源,最好创建一个 sprite sheet。
图2 Quiz的一个sprite sheets
Crafty 提供一些内置函数,以改善 sprite sheet 的处理。 它有一种方法,可以将 sprite 图拼接成单个组件。 这些组件可以应用于任何 2D 实体。
让我们看看这个示例:
// load main sprite sheet Crafty.sprite(5, "images/quiz_sprite.png", { // menu scene buttons startBtn : [ 0, 0, 74, 22 ], normalBtn : [ 0, 22, 74, 22 ], ... });
在 Crafty中,我们把 sprite sheet 分成较小的元素:tiles。 这些tiles是方形的,必须按这种方式分割 sprite sheet,使得每个tile正好属于一个 sprite。 下图阐释了此概念。 它显示了一个由5个sprite(颜色不同)组成的sprite sheet,这个sprite sheet被分成15个tile。
图3 被分割为tile的 sprite sheet 示例
Crafty.sprite() 函数有三个参数:
- tile - tile的大小(以像素为单位)。 默认值是 1。 为了获得最佳性能,尽可能选择大的tile来分割sprite sheet。 通常,您的sprite sheet是由相同大小的对象组成的。 如果它们是正方形,则选择tile的大小就很容易 - 正方形边的长度。 当不同的 sprite 有不同的尺寸和形状时,选择其尺寸的最大公约数。 在示例Quiz游戏中,每个 sprite 的尺寸都是 5 的倍数。
- url - sprite图像的路径。
- map - array of [key : position] 。 位置是四个值的数组:sprite sheet 上 sprite 的左上角的 x 坐标、sprite sheet 上 sprite 的左上角的 y 坐标、 sprite 的宽度、 sprite 的高度。 所有这些值都不是以像素为单位,而是以tile为单位。
Tile使代码更加易读。 当单个 sprite sheet 上的所有 sprite 都是相等正方形时效果尤其好。 只要有可能,最好创建适用于此规则的 sprite 地图。 在我们的示例Quiz游戏中,有各式各样不同大小的 sprite,所以使用tile便可让代码中的数字变小。 我们以tile而不是以像素为单位显示值 (这里 1 个tile = 5 个像素)。 使用默认的tile值 (1) 相同的代码片段如下所示:
Crafty.sprite(1, "images/quiz_sprite.png", { // menu scene buttons startBtn : [ 0, 0, 370, 110 ], normalBtn : [ 0, 110, 370, 110 ], ... });
一旦sprite sheet被加载,每个 sprite 就成为一个实体,其名称被定义在“key”中。 这意味着您可以在这样在实体定义中使用它:
varstartButton = Crafty.e("2D, DOM, startBtn").attr({ x : 455, y : 225 });
在上述代码中,一个 startButton 实体被创建。 它使用三个组件:“2D”、“DOM”和“startBtn”。 在加载sprite时,“startBtn”组件已经被定义了。 设置属性 x 和 y,为我们的对象定位。 上面的代码可以在屏幕上显示“startBtn”图像。
实体
如前面所述,实体可以由许多不同的组件构成,作为字符串传递到 Crafty.e() 函数。 通过向实体添加新组件,您还添加了这些组件的函数。 让我们看看这个示例。 此实体显示一个问题的文本。
var questionText = Crafty.e("2D, DOM, Text").attr({ w : 1170, h : 200, x : 55, y : 185 }).text("The country famous for Samba dance is?").textFont({ size : '20px' });
您可以看到用于定义此对象的链接类似于 JQuery 样式。 大多数 Crafty 元素都支持它。 Attr() 函数用于“2D”组件,而 text() 和 textFont() 函数用于“Text”组件。 您可以在组件文档中看到可用方法的所有列表。 在这里,我们将只讨论其中一些。
您可以使用 attr() 函数设置 2D 元素的基本属性:
- x - 平台上的水平位置
- y - 平台上的垂直位置
- w - 宽度
- h - 高度
- alpha - 一个实体的透明度(0 为完全透明,1 为完全不透明)
- rotation - 实体的旋转(以度为单位按顺时针方向)
- visible - 实体是否可见 (true/false)。
您也可以忽略链接。 也可能先创建一个空的实体,以后再给实体添加控件:
var questionText = Crafty.e(); questionText.addComponent("2D, DOM, Text"); questionText.attr({ w : 1170, h : 200, x : 55, y : 185 }); questionText.text("The country famous for Samba dance is?"); questionText.textFont({ size : '20px' });
现在您应该了解如何基于各种组件创建实体。 创建您自己的自定义组件将在下文中解释。
渐变动画
渐变动画是最简单的动画之一。 在 Crafty 中,它被定义为一个组件。 它使您可以随着时间的推移对 2D 实体的属性进行动画处理。 您可以更改的属性有: x、 y、 w、 h、 alpha和rotation。
Crafty.e("2D, DOM, Tween").attr({ x : 0, y : 0 }).tween({ x : 100, y : 100 }, 60).bind("TweenEnd", function() { // todo });
上面的示例演示如何创建一个简单的渐变动画。 您所要做的就是将“Tween”组件添加到您的实体,设置一些 2D 属性,并随着时间的推移使用 tween() 函数来更改它们。 Tween() 函数有两个参数:
- properties - 将要进行更改的对象属性(连同它们的最终值)
- duration — 动画持续时间(按帧数)。
您还可以添加动画结束时将触发的事件监听器。 Bind() 函数有两个参数:
- 事件类型(在这里指“TweenEnd”),
- 要触发的函数。
使用此事件监听器,我们可以创建渐变链接 - 一个渐变结束时,另一个渐变即开始。 我们会用一个示例来说明这一点。
在“Quiz”应用程序中,主菜单tile从屏幕的顶部往下掉。 它慢慢涌现,从顶部向下滑动,然后弹起,并再次跌落。 下面的图片说明了这个过程。
图4 标题渐变动画的步骤
我们使用如下的代码来实现这个过程:
Crafty.e("2D, DOM, gameTitle, Tween").attr({ x : 295, y : 0 }).tween({ y : 65 }, 60).bind("TweenEnd", function() { this.unbind("TweenEnd"); this.tween({ y : 55 }, 20); this.bind("TweenEnd", function() { this.tween({ y : 65, }, 20); }); });
在我们的示例中,渐变动画仅更改标题的垂直位置。 您还可以看到一个组件“gameTitle”,其中添加了图形。 此组件代表游戏标题 sprite,并在加载 sprite sheet 时由 Crafty.sprite() 方法创建。
Sprite 动画
Sprite 动画是另一种动画类型。 不同阶段的动画以单独图片的方式保存在sprite sheet上。 Sprite 具有相同宽度时,效率尤其高。
图5 sprite sheet示例
我们假设 1-5 的正方形是动画的帧,每个正方形的边长为 10 像素。 我们想要对我们的对象动画化,从帧编号 1 开始,经过 2、 3 和 4 直到第5帧。 首先,我们加载图 5 所示的 sprite。 正如您所知,我们使用 Crafty.sprite() 方法来操作:
Crafty.sprite(10, "sprite.png", { object : [ 0, 0 ] });
请注意,我们使用 10 作为tile的大小。 现在我们可以定义一个具有动画的实体。 它必须使用“SpriteAnimation”组件。
var player = Crafty.e("2D, DOM, object, SpriteAnimation").attr({ x : 0, y : 0 }).animate("anim", 0, 0, 4); // setup animation
设置动画的 animate() 函数有四个参数:
- id - 动画名称
- formX - sprite图上的水平起始位置(以tile为单位),
- y sprite 图中的垂直位置(以tile为单位),
- toX - 水平方向的终点 (以tile为单位)。
请注意,y 位置在整个动画中保持不变。 如果您要强制改变它的话,您需要使用特定的方法,该方法在后面讲解。
现在,我们的动画已经设置好了,我们可以使用animation()函数来运行该动画,animation()函数有三个参数:
- id - 动画 id,
- duration - 整个动画的持续时间(按帧数),
- repeatCount - 重复动画的次数。
player.animate('PlayerRunning', 15, 0); // play animation once player.animate('PlayerRunning', 15, -1); // play animation infinitely
当一个游戏人物必须一直运动时(例如人物在跑动),无限播放动画在游戏中是非常有用的。 有一些额外的函数来处理动画:stop () 和 reset ():
player.stop(); //stop all entity's animiations player.reset(); //reset all entity's animations
要检查动画当前是否正在播放,可使用 isPlaying() 函数:
player.isPlaying("anim"); //check if the animation if currently playing
如果您用空的参数进行调用,您会检查是否有任何动画在播放。
当动画的帧具有相同宽度并在 sprite 地图上排成一行时,上段中所示的方法会非常有效,且易于配置。 否则,您必须分别传递每个帧的坐标。
button.animate("blink", [ [ 222, 50 ], [ 222, 75 ], [ 222, 50 ], [ 222, 75 ], [ 222, 50 ], [ 222, 75 ] ]);
在上面代码中定义的动画将闪烁三次。 在下一篇文章中,您将了解更多关于这种动画类型的信息。
总结
这篇文章已介绍了 CraftyJS 框架及其在 Tizen 应用程序中的应用, 并对框架的结构,包括场景和实体/组件系统进行了讲解。 您学习了如何使用 sprite 和创建简单实体。 文章末尾呈现了一些基本的动画。 要了解更多有关在 Tizen 上使用 Crafty 的信息,请阅读这系列文章的下一篇:“使用 CraftyJS 库 - 后续”