【精品教程】Cocos2d-x v3.6制作射箭游戏(二)

移动开发 游戏开发
本章我们的主要任务是创建射箭的弓箭手(也就是游戏猪脚),并且让这个猪脚随着触摸点的改变不断的旋转手中的弓箭。

本章我们的主要任务是创建射箭的弓箭手(也就是游戏猪脚),并且让这个猪脚随着触摸点的改变不断的旋转手中的弓箭。

分析:

对于这个射箭的角色而言,它能不停的射出弓箭。当我们按住屏幕上某点时,会从该角色拿弓箭的手的位置“画”一条标注箭支运动轨迹的红线(看似抛物线);当在屏幕上滑动手指或鼠标时,这条红线会随着触摸点的位置不停的变换轨迹;当松开屏幕上的手指或鼠标时,会射出一支弓箭,这支弓箭会按最终的红线路径移动。另外,玩家手中的弓箭会随着屏幕上的手指或鼠标旋转。

Player 类

下面我们一起来创建这个 Player 猪脚类,其初步定义如下:

 
  1. class Player: public Sprite 
  2.     public
  3.     Player(); 
  4.    
  5.     bool init(Vec2 playerPos); 
  6.     static Player* create(Vec2 playerPos); 
  7.    
  8.     void createPlayer(); 
  9.     void createPlayerHpBar(); 
  10.     void rotateArrow(Point touchPoint); 
  11.     void createAndShootArrow( Point touchPoint); 
  12.     void shootArrow(); 
  13.     void finishRunAction(); 
  14.     void update(float dt);       
  15.    
  16.     CC_SYNTHESIZE(int, playerHp, PlayerHp);        // 玩家血量值 
  17.     CC_SYNTHESIZE(bool, startDraw, StartDraw);     // 是否开始画红色的路径线 
  18.     CC_SYNTHESIZE(bool, isRunAction, IsRunAction); // 玩家是否正在执行射箭动画 
  19.    
  20.     private
  21.     Vec2 playerPos;            // 角色在 tmx 地图上的位置 
  22.     Size playerSize;           // 角色尺寸 
  23.     Size winSize;              // 屏幕窗口尺寸 
  24.     Sprite* playerbody;        // 角色身体 
  25.     Sprite* playerarrow;       // 角色的弓箭,也就是会随触摸点旋转的弓和箭部分 
  26.     Sprite* hPBgSprite;        // 角色血条背景精灵 
  27.     ProgressTimer* hpBar;      // 角色血条 
  28.     ccQuadBezierConfig bezier; // 路径贝赛尔 
  29.     DrawNode* drawNode;        // 这里表示我们的线条对象 
  30.    
  31. }; 

以上的各方法都是我们这两章需要实现的,其他更多的方法我们将在后面需要的时候再扩充。 

其中CC_SYNTHESIZE宏的作用是定义一个保护型的变量,并声明一个getfunName函数和setfunName函数,你可以用getfunName函数得到变量的值,用setfunName函数设置变量得值。如:CC_SYNTHESIZE(int, playerHp, PlayerHp);定义了一个整型的 playerHp 变量,同时还声明了 getPlayerHp() 和 setPlayerHp() 两个方法。 

ccQuadBezierConfig是我们新定义的一个结构体,后面我们会详细的讲解。

下面我们就从上到下依次来看看以上的各方法。

创建角色

首先是 Player 的初始化(init)和创建(create),这里我们通过给定 Player 的位置来创建该角色,而这个传入的坐标位置应该是我们从 TiledMap 的对象层中读取到的位置(上章有讲)。具体代码如下:

 
  1. Player * Player::create(Vec2 playerPos) 
  2.     Player *pRet  = new Player(); 
  3.     if (pRet && pRet->init(playerPos)) 
  4.     { 
  5.         pRet->autorelease(); 
  6.         return pRet; 
  7.     }else 
  8.     { 
  9.         delete pRet; 
  10.         pRet = NULL; 
  11.         return NULL; 
  12.     } 
  13. bool Player::init(Vec2 playerPos) 
  14.     if (!Sprite::init()) 
  15.     { 
  16.         return false
  17.     } 
  18.     this->playerPos = playerPos; 
  19.     createPlayer();        // 创建角色 
  20.     createPlayerHpBar();   // 创建角色血量条 
  21.     scheduleUpdate(); 
  22.     return true

下面我们接着来看看 createPlayer 方法,该方法将初始化我们的 Player 角色,代码如下所示:

  1. void Player::createPlayer() 
  2.     playerbody = Sprite::createWithSpriteFrameName("playerbody.png"); 
  3.     playerSize = Size(playerbody->getContentSize().width/2, playerbody->getContentSize().height / 3*2);   
  4.     // 设置Player的尺寸,大小略小于playerbody的尺寸,这样利于我们后面更准确的进行碰撞设置。 
  5.     playerbody->setAnchorPoint(Vec2(0.7f, 0.4f)); 
  6.     this->addChild(playerbody); 
  7.     this->setPosition(Vec2(playerPos.x+ GameManager::getInstance()->getObjectPosOffX(), playerPos.y + playerSize.height * 0.4f)); 
  8.    
  9.     playerarrow = Sprite::createWithSpriteFrameName("playerarrow.png"); 
  10.     playerarrow->setPosition(Vec2(00)); 
  11.     playerarrow->setAnchorPoint(Vec2(0.3f, 0.5f)); 
  12.     this->addChild(playerarrow);    

createPlayer 方法中我们将创建如下所示的一个游戏角色。

201506251005113376.jpg

因为没有找到合适的游戏资源(原游戏中得到的资源都是零件,要使用需要把它们一帧一帧重组),所以我们的游戏一切从简,不整那些复杂的。 

这里我们只把角色简单分成了两个部分,第一部分当然是玩家的身体playerbody,第二部分是随着触摸点/鼠标旋转的手和弓箭playerarrow。(PS:当然因为资源限制这个原因,可能会稍稍降低咱游戏的档次,应该不能怪我啰!O(∩_∩)O~)

设置playerbody位置时,你可能已经发现,我们并没有把角色身体设置在传入的playerPos处,而是对它稍微做了一定的调整。这是因为我们传入的位置它是紧贴本格瓦片底部的(我们制作tmx文件时,需要这样做。上章没说清楚,这章补起,要记住哦!)。如下图所示:

201506251006122519.jpg

Y值坐标也不可太接近本格瓦片底部,也就是不要设为9.990,9.998这类太接近10的,因为 tmx 文件中存放的坐标值是整数,如果设为9.990,9.998,那么存放的值会是9.990 X 32 = 319.68 = 320,同理 9.998 X 32 也是 320。320 对于瓦片大小是32 X 32的地图来说是个特殊的数字,因为 320 /32 = 10。这样在程序中就会误以为9.990,9.998之类的点是坐标上的第10个点。

而且上章我们也说过,由于分辨率适配的原因,对象组中对象的位置与实际的位置是有一定的偏差的,所以我们在设置角色身体位置时,需要修正这些偏差。 

以上代码中设置位置的原理图如下:

201506251008052690.jpg

其中,对象组在 X 轴上的偏移值我们把它保存在了 GameManager 中,而 GameManager 是个单例类,后面章节我们会详细的讲解。当然如果你现在就想运行代码,那就先把GameManager::getInstance()->getObjectPosOffX()部分去掉吧。

创建好角色后,接下来我们需要创建角色的血量条,血量条可通过 Cocos2d-x 中封装好的进度条类 ProgressTimer 来创建。其代码段如下:

  1. void Player::createPlayerHpBar() 
  2.     // 创建血条底,即进度条的底背景     
  3.     hPBgSprite = Sprite::createWithSpriteFrameName("hpbg.png"); 
  4.     hPBgSprite->setPosition(Vec2(playerbody->getContentSize().width / 2, playerbody->getContentSize().height)); 
  5.     playerbody->addChild(hPBgSprite); 
  6.     // 创建血条  
  7.     hpBar = ProgressTimer::create(Sprite::createWithSpriteFrameName("hp1.png")); 
  8.     hpBar->setType(ProgressTimer::Type::BAR); // 设置进度条样式(条形或环形) 
  9.     hpBar->setMidpoint(Vec2(00.5f));        // 设置进度条的起始点,(0,y)表示最左边,(1,y)表示最右边,(x,1)表示最上面,(x,0)表示最下面。 
  10.     hpBar->setBarChangeRate(Vec2(10));      // 设置进度条变化方向,(1,0)表示横方向,(0,1)表示纵方向。 
  11.     hpBar->setPercentage(100);                // 设置当前进度条的进度 
  12.     hpBar->setPosition(Vec2(hPBgSprite->getContentSize().width / 2, hPBgSprite->getContentSize().height / 2 )); 
  13.     hPBgSprite->addChild(hpBar); 
  14.     hPBgSprite->setVisible(false);   // 设置整个血条不可见,我们将在Player 遭受攻击的时候再显示血条。 

#p#

旋转角色弓箭

接下来我们来让 Player 的弓箭部分跟随着触摸点/鼠标旋转。所以我们定义了如下的函数:

  1. void Player::rotateArrow(Point touchPoint) 
  2.     // 1     
  3.     auto playerPos = this->getPosition(); 
  4.     auto pos = playerPos + playerarrow->getPosition(); 
  5.     // 2 
  6.     Point vector = touchPoint - pos; 
  7.     auto rotateRadians = vector.getAngle(); 
  8.     auto rotateDegrees = CC_RADIANS_TO_DEGREES( -1 * rotateRadians); 
  9.     // 3 
  10.     if (rotateDegrees >= -180 && rotateDegrees <= -90){ 
  11.         rotateDegrees = -90
  12.     } 
  13.     else if (rotateDegrees >= 90 && rotateDegrees <= 180){ 
  14.         rotateDegrees = 90
  15.     } 
  16.     // 4 
  17.     auto speed = 0.5 / M_PI; 
  18.     auto rotateDuration = fabs(rotateRadians * speed); 
  19.     // 5 
  20.     playerarrow->runAction( RotateTo::create(rotateDuration, rotateDegrees)); 

rotateArrow方法的参数为触摸点的位置。

1)获取角色弓箭在游戏场景中位置;

2)计算弓箭的旋转角度。 

这里利用三角正切函数来计算,原理如下图所示: 

201506251009138662.png

vector(offX,offY) 是触摸点到弓箭之间的向量,通过 getAngle 方法,我们可以得到 vector 向量与X轴之间的弧度。 

再者,我们需要把弧度 rotateRadians 转化为角度,CC_RADIANS_TO_DEGREES就是能把弧度转化为角度的宏。转化时乘 -1 是因为Cocos2d-x中规定顺时针方向为正,这与我们计算出的角度方向相反,所以转化的时候需要把角度a变为-a。

3)控制旋转角度的范围,即只让它在角色右半边内旋转。

4)计算弓箭旋转时间。 

speed表示炮塔旋转的速度,0.5 / M_PI其实就是 1 / 2PI,它表示1秒钟旋转1个圆。 

rotateDuration表示旋转特定的角度需要的时间,计算它用弧度乘以速度。

5)让弓箭执行旋转动作。

触摸响应

好了,现在 Player 就初步定义好了。接下来,我们回到游戏场景把Player加入进去,并来测试下弓箭是否跟随触摸点旋转。

在 Cocos2d-x 3.x 引擎中,实现触摸响应的流程基本是一致的。所以在 3.6 中,其过程依旧是:

  • 重载触摸回调函数;
  • 创建并绑定触摸事件;
  • 实现触摸回调函数。

所以我们要测试弓箭是否跟随触摸点旋转,第一步请先在 GameScene 中重写如下的触摸回调函数,并声明变量:

  1. virtual bool onTouchBegan(Touch *touch, Event *unused_event);  // 开始触摸屏幕时响应 
  2. virtual void onTouchMoved(Touch *touch, Event *unused_event);  // 触摸屏幕并在屏幕上滑动时响应 
  3. virtual void onTouchEnded(Touch *touch, Event *unused_event);  // 触摸结束时响应 
  4.    
  5. private
  6.     Point preTouchPoint;      // 上一个触摸点 
  7.     Point currTouchPoint;     // 当前触摸点 

接着,我们需要在 GameScene 的 init 初始化函数中创建并绑定触摸事件,并先随便创建一个 Player 对象,用于测试。如下:

 
  1. SpriteFrameCache::getInstance()->addSpriteFramesWithFile("texture.plist""texture.pvr.ccz");   
  2. player = Player::create(Vec2(winSize.width / 4, winSize.height/5));  
  3. this->addChild(player); 
  4.    
  5. // 获取事件分发器 
  6. auto dispatcher = Director::getInstance()->getEventDispatcher(); 
  7. // 创建单点触摸监听器 
  8. auto listener = EventListenerTouchOneByOne::create(); 
  9. // 让监听器绑定事件处理函数 
  10. listener->onTouchBegan = CC_CALLBACK_2(GameScene::onTouchBegan,this); 
  11. listener->onTouchMoved = CC_CALLBACK_2(GameScene::onTouchMoved,this); 
  12. listener->onTouchEnded = CC_CALLBACK_2(GameScene::onTouchEnded,this); 
  13. // 将事件监听器添加到事件调度器 
  14. dispatcher->addEventListenerWithSceneGraphPriority(listener,this); 

Player 的位置是固定的,我们当然不能随便设,这里只是为了测试。后面的章节中我们会创建一个类来专门管理从 TiledMap 中得到的对象,包括Player、敌人、道具,砖块等。

以上 plist 和 pvr.ccz文件是我们的打包资源,它们是用 Texturepacker 编辑器打包而来。更多详细内容请点此查看

绑定好触摸事件后,最后我们需要实现它们,代码如下:

 
  1. bool GameScene::onTouchBegan(Touch *touch, Event *unused_event) 
  2.     currTouchPoint = touch->getLocation(); 
  3.     if( !currTouchPoint.equals(preTouchPoint)){ 
  4.         player->rotateArrow(currTouchPoint); 
  5.     } 
  6.     preTouchPoint = currTouchPoint;   
  7.     return true;   
  8.    
  9. void GameScene::onTouchMoved(Touch *touch, Event *unused_event) 
  10.     currTouchPoint = touch->getLocation(); 
  11.     if( !currTouchPoint.equals(preTouchPoint)){ 
  12.         player->rotateArrow(currTouchPoint); 
  13.     } 
  14.     preTouchPoint = currTouchPoint; 
  15.    
  16. void GameScene::onTouchEnded(Touch *touch, Event *unused_event) 
  17.     // 射箭,下章内容 

在 onTouchBegan 和 onTouchMoved 函数中,处理方法是一样的。即当当前触摸点与之前的触摸点不一致时,就旋转 Player 的弓箭。 

getLocation 方法将 touch 对象中保存的屏幕坐标转换成我们需要的 Cocos2d 坐标。 分不清屏幕坐标和Cocos2d 坐标的童鞋请参考Cocos2d-x3.0坐标系详解一文。

当触摸结束时,Player 对象需要射出弓箭,这个我们暂时不写。

运行游戏,此时你就可以看到想要的效果了。关于本章资源,请点此下载

责任编辑:倪明
相关推荐

2015-07-17 10:38:21

教程COCOS射箭游戏

2011-12-12 10:40:08

Cocos2d-X游戏开发开发环境

2013-12-03 10:58:50

Cocos2D-X砖块地图

2012-04-17 12:58:44

Cocos2D-X

2013-05-22 15:49:46

2012-02-19 20:10:23

Cocos2d-x fCocos2dWindows Pho

2012-04-17 12:44:38

cocos2d-x

2012-04-17 12:38:46

cocos2d-x

2014-08-13 10:07:02

游戏引擎

2013-04-16 10:02:47

cocos2d-x懒人Android开发

2012-04-17 12:52:01

cocos2d-x

2014-04-21 14:58:27

触控Cocos2d-x触控科技

2014-04-11 11:10:14

Cocos2d-x v手游引擎

2014-07-31 16:57:30

2013-06-03 17:04:20

CocoStudioCocos2D-X添加CocoStudi

2013-05-22 14:38:44

iOS开发Cocos2d-x坐标系统

2012-04-17 10:06:08

cocos2d-x

2012-04-17 10:59:31

cocos2d-x

2012-04-17 12:47:27

cocos2d-x

2012-04-17 13:09:13

Cocos2d-x
点赞
收藏

51CTO技术栈公众号