使用CraftyJS库开发游戏 - 后续
PUBLISHED
简介
本文是介绍如何在Tizen上使用CraftyJS系列文章的第二篇。 第一篇文章 "使用 CraftyJS 库开发游戏 — 简介"介绍了CraftyJS的基本框架。 本文则阐述了一些更高级的功能,如创建自己的组件,设计复杂的动画或播放音频文件。 并在文章的附件中给出了一个示例程序,即Quiz游戏用来介绍这几个功能。
Crafty
在CraftyJS系列的第一篇文章中,我们已经介绍了两种类型的动画:Tween动画和Sprite动画。 现在,我们将用它们来创建更复杂的UI。
动画按钮的例子
在我们的示例应用程序中,每个按钮都是动画的。 我们以主菜单中的START按钮为例。 START按钮为绿色,文字为暗绿色。 当我们按下它,它会变成蓝色,并且文字也变为深蓝色。 如果我们把手指从按钮上移开(但手指不离开屏幕),则按钮应该变回成绿色。 为了用于处理触摸(touch)事件,我们将使用“Mouse”的组件。 该组件可以监听如下几个事件:
- MouseOver(鼠标悬停),
- MouseOut(鼠标移出),
- MouseDown(鼠标按下),
- MouseUp(鼠标释放)
- Click(点击),
- DoubleClick(双击),
- MouseMove(鼠标移动).
我们的应用程序运行在带有触摸屏的移动设备上,所以我们使用其中的三个事件:MouseDown,mouseout和click。 请看如下代码:
// START BUTTON var startButton = Crafty.e("2D, DOM, startBtn, SpriteAnimation").attr({ x : 455, y : 225 }).animate("press", [ [ 74, 0 ] ]).animate("release", [ [ 0, 0 ] ]); var startText = Crafty.e("2D, DOM, startText, SpriteAnimation, Mouse").attr({ x : 455, y : 225 }).animate("press", [ [ 0, 66 ] ]).animate("release", [ [ 0, 44 ] ]).bind( 'MouseDown', function() { startButton.animate("press", 1, 0); startText.animate("press", 1, 0); }).bind('MouseOut', function() { startButton.animate("release", 1, 0); startText.animate("release", 1, 0); }).bind('Click', function() { // start the game });
正如你所看到的,我们为按钮准备了两个独立的实体:背景图形(startButton)和文字(startText)。 它们使用相同大小的sprite:startBtn和startText,并且它们的位置也相同。 StartText是后来创建的,因此它会在startButton的顶部。 这意味着用户仅能够点击StartText实体 - 我们便不用监听来自startButton实体的事件。
startButton声明了两个动画:一个是按下事件(press),一个用于释放事件(release)。 他们很简单,每一帧动画负责改变按钮的颜色。 startText实体同样有两个动画,它们负责用户触发startText时的处理逻辑。 出现鼠标按下(MouseDown)事件时会触发“press”动画,出现鼠标释放(MouseOut)事件时会运行“release”动画。 还有一个额外的侦听器用来监听Click事件,当用户真实的点击了按钮时,便会触发Click事件。 Crafty允许用户使用带有CSS标签的文本。 当你使用“DOM”组件时,便可以使用css()函数了。 下面的示例代码显示了游戏画面中四个答案按钮的文本格式:
answerText0 = Crafty.e("2D, DOM, Text, Mouse").attr({ x : 55, y : 420, w : 560, h : 125 }).textFont({ size : '20px' }).css({ "text-align" : "center", "font-size" : "50px", "font-family" : "Droid Sans", "font-weight" : "bold", "color" : "#ffffff", "vertical-align" : "middle", "padding-top" : "37px" });
CSS()可以实现在DOM元素中添加CSS属性。 它有一个参数:一个使用key作为style属性,并且key值即style值的对象。 您可以使用CSS或JavaScript的符号(如text-align 或textAlign)。
创建动画时钟
在Quiz应用程序中,我们需要一个动画时钟。 该时钟用于显示还有多少时间回答问题。 如下图所示的画面。
图1 动画时钟
对于动画的时钟,我们使用了两个不同的精灵(sprite):一个用于背景图像,一个用于移动时钟指针。
// CLOCK Crafty.e("2D, DOM, clock").attr({ x : 1090, y : 40, }); var clockHand = Crafty.e("2D, DOM, clockHand, Tween").origin(3, 60).attr({ x : 1155, y : 48, w : 5, h : 60, rotation : 0 });
请注意,我们必须设置时钟指针的中心点。 默认情况下,用于动画的原点被设置为实体的左上角。 我们想让指针围绕最左下角的(0, 60)点进行旋转, 由于时钟指针并不会精确地从sprite的最坐下角对应的点启动,所以我们在右边增加了额外的3个单元:(3,60)。 当然您也可以使用符号,如“top left”等:
Crafty.e("2D, DOM, Tween").origin(x, y); Crafty.e("2D, DOM, Tween").origin("top left"); Crafty.e("2D, DOM, Tween").origin("center"); Crafty.e("2D, DOM, Tween").origin("bottom left"); Crafty.e("2D, DOM, Tween").origin("middle right");
为了使时钟指针转动,你可以使用一个简单的Tween动画。 你必须设置初始旋转值为0,然后通过调用clockHand.tween()函数改变它。
clockHand.tween({ rotation : 360 }, quiz.config.timeForQuestion * Crafty.timer.getFPS());
上面的代码定义了一个完整的360度旋转的动画。 quiz.config.timeForQuestion参数表示回答一个问题允许的最大时间值(以秒为单位)。 该值在游戏选项中设置。 如之前所提到的,我们把Tween方法作为动画中变换帧的时间长度。 因此我们必须将每秒帧的数目乘以时间(秒)。 你可以通过Crafty.timer.getFPS()函数得到该值。 需要注意的是它返回的是目标帧速率。 实际值可能可能有些差异,这取决于硬件。 我们也想知道动画在什么时候结束,进而显示游戏结束画面。 为做到这一点,我们可以使用一个简单的监听器来监听TweenEnd事件:
clockHand.bind("TweenEnd", function() { // game over screen });
创建自定义组件
Crafty库中预定义了很多有用的组件。 您可以从网上下载一些,比如从 http://craftycomponents.com/. 然而,有时您需要创建可以在不同实体中重复使用的自定义组件。 我们将使用一个示例来演示创建过程。 在Quiz游戏中,有些场景,我们需要隐藏或显示的实体的一部分。 例如,作为游戏场景一部分的游戏退出画面。 只有当用户点击了屏幕左上角的exit按钮时,“quit”和“resume”按钮组件才可以显示,并且可以点击。 当用户点击“resume”时,我们便将它们隐藏起来。 当显示/隐藏这些实体的时候,我们必须相应的将他们放置到屏幕前或屏幕后,从而在他们显示出来的的时候,用户可以点击到。 我们在帮助场景中使用了previous/next提示箭头作为示例。 有四个提示。 当第一个提示可见时,应该只有一个箭头 - “next hint”可见。 当只有最后一个提示可见时,应该只显示左箭头 - “previous hint”。 在其他情况下,我们应该能同时看到这两个箭头。 如下图所示,您可以同时看到帮助屏幕上的两个箭头:
图2 帮助屏幕
我们可以通过改变组件的属性来显示或者隐藏它们:
// ARROWS var leftArrow = Crafty.e("2D, DOM, arrowLeft, SpriteAnimation, Mouse").attr({ x : 490, y : 400, z : 0, alpha : 0.01 }); // show entity leftArrow.attr({ z : 2, alpha : 1 }); // hide entity leftArrow.attr({ z : 0, alpha : 0.01 });
我们操作的Z指数和alpha值, 只占代码的很少一部分,但是如果频繁使用它们时,将会使代码变得很长,并且不清晰。 我们将展示如何创建一个自定义组件,并使该代码更好看。 注意:由于Crafty库存在bug,我们需要将隐藏的实体的Alpha值设置成0.01,而不是0。 这是一个已知问题,在下一个发行版本中会得到解决。 让我们直奔创建新组件的代码:
// Define component "HideShow" (functions to hide/show entity by setting z and alpha values) Crafty.c("HideShow", { hide : function() { this.attr({ z : 0, alpha : 0.01 }); }, show : function() { this.attr({ z : 2, alpha : 1 }); } });
创建新的组件并不困难。 您需要给新的组件的命名,并创建该组件的框体(body),然后使用Crafty.c提供的函数。 在我们的示例中,只有两个用于改变改变实体属性的函数:show() 和hide()。 现在你可以这样使用它们:
// ARROWS var leftArrow = Crafty .e("2D, DOM, arrowLeft, SpriteAnimation, HideShow, Mouse").attr({ x : 490, y : 400, z : 0, alpha : 0.01 }); leftArrow.show();// show entity leftArrow.hide();// hide entity
你所要做的唯一事情就是将“HideShow”组件添加到实体,这样便可以使用它的功能了。 您也可以在其他实体上使用这个组件。 稍后我们会介绍一些例子 在写您自己的组件时需要一些约定。 即以下划线开头的属性和方法都是私有的。 您可以创建一个方法叫做init()方法,它将在该组件被添加到该实体时自动调用。 如果您定义的方法和组件名同名,那么该方法的作用等同于一个构造函数。 您可以将一些配置数据传递每个继承实体的组件。
场景中的透明画面
游戏场景中,我们有两个透明画面。 当用户点击返回按钮时,会显示第一个透明画面。
图3 退出游戏画面
当游戏结束时显示第二个透明画面:
图4 Quiz结束画面
为简单起见,我们在游戏场景初始化过程中,创建了两个画面的所有元素和游戏主画面(时钟,问题,回答等)的所有元素。 屏幕上的所有实体使用我们的HideShow组件,并且在开始的时候隐藏这些组件。 每个屏幕均需要合理数量的代码来组织,所以我们将其移动到单独的文件中。 我们使用退出游戏画面作为一个例子。 下面是这个模块(稍微简化)的代码:
var exitGameScreen = function($) { return { dimRectangle : null, resumeButton : null, quitButton : null, resumeText : null, quitText : null, questionMark : null, initialize : function() { var that = this; // DIM RECTANGLE this.dimRectangle = Crafty.e("2D, DOM, Color, HideShow").attr({ w : quiz.config.width, h : quiz.config.height, z : 0, alpha : 0.001 }).color("black"); // RESUME BUTTON this.resumeButton = Crafty.e("2D, DOM, startBtn, HideShow").attr({ x : 455, y : 225, z : 0, alpha : 0.01 }); this.resumeText = Crafty.e("2D, DOM, resumeText, HideShow, Mouse") .attr({ x : 455, y : 225, z : 0, alpha : 0.01 }); ...// quit button and question mark declarations (omitted in this code // sample) }, // exit the scene screen show : function() { this.dimRectangle.attr({ z : 1, alpha : 0.8 }); this.resumeButton.show(); this.resumeText.show(); this.quitText.show(); this.questionMark.show(); this.resumeText.bind('Click', function() { that.cancelExit(); }); this.quitText.bind('Click', function() { // quit the game }); }, // return to the game (from exit screen) cancelExit : function() { this.dimRectangle.hide(); this.resumeButton.hide(); this.quitButton.hide(); this.resumeText.hide(); this.quitText.hide(); this.questionMark.hide(); this.resumeText.unbind('Click'); this.quitText.unbind('Click'); }, }; }($);
最后屏幕中使用的实体的所有声明都移到此屏幕模块中。 这是非常方便的,因为它使我们的屏幕可重复使用。 每一个实体被定义为不可见的。 注意,它们都使用了HideShow组件。 对于调光的效果,我们使用一个黑色的矩形覆盖整个屏幕。 这里我们不能使用HideShow组件的show()函数,因为“visible”矩形的alpha值为0,8。 为了得到一个纯色的矩形,我们使用了一个新的组件:Color。 该组件在Crafty库中预定义好了,并且只有一个函数:color()。 这种方法将整个实体绘制成纯色。 同样,还定义了两个方法:show() 和 cancelExit()。 show()方法使用了HideShow's show()方法,并设置dimRectangle实体的alpha值。 它同时也绑定了按钮的click事件监听器。 这里绑定这些事件很重要。 请不要提前做这些事情,防止点击无形的实体(如果有任何其他实体具有相同/低Z指数的话)。 cancelExit()也一样, 使用hide()函数隐藏所有的实体,并且在发生click事件时,释放绑定关系。
播放音频文件
Crafty中,播放音频文件是很容易的。 首先您必须加载文件:
Crafty.audio.add("correctAnswer", "sounds/game_correct.wav");
然后播放:
Crafty.audio.play("correctAnswer");
这是播放简短声音最简单的方法。 Crafty也可以帮助您确保跨浏览器时的兼容性。 要做到这点,我们需要三种类型的音频文件:MP3,OGG和WAV。 每种浏览器会选择最适合的音频格式。 您可以添加一个声音...
// adding a single sound Crafty.audio.add("correctAnswer", [ "sounds/game_correct.mp3", "sounds/game_correct.ogg", "sounds/game_correct.wav" ]);
......或者一次添加多个声音:
// adding audio from an object Crafty.audio.play("correctAnswer"); Crafty.audio.add({ correctAnswer : [ "sounds/game_correct.wav", "sounds/game_correct.mp3", "sounds/game_correct.ogg" ], wrongAnswer : [ "sounds/game_wrong.wav", "sounds/game_wrong.mp3", "sounds/game_wrong.ogg" ] });
现在,您不必担心如何选择最佳的音频格式。 您只需简单的调用Crafty.audio.play()函数。 Crafty.audio.play()需要三个参数:
- ID - 添加声音时对应声音的ID,
- repeatCount - 重复播放该声音的次数 (-1 表示持续播放)
- volume - 音量值,在0和1之取值。
只有第一个参数是必须的,您可以省略其他两个。 默认的repeatCount和volume值分别为0和1。
总结
本文介绍了一些更复杂的动画。 你已经学会了如何创建自己的组件和模块,并使用它们令你的代码保持干净整洁。 同样介绍了如何在Crafty游戏中使用音频。 在本系列文中的下一篇中,您将学会如何在Tizen游戏中使用数据库 ("Working with databases in Tizen - Activerecord.js library").