使用CraftyJS库开发游戏 - 后续

简介

本文是介绍如何在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实体的事件。

提示:您可以通过使用“2D”组件中的Z参数更改对象的顺序。 Z指数越高,则对象月靠近屏幕的最前面。 全局的Z指数是基于z值和GID(指示哪个实体是第一个创建的)的。 因此,具有相同Z值的实体按照它们的创建顺序进行排序。

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 退出游戏画面

当游戏结束时显示第二个透明画面:

Quiz结束画面

图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").

文件附件: