[转]Cocos2dx游戏开发系列笔记18:《跑酷》游戏源码解析

懒骨头(http://blog.csdn.net/iamlazybone QQ:124774397 )

再来看一个比较常见的跑酷游戏

其实吧

我觉得就是有人用《萝莉快跑》的例子改的

不过肯定有新东西学习

这是骨头见过最好的demo,不用修改一点直接能运行。

先报环境 vs2012 和 cocos2dx 2.2 和 win8.1

我知道出新版本了

好多人整天盼着新版本但。。。

1 用python脚本新建一个demo

2点此下载跑酷游戏源码 (感谢 blog.csdn.net/haomengzhu 的demo)

3 拷贝 Classes 和 Resources 到新demo里

4 vs打开新的demo,导入现有项,编译,一次ok

===================================================

===========================================================

主角类:Player

在H文件里定义了玩家状态的枚举:移动降落死亡

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. typedef enum
  2.     kPlayerMoving,//主角在移动
  3.     kPlayerFalling,//主角在降落
  4.     kPlayerDying//主角死亡
  5. } PlayerState; 

还有些基本的数据:

骑行动画,漂浮动画,漂浮时间,速度等等。

还有很多的 inline 方法 和inline虚方法

关于inline引用百科的介绍:

[html] view plaincopy在CODE上查看代码片派生到我的代码片

  1. inline关键字用来定义一个类的内联函数,引入它的主要原因是用它<strong>替代C中表达式形式的宏定义</strong>。 

[html] view plaincopy在CODE上查看代码片派生到我的代码片

  1. 表达式形式的宏定义 
  2. #define ExpressionName(Var1,Var2) ((Var1)+(Var2))*((Var1)-(Var2)) 
  3. 1. C中使用这种形式宏定义的原因,C语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成等一系列的操作,因此,效率很高  
  4. 2. 形式上类似函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它<strong>不能进行参数有效性的检测</strong>,也就<strong>不能享受C++编译器严格类型检查</strong>的好处,另外它的返回值也不<strong>能被强制转换</strong>为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。 
  5. 3. 在C++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及到类的保护成员或私有成员,你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。 
  6. 4. inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了宏定义的缺点,同时又很好地继承了宏定义的优点。 
  7.     inline int left() { 
  8.         return this->getPositionX() – _width * 0.5f; 
  9.     } 
  10. 1. inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。 
  11. 2. 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。 
  12. 3. inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。 
  13. 在何时使用inline函数: 
  14. 首先,你可以使用inline函数完全<strong>取代表达式形式的宏定义</strong>。 
  15. 另外要注意,内联函数一般只会<strong>用在函数内容非常简单</strong>的时候,这是因为,内联函数的代码会在任何调用它的地方展开,如果函数太复杂,代码膨胀带来的恶果很可能会大于效率的提高带来的益处。<span style="font-family: Arial, Helvetica, sans-serif;">内联函数最重要的使用地方是用于<strong>类的存取函数</strong>。</span>

骨头稍微明白一点了,以后慢慢理解吧。

在Player.CPP里,有构造方法,create方法。

构造方法主要初始化一些值

create方法主要是创建CCSprite这个主要对象,一些动画和参数的设置在 init 方法里。

主角滑翔方法,也是简单的播放滑翔动画和修改速度。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. //设置下降动作
  2. void Player::setFloating (bool value) { 
  3. if (_floating == value)  
  4. return; 
  5. if (value && _hasFloated)  
  6. return; 
  7.     _floating = value; 
  8. this->stopAllActions(); 
  9. if (value) { 
  10.         _hasFloated = true; 
  11. this->setDisplayFrame(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName("player_float.png")); 
  12. this->runAction(_floatAnimation); 
  13.         _vector.y += PLAYER_JUMP * 0.5f; 
  14.     } else { 
  15. this->runAction(_rideAnimation); 
  16.     }  

还有void Player::update (float dt) 更新方法:

这里会慢慢的加速到速度最大值,然后匀速。

然后根据玩家的状态,来调整速度,是掉落、前进或者跳跃等等。

速度相关的,很好理解,但是很乱。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. void Player::update (float dt) { 
  2. //加速度到最大,然后匀速
  3. if (_speed + ACCELERATION <= _maxSpeed) { 
  4.         _speed += ACCELERATION; 
  5.     } else { 
  6.         _speed = _maxSpeed; 
  7.     } 
  8.     _vector.x = _speed; 
  9. //CCLog("play state:%d",_state);
  10. switch (_state) { 
  11. case kPlayerMoving: 
  12.         _vector.y -= GRAVITY; 
  13. if (_hasFloated)  
  14.             _hasFloated = false; 
  15. break; 
  16. case kPlayerFalling: 
  17. if (_floating ) { 
  18.             _vector.y -= FLOATNG_GRAVITY; 
  19.             _vector.x *= FLOATING_FRICTION; 
  20.         } else { 
  21.             _vector.y -= GRAVITY; 
  22.             _vector.x *= AIR_FRICTION; 
  23.             _floatingTimer = 0; 
  24.         } 
  25. break; 
  26. case kPlayerDying: 
  27.         _vector.y -= GRAVITY; 
  28.         _vector.x = -_speed; 
  29. this->setPositionX(this->getPositionX() + _vector.x); 
  30. break; 
  31.     } 
  32. if (_jumping) { 
  33.         _state = kPlayerFalling; 
  34.         _vector.y += PLAYER_JUMP * 0.25f; 
  35. if (_vector.y > PLAYER_JUMP )  
  36.             _jumping = false; 
  37.     } 
  38. if (_vector.y < -TERMINAL_VELOCITY)  
  39.         _vector.y = -TERMINAL_VELOCITY; 
  40.     _nextPosition.y = this->getPositionY() + _vector.y; 
  41. if (_vector.x * _vector.x < 0.01)  
  42.         _vector.x = 0; 
  43. if (_vector.y * _vector.y < 0.01)  
  44.         _vector.y = 0; 
  45. if (_floating) { 
  46.         _floatingTimer += dt; 
  47. if (_floatingTimer > _floatingTimerMax) { 
  48.             _floatingTimer = 0; 
  49. this->setFloating(false); 
  50.         } 
  51.     } 

=======================================================================

地图贴砖类:Block

就是左边这种方块,根据一定的顺序,拼成了游戏中的地图。

一般使用地图编辑器,编辑好了导入到游戏中。

以前笔记遇到过,骨头还没学到那,暂时一放。

Block.h 中,有类型枚举、间隔(?)枚举。

定义了贴砖的宽度,高度,贴砖图片,和一些基本的动画,如移动,小时,缩放等。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. CC_SYNTHESIZE(int, _type, Type); 
  2. //声明一个成员变量_puffing以及getfunName函数,没有set函数。getfunName函数的实现要自己做
  3. CC_SYNTHESIZE_READONLY(bool, _puffing, Puffing); 
  4. //声明成员变量数组:烟囱
  5. CC_SYNTHESIZE(CCArray *, _chimneys, Chimneys); 

关于这种用法,骨头不是很熟悉,

首先看看   CC_SYNTHESIZE(int, _type, Type); 的源码

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. #define CC_SYNTHESIZE(varType, varName, funName)\
  2. protected: varType varName;\ 
  3. public: virtual varType get##funName(void) const { return varName; }\ 
  4. public: virtual void set##funName(varType var){ varName = var; } 

应该是自动生成 set get 方法的一个宏方法。

第二个CC_SYNTHESIZE_READONLY(bool, _puffing, Puffing); 方法

应该也是自动生成 get 方法的宏方法。

这样肯定是会减少代码量,但对于骨头这种新手来说,比较乱容易弄混。使用时必须小心。

然后 h文件里还剩下几个 inline 内联方法。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. inline virtual int left() { 
  2. return this->getPositionX(); 
  3.     } 
  4. inline virtual int right() { 
  5. return this->getPositionX() + _width; 
  6.     } 

再来看下 Block.cpp 文件:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. Block::~Block () { 
  2. //安全释放
  3.     CC_SAFE_RELEASE(_chimneys); 
  4.     CC_SAFE_RELEASE(_wallTiles); 
  5.     CC_SAFE_RELEASE(_roofTiles); 
  6.     CC_SAFE_RELEASE(_puffAnimation); 
  7.     CC_SAFE_RELEASE(_puffSpawn); 
  8.     CC_SAFE_RELEASE(_puffMove); 
  9.     CC_SAFE_RELEASE(_puffFade); 
  10.     CC_SAFE_RELEASE(_puffScale); 

在贴砖类的析构方法里,使用了这种表达式的宏定义,去看下源码:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. #define CC_SAFE_DELETE(p)            do { if(p) { delete (p); (p) = 0; } } while(0)
  2. #define CC_SAFE_DELETE_ARRAY(p)     do { if(p) { delete[] (p); (p) = 0; } } while(0)
  3. #define CC_SAFE_FREE(p)                do { if(p) { free(p); (p) = 0; } } while(0)
  4. #define CC_SAFE_RELEASE(p)            do { if(p) { (p)->release(); } } while(0)
  5. #define CC_SAFE_RELEASE_NULL(p)        do { if(p) { (p)->release(); (p) = 0; } } while(0)
  6. #define CC_SAFE_RETAIN(p)            do { if(p) { (p)->retain(); } } while(0)
  7. #define CC_BREAK_IF(cond)            if(cond) break

关于 表达式的宏定义里使用 do{…}while(0),好像还有讲头:等骨头学习一阵在研究。

同样的,构造方法里是初始化一些值,create方法里是实例化 Blocl这个CCSprite对象。

初始化街区配置信息 void Block::initBlock()

先初始化不同类型的贴砖CCsprite:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. _tile1 = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName ("building_1.png"); 
  2. _tile2 = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName ("building_2.png"); 
  3. _tile3 = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName ("building_3.png"); 
  4. _tile4 = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName ("building_4.png"); 
  5. _roof1 = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName ("roof_1.png"); 
  6. _roof2 = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName ("roof_2.png"); 

然后放到两个数组里:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. int i; 
  2.     _wallTiles = CCArray::createWithCapacity(20); 
  3.     _wallTiles->retain(); 
  4.     _roofTiles = CCArray::createWithCapacity(5); 
  5.     _roofTiles->retain(); 
  6.     CCSprite * tile; 
  7. for (i = 0; i < 5; i++) { 
  8.         tile = CCSprite::createWithSpriteFrameName("roof_1.png"); 
  9.         tile->setAnchorPoint(ccp(0, 1)); 
  10.         tile->setPosition(ccp(i * _tileWidth, 0)); 
  11.         tile->setVisible(false); 
  12. this->addChild(tile, kMiddleground, kRoofTile); 
  13.         _roofTiles->addObject(tile); 
  14. for (int j = 0; j < 4; j++) { 
  15.             tile = CCSprite::createWithSpriteFrameName("building_1.png"); 
  16.             tile->setAnchorPoint(ccp(0, 1)); 
  17.             tile->setPosition(ccp(i * _tileWidth, -1 * (_tileHeight * 0.47f + j * _tileHeight))); 
  18.             tile->setVisible(false); 
  19. this->addChild(tile, kBackground, kWallTile); 
  20.             _wallTiles->addObject(tile); 
  21.         } 
  22.     } 

接下来是初始化冒烟动画:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. CCAnimation* animation; 
  2. animation = CCAnimation::create(); 
  3. CCSpriteFrame * frame; 
  4. for(i = 1; i <= 4; i++) { 
  5. char szName[100] = {0}; 
  6.     sprintf(szName, "puff_%i.png", i); 
  7.     frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(szName); 
  8.     animation->addSpriteFrame(frame); 
  9. animation->setDelayPerUnit(0.75f / 4.0f); 
  10. animation->setRestoreOriginalFrame(false); 
  11. animation->setLoops(-1); 
  12. _puffAnimation = CCAnimate::create(animation); 
  13. _puffAnimation->retain(); 

最后,创建冒烟动画:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. void Block::createPuff () { 
  2. int count = _chimneys->count(); 
  3.     CCSprite * chimney; 
  4.     CCSprite * puff; 
  5. for (int i = 0; i < count; i++) { 
  6.         chimney = (CCSprite * ) _chimneys->objectAtIndex(i); 
  7. if (chimney->isVisible()) { 
  8.             puff = (CCSprite *) chimney->getChildByTag(_puffIndex); 
  9.             puff->setVisible(true); 
  10.             puff->stopAllActions(); 
  11.             puff->setScale(1.0); 
  12.             puff->setOpacity(255); 
  13.             puff->setPosition(ccp(0,0)); 
  14.             puff->runAction((CCAction *) _puffAnimation->copy()->autorelease()); 
  15.             puff->runAction((CCAction *) _puffMove->copy()->autorelease()); 
  16. //puff->runAction((CCAction *) _puffFade->copy()->autorelease());
  17.             puff->runAction((CCAction *) _puffScale->copy()->autorelease()); 
  18.         } 
  19.     } 
  20.     _puffIndex++; 
  21. if (_puffIndex == TOTAL_PUFFS)  
  22.         _puffIndex = 0; 

=======================================================================

游戏逻辑类 GameLayer.cpp

主要有以下方法:

1 //创建游戏界面

把所有的元素加到界面中

包括 主角 地图  云彩 帽子 积分 各种按钮 等等。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. //创建游戏界面
  2. createGameScreen(); 
  3. // 显示教程
  4. void GameLayer::showTutorial (CCObject* pSender) 
  5. //重新开始游戏
  6. resetGame();  
  7. // 开始游戏方法
  8. void GameLayer::startGame (CCObject* pSender) 
  9. //主循环
  10. this->schedule(schedule_selector(GameLayer::update)); 
  11. // 处理按键
  12. void GameLayer::ccTouchesBegan(CCSet* pTouches, CCEvent* event) 

在update里,前景、背景等都是按照主角的速度的一定比例来进行向左移动:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. if (_player->getVector().x > 0) {<span style="font-family: Arial, Helvetica, sans-serif;">//一直往左移</span>
  2.      _background->setPositionX(_background->getPosition().x – _player->getVector().x * 0.25f); 
  3. float diffx; 
  4. //移完一个宽度时,重新把位置设置为接近0的位置
  5. if (_background->getPositionX() < -_background->getContentSize().width) { 
  6.          diffx = fabs(_background->getPositionX()) – _background->getContentSize().width; 
  7.          _background->setPositionX(-diffx); 
  8.      } 
  9.      _foreground->setPositionX(_foreground->getPosition().x – _player->getVector().x * 4); 
  10. if (_foreground->getPositionX() < -_foreground->getContentSize().width * 4) { 
  11.          diffx = fabs(_foreground->getPositionX()) – _foreground->getContentSize().width * 4; 
  12.          _foreground->setPositionX(-diffx); 
  13.      } 
  14. int count = _clouds->count(); 
  15.      CCSprite * cloud; 
  16. for (int i = 0; i < count; i++) { 
  17.          cloud = (CCSprite *) _clouds->objectAtIndex(i); 
  18.          cloud->setPositionX(cloud->getPositionX() – _player->getVector().x * 0.15f); 
  19. if (cloud->getPositionX() + cloud->boundingBox().size.width * 0.5f < 0 ) 
  20.              cloud->setPositionX(_screenSize.width + cloud->boundingBox().size.width * 0.5f); 
  21.      } 

update了里。整体流程使用状态机,如果是教程,则隐藏部分元素,停止游戏,显示提示,

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

  1. if (_state > kGameTutorial) { 
  2. if (_state == kGameTutorialJump) { 
  3. if (_player->getState() == kPlayerFalling && _player->getVector().y < 0) { 
  4.             _player->stopAllActions(); 
  5.             _jam->setVisible(false); 
  6.             _jam->stopAllActions(); 
  7.             _running = false; 
  8.             _tutorialLabel->setString("While in the air, tap the screen to float."); 
  9.             _state = kGameTutorialFloat; 
  10.         } 
  11.     } else if (_state == kGameTutorialFloat) { 
  12. if (_player->getPositionY() < _screenSize.height * 0.95f) { 
  13.             _player->stopAllActions(); 
  14.             _running = false; 
  15.             _tutorialLabel->setString("While floating, tap the screen again to drop."); 
  16.             _state = kGameTutorialDrop; 
  17.         } 
  18.     } else { 
  19.         _tutorialLabel->setString("That’s it. Tap the screen to play."); 
  20.         _state = kGameTutorial; 
  21.     } 

触摸方法里ccTouchesBegan,也是一系列的状态机判断。

包括整体游戏状态,包括玩家游戏中状态,包括游戏引导中德下一步。

无它

这个例子看得比较艹

毕竟跟之前的几个demo大同小异

只是地图移动那块比较麻烦

单纯阅读代码效果不好

需要自己敲敲改改加深印象

最近重玩《上古卷轴5》

现在攥钱买雪漫城的房子中

差距啊

骨头比较喜欢这种高自由度的游戏

努力把

共有 0 条评论

Top