思路
1.定义一个类继承CCMenu,并重写itemForTouch方法,该方法在CCMenu被触摸的时候调用,用于获取触摸的菜单条目。在itemForTouch方法中自定义触摸判定。
2.定义一个方法(这里是swirlItemsRadially),在该方法中调用this->getChildren()迭代菜单中每个条目,计算它们的位置,调用item的runAction方法增加动画效果(这里使用贝塞尔曲线实现动画效果【点击查看关于贝塞尔曲线】)。
代码
#ifndef __RADIALMENU_H__
#define __RADIALMENU_H__
#include "cocos2d.h"
USING_NS_CC;
/** 环形菜单控件,可以使菜单条目均匀地显示成一个圆形 **/
/**
工程git地址:https://coding.net/u/linchaolong/p/Cocos2d-x_RadialMenu/git
参考工程地址:https://github.com/sartak/CCRadialMenu
**/
class RadialMenu:public CCMenu
{
public:
static RadialMenu* create(CCArray* items, float radius);
static RadialMenu* create(CCArray* items, float radius, float swirlOutDuration);
/**
* items : CCMenuItem,菜单条目集合
* radius:环形菜单半径
* swirlOutDuration:动画时长
* adjustAnchors:是否自动调整锚点
**/
static RadialMenu* create(CCArray* items, float radius, float swirlOutDuration, bool adjustAnchors);
//菜单条目动画结束回调
void animEndedCallback(CCNode*, void*);
/** 直接排列菜单条目成环形 **/
void alignItemsRadially();
/** 排列菜单条目成环形(有动画效果) **/
void swirlItemsRadially(float duration);
/**
* duration:动画时长
* adjustAnchors:是否自动调整锚点
**/
void swirlItemsRadially(float duration, bool adjustAnchors);
protected:
//该方法在CCMenu的touchBegan和touchMove时候调用,用于查找触摸的菜单条目
CCMenuItem* itemForTouch(CCTouch * touch);
private:
RadialMenu* init(CCArray* items, float radius, float swirlOutDuration, bool adjustAnchors);
float radius_;
};
#endif
#include "RadialMenu.h"
RadialMenu* RadialMenu::create(CCArray* items, float radius){
RadialMenu *pRet = new RadialMenu();
if (pRet && pRet->init(items,radius,0,false))
{
pRet->autorelease();
return pRet;
}
else
{
delete pRet;
pRet = NULL;
return NULL;
}
}
RadialMenu* RadialMenu::create(CCArray* items, float radius, float swirlOutDuration){
RadialMenu *pRet = new RadialMenu();
if (pRet && pRet->init(items,radius,swirlOutDuration,false))
{
pRet->autorelease();
return pRet;
}
else
{
delete pRet;
pRet = NULL;
return NULL;
}
}
RadialMenu* RadialMenu::create(CCArray* items, float radius, float swirlOutDuration, bool adjustAnchors){
RadialMenu *pRet = new RadialMenu();
if (pRet && pRet->init(items,radius,swirlOutDuration,adjustAnchors))
{
pRet->autorelease();
return pRet;
}
else
{
delete pRet;
pRet = NULL;
return NULL;
}
}
RadialMenu* RadialMenu::init(CCArray* items, float radius, float swirlOutDuration, bool adjustAnchors){
if (CCMenu::initWithArray(items))
{
radius_ = radius;
if (swirlOutDuration)
{
this->swirlItemsRadially(swirlOutDuration,adjustAnchors);
}
}
return this;
}
void RadialMenu::alignItemsRadially(){
CCObject* obj = NULL;
CCMenuItem* item = NULL;
int count = this->getChildrenCount();
double sliceAngle = (2 * 3.14) / count;
int i = 0;
CCARRAY_FOREACH(this->getChildren(),obj){
double theta = sliceAngle * i;
double x = radius_ * sin(theta);
double y = radius_ * cos(theta);
item = (CCMenuItem*)obj;
item->setPosition(ccp(x,y));
++i;
}
obj = NULL;
item = NULL;
}
void RadialMenu::swirlItemsRadially(float duration){
this->swirlItemsRadially(duration,false);
}
void RadialMenu::swirlItemsRadially(float duration, bool adjustAnchors){
CCObject* obj = NULL;
CCMenuItem* item = NULL;
int count = this->getChildrenCount();
double sliceAngle = (2 * 3.14) / count;
double base_length = sqrt(2.0) * radius_;
int i = 0;
CCARRAY_FOREACH(this->getChildren(), obj){
double theta = sliceAngle * i;
double x = radius_ * sin(theta);
double y = radius_ * cos(theta);
item = (CCMenuItem*)obj;
if (adjustAnchors) {
if (theta <= 0.01) {
}
else if (theta <= M_PI) {
item->setAnchorPoint(ccp(0.2, 0.5));
}
else if (theta > M_PI) {
item->setAnchorPoint(ccp(0.8, 0.5));
}
}
/* swirl */
ccBezierConfig bezier;
bezier.controlPoint_1 = ccp(radius_ * sin(theta - 3.14/4), radius_ * cos(theta - 3.14/4));
bezier.controlPoint_2 = ccp(base_length * sin(theta - 3.14/8), base_length * cos(theta - 3.14/8));
bezier.endPosition = ccp(x, y);
item->runAction(CCBezierBy::create(duration,bezier));
/* rotate */
item->setRotation(90);
item->runAction(CCRotateBy::create(duration,item->getRotation()-90));
/* disable */
item->setEnabled(false);
item->runAction(CCSequence::create(
CCDelayTime::create(duration),
CCCallFuncND::create(this,callfuncND_selector(RadialMenu::animEndedCallback),item),
NULL));
/*
* brighten to white.
* I use TintTo gray in 0s to avoid setColor: not being a method in CCMenuItem
*/
item->runAction(CCSequence::create(
CCTintTo::create(0,192,192,192),
CCTintTo::create(duration,255,255,255),
NULL));
++i;
}
obj = NULL;
item = NULL;
}
void RadialMenu::animEndedCallback(CCNode*, void* item){
CCMenuItem* pItem = (CCMenuItem*)item;
pItem->setEnabled(true);
}
CCMenuItem* RadialMenu::itemForTouch(CCTouch * touch){
CCPoint touchLocation = touch->getLocationInView();
touchLocation = CCDirector::sharedDirector()->convertToGL(touchLocation);
CCPoint location = this->convertToNodeSpace(touchLocation);
float distance = sqrt( location.x*location.x + location.y*location.y );
if (distance >= radius_ * 0.5 && distance <= radius_ * 1.5) {
int count = this->getChildrenCount();
double factor = (2 * 3.14) / count;
double theta = atan2(location.x, location.y);
/* I use [0, 2pi) for layout, but atan2 returns [-pi, pi) */
if (theta < 0) {
theta += 2 * M_PI;
}
/* find closest item */
int i = theta/factor + 0.5;
/* don't let them tap halfway across the circle if there are few items */
double angle_distance = fabs(factor * i - theta);
if (angle_distance < M_PI/5) {
/* tapping at 11 o'clock is like tapping at 1 o'clock */
if (i == count) { i = 0; }
CCMenuItem* item = (CCMenuItem*)(this->getChildren()->objectAtIndex(i));
if (item->isVisible() && item->isEnabled())
{
return item;
}
}
}
/* delegate for items that move, etc */
return CCMenu::itemForTouch(touch);
}
效果图: