65.9K
CodeProject 正在变化。 阅读更多。
Home

配对游戏

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (20投票s)

2012年8月8日

BSD

6分钟阅读

viewsIcon

90152

downloadIcon

8783

如何为大约 90% 的智能手机创建配对游戏?

  Content

引言

Pairs,也称为Concentration、Memory cards、Pexeso等,是一款简单的纸牌游戏,玩家必须找出两张相同的牌。这款游戏非常适合训练记忆力,也很适合孩子和成人消磨时间。尽管如此,我们的目标是向开发人员展示如何用几行代码为许多移动操作系统创建一个简单的游戏。

背景

为多个平台创建游戏的最佳选择是使用跨平台SDK。本文讨论了名为Moscrif SDK的跨平台开发工具。目前,支持iOS、Android和Bada设备

我们的应用程序可以在许多不同的设备上运行,从只有600MHz处理器的低端设备到最新的高端设备,如最新的iPad、Samsung Galaxy S 3等。这些设备的性能能力和屏幕分辨率各不相同。克服不同画面尺寸的最佳方法是使用矢量图形;然而,这并非总是可能的。

用户界面 

如今,应用程序正以快速的步伐开发,通常需要提供用户友好的界面来赢得客户的忠诚度。我们的游戏应用程序由一个工具栏组成,只有两个按钮(关闭和新游戏)和一个计时器。我们应用程序的主要部分是一个有十二张卡片的网格。卡片以精美的3D旋转效果揭开。

背景和工具栏用位图图形填充,这些图形是为三种分辨率单独创建的。卡片上的简单图像允许我们为它们使用矢量图形。矢量图形在调整大小后也能保持所有细节。矢量图形比位图尺寸小。

图片:图形建议



开发过程

为了开发这个游戏,我们使用了Moscirf的游戏框架(Game、Scene、Layer、sprite、GameControl和ImageButton类)。

  • Game类是基类,它创建了运行我们游戏的环​​境。它管理用户和系统事件,并包含其他场景或精灵对象。
  • Scene是我们游戏的一部分,它包含几个层或直接的Sprite对象。开发人员也可以在场景中管理用户和系统事件。
  • Layer用于创建场景的独立部分,如菜单、游戏区等。
  • Sprites创建最终对象,如球、球拍等。Sprite对象不管理用户事件。它们可以被添加到场景、层或游戏对象中。
  • GameControl类的实例可以被添加到场景、层或直接添加到游戏对象中,但它也管理用户事件。在我们的游戏中,Card类继承自GameControl类。

启动文件

默认情况下,启动文件是main.ms。我们将Game类的实例放入此文件中,绘制背景。它还管理onKeyPressed事件。当用户按下硬件键时,会触发此事件。

示例:启动文件

include "lib://game2d/game.ms"
include "lib://game2d/layer.ms"
include "lib://game2d/imageButton.ms"
include "app://vectorImage.ms"
 
include "app://resources.ms"
include "app://cardLayer.ms"
include "app://gameScene.ms"
include "app://card.ms"
include "app://menuLayer.ms"
include "app://timerSprite.ms"
 
var game = new Game();
var res = new Resources()
 
// onstart what top do
game.onStart = function(sender) {
    this.gameScene = new GameScene({width : System.width, height : System.height});
    this.push(this.gameScene);
}
 
// clear screen
game.onDraw = function(sender, canvas) {
    canvas.drawBitmapRect(res.images.background, 0, 0, res.images.background.width, res.images.background.height, 0, 0, System.width, System.height);
}
 
game.onKeyPressed = function(sender, keyCode)
{
    // quit game when user click onto back hardwaere key
    if (keyCode == #back)
        this.quit();
}
 
game.run();

GameScene 

游戏场景由我们游戏中的一个场景组成。该场景有两个层,第一个层创建所有卡片,第二个层创建菜单。

卡片

每张卡片都是Card类的实例。这个类提供三个功能:

  • 绘制卡片
  • 显示卡片正面
  • 隐藏卡片正面

卡片具有3D效果旋转。Moscrif SDK提供View3D对象,该对象管理3D世界中的对象变换。View3D仅为普通画布计算变换。这意味着程序员只需要对画布应用变换。

this._view3D.applyToCanvas(canvas);

使用rotate(angle)、rotateY(angle)和rotateZ(angle)函数进行旋转。

this._view3D.rotateY(degrees); 

默认情况下,画布围绕屏幕左上角旋转,因此卡片旋转不真实。

图片:卡片旋转





为了实现逼真的旋转效果,需要将旋转中心移到中间。画布也进行了缩放,以在卡片之间创建间隙。

示例:绘制卡片

// drawing
function draw(canvas)
{
    super.draw(canvas);
 
    canvas.save();
    canvas.translate(this.x, this.y);
    canvas.scale(0.85, 0.85);
    // apply 3d rotation
    this._view3D.applyToCanvas(canvas);
    if (this._side == #front) {
        this._drawFront(canvas);
    } else {
        this._drawBack(canvas);
    }
    canvas.restore();
}

图片:卡片旋转




动画卡片 

卡片通过show和hide两个函数进行旋转。这些函数创建动画对象。动画对象创建动画的数学背景。动画最重要的属性是duration(持续时间)和transition(过渡)。使用这个类,动画不必具有线性行为。根据所需的过渡,动画对象会调用addSubject函数指定的​​回调函数。回调函数只有一个参数,即state,它指定了动画中的当前位置。

在我们的游戏中,我们在回调函数中将旋转应用于View3D。因为view3D是从最后一个位置旋转,而不是从基础位置(无旋转)旋转,所以我们需要先将view3D恢复到基础位置(无旋转)然后再旋转它

示例:显示卡片

function show()
{
    this.rotating = true;
    var animator = new Animator({
        transition: Animator.Transition.easeOut,
        duration: 1500,                         // length of animation in miliseconds
    });
    animator.addSubject(function(state) {       // state starts from 1.0 to 0.0
        var self = this super;
        self._view3D.restore();
        self._view3D.save();
        self._view3D.rotateY(state * 180);
        if (state * 180 > 90) {
            self._side = #front;
        }
    });
    animator.onComplete = function() {
        this super.rotating = false;
    };
    animator.play();
}

卡片层 

卡片层创建卡片网格并将图像分布到卡片上。

卡片分布

每次游戏重新开始时,卡片顶面的图像都必须重新分布。图片从资源加载,然后推送到availableImages数组。然后,所有卡片都被推送到freeCards数组。这个数组包含所有需要更改图像的卡片。分发继续进行三个步骤,直到所有图像都分发完毕。

  1. 加载一张随机图片,并从此加载的图片中移除availableImages。
  2. 将图片设置到第一张卡片上,并从freeCards中移除这张卡片。
  3. 将相同的图片设置到第二张卡片上,并从freeCards中移除这张卡片。

该算法允许以最少的循环次数非常快速地分发图像。

图片:将图像分布到卡片上




示例:将图像分布到卡片上

//fill image grid with random images
function _fillImages()
{
    // load images into resources
    res.getCards();
    // mark all card images as available
    var availableImages = res.images.cards;
    // mark all Cards
    var freeCards = [];
    for (var card in this._cards)
        freeCards.push(card);
    var needed = this._rows * this._columns / 2;
    for (var i=0; i < needed; i++) {
        // no more empty cards?
        if (freeCards.length == 0)
            return;
        // get random vector file
        var randomImageIndex = rand(availableImages.length);
        var imageFile = availableImages[randomImageIndex];
        // remove loaded file from available images
        availableImages.removeByValue(imageFile);
 
        // initialize cell
        var randomCardIndex = rand(freeCards.length);
        // create & write cell view
        freeCards[randomCardIndex].cardImage = imageFile;
        // remove cell from freeCards
        freeCards.removeByValue(freeCards[randomCardIndex]);
 
        // initialize cards's pair (random)
        randomCardIndex = rand(freeCards.length);
        freeCards[randomCardIndex].cardImage = imageFile;
        freeCards.removeByValue(freeCards[randomCardIndex]);
    }
    availableImages = null;
}

卡片比较 

当两张卡片被揭开时,卡片层也会比较这两张卡片。如果两张卡片相同,它们将保持揭开状态,否则将在短暂延迟后翻转回去。当两张卡片被揭开时,会调用_turnCards函数。它使用_compare函数来比较已揭开的卡片。它比较它们的ID,因为具有相同图像的卡片具有相同的ID。

示例:比较函数

function _compare()
{
    if (this._opened[0]._id == this._opened[1]._id)
        return true;
    else
        return false;
}

如果两张卡片相同,则存储已打开卡片数量的变量设置为零,这允许用户打开其他卡片,并且还会检查是否所有卡片都已打开。如果卡片不同,则启动隐藏两张卡片的计时器。

示例:比较卡片

function _turnCards()
{
    var timer = null;
    if (this._compare()) {
        //let images uncovered, and resume game
        this._opened[0] = 0;
        this._opened[1] = 0;
        this._openedCount = 0;
        this.numberOfOpened += 1;
        this._wav.play();
        if (this.numberOfOpened == this._rows * this._columns / 2)
            game.gameScene.menu.timer.stop();
    } else {
        //after one second rotete card back
        timer = new Timer(10, false);
        timer.onTick = function()
        {
            if (this super._opened[0])
                this super._opened[0].hide();
            if (this super._opened[0])
                this super._opened[1].hide();
            this super._openedCount = 0;
        }
        timer.start(1200);
    }
}

菜单层 

菜单层创建了我们在游戏中使用的简单菜单。它仅由两个按钮组成。启动新游戏和退出游戏,还有一个计时器。按钮通过使用ImageButton控件轻松创建。下一个示例展示了如何创建一个新游戏按钮。此按钮为正常状态和按下状态具有不同的图像。

示例:创建新游戏按钮

var newGame = new ImageButton({
    image           : res.images.newGame,
    imagePressed    : res.images.newGameClicked,
    mode            : #image,
    x               : System.width / 2,
    y               : System.height - this.height / 2,
});
newGame.onClick = function()
{
    game.gameScene.cardLayer.newGame();
}
this.add(newGame);

总结

现在您知道如何创建自己的益智游戏,只需更改图像或卡片数量即可轻松地根据您的需求进行调整。最好的部分是,使用Moscrif SDK只需创建一个应用程序,就可以在目前市场上大约90%的智能手机上发布(2012年6月美国统计数据)。祝编码愉快。

© . All rights reserved.