基于cocos2dx的伪立体菜单
RotateMenu说明
最终效果图
下面这个引用自乱斗西游
设计说明
1.菜单项(MenuItem)平均分布在椭圆(类似)上
2.椭圆长轴为2/3width,短轴为2/8 height
3.最前面的菜单项Scale=1,opacity=255,最后面Scale=0.5,opacity=129.其它位置根据三角函数变换(updatePosition中实现)
4.默认最前面菜单被选中(selected)
5.单位角度(unitAngle)是2*PI/菜单项的数量
6.滑动一个width,菜单旋转两个单位角度
7.Touch结束会自动调整位置,保证最前面位置有菜单项
8.滑动超过1/3单位角度会向前舍入
9.移动小于1/6单位角度会判定点击菜单
10.默认菜单大小不是全屏,而是屏幕的2/3,通过Node::setContentSize()设置
使用
使用这个菜单只要知道两个函数
1.构造函数
RotateMenu::create()(由CREATE_FUNC创建)
2.添加MenuItem
void addMenuItem(cocos2d::MenuItem *item);
其它函数可以看代码
相关参数的函数设置还未添加
代码
声明
- #ifndef __ROTA__TE_MENU_H__
- #define __ROTA__TE_MENU_H__
- #include "cocos2d.h"
- /*
- *模仿乱斗西游主界面的旋转菜单
- */
- class RotateMenu :public cocos2d::Layer{
- public:
- //构造方法
- CREATE_FUNC(RotateMenu);
- //添加菜单项
- void addMenuItem(cocos2d::MenuItem *item);
- //更新位置
- void updatePosition();
- //更新位置,有动画
- void updatePositionWithAnimation();
- //位置矫正 修改角度 forward为移动方向 当超过1/3,进1
- //true 为正向 false 负
- void rectify(bool forward);
- //初始化
- virtual bool init();
- //重置 操作有旋转角度设为0
- void reset();
- private:
- //设置角度 弧度
- void setAngle(float angle);
- float getAngle();
- //设置单位角度 弧度
- void setUnitAngle(float angle);
- float getUnitAngle();
- //滑动距离转换角度,转换策略为 移动半个Menu.width等于_unitAngle
- float disToAngle(float dis);
- //返回被选中的item
- cocos2d::MenuItem * getCurrentItem();
- private:
- //菜单已经旋转角度 弧度
- float _angle;
- //菜单项集合,_children顺序会变化,新建数组保存顺序
- cocos2d::Vector<cocos2d::MenuItem *> _items;
- //单位角度 弧度
- float _unitAngle;
- //监听函数
- virtual bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event);
- virtual void onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event);
- virtual void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event);
- //动画完结调用函数
- void actionEndCallBack(float dx);
- //当前被选择的item
- cocos2d::MenuItem *_selectedItem;
- //动画运行时间
- float animationDuration = 0.3f;
- };
- #endif
实现
- #include "RotateMenu.h"
- #include <math.h>
- #define PI acos(-1)
- USING_NS_CC;
- bool RotateMenu::init(){
- if (!Layer::init())
- return false;
- _angle = 0.0;
- this->ignoreAnchorPointForPosition(false);
- _selectedItem = nullptr;
- Size s = Director::getInstance()->getWinSize();
- this->setContentSize(s/3*2);
- this->setAnchorPoint(Vec2(0.5f, 0.5f));
- auto listener = EventListenerTouchOneByOne::create();
- listener->onTouchBegan = CC_CALLBACK_2(RotateMenu::onTouchBegan,this);
- listener->onTouchMoved = CC_CALLBACK_2(RotateMenu::onTouchMoved, this);
- listener->onTouchEnded = CC_CALLBACK_2(RotateMenu::onTouchEnded, this);
- getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
- return true;
- };
- void RotateMenu::addMenuItem(cocos2d::MenuItem *item){
- item->setPosition(this->getContentSize() / 2);
- this->addChild(item);
- _items.pushBack(item);
- setUnitAngle(2 * PI / _items.size());
- reset();
- updatePositionWithAnimation();
- return;
- }
- void RotateMenu::updatePosition(){
- auto menuSize = getContentSize();
- auto disY = menuSize.height / 8;
- auto disX = menuSize.width / 3;
- for (int i = 0; i < _items.size(); i++){
- float x = menuSize.width / 2 + disX*sin(i*_unitAngle+getAngle());
- float y = menuSize.height / 2 - disY*cos(i*_unitAngle + getAngle());
- _items.at(i)->setPosition(Vec2(x, y));
- _items.at(i)->setZOrder(-(int)y);
- //Opacity 129~255
- _items.at(i)->setOpacity(192 + 63 * cos(i*_unitAngle + getAngle()));
- _items.at(i)->setScale(0.75 + 0.25*cos(i*_unitAngle + getAngle()));
- }
- return;
- }
- void RotateMenu::updatePositionWithAnimation(){
- //先停止所有可能存在的动作
- for (int i = 0; i < _items.size(); i++)
- _items.at(i)->stopAllActions();
- auto menuSize = getContentSize();
- auto disY = menuSize.height / 8;
- auto disX = menuSize.width / 3;
- for (int i = 0; i < _items.size(); i++){
- float x = menuSize.width / 2 + disX*sin(i*_unitAngle + getAngle());
- float y = menuSize.height / 2 - disY*cos(i*_unitAngle + getAngle());
- auto moveTo = MoveTo::create(animationDuration, Vec2(x, y));
- _items.at(i)->runAction(moveTo);
- //Opacity 129~255
- auto fadeTo = FadeTo::create(animationDuration, (192 + 63 * cos(i*_unitAngle + getAngle())));
- _items.at(i)->runAction(fadeTo);
- //缩放比例 0.5~1
- auto scaleTo = ScaleTo::create(animationDuration, 0.75 + 0.25*cos(i*_unitAngle + getAngle()));
- _items.at(i)->runAction(scaleTo);
- _items.at(i)->setZOrder(-(int)y);
- }
- scheduleOnce(schedule_selector(RotateMenu::actionEndCallBack), animationDuration);
- return;
- }
- void RotateMenu::reset(){
- _angle = 0;
- }
- void RotateMenu::setAngle(float angle){
- this->_angle = angle;
- }
- float RotateMenu::getAngle(){
- return _angle;
- }
- void RotateMenu::setUnitAngle(float angle){
- _unitAngle = angle;
- }
- float RotateMenu::getUnitAngle(){
- return _unitAngle;
- }
- float RotateMenu::disToAngle(float dis){
- float width = this->getContentSize().width / 2;
- return dis / width*getUnitAngle();
- }
- MenuItem * RotateMenu::getCurrentItem(){
- if (_items.size() == 0)
- return nullptr;
- //这里实际加上了0.1getAngle(),用来防止精度丢失
- int index = (int)((2 * PI - getAngle()) / getUnitAngle()+0.1*getUnitAngle());
- index %= _items.size();
- return _items.at(index);
- }
- bool RotateMenu::onTouchBegan(Touch* touch, Event* event){
- //先停止所有可能存在的动作
- for (int i = 0; i < _items.size(); i++)
- _items.at(i)->stopAllActions();
- if (_selectedItem)
- _selectedItem->unselected();
- auto position = this->convertToNodeSpace(touch->getLocation());
- auto size = this->getContentSize();
- auto rect = Rect(0, 0, size.width, size.height);
- if (rect.containsPoint(position)){
- return true;
- }
- return false;
- }
- void RotateMenu::onTouchEnded(Touch* touch, Event* event){
- auto xDelta = touch->getLocation().x - touch->getStartLocation().x;
- rectify(xDelta>0);
- if (disToAngle(fabs(xDelta))<getUnitAngle() / 6&&_selectedItem)
- _selectedItem->activate();
- updatePositionWithAnimation();
- return;
- }
- void RotateMenu::onTouchMoved(Touch* touch, Event* event){
- auto angle = disToAngle(touch->getDelta().x);
- setAngle(getAngle() + angle);
- updatePosition();
- return;
- }
- void RotateMenu::rectify(bool forward){
- auto angle = getAngle();
- while (angle<0)
- angle += PI * 2;
- while (angle>PI * 2)
- angle -= PI * 2;
- if(forward>0)
- angle = ((int)((angle + getUnitAngle() / 3*2) / getUnitAngle()))*getUnitAngle();
- else
- angle = ((int)((angle + getUnitAngle() / 3 ) / getUnitAngle()))*getUnitAngle();
- setAngle(angle);
- }
- void RotateMenu::actionEndCallBack(float dx){
- _selectedItem = getCurrentItem();
- if(_selectedItem)
- _selectedItem->selected();
- }
一个糟糕的Demo
声明
- #ifndef __HELLOWORLD_SCENE_H__
- #define __HELLOWORLD_SCENE_H__
- #include "cocos2d.h"
- class HelloWorld : public cocos2d::Layer
- {
- public:
- // there's no 'id' in cpp, so we recommend returning the class instance pointer
- static cocos2d::Scene* createScene();
- // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
- virtual bool init();
- // a selector callback
- void menuCloseCallback(cocos2d::Ref* pSender);
- void menuItem1Callback(cocos2d::Ref* pSender);
- void menuItem2Callback(cocos2d::Ref* pSender);
- void menuItem3Callback(cocos2d::Ref* pSender);
- void menuItem4Callback(cocos2d::Ref* pSender);
- void menuItem5Callback(cocos2d::Ref* pSender);
- void hideAllSprite();
- cocos2d::Sprite *sprite[5];
- // implement the "static create()" method manually
- CREATE_FUNC(HelloWorld);
- };
- #endif // __HELLOWORLD_SCENE_H__
声明
- #include "HelloWorldScene.h"
- #include "RotateMenu.h"
- USING_NS_CC;
- typedef struct SceneList{
- const char *name;
- std::function<cocos2d::Scene*()> callback;
- }SceneList;
- SceneList sceneList[] = {
- { "Demo1", [](){return HelloWorld::createScene(); } }
- };
- const unsigned int sceneCount = sizeof(sceneList) / sizeof(SceneList);
- #define LINE_SPACE 40
- Scene* HelloWorld::createScene()
- {
- // 'scene' is an autorelease object
- auto scene = Scene::create();
- // 'layer' is an autorelease object
- auto layer = HelloWorld::create();
- // add layer as a child to scene
- scene->addChild(layer);
- // return the scene
- return scene;
- }
- // on "init" you need to initialize your instance
- bool HelloWorld::init()
- {
- //////////////////////////////
- // 1. super init first
- if ( !Layer::init() )
- {
- return false;
- }
- Size visibleSize = Director::getInstance()->getVisibleSize();
- Vec2 origin = Director::getInstance()->getVisibleOrigin();
- /////////////////////////////
- // 2. add a menu item with "X" image, which is clicked to quit the program
- // you may modify it.
- // add a "close" icon to exit the progress. it's an autorelease object
- auto closeItem = MenuItemImage::create(
- "CloseNormal.png",
- "CloseSelected.png",
- CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
- closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
- origin.y + closeItem->getContentSize().height/2));
- // create menu, it's an autorelease object
- /* auto menu = Menu::create(closeItem, NULL);
- menu->setPosition(Vec2::ZERO);
- this->addChild(menu, 1);*/
- auto item1 = MenuItemImage::create("Demo1/item1_1.png", "Demo1/item1_0.png", CC_CALLBACK_1(HelloWorld::menuItem1Callback, this));
- auto item2 = MenuItemImage::create("Demo1/item2_1.png", "Demo1/item2_0.png", CC_CALLBACK_1(HelloWorld::menuItem2Callback, this));
- auto item3 = MenuItemImage::create("Demo1/item3_1.png", "Demo1/item3_0.png", CC_CALLBACK_1(HelloWorld::menuItem3Callback, this));
- auto item4 = MenuItemImage::create("Demo1/item4_1.png", "Demo1/item4_0.png", CC_CALLBACK_1(HelloWorld::menuItem4Callback, this));
- auto item5 = MenuItemImage::create("Demo1/item5_1.png", "Demo1/item5_0.png", CC_CALLBACK_1(HelloWorld::menuItem5Callback, this));
- RotateMenu *menu = RotateMenu::create();
- menu->addMenuItem(item1);
- menu->addMenuItem(item2);
- menu->addMenuItem(item3);
- menu->addMenuItem(item4);
- menu->addMenuItem(item5);
- menu->setPosition(visibleSize/2);
- this->addChild(menu, 2);
- for (int i = 0; i < 5; i++){
- char str[20];
- sprintf(str, "Demo1/item%d.jpg", i + 1);
- sprite[i] = Sprite::create(str);
- sprite[i]->setAnchorPoint(Vec2(0.5f, 0.5f));
- sprite[i]->setPosition(visibleSize / 2);
- this->addChild(sprite[i]);
- }
- hideAllSprite();
- /////////////////////////////
- // 3. add your codes below...
- // add a label shows "Hello World"
- // create and initialize a label
- auto label = LabelTTF::create("Hello World", "Arial", 24);
- // position the label on the center of the screen
- label->setPosition(Vec2(origin.x + visibleSize.width/2,
- origin.y + visibleSize.height - label->getContentSize().height));
- // add the label as a child to this layer
- this->addChild(label, 1);
- return true;
- }
- void HelloWorld::menuCloseCallback(Ref* pSender)
- {
- #if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
- MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.","Alert");
- return;
- #endif
- Director::getInstance()->end();
- #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
- exit(0);
- #endif
- }
- void HelloWorld::menuItem1Callback(cocos2d::Ref* pSender){
- hideAllSprite();
- sprite[0]->setVisible(true);
- }
- void HelloWorld::menuItem2Callback(cocos2d::Ref* pSender){
- hideAllSprite();
- sprite[1]->setVisible(true);
- }
- void HelloWorld::menuItem3Callback(cocos2d::Ref* pSender){
- hideAllSprite();
- sprite[2]->setVisible(true);
- }
- void HelloWorld::menuItem4Callback(cocos2d::Ref* pSender){
- hideAllSprite();
- sprite[3]->setVisible(true);
- }
- void HelloWorld::menuItem5Callback(cocos2d::Ref* pSender){
- hideAllSprite();
- sprite[4]->setVisible(true);
- }
- void HelloWorld::hideAllSprite(){
- for (auto p : sprite){
- if (p->isVisible())
- p->setVisible(false);
- }
- }
可运行的程序下载地址