Cocos2d-x游戏开发实战精解
上QQ阅读APP看书,第一时间看更新

4.5 实战:在Cocos2d-x中实现游戏摇杆

由于目前手机大多数缺少实体按键,在操作时天生就带有一定的缺陷,而外接设备又缺乏一定的便捷性。一个折中的办法就是在屏幕上使用虚拟按键,这其中一个典型的方案就是实现虚拟的摇杆来模拟手柄中的方向键(如图4-11所示),而这一方案就是通过Cocos2d-x的触摸机制来实现的。

图4-11 在游戏中使用虚拟摇杆进行操作

项目ChapterFour05是一个自定义创建虚拟摇杆的例子,下面将介绍其具体实现过程及思路。

(1)在class目录下创建文件HRocker.h和HRocker.cpp,并在HRocker.h中定义类HRocker继承自Layer,对该类的具体定义如范例4-6所示。

【范例4-6】对类HRocker的定义。

01 //枚举型:用于标识摇杆与摇杆的背景
02      typedef enum{
03              tag_rocker,
04              tag_rockerBG,
05      }tagForHRocker;
06      class HRocker : public Layer
07      {
08      public:
09           EventListenerTouchOneByOne* listener;
10              //创建摇杆(摇杆图片、摇杆背景图片、起始坐标) 
11              static HRocker* createHRocker(const char* rockerImageName, 
12                                                                      const char* rockerBGImageName, Point 
                                                                        position);
13              //启动摇杆  
14              void startRocker(bool _isStopOther);
15              //停止摇杆  
16              void stopRocker();
17      private:
18              void rockerInit(const char* rockerImageName, const char* 
                rockerBGImageName, Point position);
19              //得到半径为r的圆周运动上一个角度对应的x,y坐标  
20              Point getAnglePosition(float r, float angle);
21              //是否可操作摇杆  
22              bool isCanMove;
23              //得到摇杆与用户触屏点的角度  
24              float getRad(Point pos1, Point pos2);
25              //摇杆背景的坐标  
26              Point rockerBGPosition;
27              //摇杆背景的半径  
28              float rockerBGR;
29              //触屏事件  
30              virtual bool onTouchBegan(Touch* touch, Event* event);
31              virtual void onTouchMoved(Touch* touch, Event* event);
32              virtual void onTouchEnded(Touch* touch, Event* event);
33              CREATE_FUNC(HRocker);
34      };

(2)范例4-7中是该类中各方法的具体实现:

【范例4-7】类HRocker的具体实现。

01 HRocker* HRocker::createHRocker(const char *rockerImageName,
02                                                      const char *rockerBGImageName, Point position)
03      {
04              HRocker* layer = HRocker::create();
05              if (layer)
06              {
07                      layer->rockerInit(rockerImageName, rockerBGImageName, 
                        position);
08                      return layer;
09              }
10              CC_SAFE_DELETE(layer);
11              return NULL;
12      }
13      void HRocker::rockerInit(const char *rockerImageName, const char 
        *rockerBGImageName, Point position)
14      {
15              //添加摇杆背景图  
16              Sprite* spRockerBG = Sprite::create(rockerBGImageName);
17              spRockerBG->setVisible(false);
18              spRockerBG->setPosition(position);
19              addChild(spRockerBG, 0, tag_rockerBG);
20              //添加摇杆上方那个可移动的图  
21              Sprite* spRocker = Sprite::create(rockerImageName);
22              spRocker->setVisible(false);
23              spRocker->setPosition(position);
24              addChild(spRocker, 1, tag_rocker);
25              spRocker->setOpacity(180);
26              //摇杆背景图坐标  
27              rockerBGPosition = position;
28              //摇杆背景图半径  
29              rockerBGR = spRockerBG->getContentSize().width*0.5;
30              listener = EventListenerTouchOneByOne::create();
31              //绑定监听事件
32              listener->onTouchBegan = CC_CALLBACK_2(HRocker::onTouchBegan, 
                this);
33              listener->onTouchMoved = CC_CALLBACK_2(HRocker::onTouchMoved, 
                this);
34              listener->onTouchEnded = CC_CALLBACK_2(HRocker::onTouchEnded, 
                this);
35              listener->setSwallowTouches(true);
36      }
37      //启动摇杆  
38      void HRocker::startRocker(bool _isStopOther)
39      {
40              //精灵设置可见,设置监听  
41              Sprite* rocker = (Sprite*)getChildByTag(tag_rocker);
42              rocker->setVisible(true);
43              Sprite* rockerBG = (Sprite*)getChildByTag(tag_rockerBG);
44              rockerBG->setVisible(true);
45              Director::getInstance()->getEventDispatcher()->
                addEventListenerWithSceneGraphPriority(listener, this);
46      }
47      //停止摇杆  
48      void HRocker::stopRocker()
49      {
50              //精灵设置不可见,取消监听  
51              Sprite* rocker = (Sprite*)getChildByTag(tag_rocker);
52              rocker->setVisible(false);
53              Sprite* rockerBG = (Sprite*)getChildByTag(tag_rockerBG);
54              rockerBG->setVisible(false);
55              Director::getInstance()->getEventDispatcher()->
                removeEventListener(listener);
56      }
57      //得到两坐标的角度值  
58      float HRocker::getRad(Point pos1, Point pos2)
59      {
60              //得到两点的坐标x,y坐标值  
61              float px1 = pos1.x;
62              float py1 = pos1.y;
63              float px2 = pos2.x;
64              float py2 = pos2.y;
65              //求出两边长度  
66              float x = px2 - px1;
67              float y = py1 - py2;
68              //利用勾股定理求两点间直线距离  
69              float xie = sqrt(pow(x, 2) + pow(y, 2));
70              float cos = x / xie;
71              //反余弦定理,知道两边长求角度:cos = 邻边/斜边  
72              float rad = acos(cos);
73              //当触屏Y坐标 < 摇杆的Y坐标时,取反值  
74              if (py1 > py2)
75              {
76                      rad = -rad;
77              }
78              return rad;
79      }
80      //得到与角度对应的半径为r的圆上一坐标点  
81      Point HRocker::getAnglePosition(float r, float angle)
82      {
83              return ccp(r*cos(angle), r*sin(angle));
84      }
85      bool HRocker::onTouchBegan(Touch *touch, Event *event)
86      {
87              Sprite* sp = (Sprite*)getChildByTag(tag_rocker);
88              //得到触屏点坐标  
89              Point point = touch->getLocation();
90              //判断是否单击到sp这个精灵:boundingBox()精灵大小之内的所有坐标  
91              if (sp->boundingBox().containsPoint(point))
92              {
93                      isCanMove = true;
94              }
95              return true;
96      }
97      void HRocker::onTouchMoved(Touch *touch, Event *event)
98      {
99              if (!isCanMove)
100             {
101                     return;
102             }
103             Sprite* sp = (Sprite*)getChildByTag(tag_rocker);
104             Point point = touch->getLocation();
105             //判断两个圆心的距离是否大于摇杆背景的半径  
106             if (sqrt(pow(point.x - rockerBGPosition.x, 2) + pow(point.y - 
                rockerBGPosition.y, 2)) >= rockerBGR)
107             {
108                     //得到触点与摇杆背景圆心形成的角度  
109                     float angle = getRad(rockerBGPosition, point);
110                     //确保小圆运动范围在背景圆内  
111                     sp->setPosition(ccpAdd(getAnglePosition(rockerBGR, angle),
112                                                                      ccp(rockerBGPosition.x, 
                                                                         rockerBGPosition.y)));
113             }
114             else {
115                     //触点在背景圆内则跟随触点运动  
116                     sp->setPosition(point);
117             }
118     }
119     void HRocker::onTouchEnded(Touch *touch, Event *event)
120     {
121             if (!isCanMove)
122             {
123                     return;
124             }
125             Sprite* rocker = (Sprite*)getChildByTag(tag_rocker);
126             Sprite* rockerBG = (Sprite*)getChildByTag(tag_rockerBG);
127             rocker->stopAllActions();
128             rocker->runAction(MoveTo::create(0.08, rockerBG-> 
                getPosition()));
129             isCanMove = false;
130     }

(3)在HelloWorld类的init方法中加入如下代码,即可将虚拟摇杆引入到场景中:

HRocker* rocker = HRocker::createHRocker("rocker.png", "rockerBG.png", ccp(100, 100));
addChild(rocker);
rocker->startRocker(true);

其中rocker.png和rockerBG.png分别是虚拟摇杆本身和摇杆托盘所使用的背景文件,为了得到更好的展示效果,笔者还在场景中加入了一张背景图片,最终效果如图4-12所示。当用户滑动摇杆时,摇杆将会随着用户的操作进行响应,如图4-13所示。

图4-12 虚拟摇杆在场景中被加载

图4-13 拖动摇杆将会进行相应的响应

下面来介绍该摇杆实现所使用的方法,首先在createHRocker和rockerInit两个方法中实现空间所在节点的创建和初始化,其中初始化的部分主要在范例4-7的16~35行之间。主要分为两部分:将摇杆与配套的托盘图像加入到场景中(16~29行);为摇杆绑定响应的触摸事件(30~35行)。

实际上只要在相应的触摸事件中让摇杆随着用户的指尖拖动,就可以实现所需要的效果了,如范例中第97~118行中对onTouchMoved方法的实现,就是当用户触摸屏幕进行移动时,摇杆跟随手指进行响应。但是要区分触摸点的范围,当触点在摇杆的活动范围之内时,摇杆的坐标与手指触点是相同的,而当触点超出了摇杆运动范围时,摇杆则随着手指的移动在摇杆活动范围的边界上运动。在实现这一功能时就需要获取当前触点与摇杆原点的角度,而这是在getRad方法中实现的。

然后还要确定什么时候摇杆是可以使用的,什么时候摇杆处于“锁死”状态。这就要通过startRocker和stopRocker两个方法来实现,在触摸操作开始并且触摸点在摇杆上时进行startRocker操作,而在结束触摸操作时通过stopRocker使摇杆变为不可用。这两个方法均在上述范例的代码中有实现。