用 CraftyJS 库开发游戏 - 简介

简介

这篇文章的目的是使开发人员熟悉 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 库。

Quiz游戏图 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元素放在平台上。 在我们的例子中,平台就是整个屏幕。 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 方法将被调用,不管资产是否已加载成功。

实体/组件系统

面向对象编程是一种非常流行的组织应用程序代码的方法。 从它们的祖先继承的类的结构被创建。 Crafty 的方法有点不同。 它使用一个实体/组件系统。 它类似于一个单级多重继承。 有两个主要概念:
  • 实体 - 游戏对象,如播放器、 球或动画式 UI 项。 它是此对象的单个实例。
  • 组件 - 抽象对象或函数/属性的集合。 它可以被应用到实体 (由这些实体“继承”)。 它们是可重用的,一个单一的实体可以由许多不同的组件组成。
我们将通过下面的示例解释实体与组件之间的关系:
var answerText = Crafty.e("2D, DOM, Text, Mouse");

answerText 是一个示例实体,在屏幕上显示文本。 它包括 (“继承于”) 四个不同组件:“2D”、“DOM”、“文本”和“鼠标”。 所有这些组件都在 Crafty 引擎中被预定义。 在声明中,用逗号把它们隔开。 使用实体并创建您自己的自定义组件将在下文中解释。

使用 sprite

单独加载图片会严重损害应用程序的性能和代码的可读性。 如果您有许多图像资源,特别是有很多代表单个对象动画不同阶段的资源,最好创建一个 sprite sheet。

Quiz的一个sprite sheets图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。

被分割为tile的 sprite sheet 示例图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从屏幕的顶部往下掉。 它慢慢涌现,从顶部向下滑动,然后弹起,并再次跌落。 下面的图片说明了这个过程。

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 具有相同宽度时,效率尤其高。

sprite sheet示例图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 库 - 后续

文件附件: