一.渲染框架
导演与层
- 导演Director-Ref(单实例类)
- 层Layer-Node节点-Ref
将节点2设置为节点1的子节点的方法:
节点1->addChild(节点2);
Node的设置/获取属性的方法:
- 设置/获取位置:
setPosition/getPosition(设置位置时,如果有父节点,则是以父节点内容左下角坐标为坐标系原点,与父节点锚点无关) - 设置/获取旋转:
setRotation/getRotation - 设置/获取缩放:
setScale/getScale - 设置/获取倾斜:
setSkew/getSkew - 设置/获取翻转:
setFlipped/getFilpped(不影响子节点) - 设置/获取透明度:
setOpacity/getOpacity(取值范围0-255)(不影响子节点) - 设置/获取颜色:
setColor/getColor(不影响子节点) - 设置/获取是否可见:
setVisible/getVisible - 设置/获取锚点:
setAnchorPoint(左下角为(0,0),右上角为(1,1)),层的锚点默认在(0,0),其他节点的锚点默认在(0.5,0.5) - 设置/获取内容大小:
setContentSize/getContentSize(获取节点的大小(层的大小与屏幕大小一样),与缩放和旋转无关) - 设置ZOrder:
setZOrder/getZOrder(Z轴的顺序,Z值越大越后渲染) - 设置/获取标签:可以通过标签删除节点;
- 设置/获取名字:可以通过名字删除节点;
- 设置/获取父节点:
setParent(节点)/getParent();
移除节点的方法Node:
removeChild(Node* child);//移除某一个节点removeChildByTag();//通过标签移除节点removeChildByName();//通过名字移除节点removeAllChild();//移除所有节点removeFromParent();//从父节点上移除
精灵:
设置纹理: sp->setTexture("图片路径");
设置纹理矩形:sp->setTextureRect(Rect(左下角坐标,宽高));
Size类:由宽高组成
Rect类:由左下角坐标和矩形宽高组成;
获取可视区域的起点和大小:
_director->getVisibleOrigin();
_director->getVisibleSize();
Cocos2d-x的四大坐标系:
- OpenGL坐标系:左下角为原点,x轴向右,y轴向上;
- 世界坐标系:与OpenGL坐标系相同;
- 本地坐标系:也称为自身坐标系;
- 屏幕坐标系:左上角为原点,x轴向右,y轴向下(触摸事件得到的坐标就是屏幕坐标,需要从屏幕坐标转世界)
- 转换坐标系的函数:Node:convertTo一类
打印到输出窗口:
使用log("%d\n",hp);//字符串使用code格式+转义字符打印
二.二段构建
- MyLayer.h
#pragma once #include "cocos2d.h" //cocos2d的基本类 //继承于Scene class MyLayer:public cocos2d::Layer { public: MyLayer(); ~MyLayer(); //创建方法 static MyLayer* create(); bool init() override; }; - MyLayer.cpp
#include "MyLayer.h" using namespace cocos2d; MyLayer::MyLayer() { } MyLayer::~MyLayer() { } MyLayer *MyLayer::create() { MyLayer *ret = new (std::nothrow) MyLayer(); //判断指针是否为空和是否初始化成功 if (ret && ret->init()) { ret->autorelease(); return ret; } else { delete ret; ret = nullptr; return nullptr; } } bool MyLayer::init() { //调用父类的初始化方法 if (!Layer::init()) { return false; } return true; }
三.调度器
调度器:处理随时间变化的动态事件.
隔一段时间调用一次函数,例如:移动/发射子弹/判断碰撞等;
默认调度器:每一帧调用update函数;
- 实现默认调度器的两步:
- 开启默认调度器 Node:scheduleUpdate();
- 重写update函数 Node:update(float delta);//虚函数
单次调度器
-
1.开启单次调度器
void Node::scheduleOnce(SEL_SCHEDULE selector, float delay);// 1.方法 2.延时 void Node::scheduleOnce(const std::function<void(float)> &callback, float delay, const std::string &key); -
2.写一个返回值类型为void,形参为float类型的函数(一般写在开启单次调度器的类中)
自定义调度器:可以控制时间间隔/调用次数等;
- 开启自定义调度器schedule
```c++
参数:
1. 方法
2. 间隔时间
3. 重复次数(CC_REPEAT_FOREVER表示最大值,为一直重复)(总共调用repeat+1次)
4. 延时)
void Node::schedule(SEL_SCHEDULE selector, float interval, unsigned int repeat, float delay);
void Node::schedule(const std::function<void(float)> &callback, const std::string &key);//函数原型
```
- 写一个返回值类型为void,形参为float类型的函数
2.function与lambda
-
typedef void(Ref::*SEL_SCHEDULE)(float):
- 取别名:
- void(*pFunc)(float):pFunc指向返回值为void,形参为float类型的函数
- void(Ref::*pFunc)(float):pFunc是指向Ref类的成员函数,
- 成员函数返回值类型为void,形参为float.
- typedef void(Ref::*SEL_SCHEDULE)(float):SEL_SCHEDULE是类型名,是成员函数指针的别名.
- 成员函数指针:赋值:&类名::成员函数
- 取别名:
-
function<返回值类型(形参表))>
- 函数名的地址(只能传普通函数的地址)
- 仿函数
- lambda表达式:匿名方法(在使用方法后被销毁,称之为闭包)
[捕获列表](形参表)->返回值类型{//(->返回值类型可以省略)
函数体;
};
捕获:值捕获和引用捕获
[a]: a为值捕获,不能在闭包中赋值.
[&a]: a为引用捕获,能修改值.
[a,&b]: a为值捕获,b为引用捕获.
[=]: 全部值捕获
[&]: 全部引用捕获
[a,&]: 除a以外全部引用捕获
[=,&b]: 除b以外全部值捕获
- CC_CALLBACK_数字
- 数字:对应function中指定的形参个数
- CC_CALLBACK_1(类名::函数名,对象)
- 注意:不用加&
如何关闭调度器:
- 关闭默认调度器:
unscheduleUpdate(); - 关闭自定义调度器:
unschedule(SEL_SCHEDULE selector);//通过方法名删除unschedule(const string& key);//通过键值删除
哪些情况下会自动关闭调度器?
- 游戏暂停时;//参照游戏mainLoop
- 当节点移出舞台时;//参照:Node的析构函数
- 父节点被移除时,子节点会全部被移除;
四.内存管理机制
cocos2d内存管理机制:引用计数(Ref类) 面试必考题!!!
- Sprite - Node - Ref
- Layer - Node - Ref
- Scene - Node -Ref
- Director - Ref
retain:引用计数+1
release:引用计数-1(当引用计数为0时,释放内存)autoRelease:加入到自动释放池(这一帧绘制结束后所有在自动释放池里面的计数全部减1)retain和releae/autoRelease必须搭配使用,否则会出现内存泄漏
create:引用计数为1
addChild():引用计数+1(调用retain())remove():当前节点被移出 引用计数-1(调用release())
例子
例子1:
auto sp = Sprite::create("");//1
这一帧结束后引用计数为?//0
this->addChild(sp);//报错,内存已经被释放 child != nullptr
例子2:
auto sp = Sprite::create();//1
this->addChild(sp);//2
这一帧结束后引用计数为?//1
例子3:
auto sp = Sprite::create();//1
sp-retain();//2
这一帧结束后引用计数为?//1
this->addChild(sp);//2
sp->release();//1
例子4:
auto sp= Sprite::create("");//1
node1->addChild(sp);//2
sp->retain();//3
sp->removeFromParent();//2
这一帧结束后?//1
node2->addChild(sp);//2
sp->release();
例子5:
auto sp = Sprite::create("");//1
auto sp1 = Sprite::create("");//1
sp->addChild(sp1);//sp 1/sp1 2
sp->retain();//sp 2
this->addChild(sp);//sp 3
sp->removeChild(sp1);//sp 3/sp1 2
this->removeChild(sp);//sp 2
内存优化机制:对象池技术
- 子弹工厂:BulletFactory(简单工厂)
- 子弹管理:BulletManager(单例)
- 成员:生存池、死亡池
- 方法:
- 添加子弹到生存池中,
- 回收子弹:从生存池到死亡池
- 查找死亡池中符合条件的子弹
五.判断碰撞
判断包围盒相交:
- 获取包围盒:Node:getBoundingBox();
- Rect:
- bool containsPoint(const Vec2& point) const;//判断包围盒内是否存在某个点
- bool intersectsRect(const Rect& rect) const;//判断两个矩形是否相交
六.事件监听器与分发器
事件分发器:EventDispatcher(事件分发器按照优先级顺序派发给事件监听器)
:Node:_eventDispatcher
事件监听器:EventListener
事件:键盘/鼠标/触摸(单点触摸/多点触摸)/自定义等
单点触摸事件监听器:EventListenerTouchOneByOne
- 创建单点触摸事件监听器
- 添加触摸事件响应
- 将触摸监听添加到事件分发器中
- 设置是否吞噬触摸:是否将触摸事件往下一个优先级的监听器传递(默认不吞噬)
多点触摸监听事件监听器:EventListenerTouchAllAtOnce
- 创建多点触摸事件监听器
- 添加触摸事件响应
typedef std::function<void(const std::vector<Touch*>&, Event*)> ccTouchesCallback;
ccTouchesCallback onTouchesBegan;
ccTouchesCallback onTouchesMoved;
ccTouchesCallback onTouchesEnded;
ccTouchesCallback onTouchesCancelled;
- 将触摸监听添加到事件分发中
- 设置是否吞噬触摸
键盘事件监听器:EventListenerKeyboard
- 创建键盘事件监听器
EventListenerKeyboard* eventListener = EventListenerKeyboard::create();
- 添加键盘事件响应
- 将键盘监听添加到事件分发器中
鼠标事件监听器:EventListenerMouse
- 创建鼠标事件监听器
EventListenerMouse* mouseListener = EventListenerMouse::create();
- 添加鼠标事件响应
- 将鼠标事件监听添加到事件分发器中
自定义事件监听器:EventCustom
例如:敌机与主机碰撞,监听主机死亡事件,游戏结束;
- 创建自定义事件监听器
EventListenerCustom::create();
- 将自定义事件添加到事件分发器中
_eventDispatcher->addEventListenerWithFixedPriority();
事件分发器分发消息
//通知调用自定义回调事件
_eventDispatcher->dispatchCustomEvent(1.事件名,2.传递参数void*(也就是回调函数的EventCustom*));
七.帧动画与缓存
动作:Action(继承于Ref)
动作的执行:通过节点执行
Node:runAction(Action*)
动画的停止:
Node:stopAction(Action*);//停止某个动作
stopActionByTag();//通过标签停止动作
stopActionsByFlags();//通过Falgs停止动作
stopAllActions();//停止当前节点上的所有动作
有限时间动作分为即时动作和持续动作.
-
ActionInstant(即时动作)
- FiniteTimeAction(有限时间动作) --- Action(动作)
-
ActionInterval(持续动作)
即时动作
指能够立刻完成的动作,这类动作是在下一帧立刻完成的动作,如设定位置、设定缩放等
(Place/FlipX/FlipY/Show/Hide/CallFunc(回调)等)
持续动作
指不能够立刻完成的动作,如移动/跳跃等;
属性变化动作
通过属性值的逐渐变化来实现动画效果。需要注意的是XXTo和XXBy的区别在于XXTo是表示最终值,而XXBy则表示向量-改变值。
(MoveTo/MoveBy/JumpTo/JumpBy/BezierTo/BezierBy/ScaleTo/ScaleBy/RotateTo/RotateBy)
视觉特效动作
(FadeIn/FadeOut/FateTo/TintTo/TintBy/Blink/Animate(帧动画))
其他动作
DelayTime/Repeat/RepeatForever
复合动作
- 序列动作:Sequence(让各种动作有序执行)
- 组合动作:Spawn(使一批动作同时执行)
变速动作
Speed/ActionEase
帧动画创建步骤
//1. 创建Animation:
Animation* animation = Animation::create();
//2. 添加所有的精灵帧:
for (int i = 1; i <= 4; i++)
{
char filename[64];
sprintf_s(filename, "PlaneBattle/Images/Enemy/enemy0_down%d.png", i);
animation->addSpriteFrameWithFile(filename);
}
//3. 设置动画播放的速率:
animation->setDelayPerUnit(0.3f);
//4. 设置是否循环:
animation->setLoops(-1);//-1表示无限循环
//5. 设置当动画结束时是否回到起始帧(切换回最开始的精灵):
animation->setRestoreOriginalFrame(true);
//6.创建Animate:
Animate* animate = Animate::create(animation);
//7.播放动作:
sp->runAction(animate);
缓存
Cache:实现原理(map:一个文件名对应一张纹理(或者一个精灵帧))
纹理缓存
纹理缓存:TextureCache::getInstance();
TextureCache::getInstance()->addImage("路径名");//通过路径名添加纹理
精灵帧缓存
精灵帧缓存:SpriteFrameCache::getInstance();//精灵帧缓存最终也要添加纹理缓存;
//通过路径名添加精灵帧:会自动将纹理图片路径由.plist替换成.png
SpriteFrameCache::getInstance()->addSpriteFramesWithFile(".plist文件的路径");
SpriteFrameCache::getInstance()->addSpriteFramesWithFile(".plist文件路径", ".png文件路径");
SpriteFrameCache::getInstance()->addSpriteFramesWithFile(".plist文件路径", 纹理);
制作.plist文件:TexturePacker(.plst文件实际就是xml文件)
- 通过精灵帧缓存创建精灵帧
Sprite* sp = Sprite::createWithSpriteFrameName("MsShadaoLeft_0.png"); //注意!!!
sp->setPosition(200, 200);
this->addChild(sp);
- 通过精灵帧缓存创建帧动画
Animation* ani = Animation::create();
for (int i = 0; i < 4; i++)
{
char filename[30];
sprintf(filename, "MsShadaoLeft_%d.png", i);
//通过名字获得精灵帧
SpriteFrame* sf = SpriteFrameCache::getInstance()->getSpriteFrameByName(filename);//注意!!!
ani->addSpriteFrame(sf);//动画添加精灵帧
}
ani->setDelayPerUnit(0.3f);
ani->setLoops(-1);
ani->setRestoreOriginalFrame(true);
Animate* animate = Animate::create(ani);
sp->runAction(animate);
八.其他动作
回调动作
回调动作:通过回调动作回调函数,一般在组合或者序列动作中使用;CallFunc
CallFunc::create(callfunc_selector(类名::函数名));
CallFunc::create(CC_CALLBACK_0(类名::函数名,节点));
CallFuncN::create(callfunc_selector(类名::函数名));
CallFuncN::create(CC_CALLBACK_1(类名::函数名,节点));
//注意:CallFunc和CallFuncN不同的是CallFunc只能回调返回值为void无形参的函数,
//CallFuncN回调返回值为void形参为Node*的函数(指的是调用方法的节点)
组合动作
组合动作:使一批动作同时执行(注意必须以nullptr结尾)
Spwan::create(动作1,动作2.....动作N,nullptr);
序列动作
序列动作:让各种动作有序执行(注意必须以nullptr结尾)
Sequence::create(动作1,动作2....动作N,nullptr);
重复动作
Repeat::create(1.有限时间动作 2.次数);
RepeatForever::create(持续动作);
To和By一类动作
MoveTo/MoveBy:To指的是目的地 By指的是改变值
九.场景切换与其他效果
游戏暂停/恢复/结束
游戏暂停:Director::getInstance()->pause();(时间间隔dt = 0)
游戏恢复:Director::getInstance()->resume();
游戏结束Director::getInstance()->end();
场景转换与过渡效果
- 场景转换:
1. 第一个场景:`Director::getInstance()->runWithScene(Scene*);`
2. 切换其他场景:`Director::getInstance()->replaceScene(Scene*);`
- 场景切换特效:在两个不同场景之间直接转换的能力。例如:淡入淡出,放大缩小,旋转,跳动等。从技术上来说,一个场景转换就是在展示并控制一个新场景之前执行一个转换效果。
- TransitionScene(场景过渡的基类) - Scene - Node - Ref
- 主要的切换特效有:
TransitionRotoZoom 旋转进入
TransitionJumpZoom 跳动进入
TransitionPageTurn 翻页效果进入
TransitionRadialCCW 钟摆效果
TransitionMoveInL / TransitionMoveInR / TransitionMoveInT / TransitionMoveInB 左侧/右侧/顶部/底部进入
TransitionSlideInL/TransitionSlideInR/TransitionSlideInT/TransitionSlideInB 分别从左侧/右侧/顶部/底部滑入
TransitionShrinkGrow 交替进入
TransitionFlipX/TransitionFlipY x轴翻入(左右)/ y轴翻入(上下)
TransitionFlipAngular 左上右下轴翻入
TransitionZoomFlipX/TransitionZoomFlipY x轴翻入放大缩小效果(左右)/ y轴翻入放大缩小效果(上下)
TransitionFadeTR / TransitionFadeBL/ TransitionFadeUp/TransitionFadeDown
小方格右上角显示进入/小方格左下角显示进入/横条向上显示进入/横条向下显示进入
TransitionSplitCols / TransitionSplitRows 竖条切换进入/ 横条切换进入
TransitionZoomFlipAngular 左上右下轴翻入放大缩小效果
TransitionFade 渐隐进入
TransitionCrossFade 渐变进入
TransitionTurnOffTiles 小方格消失进入
TransitionRadialCCW/TransitionRadialCW 扇面展开收起
例如:
auto transitions = TransitionMoveInL::create(0.2f, scene);
Director::getInstance()->replaceScene(transitions);
十.声音控制
SimpleAudioEngine类(单例):控制声音和音效的播放(需要引用#include "SimpleAudioEngine.h")
声音和音效的预加载
加载音乐和音效通常是个耗时间的过程,因此为了防止由加载产生的延时导致实际播放与游戏播放不协调的现象。
在播放音效和音乐前,需要预加载音乐文件。
SimpleAudioEngine::getInstance()->preloadBackgroundMusic(文件路径);//加载背景音乐
SimpleAudioEngine::getInstance()->preloadEffect(文件路径);//加载音效
声音和音效的播放和停止
//播放背景音乐,bLoop表示是否要循环播放
SimpleAudioEngine::getInstance()->playBackgroundMusic(const char* pszFilePath, bool bLoop = false);
//播放音效,bLoop表示是否要循环播放
SimpleAudioEngine::getInstance()->playEffect(const char* pszFilePath, bool bLoop = false,float pitch = 1.0f, float pan = 0.0f, float gain = 1.0f);
//停止背景音乐
SimpleAudioEngine::getInstance()->stopBackgroundMusic(bool bReleaseData = false);
//停止指定音效,nSoundId为音效编号
SimpleAudioEngine::getInstance()->stopEffect(unsigned int nSoundId);
//停止所有音效
SimpleAudioEngine::getInstance()->stopAllEffects();
声音和音效的暂停和恢复
//暂停背景音乐
SimpleAudioEngine::getInstance()->pauseBackgroundMusic();
//暂停指定音效,nSoundId为音效编号
SimpleAudioEngine::getInstance()->pauseEffect(unsigned int nSoundId);
//暂停所以音效
SimpleAudioEngine::getInstance()->pauseAllEffects();
//恢复背景音乐
SimpleAudioEngine::getInstance()->resumeBackgroundMusic();
//恢复指定音效,nSoundId为音效编号
SimpleAudioEngine::getInstance()->resumeEffect(unsigned int nSoundId);
//恢复所有音效
SimpleAudioEngine::getInstance()->resumeAllEffects();
声音和音效的属性设置
//设置背景音乐音量
SimpleAudioEngine::getInstance()->setBackgroundMusicVolume(float volume);
//设置音效音量
SimpleAudioEngine::getInstance()->setEffectsVolume(float volume);
//重新播放背景音乐
SimpleAudioEngine::getInstance()->rewindBackgroundMusic();
//返回一个值,表示是否在播放背景音乐
SimpleAudioEngine::getInstance()->isBackgroundMusicPlaying();
十一.UI
UI
user interface(用户界面)
UI控件
例如按钮/文本/输入框/滑动条/滚动条等
所有的UI控件都继承于Widget
Widget - ProtectedNode - Node - Ref
按钮:Button
//create参数:1.正常状态图片 2.选中状态图3.禁用状态下的图片(设置为不能被点击)
1. 创建按钮:auto btn = Button::create();
2. 添加按钮触摸监听事件:
btn->addTouchEventListener();//参数:function<void(Ref*,Widget::TouchEventType)>
//其中Ref*参数表示被点击的控件,可通过dynamic_cast转换;
//TouchEventType表示触摸状态,分别分为BEGAN/MOVED/ENDED/CANCELLED
3. 可选择的属性:
btn->setSwallowTouches(false);//设置是否吞噬触摸
btn->setEnabled(false);//设置是否禁用//属于Widget方法
4. 将按钮添加到层上;
文本:Text
1. 创建文本:auto text = Text::create();//1.内容 2.字体路径 3.字体大小
2. 设置文本内容:text->setString("分数:1");//文本内容
3. 将文本添加到层上;
enableShadow():开启阴影
enableOutline():开启描边
复选框:CheckBox
//参数:1.选中状态下的图片 2.未选中状态下的图片
1. 创建复选框:auto checkBox = CheckBox::create();
2. 添加复选框监听事件
checkBox->addEventListener();//参数:function<void(Ref*,CheckBox::EventType)>
//其中Ref*参数表示被点击的控件,可通过dynamic_cast转换;
CheckBox::EventType表示选中与未选中状态,分别分为SELECTED/UNSELECTED
3. 设置选中状态
checkBox->setSelected(true);
单选框组RadioGroup
1. 创建单选框组:auto group = RadioButtonGroup::create();
2. 循环创建单选框,并将单选框作为单选框组的子节点:
for (int i = 0; i < 3; i++)
{
//---------------------添加单选框---------------------------------
//参数:1.选中状态下的图片 2.未选中状态下的图片
auto radioButton = RadioButton::create("Slot 2.png", "Star Left.png");
radioButton->setPosition(Vec2(100, 100 + i * 70));
group->addRadioButton(radioButton);
group->addChild(radioButton);
}
3. 将单选框组作为层的子节点;
this->addChild(group);
ImageView
//相比于Sprite,ImageView好在可以直接创建监听时间
1.创建ImageView:ImageView* image = ImageView::create();
2.设置开启触摸(默认不开启):setTouchEnabled(true);//必须开启触摸才能监听到触摸/点击事件
3.监听触摸/点击事件:
addTouchEventListener;
addClickEventListener;
十二.Cocostudio制作
文件资源管理FileUtils(单例)
addSearchPath:添加资源搜索相对路径
操作CocosStudio
1. 设置发布路径:Resources - res
2. 设置文件夹路径:资源与CCS中的层放在同一路径下,防止查找不到资源
3. 每次编辑完,记得发布!!!
加载cocosStudio里面的csb文件
1. CSLoder::createNode();//参数:csb文件的路径 返回值:节点(当前csb文件的根结点)
2. 将节点添加到当前层上
3. 通过根结点查找子节点:getChildByName
加载与播放CocosStudio里的动画(引用using namespace cocostudio::timeline;)
1. 制作动画:1.创建节点,再节点上右键创建精灵,将所有动画的帧往精灵上拖动
2. 添加动画:将动画添加到层上,摆放好位置
3. 创建层的根结点,通过名字获取播放动画的节点
4. 创建时间线,设置时间线参数通过节点运行动作
添加帧事件
1. 在cocostudio编辑动画时,先勾选 “开始记录动画”
2. 选中要加事件的帧的资源
十三.文件存储
文件存储:UserDefault(单例)
1. set:1.键 2.值
2. get:1.键 2.默认值
十四.瓦片地图
加载TMX文件:TMXTiledMap(瓦片地图)
1. 获取地图大小(10*10):getMapSize
2. 获取块大小(70*70):getTileSize
3. 获取图块层:getLayer(参数:图块层名 返回值:TMXLayer)
4. 获取对象层:getObjectGroup(参数:对象层名 返回值:TMXObjectGroup)
TMXLayer:(左上角为原点,左上角第一块坐标为(0,0)
1. 获得某一个块:getTileAt(参数:坐标)
2. 获取某一块的GID值:getTileGIDAt(参数:坐标)
3. 设置某一块的GID值:setTileGID(参数:1.GID值 2.坐标)
4. 获得图层的宽高:getLayerSize
TMXObjectGroup:对象层
1. 获得某一个对象:getOject(参数:对象名 返回值:ValueMap(键值对))
2. 获得所有对象:getObjects(返回值:vector<Value>)
3. 键值对<string,value>:一个键对应一个值(一个对象属性名字对应属性值);
4. 访问:键值对["键"]; //例如value["x"]/value["hp"]
对象层作用:存储数据(例如存储出怪点/怪物行走路径/建塔区域等)
1. 创建对象层
2. 选中创建矩形对象的工具(对象点/对象区域)
+ 快捷键:
- Ctrl+移动:以图块格子为单位移动
- Ctrl+缩放:以左上角等比缩放
- Shfit+缩放:以中心点缩放
- Ctrl+Shfit+缩放:以中心等比缩放
3. 修改属性:基本属性和自定义属
十五.制作技能
技能按钮
- 技能按钮类:继承于按钮类
- 使用观察者模式(一对多的关系):底层实质就是函数指针
- 监听器(观察者)
- 回调函数
SkillButton.h
#ifndef _SkillButton_H_
#define _SkillButton_H_
#include "Ui/CocosGUI.h"
class SkillButton :public cocos2d::ui::Button{
public:
SkillButton(float coldTime):
coldTime(coldTime),
isCold(false),
onColdBegan(nullptr),
onColdEnded(nullptr)
{
}
//~SkillButton();
static SkillButton* create(
float coldTime,
const std::string& normalImage,
const std::string& selectedImage = "",
const std::string& disableImage = "",
TextureResType texType = TextureResType::LOCAL);
bool init(
const std::string& normalImage,
const std::string& selectedImage = "",
const std::string& disableImage = "",
TextureResType texType = TextureResType::LOCAL);
public:
//function<返回值类型(形参表)> 变量名
typedef std::function<void()> ccColdBeganCallback;
typedef std::function<void()> ccColdEndedCallback;
ccColdBeganCallback onColdBegan;//指向当冷却开始时要调用的函数
ccColdEndedCallback onColdEnded;//指向当冷却结束时要调用的函数
private:
float coldTime;//技能冷却时间
bool isCold;//技能是否正在冷却
};
#endif // !_SkillButton_H_
SkillButton.cpp
#include "SkillButton.h"
#include "cocos2d.h"
using namespace cocos2d::ui;
using namespace cocos2d;
SkillButton* SkillButton::create(
float coldTime,
const std::string& normalImage,
const std::string& selectedImage,
const std::string& disableImage,
TextureResType texType) {
SkillButton* ret = new(std::nothrow) SkillButton(coldTime);
if (ret && ret->init(normalImage, selectedImage, disableImage, texType)) {
ret->autorelease();
} else {
delete ret;
ret = nullptr;
}
return ret;
}
bool SkillButton::init(
const std::string& normalImage,
const std::string& selectedImage,
const std::string& disableImage,
TextureResType texType) {
if (!Button::init(normalImage, selectedImage, disableImage, texType)) {
return false;
}
/*EventListenerTouchOneByOne*/
//创建遮罩精灵:灰色
Sprite* sp = Sprite::create(normalImage);
//sp->setAnchorPoint(Vec2(0, 0));
sp->setColor(Color3B::GRAY);
//ProgressTimer:进度计数器(0 - 100)
ProgressTimer* progressTimer = ProgressTimer::create(sp);
progressTimer->setAnchorPoint(Vec2::ZERO);//设置锚点
//progressTimer->setPercentage(80);//设置百分比
progressTimer->setReverseDirection(true);//翻转旋转方向
this->addChild(progressTimer);
//给当前按钮添加点击监听事件
this->addClickEventListener([this, progressTimer](Ref*) {
//判断是否在技能冷却
if (!isCold) {
//调用冷却开始的函数
if (onColdBegan != nullptr) {
onColdBegan();//函数指针(实参表)
}
isCold = true;
setTouchEnabled(false);//设置关闭触摸
//1.时间 2.开始的百分比 3.结束的百分比
auto progressFromTo = ProgressFromTo::create(coldTime, 100, 0);
auto callFunc = CallFunc::create([this]() {
//调用冷却结束的函数
if (onColdEnded != nullptr) {
onColdEnded();//函数指针(实参表)
}
isCold = false;//关闭技能冷却
setTouchEnabled(true);//开启触摸
});
auto seqAct = Sequence::create(progressFromTo, callFunc, nullptr);
progressTimer->runAction(seqAct);
}
});
return true;
}
十六.制作摇杆
制作摇杆的思路:摇杆应该由两部分组成,一个是背景一个是拖动部分。这两个部分我们用ImageView来创建,使用这个来创建可以很方便的创建监听事件。 在监听事件中,有三个四个状态,分别是:按下,移动,抬起,取消。其中我们把抬起和取消放到一起来处理,是摇杆回到初始状态即可。 按下和移动的时候我们可以分为这几个步骤来写代码
- 获取触摸点的坐标
- 转换坐标系:将touchPoint从世界转到JoyStick坐标系下
- stickIv用触摸点坐标赋值
具体代码如下
JoyStick.h
#ifndef _JoyStick_H_
#define _JoyStick_H_
#include "Ui/CocosGUI.h"
#include "cocos2d.h"
class JoyStick : public cocos2d::ui::Widget{
public:
JoyStick():
onJoyStickBegan(nullptr),
onJoyStickMoved(nullptr) ,
onJoyStickEnded(nullptr),
isTouch(false),
touchAxis(cocos2d::Vec2(0, 0))
{
}
//1.背景图片 2.遥感图片
static JoyStick* create(
const std::string& bgImage,
const std::string& stickImage
);
bool init(
const std::string& bgImage,
const std::string& stickImage
);
//当遥感被拖动时调用
void onJoyStickTouch(cocos2d::Ref* pSender, cocos2d::ui::Widget::TouchEventType type);
//默认调度器
void update(float dt) override;
public:
typedef std::function<void()> ccJoyStickBeganCallback;
typedef std::function<void(cocos2d::Vec2)> ccJoyStickMovedCallback;
typedef std::function<void()> ccJoyStickEndedCallback;
ccJoyStickBeganCallback onJoyStickBegan;//当摇杆开始拖动时
ccJoyStickMovedCallback onJoyStickMoved;//当摇杆拖动时
ccJoyStickEndedCallback onJoyStickEnded;//当摇杆结束拖动时
private:
cocos2d::ui::ImageView* stickIv;
//cocos2d::ui::ImageView* bgIv;
float redius;//半径
cocos2d::Vec2 originPos;//摇杆起始坐标
bool isTouch;//是否触摸
cocos2d::Vec2 touchAxis;//触摸方向
};
#endif // !_JoyStick_H_
JoyStick.cpp
#include "JoyStick.h"
using namespace cocos2d;
using namespace cocos2d::ui;
JoyStick* JoyStick::create(const std::string& bgImage,const std::string& stickImage) {
JoyStick* ret = new(std::nothrow)JoyStick();
if (ret && ret->init(bgImage, stickImage)) {
ret->autorelease();
} else {
delete ret;
ret = nullptr;
}
return ret;
}
bool JoyStick::init(const std::string& bgImage,const std::string& stickImage) {
if (!Widget::init()) {
return false;
}
//创建背景图片 ImageView可以直接添加触摸/监听事件
ImageView* bgIv = ImageView::create(bgImage);
this->addChild(bgIv);
//创建遥感图片 可拖动
stickIv = ImageView::create(stickImage);
stickIv->setTouchEnabled(true);//必须先开启触摸:setTouchEnabled
this->addChild(stickIv);
//半径 因为还要考虑到如果图片进行了缩放,那么还得乘上缩放比例
redius = bgIv->getContentSize().width / 2 * bgIv->getScale();
//初始坐标
originPos = stickIv->getPosition();
//给stick添加触摸监听事件
stickIv->addTouchEventListener(CC_CALLBACK_2(JoyStick::onJoyStickTouch, this));
//开启调度器
scheduleUpdate();
return true;
}
void JoyStick::update(float dt) {
if (isTouch && onJoyStickMoved != nullptr) {
onJoyStickMoved(touchAxis);
}
}
void JoyStick::onJoyStickTouch(Ref* pSender, Widget::TouchEventType type) {
//判断触摸阶段
Vec2 touchPoint = Vec2::ZERO;
Vec2 pos;
Vec2 offset;
MoveTo* mt = MoveTo::create(0.5f, originPos);;
switch (type) {
case Widget::TouchEventType::BEGAN://触摸开始
isTouch = true;
if (onJoyStickBegan != nullptr) {
onJoyStickBegan();
}
//关闭移动动作
stickIv->stopAction(mt);
//获取触摸点的坐标Began
touchPoint = stickIv->getTouchBeganPosition();
//转换坐标系:将touchPoint从世界转到JoyStick坐标系下
pos = this->convertToNodeSpaceAR(touchPoint);
//stickIv用触摸点坐标赋值
stickIv->setPosition(pos);
break;
case Widget::TouchEventType::MOVED://触摸移动
//获取触摸点的坐标Move
touchPoint = stickIv->getTouchMovePosition();
//转换坐标系:将touchPoint从世界转到JoyStick坐标系下
pos = this->convertToNodeSpaceAR(touchPoint);
//将摇杆限制在半径内
offset = pos - originPos;
if (offset.getLength() > redius) {
offset = offset.getNormalized() * redius;//getNormalized:单位向量 长度为1的向量
pos = offset + originPos;
}
touchAxis = offset.getNormalized();
//stickIv用触摸点坐标赋值
stickIv->setPosition(pos);
if (onJoyStickMoved != nullptr) {
onJoyStickMoved(touchAxis);
}
break;
default://触摸结束/取消
isTouch = false;
if (onJoyStickEnded != nullptr) {
onJoyStickEnded();
}
//遥感回到原点
stickIv->runAction(mt);
break;
}
}