1.前言:
我刚毕业就来了北京,到现在已经三年了,一直从事着计算机安全软件的研发工作,和游戏不搭边。三年的时间不短了,完全可以让你知道喜不喜欢这一领域,经过反复考虑我觉得不适合干安全。淋漓尽致的发挥创新思维,迎合时代的潮流,面向亿万的消费群体,非游戏莫属了(呵呵这都是废话了,只是自己比较喜欢玩游戏而已,找个高大上的理由)。经过调查现在主流游戏有端游、页游、手游,端游过大自己搞不太现实,页游我对web又不熟,只有手油小巧易成型,加上这几年智能手机的普及手油发展趋势迅猛,嗯就是你了手油。3个月前经过调查发现了两款不错的游戏引擎cocos2d-x、Unity,经过了反复考量我明智的选择了cocos2dx(艹 Unity收费…)。三年的开发经验我用的第三方库有很多,如果你想快速上手我的经验就是找个实例去练习,在这之前当然要先看教程但不要看的太细 观其大略,因为看多了你根本记不住,在实例中你会遇到教程中有的和没有的问题,这样把问题一一解决你就会很快熟悉这套东西。学cocos2dx我也用了这个办法,把买的书和论坛上的教程观其大略,我选择的实例就是这款在AppStore上架的休闲游戏,用cocos2dx引擎把它实现一遍。在此声明山寨这个游戏只为学习目的,无任何商业用途。好废话有点多了,下面就是正题。
2.正文:
游戏截图:
游戏网盘下载:http://pan.baidu.com/s/1mg6yNxA,1
引擎版本:cocos2dx3.0rc
开发工具:Visual Studio 2012,CocoStudio v1.5.0
开发语言:C++
游戏规则简介:这个一个跳跃的游戏,玩家控制粉色小方块跳到不同颜色、不同高度的台阶上,而天上每隔一段时间就会弹出某个颜色的小圆球,只有和天上圆球颜色一致的台阶才能成为踏板,总共有10个颜色,再具体的玩一下就知道了不复杂。
游戏资源的获得,大家看到这款游戏的画风挺简单,那我也不会画,最简单的方法就是拿来主义,下载weseewe的ipa包,改成zip后缀就可以解压出来,资源全在里面,这招是网上学的,太万恶连我这么正直的人都被他教坏了。
大家可以看到资源中主要元素都有@2x版本,@2x版本的图片分辨率都很高,网上的解释这种高分辨率的图片是为大屏幕准备的,比如ipad(有更确切的解释可以偷偷告诉我),反正我是只用了@2x的图片。还有就是资源中一些图片都分为top、bottom俩个版本,听一个做美术设计的同事说两张图片要重叠放置,还有具体哪个图片要加一些透明,但我没这么干因为他不是做游戏的,给的意见不确切,我只用了top版的图片(有更确切的解释可以偷偷告诉我)。
这个调皮的粉色小方块就是我们的主角了,旋转的时候还点萌

这个又大又粗的就是配角了,要被主角踩,而且要排成排不停的向左移动,它一共有11个颜色,这些颜色的RGB值在资源里可是找不到的,我用了一个笨方法玩游戏截图,把11个颜色都弄到手了,再用画图工具把颜色的RGB都记录下来。

Title的weseewe字体在资源里也可以找到,是fnt格式的,注意是小写。
做为一个休闲游戏欢快的音乐音效是必不可少的,背景音乐、主角跳跃音效、放礼花音效,这些音乐音效文件都是wav格式的,cocos2dx可以支持。
还有其他跑龙套的资源比如开始按钮、声音按钮、排行按钮、背景里漂浮的云彩等,就不一一介绍了。
重要元素介绍:
1.漂浮的云彩
bool HelloWorld::init() {
//////////////////////////////
// 1. super init first
if ( !Layer::init() ) {
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
//1.设置背景颜色
auto layer = CCLayerColor::create(ccc4(159, 213, 204, 0xff), visibleSize.width, visibleSize.height);
this->addChild(layer, 0);
//2.云彩动画
this->schedule(schedule_selector(HelloWorld::CloudTimerCallBack), 9.0f, kRepeatForever, 0.1f);
//3.初始化GameLayer
InitGameLayer();
return true;
}
void HelloWorld::CloudTimerCallBack( float dt ) {
INIT_RANDOM_ENGINE;
auto CloudSprite = CCSprite::create("cloudBottom@2x.png");
Size visibleSize = Director::getInstance()->getVisibleSize();
Size CloudSize = CloudSprite->getContentSize();
CloudSprite->setPosition(
ccp(visibleSize.width + CloudSize.width / 2,
visibleSize.height * (mf::rn(6, 8) / 10)));
auto CloudSpriteTop = CCSprite::create("cloudTop@2x.png");
CloudSpriteTop->setPosition(CloudSprite->getPosition());
CloudSprite->addChild(CloudSpriteTop,1);
this->addChild(CloudSprite, 1);
auto CloudRotate = CCRotateBy::create(80,
mf::rn(0, 1) ? 360 : -360);
auto CloudRotateForever = CCRepeatForever::create(CloudRotate);
auto CloudMove = CCMoveBy::create(30, ccp(-(visibleSize.width + CloudSize.width), 0));
auto CloudActionSeque = CCSequence::create(CloudMove,
CCCallFuncN::create(this, callfuncN_selector(HelloWorld::CloudMoveDoneCallBack)), NULL);
CloudSprite->runAction(CloudRotateForever);
CloudSprite->runAction(CloudActionSeque);
}
void HelloWorld::CloudMoveDoneCallBack( cocos2d::Node *sprite ) {
this->removeChild(sprite);
}
```
云彩的漂动我是用定时器来控制的,Timer被调用一次我就从屏幕右侧造一个云彩sprite,赋予云彩旋转和移动action,让它漂到屏幕左侧,其中旋转的方向和漂浮的高度是随机的,在这个游戏中随机以后还会被用到,下面是我的随机代码:
namespace MyFunc {
float GetRandomNumber(std::default_random_engine &e,int i1, int i2);
#define INIT_RANDOM_ENGINE std::default_random_engine e(time(0))
#define rn(a,b) GetRandomNumber(e,a,b)
}
#define NS_MF namespace mf = MyFunc
float MyFunc::GetRandomNumber(std::default_random_engine &e,int i1, int i2) {
std::uniform_int_distribution u(i1, i2);
return float(u(e));
}
```
2.进场和开始UI动画
上面看到的图是我从CocoStudio的UI编辑器截取的,真的非常方便简单,只需要拖拖拽拽设置下帧数就能做成这样的效果,
如果有哪位同学不会的可以参考站内大神的UI编辑器教学贴http://www.cocoachina.com/bbs/read.php?tid=192723,1
//5.UI
auto StartUpUI = dynamic_cast(cocostudio::GUIReader::shareReader()->widgetFromJsonFile("AnimationEditer/StartupUI_1/StartupUI_1.json"));
this->addChild(StartUpUI,3,8);
_uiIn = cocostudio::ActionManagerEx::getInstance()->playActionByName("StartupUI_1.json","In");
//获取点击按钮
auto doItButton = static_cast(StartUpUI->getChildByName("Begin"));
auto helpButton = static_cast(StartUpUI->getChildByName("help"));
auto soundButton = static_cast(StartUpUI->getChildByName("sound"));
auto scoreButton = static_cast(StartUpUI->getChildByName("score"));
//设置按钮点击事件响应
doItButton->addTouchEventListener( this, toucheventselector(GameLayer::doItButtonCheck));
helpButton->addTouchEventListener( this, toucheventselector(GameLayer::helpButtonCheck));
soundButton->addTouchEventListener( this, toucheventselector(GameLayer::soundButtonCheck));
scoreButton->addTouchEventListener( this, toucheventselector(GameLayer::scoreButtonCheck));
```
在这里说一下cocos2dx3.0以后cocos2d::ui::Layout是可以直接当成cocos2d::Node使用,而特别注意的是在使用UI编辑器导出的action后一定要手动stop,还要在场景结束前调用cocostudio::ActionManagerEx::destroyInstance(),不然会引发崩溃,这也是CocoStudio和cocos2dx接口中的BUG吧。
3.block台阶
这个是最麻烦的了,台阶和云彩一样也是从右向左移动,但台阶和台阶之间要无缝排列移动,如果用云彩那种方法实现你会发现台阶之间会出现不同大小的缝隙,我的办法是把台阶都放到一个Layer里,通过移动Layer来实现台阶一直向左移动。
#define BLOCKS_LAYER_TAG 61
class BlocksLayer:public cocos2d::Layer
{
#define LAST_BLOCK (Blocks_)
public:
CREATE_FUNC(BlocksLayer);
virtual bool init();
Block * AddBlock( BlockAttributes &blockAttri,cocos2d::Point Point);
Block * AddBlockTail(BlockAttributes &blockAttri);
const std::vector& getBlocks();
void CleanupFirstBlock();
void runBlocksMoveAction(float Duration);
float getBlocksMoveDuration() {return _blockMoveDuration;}
Colors _colors;
int _colorIndex;
virtual void setPosition(const cocos2d::Point &position){
auto offsetPos = position - this->getPosition();
for(int i=0;isetPosition(Blocks_->getPosition()+offsetPos);
}
}
protected:
void AddColorTimer(float dt);
private:
std::vector Blocks_;
std::vector _blocksAttribute;
int _blockIndex;
float _blockMoveDuration;
void InitBlocksAttribute();
void BlockMoveDoneCallBack(cocos2d::Node *Block);
};
```
上面的BlocksLayer就是我的台阶层,为了实现动态向尾部添加台阶,我把台阶都放在一个vector中方便管理也就是类中成员Blocks_。
//让台阶层从右向左移动
void BlocksLayer::runBlocksMoveAction( float Duration )
{
this->stopActionByTag(30);
_blockMoveDuration = Duration;
//每次移动204
auto BlocksMove = CCMoveBy::create(_blockMoveDuration, ccp(-204,0));
auto BlocksActionSeque = CCSequence::create(BlocksMove,
CCCallFuncN::create(this, callfuncN_selector(BlocksLayer::BlockMoveDoneCallBack)), NULL); //每次移动后调用BlockMoveDoneCallBack
//无限循环移动
auto BlocksMoveForever = CCRepeatForever::create(BlocksActionSeque);
BlocksMoveForever->setTag(30);
this->runAction(BlocksMoveForever);
}
void BlocksLayer::BlockMoveDoneCallBack( cocos2d::Node *Block )
{
GameLayer *parent = (GameLayer *)this->getParent();
if (parent->getGameState() == GS_READY) {
this->AddBlockTail(_blocksAttribute);
} else if (parent->getGameState() == GS_BEGIN) {
//向队列的末尾添加台阶
this->AddBlockTail(_blocksAttribute);
if (_blockIndex < _blocksAttribute.size()) {
if (_blockIndex % 15 == 0) {
_colorIndex++;
this->scheduleOnce(schedule_selector(BlocksLayer::AddColorTimer),1.2);
}
} else {
parent->setGameState(GS_END);
}
} else if (parent->getGameState() == GS_END) {
}
//清理已经跑到屏幕外的台阶
CleanupFirstBlock();
}
Block * BlocksLayer::AddBlock( BlockAttributes &blockAttri,cocos2d::Point Point)
{
auto BlockSprite = Block::create();
auto BlockSize = BlockSprite->getContentSize();
BlockSprite->setPosition(Point.x+BlockSize.width/2,Point.y);
BlockSprite->setAttributes(blockAttri);
if(blockAttri._allow){
auto body = PhysicsBody::createEdgeBox(BlockSprite->getContentSize());
body->setCategoryBitmask(1); // 0001
body->setContactTestBitmask(-1); // 0100
body->setCollisionBitmask(-1); // 0011
BlockSprite->setPhysicsBody(body);
}
this->addChild(BlockSprite/*,--_order*/);
return BlockSprite;
}
Block * BlocksLayer::AddBlockTail( BlockAttributes &blockAttri)
{
Point TailBlockPoint = LAST_BLOCK->getPosition();
auto BlockSize = LAST_BLOCK->getContentSize();
Point BPoint = ccp(TailBlockPoint.x+BlockSize.width/2,blockAttri._y);
auto BlockSprite = AddBlock(blockAttri,BPoint);
Blocks_.push_back(BlockSprite);
return BlockSprite;
}
void BlocksLayer::CleanupFirstBlock()
{
auto Cnt = Blocks_.size();
for (int i = 0; i < Cnt; i++) {
auto Pt = convertToWorldSpace(Blocks_->getPosition());
auto Sz = Blocks_->getContentSize();
if (Pt.x < -Sz.width) {
this->removeChild(Blocks_);
Blocks_.erase(Blocks_.begin());
} else {
break;
}
}
}
```
当然因为游戏的规则台阶还被赋予了其它属性,高度、颜色、是否允许被踩,上面的图可以看到天上只有2种颜色,只有对应颜色的台阶主角才能跳上去,而没有的颜色主角跳上去会踩空,为此我重写了一个Sprite类赋予了它一些新的属性。
//台阶属性类
class BlockAttributes
{
public:
BlockAttributes():_y(0),_color(cocos2d::ccc3(0,0,0)),_allow(false){
}
BlockAttributes(float y,cocos2d::Color3B color,bool allow):
_y(y),_color(color),_allow(allow){
}
BlockAttributes& operator= (const BlockAttributes& other){
_y = other._y;
_color=other._color;
_allow =other._allow;
return *this;
}
bool isAllow(){return _allow;}
float _y; //高度
cocos2d::Color3B _color; //颜色
bool _allow; //是否允许被踩
protected:
private:
};
class Block:public cocos2d::Sprite,public BlockAttributes
{
public:
Block(){
this->setTag(BLOCK_TAG);
}
void setAttributes(const BlockAttributes &ba){
_y=ba._y;
_color=ba._color;
_allow=ba._allow;
this->setColor(_color);
}
virtual bool init(){
this->initWithFile("blocktop@2x.png",cocos2d::CCRectMake(26,30,204,482));
return true;
}
CREATE_FUNC(Block);
protected:
private:
};
```
游戏中颜色只有11个,每跳过15个台阶就会新出现一种颜色,跳过10*15个台阶游戏就结束了,主角可以二次跳跃,但最多可以跳过2个台阶,也就是说在随机台阶属性的时候不能让3个连续的台阶都是不可踩的。每次游戏开始都会先初始化出所有台阶的属性,先把11种颜色重新随机排列,然后把属性也放到vector中,下面是台阶属性初始化的代码:
class Color {
public:
Color(): _r(0), _b(0), _g(0) {}
Color(GLubyte r, GLubyte g, GLubyte b): _r(r), _g(g), _b(b) {
}
Color& operator= (const Color& other) {
_r = other._r;
_g = other._g;
_b = other._b;
return *this;
}
const cocos2d::Color3B getCCColor() {
return cocos2d::ccc3(_r, _g, _b);
}
GLubyte _r;
GLubyte _g;
GLubyte _b;
protected:
private:
};
class Colors {
public:
Colors() {
//辛苦收集的11种颜色RGB
_allColors.push_back(Color(167, 175, 179));
_allColors.push_back(Color(0, 204, 102));
_allColors.push_back(Color(255, 153, 153));
_allColors.push_back(Color(96, 98, 128));
_allColors.push_back(Color(242, 48, 85));
_allColors.push_back(Color(145, 57, 153));
_allColors.push_back(Color(255, 102, 51));
_allColors.push_back(Color(255, 255, 204));
_allColors.push_back(Color(51, 204, 204));
_allColors.push_back(Color(255, 204, 51));
_allColors.push_back(Color(189, 116, 52));
}
//随机排列颜色
void randomColors() {
//random_shuffle(_allColors.begin(),_allColors.end(),MyRand());
std::vector tmp;
std::default_random_engine e(time(0));//生成随机无符号数
while (_allColors.size() > 0) {
std::uniform_int_distribution u(0, _allColors.size() - 1);
auto iter = _allColors.begin() + (int)u(e);
tmp.push_back(*iter);
_allColors.erase(iter);
}
_allColors = tmp;
}
ssize_t cnt() {
return _allColors.size();
}
const cocos2d::Color3B operator](int index) {
return _allColors.getCCColor();
}
protected:
private:
std::vector _allColors;
};
void BlocksLayer::InitBlocksAttribute()
{
INIT_RANDOM_ENGINE;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 15; j++) {
Color3B color;
float y = 0;
bool allow;
int blockIdx = i * 15 + j;
if (blockIdx < 2) {
//开始的前两个台阶是初始颜色
color = _colors;
allow = true;
} else {
auto b1 = _blocksAttribute._allow;
auto b2 = _blocksAttribute._allow;
if (!b1 && !b2) {
allow = true; //如果前俩个台阶都是不能踩的,那这个一定是能踩的
} else if (b1 && b2) {
allow = mf::rn(0, 6) < 3; //如果前俩个台阶都能踩,那这个台阶只有7分之3的几率能踩
} else if ((b2 && !b1) || (!b2 && b1)) {
allow = mf::rn(0, 1); //如果前俩个台阶只有一个能踩,那这个台阶有50%的几率能踩
}
if (allow) {
color = _colors; //在能踩的颜色中随机出一个颜色
} else {
color = _colors; //在不能踩的颜色中随机出一个颜色
}
y=mf::rn(-120,120); //台阶的高度要考虑到主角的跳跃高度
//y=?
}
_blocksAttribute.push_back(BlockAttributes(y, color, allow)); //放入vector中
}
}
}
```
4.物理引擎和物理碰撞检测
Scene* HelloWorld::createScene() {
// 'scene' is an autorelease object
auto scene = Scene::createWithPhysics(); //既然是物理世界就要用物理场景
//scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
// 'layer' is an autorelease object
auto layer = HelloWorld::create();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
bool GameLayer::init()
{
//………………………………
auto contactListener = EventListenerPhysicsContact::create(); //创建一个物理接触监听事件
contactListener->onContactBegin = CC_CALLBACK_1(GameLayer::onContactBegin,this); //接触事件回调
contactListener->onContactSeperate = CC_CALLBACK_1(GameLayer::onContactSeperate,this); //接触分离回调
dispatcher->addEventListenerWithSceneGraphPriority(contactListener, this); //注册事件
//2.初始化player
_player = Player::create();
auto playerBody = PhysicsBody::createBox(_player->getContentSize(),PhysicsMaterial(0.99f,0.0f,0.0f)); //创建一个物理刚体
playerBody->setCategoryBitmask(1); // 0001
playerBody->setContactTestBitmask(-1); // 0100
playerBody->setCollisionBitmask(-1); // 0011
playerBody->setGravityEnable(false); //关闭重力
_player->setPhysicsBody(playerBody); //赋予主角刚体
_player->setPosition(300,850);
this->addChild(_player,2);
this->scheduleUpdate();
this->scheduleOnce(schedule_selector(GameLayer::playerStartTimer),1);
//………………………………
}
```
上面我为主角player创建了一个动态刚体,设置了刚体的三个属性setCategoryBitmask、setContactTestBitmask、setCollisionBitmask,这三个属性必须设置不然物理接触监听事件不会检测到它的碰撞,再就是我把刚体的重力关了,这样主角就不受重力的影响,因为我要自己实现跳跃和下落。只有俩个sprite都有刚体结构才能检测到碰撞,所以我们也要赋予台阶刚体结构:
Block * BlocksLayer::AddBlock( BlockAttributes &blockAttri,cocos2d::Point Point)
{
auto BlockSprite = Block::create();
auto BlockSize = BlockSprite->getContentSize();
BlockSprite->setPosition(Point.x+BlockSize.width/2,Point.y);
BlockSprite->setAttributes(blockAttri);
if(blockAttri._allow){ //判断是否可以被主角踩
auto body = PhysicsBody::createEdgeBox(BlockSprite->getContentSize());
body->setCategoryBitmask(1); // 0001
body->setContactTestBitmask(-1); // 0100
body->setCollisionBitmask(-1); // 0011
BlockSprite->setPhysicsBody(body);
}
this->addChild(BlockSprite/*,--_order*/);
return BlockSprite;
}
```
上面给台阶加刚体结构,我只给能被主角踩的台阶加刚体,而加的是静态刚体EdgeBox,它的特性是不受重力影响,不会被其他物理刚体弹开。这样主角和配角都有刚体外衣了,下面就是物理碰撞监听事件:
bool GameLayer::onContactBegin( cocos2d::PhysicsContact& contact ) {
//获取接触的俩个sprite
auto nodeA = contact.getShapeA()->getBody()->getNode();
auto nodeB = contact.getShapeB()->getBody()->getNode();
//判断哪个是block台阶
auto block = _player == nodeA ? nodeB : nodeA;
auto contactData = contact.getContactData();
//都转换成世界坐标
auto blockPos = _blockLayer->convertToWorldSpace(block->getPosition());
auto playerPos = this->convertToWorldSpace(_player->getPosition());
_contactBlockCnt++;//递增接触台阶的数量
if (contactData->normal.y > 0) {//判断是y轴接触,也就下主角下落碰到台阶
if (!_player->stateConf(Player::PS_BACK)) {
auto pos = this->convertToNodeSpace(ccp(0, blockPos.y + block->getContentSize().height / 2));
_player->runWalk(pos.y); //既然是下落有台阶接住主角,让主角停止下落
}
_treadBlockCnt++;//递增被踩台阶的数量
} else if (contactData->normal.x < 0) {//判断是x轴接触,也就是主角被台阶阻挡
//主角被台阶阻挡要后退
auto pos = this->convertToNodeSpace(ccp(blockPos.x - block->getContentSize().width / 2 - _player->getContentSize().width / 2, playerPos.y));
_player->setPosition(pos);
_player->runMoveBackAction(_blockLayer->getBlocksMoveDuration(), ccp(-204, 0));
}
return true;
}
//接触分离事件回调
void GameLayer::onContactSeperate( cocos2d::PhysicsContact& contact ) {
auto contactData = contact.getContactData();
if(!contactData){
return;
}
if (contactData->normal.y > 0) //判断是y轴接触,也就下主角下落碰到台阶
_treadBlockCnt--; //递减被踩台阶的数量
_contactBlockCnt--;//递减接触台阶的数量
if (_treadBlockCnt == 0) {
if (!_player->stateConf(Player::PS_JUMP))
_player->runDownAction(); //如果被踩台阶数量是0,那么主角下落
}
if(_contactBlockCnt==0){
if(_player->stateConf(Player::PS_BACK)){
_player->stopMoveBackAction(); //如果接触的台阶数量是0,那么主角停止后退
}
}
}
```
上面注释写的很清晰,主要说下contactData这个数据,不管是接触回调或是分离回调,都会给这个数据,其中contactData->normal成员说明这次接触是Y轴接触还是X轴接触,上面的判断都是我试出来的,因为在网上没找到相关文档。
下面说下我用cocos2dx物理引擎碰到的问题和解决办法。
第一个是给player主角加上PhysicsBody,player再去做旋转action无效,这个网上其他人也碰到过,解决办法是重写sprite的setRotationSkewX和setRotationSkewY:
class Player: public cocos2d::Sprite {
//………………………………
protected:
virtual void setRotationSkewX(float rotationX){
}
virtual void setRotationSkewY(float rotationY){
cocos2d::Node::setRotation(rotationY);
if (! _recursiveDirty) {
_recursiveDirty = true;
setDirty(true);
if (!_children.empty())
setDirtyRecursively(true);
}
}
//…………………………
};
```
第二个是移动Layer,Layer中的PhysicsBody不会随Layer移动,这应该是cocos2dx的BUG,解决办法是重写Layer的setPosition:
class BlocksLayer:public cocos2d::Layer
{
//………………………………
virtual void setPosition(const cocos2d::Point &position){
auto offsetPos = position - this->getPosition(); //计算位移
for(int i=0;isetPosition(Blocks_->getPosition()+offsetPos); //自己移动需要移动的sprite
}
}
//……………………
};
```
5.礼花
礼花是主角掉到场外gameover出的动画,实现还是比较简单的。
class FireWorks:public cocos2d::Sprite
{
public:
void fire(const cocos2d::Rect &rect);
CREATE_FUNC(FireWorks);
protected:
virtual bool init();
private:
};
bool FireWorks::init()
{
INIT_RANDOM_ENGINE;
Colors color;
this->initWithFile("particle.png");
this->setPosition(-1000,0);
for (int i=0;i<50;i++) //50个彩片
{
auto sprite = CCSprite::create("particle.png");
sprite->setColor(color); //从10个颜色中随机
this->addChild(sprite,1,i);
sprite->setPosition(0,0);
sprite->retain();
}
return true;
}
void FireWorks::fire( const cocos2d::Rect &rect )
{
auto worldOrg = this->getParent()->convertToWorldSpace(rect.origin);
auto org = this->convertToNodeSpace(worldOrg);
INIT_RANDOM_ENGINE;
for(int i=0;i<50;i++){
auto movepos = ccp(org.x+mf::rn(0,rect.size.width),
org.y+mf::rn(0,rect.size.height));
auto moveAction = CCMoveTo::create(0.7,movepos);
auto moveesi = CCEaseSineOut::create(moveAction);
auto moveBack=CCMoveBy::create(6,ccp(0,-100));
auto seq = CCSequence::create(moveesi,moveBack,NULL);
auto rotate = CCRotateBy::create(mf::rn(2,6),mf::rn(0,1)?-360:360);
auto rotateForever = CCRepeatForever::create(rotate);
auto sprite = this->getChildByTag(i);
sprite->runAction(rotateForever);
sprite->runAction(seq);
}
}
```
代码很简单就是把50个sprite灌入不同的颜色,fire函数的参数是一个rect,表示礼花要在这个矩形中绽放,彩片飞出去时是快出慢停所以用CCEaseSineOut,再给彩片加上正反随机的旋转,坐标是随机在参数rect里的。
6.颜色球
颜色球是提醒玩家现在哪些颜色的台阶可以踩,动画是我用action写的。
void GameLayer::AddColorDot(const cocos2d::Color3B &color )
{
Size visibleSize = Director::getInstance()->getVisibleSize();
auto colorDotSprite = CCSprite::create("colorDot@2x.png");
colorDotSprite->setTag(200+_colorDotCnt);
auto size = colorDotSprite->getContentSize();
colorDotSprite->setColor(color);
Point pt,movePt;
if (_colorDotCnt < 5) { //_colorDotCnt是当前实现的颜色计数
//前5个颜色会显示在一排
pt = ccp(visibleSize.width + size.width, visibleSize.height - 250);
if (_colorDotCnt == 0) {
movePt = ccp(visibleSize.width / 2, pt.y);
} else {
for (int i = 0; i < _colorDotCnt; i++) {
auto preDotSprite = this->getChildByTag(200 + i);
auto preMove = CCMoveBy::create(0.8, ccp(-(size.width/2), 0));
auto easeElasticOut = CCEaseElasticOut::create(preMove);
preDotSprite->runAction(easeElasticOut);
if (i == _colorDotCnt - 1) {
movePt = ccp((preDotSprite->getPosition().x+ size.width/2), pt.y);
}
}
}
} else {
//后5个颜色会显示在第二排
pt = ccp(visibleSize.width + size.width, visibleSize.height - 250 - 20 - size.height);
if (_colorDotCnt == 5) {
movePt = ccp(visibleSize.width / 2, pt.y);
} else {
for (int i = 5; i < _colorDotCnt; i++) {
auto preDotSprite = this->getChildByTag(200 + i);
auto preMove = CCMoveBy::create(0.8, ccp(-(size.width/2), 0));
auto easeElasticOut = CCEaseElasticOut::create(preMove);
preDotSprite->runAction(easeElasticOut);
if (i == _colorDotCnt - 1) {
movePt = ccp((preDotSprite->getPosition().x+ size.width/2), pt.y);
}
}
}
}
colorDotSprite->setPosition(pt);
this->addChild(colorDotSprite);
auto moveAction = CCMoveTo::create(1.5, movePt);
auto easeElasticOut = CCEaseElasticOut::create(moveAction);
colorDotSprite->runAction(easeElasticOut);
if (_soundOnoff)
cd::SimpleAudioEngine::sharedEngine()->playEffect("sounds/color.wav");
_colorDotCnt++;
}
```
是不是代码也很简单,因为要做到弹簧的效果就要用CCEaseElasticOut来移动sprite。
总结:
总的来说cocos2dx给我的感觉就是简单易学易实现,这也是引擎开发者的本意吧,虽然有些bug,但我相信这个引擎会优化的越来越好。分享写的有点乱,还有看不懂的可以参考我的源码就在下面,因为第一次用这个引擎肯定会有不足差强人意的地方,希望大家指正给出建议,我的qq是584592187有兴趣的朋友可以加我,大家可以一起讨论共同进步。好就写这些,多谢大家支持我。
源码:http://pan.baidu.com/s/1gdmqwPP,1











