标签:
上章我们已经创建好了射箭的游戏角色,接下来这章我们将开始实现射箭的部分。
本章主要内容:
首先我们来看看射箭时(弓箭离弦之前) 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