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使摇杆变为不可用。这两个方法均在上述范例的代码中有实现。