时下最热门的英雄联盟,用cocos2d-x尝试下它的皮肤选择菜单,也算是初学者练手了。
最终效果图
英雄联盟皮肤选择
设计说明
实现目标所需要的动作
移动(MoveTo),伸缩(ScaleTo),倾斜(OrbitCamera)
实现目标所需要函数(这是一个数学函数)
x/(x+a)
其中a为常量,用来计算上面三个动作的值
大小
与原版Menu不同,大小不是全屏的,默认是屏幕的(2/3),可以通过setContentSize()函数设置
_index变量
将所有的菜单项平铺构成一个长方形,_index表示目前在中间位置的点,如下图
显示方式
将菜单项距中心的距离(i-_indxe)作为函数变量x,具体内容查看LOLMenu::updatePosition();
操作说明
滑动四分之一菜单宽的距离为一个单位的_index,距离大于0.6小于1.0的部分进1
使用
使用这个菜单只要知道两个函数:
1.构造函数:LOLMenu::create()(由CREATE_FUNC创建)
2.添加MenuItem:void addMenuItem(cocos2d::MenuItem *item);
其它函数可以看代码
菜单代码
LOLMenu.h
#ifndef __LOL__TE_MENU_H__
#define __LOL__TE_MENU_H__
#include "cocos2d.h"
/*
*模仿英雄联盟的皮肤选择菜单
*不同点在于,英雄联盟当皮肤过多时,部分皮肤会移出边缘,不显示
*我会显示所以菜单项,向边缘移动会不断减缓
*/
class LOLMenu :public cocos2d::Layer{
public:
//构造方法
CREATE_FUNC(LOLMenu);
//添加菜单项
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 setIndex(int index);
//设置当前显示菜单项的索引号
float getIndex();
//返回被选中的item
cocos2d::MenuItem * getCurrentItem();
//数学计算式width*index/(abs(index)+CALC_A) ,其中CALC_A为常数
float calcFunction(float index, float width);
private:
//菜单菜单项的索引号
float _index;
//上一次菜单项的索引号
float _lastIndex;
//菜单项集合,_children顺序会变化,新建数组保存顺序
cocos2d::Vector
//监听函数
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;
};
#endif
LOLMenu.cpp
#include "LOLMenu.h"
#include
#define PI acos(-1)
//菜单的缩小比例 最小的比例是1-MENU_SCALE
#define MENU_SCALE 0.3
//菜单的倾斜度 最多为45度
#define MENU_ASLOPE 60.0
//calcFunction(x) 为 x/(x+a),其中a为常数
#define CALC_A 1
//动画运行时间
#define ANIMATION_DURATION 0.3f
//菜单项的大小与屏幕的比例,当然可以通过setContentSize设置
#define CONTENT_SIZE_SCALE (2.0/3)
//菜单项长度与菜单长度的比例 滑动一个菜单项长度,菜单项变化一个
#define ITEM_SIZE_SCALE (1.0/4)
/*
代码里面还有可以设置的参数,这里没有一一例出或给出函数
*/
USING_NS_CC;
bool LOLMenu::init(){
if (!Layer::init())
return false;
_index=0;
_lastIndex = 0;
this->ignoreAnchorPointForPosition(false);
_selectedItem = nullptr;
auto size = Director::getInstance()->getWinSize();
this->setContentSize(size*CONTENT_SIZE_SCALE);
this->setAnchorPoint(Vec2(0.5f, 0.5f));
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(LOLMenu::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(LOLMenu::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(LOLMenu::onTouchEnded, this);
getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
return true;
};
void LOLMenu::addMenuItem(cocos2d::MenuItem *item){
item->setPosition(this->getContentSize() / 2);
this->addChild(item);
_items.pushBack(item);
reset();
//如果希望开始没有移动效果,改成updatePosition函数即可
updatePositionWithAnimation();
return;
}
void LOLMenu::updatePosition(){
auto menuSize = getContentSize();
for (int i = 0; i < _items.size(); i++){
//设置位置
float x = calcFunction(i - _index, menuSize.width / 2);
_items.at(i)->setPosition(Vec2(menuSize.width/2+x, menuSize.height/2));
//设置zOrder,即绘制顺序
_items.at(i)->setZOrder(-abs((i - _index) * 100));
//设置伸缩比例
_items.at(i)->setScale(1.0-abs(calcFunction(i - _index, MENU_SCALE)));
//设置倾斜,Node没有setCamera函数,将OrbitCamera的运行时间设为0来达到效果
auto orbit1 = OrbitCamera::create(0, 1, 0, calcFunction(i - _lastIndex, MENU_ASLOPE), calcFunction(i - _lastIndex, MENU_ASLOPE) - calcFunction(i - _index, MENU_ASLOPE), 0, 0);
_items.at(i)->runAction(orbit1);
}
return;
}
void LOLMenu::updatePositionWithAnimation(){
//先停止所有可能存在的动作
for (int i = 0; i < _items.size(); i++)
_items.at(i)->stopAllActions();
auto menuSize = getContentSize();
for (int i = 0; i < _items.size(); i++){
_items.at(i)->setZOrder(-abs((i - _index)*100));
float x = calcFunction(i - _index, menuSize.width / 2);
auto moveTo = MoveTo::create(ANIMATION_DURATION, Vec2(menuSize.width / 2 + x, menuSize.height / 2));
_items.at(i)->runAction(moveTo);
auto scaleTo = ScaleTo::create(ANIMATION_DURATION, (1 - abs(calcFunction(i - _index, MENU_SCALE))));
_items.at(i)->runAction(scaleTo);
auto orbit1 = OrbitCamera::create(ANIMATION_DURATION, 1, 0, calcFunction(i - _lastIndex, MENU_ASLOPE), calcFunction(i - _index, MENU_ASLOPE) - calcFunction(i - _lastIndex, MENU_ASLOPE), 0, 0);
_items.at(i)->runAction(orbit1);
}
scheduleOnce(schedule_selector(LOLMenu::actionEndCallBack), ANIMATION_DURATION);
return;
}
void LOLMenu::reset(){
_lastIndex = 0;
_index = 0;
}
void LOLMenu::setIndex(int index){
_lastIndex = _index;
this->_index = index;
}
float LOLMenu::getIndex(){
return _index;
}
MenuItem * LOLMenu::getCurrentItem(){
if (_items.size() == 0)
return nullptr;
return _items.at(_index);
}
bool LOLMenu::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 LOLMenu::onTouchEnded(Touch* touch, Event* event){
auto size = getContentSize();
auto xDelta = touch->getLocation().x - touch->getStartLocation().x;
rectify(xDelta>0);
if (abs(xDelta)
_selectedItem->activate();
updatePositionWithAnimation();
return;
}
void LOLMenu::onTouchMoved(Touch* touch, Event* event){
auto xDelta = touch->getDelta().x;
auto size = getContentSize();
_lastIndex = _index;
_index -= xDelta / (size.width *ITEM_SIZE_SCALE);
updatePosition();
return;
}
void LOLMenu::rectify(bool forward){
auto index = getIndex();
if (index < 0)
index = 0;
if (index>_items.size() - 1)
index = _items.size() - 1;
if (forward){
index = (int)(index + 0.4);
}
else
index = (int)(index + 0.6);
setIndex((int)index);
}
void LOLMenu::actionEndCallBack(float dx){
_selectedItem = getCurrentItem();
if (_selectedItem)
_selectedItem->selected();
}
float LOLMenu::calcFunction(float index, float width){
return width*index / (abs(index) + CALC_A);
}
演示代码
LOLMenuDemo.h
#ifndef __LOLMenu_SCENE_H__
#define __LOLMenu_SCENE_H__
#include "cocos2d.h"
class LOLMenuDemo : 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(LOLMenuDemo);
};
#endif // __HELLOWORLD_SCENE_H__
LOLMenuDemo.cpp
#include "LOLMenuDemo.h"
#include "LOLMenu.h"
USING_NS_CC;
Scene* LOLMenuDemo::createScene()
{
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = LOLMenuDemo::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 LOLMenuDemo::init()
{
//////////////////////////////
// 1. super init first
if (!Layer::init())
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
auto item1 = MenuItemImage::create("4_LOL_MENU/item1_0.png", "4_LOL_MENU/item1_0.png", CC_CALLBACK_1(LOLMenuDemo::menuItem1Callback, this));
auto item2 = MenuItemImage::create("4_LOL_MENU/item2_0.png", "4_LOL_MENU/item2_0.png", CC_CALLBACK_1(LOLMenuDemo::menuItem2Callback, this));
auto item3 = MenuItemImage::create("4_LOL_MENU/item3_0.png", "4_LOL_MENU/item3_0.png", CC_CALLBACK_1(LOLMenuDemo::menuItem3Callback, this));
auto item4 = MenuItemImage::create("4_LOL_MENU/item4_0.png", "4_LOL_MENU/item4_0.png", CC_CALLBACK_1(LOLMenuDemo::menuItem4Callback, this));
auto item5 = MenuItemImage::create("4_LOL_MENU/item5_0.png", "4_LOL_MENU/item5_0.png", CC_CALLBACK_1(LOLMenuDemo::menuItem5Callback, this));
LOLMenu *menu = LOLMenu::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[100];
sprintf(str, "4_LOL_MENU/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();
return true;
}
void LOLMenuDemo::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 LOLMenuDemo::menuItem1Callback(cocos2d::Ref* pSender){
hideAllSprite();
sprite[0]->setVisible(true);
}
void LOLMenuDemo::menuItem2Callback(cocos2d::Ref* pSender){
hideAllSprite();
sprite[1]->setVisible(true);
}
void LOLMenuDemo::menuItem3Callback(cocos2d::Ref* pSender){
hideAllSprite();
sprite[2]->setVisible(true);
}
void LOLMenuDemo::menuItem4Callback(cocos2d::Ref* pSender){
hideAllSprite();
sprite[3]->setVisible(true);
}
void LOLMenuDemo::menuItem5Callback(cocos2d::Ref* pSender){
hideAllSprite();
sprite[4]->setVisible(true);
}
void LOLMenuDemo::hideAllSprite(){
for (auto p : sprite){
if (p->isVisible())
p->setVisible(false);
}
}