***************************************转载请注明出处:http://blog.csdn.net/lttree*******************************************
前文:
之前做 >>>CatchingJoy <<< 的时候,用到了摇杆,
网上有四方向的,
我用到的是八个方向,看了下它的思路,改动了一些,就成了8方向拉~
嘿嘿~ let it go~
@H_404_52@****************************************************************转载请注明出处:********************************************************************
正文:
1.先准备好图片:
需要移动的小方块:
摇杆背景 和 摇杆中心:
********************************************************************
2.原理
首先,放摇杆的需要是一个层,放着摇杆背景 和 中心(显然中心要在背景上层),
然后我们再把这个层放在场景中。
这个层需要接受触摸(单点),并屏蔽下层触摸(一般来说)。
然后,摇杆 如何瓜分?
(Windows画图绘制,很渣,凑合看吧= =。)
每个方向 都占45° 45*8 = 360
其次,TouchBegan,接受到触摸点,判断是否在 摇杆中心 那个范围,用contiansPoint实现。
然后,TouchMoved,看往哪个方向移动,移动的距离,如果拉扯的距离大于半径,摇杆中心是不能出摇杆背景的,如果拉扯距离小于等于半径,中心该在哪就在哪。
最后,TouchEnd,要让摇杆中心从当前位置移动回初始位置。
所以,比较麻烦的地方仅仅在于 TouchMoved部分,要判断距离,判断摇杆中心该放在哪里等...
****************************************************************转载请注明出处:********************************************************************
3.实现
> 准备工作
(1) 先做个 enum(枚举),把8个方向用英语比1,2,3...8好很多:
//用于标识摇杆方向 typedef enum{ rocker_stay = 0,rocker_right,rocker_up,rocker_left,rocker_down,rocker_leftUp,rocker_rightUp,rocker_leftDown,rocker_rightDown,}rockerDirecton;
可以看到,除了8个方向,还有一个stay(原地不动)
(2) 这里,设置摇杆位置,可以自定义也可以固定,我用的自定义(好处不多说),
所以原来那种 create,init就不用了,用其他的替代:
// JoyRocker.h static JoyRocker* create(Vec2 pos); bool initRocker(Vec2 pos); // JoyRocker.cpp JoyRocker* JoyRocker::create(Vec2 pos) { JoyRocker* layer = JoyRocker::create(); if ( layer ) { layer->initRocker(pos); return layer; } CC_SAFE_DELETE(layer); return NULL; } bool JoyRocker::initRocker(Vec2 pos) { // 摇杆背景 图片 Sprite* spRockerBG = Sprite::create("spi_joystickBG.png"); spRockerBG->setPosition(pos); spRockerBG->setTag(1); this->addChild(spRockerBG,0); // 摇杆中心 图片 Sprite* spRockerCenter = Sprite::create("spi_joystickCenter.png"); spRockerCenter->setPosition(pos); spRockerCenter->setTag(2); this->addChild(spRockerCenter,1); // 设置 摇杆中心 位置 rockerCenterPos = pos; // 获取 摇杆背景 半径 rockerBGR = spRockerBG->getContentSize().width*0.5; // 设置 摇杆 初始方向 rocketDirection = 0; // 事件监听部分 listener = EventListenerTouchOneByOne::create(); // 吞掉这个触摸 listener->setSwallowTouches(true); listener->onTouchBegan = CC_CALLBACK_2(JoyRocker::TouchBegan,this); listener->onTouchMoved = CC_CALLBACK_2(JoyRocker::TouchMoved,this); listener->onTouchEnded = CC_CALLBACK_2(JoyRocker::TouchEnded,this); // 注册事件监听机制 eventDispatcher = Director::getInstance()->getEventDispatcher(); return true; }
> 然后就是 Touch 三兄弟,这里我用的传统的方式,没有用lamda表达式= =。
TouchBegan:
bool JoyRocker::TouchBegan(Touch* touch,Event* event) { Sprite* sp = (Sprite*)this->getChildByTag(2); //得到触屏点坐标 Vec2 point = touch->getLocation(); //判断是否点击到sp这个精灵:boundingBox()精灵大小之内的所有坐标 if(sp->boundingBox().containsPoint(point)) { // 可以移动了 isCanMove = true; } return true; }
TouchMoved:
void JoyRocker::TouchMoved(Touch* touch,Event* event) { // 如果不能移动,直接返回 if(!isCanMove) { return; } Sprite* sp = (Sprite*)getChildByTag(2); Vec2 point = touch->getLocation(); //得到摇杆与触屏点所形成的角度 float angle = getRad(rockerCenterPos,point); //判断两个圆的圆心距是否大于摇杆背景的半径 if (sqrt(pow((rockerCenterPos.x - point.x),2) + pow((rockerCenterPos.y - point.y),2)) >= rockerBGR) { //保证内部小圆运动的长度限制 sp->setPosition(ccpAdd(getAnglePosition(rockerBGR,angle),Vec2(rockerCenterPos.x,rockerCenterPos.y))); } else { //当没有超过,让摇杆跟随用户触屏点移动即可 sp->setPosition(point); } //判断方向 // 右方 if( angle>=-PI/8 && angle<PI/8 ) { rocketDirection = rocker_right; isLeft = false; } // 右上方 else if( angle>=PI/8 && angle<3*PI/8 ) { rocketDirection = rocker_rightUp; isLeft = false; } // 上方 else if( angle>=3*PI/8 && angle<5*PI/8 ) { rocketDirection = rocker_up; } // 左上方 else if( angle>=5*PI/8 && angle<7*PI/8 ) { rocketDirection = rocker_leftUp; isLeft = true; } // 左方 else if( (angle>=7*PI/8&&angle<=PI) || (angle>=-PI&&angle<-7*PI/8) ) { rocketDirection = rocker_left; isLeft = true; } // 左下方 else if( angle>=-7*PI/8 && angle<-5*PI/8 ) { rocketDirection = rocker_leftDown; isLeft = true; } // 下方 else if( angle>=-5*PI/8 && angle<-3*PI/8 ) { rocketDirection = rocker_down; } // 右下方 else if( angle>=-3*PI/8 && angle<-PI/8 ) { rocketDirection = rocker_rightDown; isLeft = false; } }
要解释的有三点:
① 两个工具函数
· 用户 触摸 一个点后,这个点与 摇杆中心 相连,这条线段与水平方向 所构成的角度,
就是——函数 getRad(返回的是 弧度值)
float JoyRocker::getRad(Vec2 pos1,Vec2 pos2) { float px1 = pos1.x; float py1 = pos1.y; float px2 = pos2.x; float py2 = pos2.y; //得到两点x的距离 float x = px2 - px1; //得到两点y的距离 float y = py1 - py2; //算出斜边长度 float xie = sqrt(pow(x,2) + pow(y,2)); //得到这个角度的余弦值(通过三角函数中的点里:角度余弦值=斜边/斜边) float cosAngle = x / xie; //通过反余弦定理获取到期角度的弧度 float rad = acos(cosAngle); //注意:当触屏的位置Y坐标<摇杆的Y坐标,我们要去反值-0~-180 if (py2 < py1) { rad = -rad; } return rad; }
用简单的高数知识推一推,在纸上画一画,就出来了,这里不解释了就(画图太麻烦啊)...o(╯□╰)o...
如果实在不懂,回复我,我再更新上去
· 第二个工具函数
根据 与水平方向形成的角度的弧度值,返回对应点的位置,就是第一个工具函数的 反
// 根据角度,返回点坐标 Vec2 JoyRocker::getAnglePosition(float r,float angle) { return Vec2(r*cos(angle),r*sin(angle)); }
三角形的那一串,应该不用解释了= =。。。
② 关于 判断圆心距那块的 函数 cppAdd
其实,并不神秘的东西,跳转到定义就发现:
ccpAdd(const Vec2& v1,const Vec2& v2) { return v1 + v2; }
就是这么简单,当然那一系列还有:
加减乘除,取反,取中点等等等等,
具体的看 头文件--> CCDeprecated.h
③ 关于 isLeft
每次判断完方向,都会对 isLeft赋值,
这个的作用是 标记 正面朝向,
假设,我们做的图是人物走动的,肯定只是做一个方向的(当然,也可以做两个方向,但。。)
然后,什么时候用向右走,什么时候用向左走,(如果向没做图的那个方向,我们可以用 setFlipedX(true),让Sprite 180° 大翻转)
这时候,isLeft 就用到了~
> TouchEnd
void JoyRocker::TouchEnded(Touch* touch,Event* event) { if(!isCanMove) { return; } // 获取 摇杆背景 与 摇杆中心 Sprite* rocker = (Sprite*)getChildByTag(2); Sprite* rockerBG = (Sprite*)getChildByTag(1); // 让 摇杆中心 停止之前所有动作,然后开始 执行归位 rocker->stopAllActions(); rocker->runAction(MoveTo::create(0.08,rockerBG->getPosition())); // 设置 方向为 stay,并且 在下次触摸开始前 不可移动 rocketDirection=rocker_stay; isCanMove = false; }
摇杆已经OK啦
>现在 把小方块加进来,动起来
// 场景的 init 函数 // 获取 屏幕大小 visibleSize = Director::getInstance()->getVisibleSize(); // 添加绿色小方块 bg = Sprite::create("green.png"); bg->setPosition(visibleSize.width/2,visibleSize.height/2); this->addChild(bg); // 添加 摇杆 jr = JoyRocker::create(Vec2(visibleSize.width-100,100)); this->addChild(jr); // 时时更新函数 this->scheduleUpdate();
这里,我们调用 scheduleUpdate,就是让程序 每一帧 都自动调用 void update(float ft) 函数,
然后,我们重写下 update 函数:
void DemoScene::update(float ft) { //判断是否按下摇杆及其类型 switch( jr->getDirection() ) { case 1: bg->setPosition(Vec2(bg->getPosition().x+2,bg->getPosition().y)); //向右走 break; case 2: bg->setPosition(Vec2(bg->getPosition().x,bg->getPosition().y+2)); //向上走 break; case 3: bg->setPosition(Vec2(bg->getPosition().x-2,bg->getPosition().y)); //向左走 break; case 4: bg->setPosition(Vec2(bg->getPosition().x,bg->getPosition().y-2)); //向下走 break; case 5: bg->setPosition(Vec2(bg->getPosition().x-1,bg->getPosition().y+1)); //向左上走 break; case 6: bg->setPosition(Vec2(bg->getPosition().x+1,bg->getPosition().y+1)); //向右上走 break; case 7: bg->setPosition(Vec2(bg->getPosition().x-1,bg->getPosition().y-1)); //向左下走 break; case 8: bg->setPosition(Vec2(bg->getPosition().x+1,bg->getPosition().y-1)); //向右下走 break; default: break; } }
其实就是每帧都获取 方向盘的方向,
然后,根据方向,执行对应动作,
这里,我设置的,如果单纯往某个方向走,移动速度为 每帧移动2,
如果 向复合方向走(左上、左下、右上、右下),每个相应方向只是 每帧移动1。
Ok,方向盘就到这里啦,
可以再优化一下,比如 不让 小绿块 飞出界面(就在移动前加个判断就行)...
上面代码不全,上传一个JoyRocker类 和 小绿块的场景类,
里面可能有些小改动,但大体都是一样的,
百度云: >点这里 <
*******************************************