之前下载了老G前辈的一个项目实例,发现在项目中用到了虚拟摇杆,所以我就想把这个功能拿出来单独的讲一讲,只为学习交流,并无侵权之意。同时感谢老G前辈的分享。项目所用图片资源下载:http://download.csdn.net/detail/jukai7/5096755
解压我们下载的图片资源,有三个图片,其中飞机图片是被控制的对象,剩下的两个我们组合起来制作摇杆,想必这个摇杆最后的样子大家已经猜到了吧,如下所示,即用圆圈作为控制按钮,滑动圆圈,根据其所在位置来设置飞机应该运行的方向。
首先我们新建一个工程,取名为JoyStick,并把刚才下载的图片资源添加进我们的项目。具体的添加方法就不说了,之前的文章都有过说明。在开始编写代码之前,我们先来分析一下摇杆实现的原理。我们是根据圆圈所在位置来判定飞机的移动方向的,我画了一个简单的示意图,来跟大家说明一下(图画的很粗糙,见谅……)。
从这个简单的示意图我们可以看到,我把这个摇杆图片用一个外切正方形框了起来,用虚线分成了九个大小相等的正方形区域,然后又把中间的圆圈所在正方形区域分成了大小相等的四个等腰直角三角形区域。于是我们得到了大小共12个区域,并进行了编号处理。接下来我们就可以根据这些区域来进行判定了。
当我们滑动圆圈时,触点移动到区域1,3,10,12时,我们分别规定飞机的飞行方向为:左上,右上,左下,右下。当触点移动到区域2或区域5时,我们规定飞机向上飞行;移动到区域6或11时,向下;区域4或7时,向左;区域9或8时,向右。
好了,基本原理已经说清楚了,我们就来说一下怎么具体判定触点是在哪块区域里面的?这里我们首先定义一些变量,我们规定大圆的半径为R,中间小圆的半径为r,九个相同大小的正方形的边长为d,即2r,大圆和小圆共同的中心点为O,触点为location。
首先左上,右上,左下,右下四个方向我们比较容易进行判定,左上方向即location.x<(O.x-r)&&location.y>(O.y+r);右上方向为location.x>(O.x+r)&&location.y>(O.y+r);左下为location.x<(O.x-r)&&location.y<(O.y-r);右下为location.x>(O.x+r)&&location.y>(O.y-r)。
然后是上下左右四个方向。每个方向上我们分为两个区域进行判断,首先是判定触点在区域2,11,4,9分别来决定飞机的方向为上,下,左,右。这四个区域都是正方形,比较好判断,例如判定方向上,只要证明触点在以区域2左下角为原点,边长d的正方形内即可。判定是区域2(上)的代码实现为CCRectMake(O.x-r,O.y+r,d,d).containsPoint(location);区域11(下)的代码实现为CCRectMake(O.x-r,O.y-radius1-d,d,d).containsPoint(location);区域4(左)的代码实现为CCRectMake(O.x-r-d,O.y-r,d,d).containsPoint(location);区域9(右)的代码实现为CCRectMake(O.x+r,O.y-r,d,d).containsPoint(location)。
最后是中间四个三角形区域5,6,7,8的判定。要想判定触点在一个三角形内,我们会模糊的记着以前高中是不是经常出这种证明题?对啦,我们就用三角形面积来做。如果一个点包含在一个三角形内,那么从三角形三个顶点中相邻两个顶点到这个点做连线形成新的三个小三角形,这三个新三角形的面积和一定等于原先的那个大三角形面积。相反,如果这个点在三角形外部则一定大于原先三角形的面积,如下图所示:
那么我们怎么求这四个三角形的面积呢?可以根据海伦公式来求。我们知道三角形三个顶点和触点的坐标,那么根据两点间直线距离公式来求得每个三角形的三条边的大小,假设为a,b,c。那么每个三角形的半周长为L=(a+b+c)/2。
于是我们根据海伦公式求得每个三角形的面积。根据求得的结果进行判断即可得知触点是否在四个三角形区域内。例如判断是否在区域5内,我们指定三角形的三个顶点坐标为p1(O.x,O.y),p2(O.x-r,O.y+r),P3(O.x+r,O.y+r)。触点坐标为p(location.x,location.y)。那么S为p1,p2,p3三点组成三角形的面积,S1为p1,p2,p三点组成三角形面积,S2为p2,p3,p三点,S3为p3,p1,p三点。那么判断是否S=S1+S2+s3即可。向下,左,右三方向同理可证。
讲了这么多,我们是时候用代码来具体的实现了。代码我就不做详细的注释了,因为该了解的上面都讲过了。回到我们一开始创建的项目JoyStick,我们需要的图片已经导入到工程中了。我把修改后的HelloWorld.h文件和HelloWorld.cpp文件内容粘贴出来,大家看一下,具体实现的过程我就不讲了,必要的注释也都加上了,有不懂得地方再讨论交流。
HelloWorld.h文件内容如下:
#ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos2d.h" #include "SimpleAudioEngine.h" class HelloWorld : public cocos2d::CCLayer { public: // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); static cocos2d::CCScene* scene(); void menuCloseCallback(CCObject* pSender); virtual void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent); virtual void ccTouchesMoved(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent); virtual void ccTouchesEnded(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent); //求三角形面积的函数 float heronsformula(float x1,float y1,float x2,float y2,float x3,float y3); //判断三个新三角形面积和是否等于原先三角形面积的函数 bool triangleContainPoint(float x1,float y1,float x2,float y2,float x3,float y3,float px,float py); CREATE_FUNC(HelloWorld); private: void flying(float dt); cocos2d::CCSprite *joystick; //中心点O cocos2d::CCPoint O; //大圆半径 float R; cocos2d::CCSprite *plane; //飞机飞行的速度分量值 float speedX; float speedY; //是否飞行的标志 bool isFlying; }; #endif // __HELLOWORLD_SCENE_H__
#include "HelloWorldScene.h" using namespace cocos2d; CCScene* HelloWorld::scene() { CCScene * scene = NULL; do { // 'scene' is an autorelease object scene = CCScene::create(); CC_BREAK_IF(! scene); // 'layer' is an autorelease object HelloWorld *layer = HelloWorld::create(); CC_BREAK_IF(! layer); // add layer as a child to scene scene->addChild(layer); } while (0); // return the scene return scene; } // on "init" you need to initialize your instance bool HelloWorld::init() { bool bRet = false; do { ////////////////////////////////////////////////////////////////////////// // super init first ////////////////////////////////////////////////////////////////////////// CC_BREAK_IF(! CCLayer::init()); ////////////////////////////////////////////////////////////////////////// // add your codes below... ////////////////////////////////////////////////////////////////////////// // 1. Add a menu item with "X" image, which is clicked to quit the program. // Create a "close" menu item with close icon, it's an auto release object. CCMenuItemImage *pCloseItem = CCMenuItemImage::create( "CloseNormal.png", "CloseSelected.png", this, menu_selector(HelloWorld::menuCloseCallback)); CC_BREAK_IF(! pCloseItem); // Place the menu item bottom-right conner. pCloseItem->setPosition(ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20)); // Create a menu with the "close" menu item, it's an auto release object. CCMenu* pMenu = CCMenu::create(pCloseItem, NULL); pMenu->setPosition(CCPointZero); CC_BREAK_IF(! pMenu); // Add the menu to HelloWorld layer as a child layer. this->addChild(pMenu, 1); //取得屏幕大小 CCSize size = CCDirector::sharedDirector()->getWinSize(); //创建飞机 plane = CCSprite::create("plane.png"); CC_BREAK_IF(! plane); //将飞机添加进布景并设置位置 plane->setPosition(ccp(size.width/2, size.height/2)); this->addChild(plane, 0); //创建摇杆下面部分 CCSprite *joystick1=CCSprite::create("joystick1.png"); //设置透明度,锚点,位置 joystick1->setOpacity(191); joystick1->setAnchorPoint(ccp(0,0)); joystick1->setPosition(ccp(0,0)); //大圆半径 R=joystick1->getContentSize().width/2; //中心点 O=ccp(R,R); //添加进布景 this->addChild(joystick1,1); //创建摇杆上面圆圈部分 joystick=CCSprite::create("joystick2.png"); //设置位置为摇杆中心点并添加进布景 joystick->setPosition(ccp(O.x,O.y)); this->addChild(joystick,2); //设置可触摸 this->setTouchEnabled(true); //每帧要执行的函数 this->schedule(schedule_selector(HelloWorld::flying)); //初始化需要的变量 isFlying=false; speedX=speedY=0; bRet = true; } while (0); return bRet; } void HelloWorld::menuCloseCallback(CCObject* pSender) { // "close" menu item clicked CCDirector::sharedDirector()->end(); } //飞机飞行函数 void HelloWorld::flying(float dt) { if (isFlying&&(speedX!=0||speedY!=0)){ //飞机飞行 CCPoint position=ccp(plane->getPosition().x+speedX,plane->getPosition().y+speedY); CCSize size=CCDirector::sharedDirector()->getWinSize(); CCRect rect=CCRectMake(0,0,size.width,size.height); //判断触摸点是否在屏幕内 if(rect.containsPoint(position)){ plane->setPosition(position); } } } //触摸开始函数,判断触摸开始点是否在圆圈内,若是,则设置isFlying标志为true void HelloWorld::ccTouchesBegan( CCSet *pTouches, CCEvent *pEvent ) { CCTouch *touch = (CCTouch*)pTouches->anyObject(); CCPoint location = touch->getLocation(); CCRect rect=joystick->boundingBox(); if (rect.containsPoint(location)) { isFlying=true; } } //触摸滑动函数 void HelloWorld::ccTouchesMoved( CCSet *pTouches, CCEvent *pEvent ) { CCTouch *touch = (CCTouch*)pTouches->anyObject(); CCPoint location = touch->getLocation(); //判断触摸滑动点是否在摇杆范围内 bool inRange=pow(O.x-location.x,2)+pow(O.y-location.y,2)<pow(R,2); if(isFlying&&inRange){ CCPoint position=plane->getPosition(); joystick->setPosition(location); float r=R*2/6; float d=R*2/3; //上,区域2或5 if(triangleContainPoint(O.x,O.y,O.x-r,O.y+r,O.x+r,O.y+r,location.x,location.y) ||CCRectMake(O.x-r,O.y+r,d,d).containsPoint(location)){ speedX=0; speedY=1; } //下,区域6或11 else if(triangleContainPoint(O.x,O.y,O.x-r,O.y-r,O.x+r,O.y-r,location.x,location.y) ||CCRectMake(O.x-r,O.y-r-d,d,d).containsPoint(location)){ speedX=0; speedY=-1; } //左,区域4或7 else if(triangleContainPoint(O.x,O.y,O.x-r,O.y+r,O.x-r,O.y-r,location.x,location.y) ||CCRectMake(O.x-r-d,O.y-r,d,d).containsPoint(location)){ speedX=-1; speedY=0; } //右,区域9或8 else if(triangleContainPoint(O.x,O.y,O.x+r,O.y+r,O.x+r,O.y-r,location.x,location.y) ||CCRectMake(O.x+r,O.y-r,d,d).containsPoint(location)){ speedX=1; speedY=0; } //右上,区域3 else if(location.x-(O.x+r)>0&&location.y-(O.y+r)>0){ speedX=0.7f; speedY=0.7f; } //左上,区域1 else if(location.x-(O.x-r)<0&&location.y-(O.y+r)>0){ speedX=-0.7f; speedY=0.7f; } //左下,区域10 else if(location.x-(O.x-r)<0&&location.y-(O.y-r)<0){ speedX=-0.7f; speedY=-0.7f; } //右下,区域12 else if(location.x-(O.x+r)>0&&location.y-(O.y-r)<0){ speedX=0.7f; speedY=-0.7f; } } } //触摸结束函数 void HelloWorld::ccTouchesEnded( CCSet *pTouches, CCEvent *pEvent ) { isFlying=false; joystick->setPosition(O); speedX=speedY=0; } //求三角形面积的函数的实现 float HelloWorld::heronsformula(float x1,float y1,float x2,float y2,float x3,float y3) { //求边长a float a=sqrt(pow(x1-x2,2)+pow(y1-y2,2)); //求边长b float b=sqrt(pow(x2-x3,2)+pow(y2-y3,2)); //求边长c float c=sqrt(pow(x3-x1,2)+pow(y3-y1,2)); //求半周长s float s=(a+b+c)/2; //根据海伦公式返回三角形面积 return sqrt(s*(s-a)*(s-b)*(s-c)); } //判断三个新三角形面积和是否等于原先三角形面积的函数的实现 bool HelloWorld::triangleContainPoint(float x1,float y1,float x2,float y2,float x3,float y3,float px,float py) { //求S1的面积 float s1=heronsformula(x1,y1,x2,y2,px,py); //求S2的面积 float s2=heronsformula(x2,y2,x3,y3,px,py); //求S3的面积 float s3=heronsformula(x3,y3,x1,y1,px,py); //求S的面积 float s=heronsformula(x1,y1,x2,y2,x3,y3); //返回S是否等于S1,S2,S3的和 return abs(s-(s1+s2+s3))<0.001f; }
此外,摇杆还有许多种实现方法和各种各样的外观,我们都要更多的去了解,做出更适合人体学的游戏方式。
感谢前辈们做出的奉献!希望大家都能够开心快乐!