标签:
上章我们已经创建好了射箭的游戏角色,接下来这章我们将开始实现射箭的部分。
本章主要内容:
首先我们来看看射箭时(弓箭离弦之前) 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));} | 
以上代码注释中提到了缓存的动画数据,以及 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 里必须有的帧,如下:

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 箭支的运行轨迹在某种程度上更像是抛物线,它更像是由一个控制点确定的曲线,如下所示:



所以为了更方便的明确曲线的轨迹,我们将把曲线改成为由一个控制点控制。具体的实现我们将在下章“新进贝赛尔”中讲解,敬请关注。
标签:
原文地址:http://www.cnblogs.com/dan-alone/p/4783991.html