【Cocos2d-x】自定义环形菜单

本文介绍了如何在Cocos2d-x中创建自定义的环形菜单。通过继承CCMenu并重写itemForTouch方法,实现触摸判定的定制。同时,详细阐述了swirlItemsRadially方法,该方法遍历菜单项,计算它们的位置,并使用贝塞尔曲线动画效果增强用户体验。
摘要由CSDN通过智能技术生成

思路


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);	
}
效果图:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值