码迷,mamicode.com
首页 > 其他好文 > 详细

Cocos2d-x v3.6制作射箭游戏(三)

时间:2015-09-05 22:16:40      阅读:254      评论:0      收藏:0      [点我收藏+]

标签:

上章我们已经创建好了射箭的游戏角色,接下来这章我们将开始实现射箭的部分。

本章主要内容:

  1. 添加玩家的射箭逻辑;
  2. 讲解弓箭做曲线运动的原理。

射箭

首先我们来看看射箭时(弓箭离弦之前) Player 的一些逻辑行为吧。

先简单的分析了一下:

当用户触摸结束时,Player 会有一个拔箭准备射箭的动作,等这个动作执行完的时候,会在 Player 拿弓箭的手的地方创建一支箭,并且这支箭会按曲线轨迹飞出去。
但试想一下,如果我们在拔箭动作还没执行完的时候又点击了屏幕,那么还没等上一支弓箭发射出去马上又要准备发射第二支,不停的点击屏幕,就会不断的创建发 射弓箭,显然,这样的结果不是我们想要的。所以在执行拔箭动作的时候,我们需要给 Player 添加一个控制它并不让它乱射箭的变量,也就是上章声明的 isRunAction。

分析完以后,接下来我们就来看看具体的实现吧。其代码我们写在上章声明的 createAndShootArrow 函数中,在该函数将完成所有的射箭行为。实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Player::createAndShootArrow( Point touchPoint)
{
    // 1   
    isRunAction = true;
    // 2
    auto animation = AnimationCache::getInstance()->getAnimation("player");
    auto animate = Animate::create(animation);
    // 3
    auto funcall1= CallFunc::create(CC_CALLBACK_0(Player::shootArrow, this));
    // 4
    auto delayTime = DelayTime::create(0.5f);
    // 5
    auto funcall2= CallFunc::create(CC_CALLBACK_0(Player::finishRunAction, this));
    // 6
    playerbody->runAction(Sequence::create(animate, funcall1, delayTime, funcall2, NULL));
}
  1. 在开始调用 createAndShootArrow 的时候,我们先把 isRunAction 设为 true,即告诉程序:现在大爷开始拔箭了。
  2. animation 是从 AnimationCache (动画缓存)中得到的一个叫做“player”的缓存动画数据,它是一个 Animation(名词)对象,这里代表了的是 Player 的拔箭动画。而 animate 则是用这一数据生成的动画动作,它是个 Animate(动词)对象。
    现在如果你要问我缓存中怎么会有 “player” 这个动画数据,我只能告诉你,当然是我加进去的啊,这个待会我们马上会讲。
  3. funcall1 是个 CallFunc 类型的函数回调动作。
    有时候某些动作完成后,需要执行一些数据上的处理,比如攻击完一个敌人后需要处理加减血等。这时就需要使用函数回调动作 CallFunc。
    这里 funcall1 的作用是调用 shootArrow 方法创建并射出弓箭。
  4. 延时动作,延时0.5秒。
    延时动作是一个"什么都不做"的动作,它最常见的用法就是在一个 Sequence 序列动作中,打入若干延时时间,让动作的执行速度慢下来,不至于眼花缭乱,让人反应不过来。
  5. 回调动作,执行 finishRunAction 函数,finishRunAction 函数中将把 isRunAction 设为 false,即告诉程序:大爷这次的射箭过程完成了,你个渣渣可以开始射第二次了。
  6. 让 playerbody 对象依次执行以上的各个动作,即先执行拔箭动作,再开始射箭,再延时一小会,再结束此次射箭。这里 Sequence 是个能使Node顺序执行一系列动作的动作。

以上代码注释中提到了缓存的动画数据,以及 shootArrow,finishRunAction 函数。其中 finishRunAction 函数是很简单的,它的定义如下:

1
2
3
4
void Player::finishRunAction()
{
    isRunAction = false;
}

接下来我们主要来看看另外两个是怎样实现的。

缓存数据的由来

关于缓存中的动画数据,这里我们是在 GameScene 类中载入的。如下,我们在 GameScene.h 中声明一个 createAnimation 函数。

1
Animation* createAnimation(std::string prefixName, int framesNum, float delay);

并在 GameScene.cpp 中定义它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Animation* GameScene::createAnimation(std::string prefixName, int framesNum, float delay)
{
    // 1
    Vector<SpriteFrame*> animFrames;
    // 2
    for (int i = 1; i <= framesNum; i++)
    {
        char buffer[20] = { 0 };
        sprintf(buffer, "%i.png",  i);
        std::string str =  prefixName + buffer;
        auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str);
        animFrames.pushBack(frame);
    }
    // 3
    return Animation::createWithSpriteFrames(animFrames, delay);
}

createAnimation 方法的作用是把精灵帧添加到 Animation 生成动画数据,它的参数如下。

参数1: 精灵帧前缀名称;比如前缀我们设为“player”,那么我们在程序中将加上它的中间部分的号数和后缀。即“player” + “第几帧” + “.png” = player1.png、player2.png 之类的。 参数2: 总帧数;即该精灵帧动画到底有几帧。 参数3: 帧内延时,即每帧之间播放所需时间,控制动画播放速率。

这里需要注意的是,前缀名称和总帧数是不能乱给的,它必须是 SpriteFrameCache 中有的数据,即 texture.plist 里必须有的帧,如下: 技术分享

  1. 定义一个 SpriteFrame 精灵帧数组,用于存放一个动画的所有精灵帧数据。
  2. 遍历所有帧,取出相应的精灵帧,并把它们依次压入 animFrames 数组中。
    这里 SpriteFrameCache 是精灵帧缓存类,在我们第二章添加打包资源 plist 和 pvr.ccz文件时就已经向 SpriteFrameCache 中载入了需要的精灵帧。更多详细内容请还是点此查阅。
  3. 通过精灵帧数组创建一个 Animation 对象并返回。

createAnimation 方法实现后,我们就可以通过该函数来创建需要的动画对象了。回到 GameScene 的 init 方法中,我们添加如下的代码:

1
2
auto animation = createAnimation("player", 8, 0.06f);
AnimationCache::getInstance()->addAnimation(animation, "player");

这里我们通过 createAnimation 方法创建了一个 Animation 动画数据对象,同时把这样一个对象加入到了动画缓存类中,名字设为了“player”。如此一来,在后面的进程中,我们只要通过 AnimationCache 这个单例类,就可以在任何地方获得名字叫做“player”的动画对象了。

今后的敌人跳跃动画、攻击动画、死亡动画,以及其他游戏需要的动画,我们都是通过以上的方法实现的。即如:

1
2
3
4
5
6
7
8
9
10
11
auto animation = createAnimation("player", 8, 0.06f);
AnimationCache::getInstance()->addAnimation(animation, "player");
animation = createAnimation("arrowfire", 5, 0.05f);
AnimationCache::getInstance()->addAnimation(animation, "arrowfire");
animation = createAnimation("arrowdrug", 5, 0.1f);
AnimationCache::getInstance()->addAnimation(animation, "arrowdrug");
animation = createAnimation("enemy1_XXX", X, X);
AnimationCache::getInstance()->addAnimation(animation, "enemy1_XXX");
animation = createAnimation("enemy2_XXX", X, X);
AnimationCache::getInstance()->addAnimation(animation, "enemy2_XXX");
// .....................等等诸如此类的,很多。

射箭的实现

shootArrow 方法将创建并发射弓箭,其中创建弓箭的部分是很好实现的,这里我们主要需要攻克的问题是如何让箭射出去。下面我们先看看 shootArrow 方法中创建弓箭的代码,如下:

1
2
3
4
5
6
7
8
9
10
void Player::shootArrow()
{
    // 创建箭
    Sprite* arrow = Sprite::createWithSpriteFrameName("arrow1.png");
    arrow->setPosition(Vec2(playerarrow->getPosition().x/2, playerarrow->getPosition().y));
    this->addChild(arrow);
    // 以下将写射出箭的代码
    // .................
 
}

shootArrow方法的后半段我们将加入弓箭曲线运行的逻辑,此部分参考下文。

惯用贝赛尔

通常情况下,结合 Cocos2d-x 引擎的特点,大家一般都会想到使用贝赛尔动作(BezierTo / BezierBy)来实现弓箭曲线飞行的这一过程。下面,我们就先试试使用贝赛尔动作来实现这个功能吧。

在 shootArrow() 方法中,我们试着添加如下的一段代码:

1
2
3
4
5
6
7
ccBezierConfig bezier;
 
bezier.controlPoint_1 = Vec2(100, 200);
bezier.controlPoint_2 = Vec2(200, 200);
bezier.endPosition = Vec2(300, 0);
auto action = BezierTo::create(2, bezier);
arrow->runAction(action);

该段代码创建了一个贝赛尔动作,并让弓箭对象 arrow 来执行该动作。这里控制点、终点都是为了测试随便设的,其效果会像下面一样。

技术分享

从图中大家会发现,弓箭虽然做了曲线运动,但它并没有跟着曲线路径变换角度,所以,我们急需重写一个新的动作来替代以上的方法。

这里我们简单介绍下上面的贝赛尔曲线,初学者可以看看。

贝赛尔曲线是应用于二维图形应用程序的数学曲线,它是依据四个位置任意的点坐标绘制出的一条光滑曲线。曲线定义的四个点分别是:起始点、终止点以及两个相互分离的中间点。滑动两个中间点,贝塞尔曲线的形状就会发生变化。

Cocos2d-x 引擎中定义的贝塞尔曲线的配置信息如下:

1
2
3
4
5
6
7
8
typedef struct _ccBezierConfig {
    //! end position of the bezier
    Vec2 endPosition;
    //! Bezier control point 1
    Vec2 controlPoint_1;
    //! Bezier control point 2
    Vec2 controlPoint_2;
} ccBezierConfig;

结构体中三个点分别是:曲线的终点,控制点1和控制点2。曲线的起点这里没有配置进去,这是因为引擎中 Bezier 的起始点我们认为就是它当前的坐标原点。

引擎中 BezierTo / BezierBy 类是根据贝赛尔曲线原理封装的两个动作,它们的区别在于 BezierTo 是设置终点到指定坐标位置,而 BezierBy 则是设置终点到相对的坐标位置。还不清楚的童鞋可点击此文查找相关的说明,该文也可作为扩展学习资源。
在使用该类型动作时,我们需要明确 ccBezierConfig 里所有的变量数据,也就是确定贝赛尔曲线的终点和两个控制点,如上例所示。

回到我们的游戏中,细想一下,其实我们只需在已有贝塞尔动作的基础上稍稍改善,前面的方法还是可行的。只不过有个小问题是:Cocos2d-x 引擎中提供的贝塞尔曲线(BezierTo / BezierBy类)是由两个控制点和一个终点来控制的,而这些点在本游戏中又是不断变换且不好确定的点。

所以如果我们继续延用 BezierTo / BezierBy 的曲线算法,那么我们后面在确定控制点和终点时,会比较麻烦,且效果不好。得到的贝塞尔曲线一般都是“歪七扭八”不好控制的。

技术分享技术分享技术分享技术分享

就算我们把控制点1,2设在相同的位置,它的轨迹依旧看着很变扭,如图4。

其实对于本游戏来说,Player 箭支的运行轨迹在某种程度上更像是抛物线,它更像是由一个控制点确定的曲线,如下所示:

技术分享技术分享技术分享

所以为了更方便的明确曲线的轨迹,我们将把曲线改成为由一个控制点控制。具体的实现我们将在下章“新进贝赛尔”中讲解,敬请关注。

Cocos2d-x v3.6制作射箭游戏(三)

标签:

原文地址:http://www.cnblogs.com/dan-alone/p/4783991.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!