cocos2dx实例开发之2D横版跑酷
2015-09-08 00:47
701 查看
从网上下了一点素材资源,外加自己ps一点资源,然后东拼西凑写了一个横版跑酷的小游戏
ps:csdn好不爽,无法传大点的gif,所以只好录了个短的gif,而且压缩之后凑合能看
win8.1
vs2013
cocos2dx 3.2
代码目录
游戏组成结构
主要有以下几个场景
预加载场景,用于loading等待
主菜单场景
游戏主场景(背景、移动地图、角色、拾取物)
选项界面
关于界面
这个头文件里面存储了一些定义的全局变量,比如关卡、男女角色选取等
游戏里面设置了两种关卡,分别对应了春天和冬天的地图,春天的怪物是绿水灵,冬天的怪物是蓝蘑菇,(玩过冒险岛的都知道),同时还可以选择男、女两种角色。
都是用全局变量进行控制。
游戏角色player类
角色还有奔跑动画,同时还要为角色绑定刚体,需要受物理引擎作用
游戏移动地图类
主要是地图中的砖块和怪物等元素的无限滚屏移动
首先,在初始化里面,手动设置初始地图,放置砖块、怪物、拾取物等等
然后需要启动无限滚屏
游戏主场景类
这里有游戏最重要的逻辑代码。
一开始需要初始化物理引擎
添加背景滚屏,这是底层背景,移动速度比地图移动速度慢,这样有层次感,有景深感觉,让画面更加动感
添加角色
添加地图
添加两个控制按钮
触摸检测,用于控制按钮
滑行时动态更换纹理
跳跃时给角色一个向上的速度,暂停动画,更换纹理
要注意的是,一开始只能二段跳,接了道具之后可以三段跳
碰撞检测
碰撞检测的逻辑主要是
判断碰撞的是砖块则继续奔跑
判断碰撞的是怪物,并且是踩在投上,则消除怪物,得分
判断碰撞的是拾取物,则消除拾取物,得分,如果是道具的话则获得三段跳的能力
判断碰撞是正面遇到怪物,则游戏结束
游戏结束的逻辑
正面遇到怪物
落到屏幕底下
其他逻辑
都放到update这个函数里面进行
角色被阻挡后赶到原来的位置
加分
整个demo也做了蛮久的,很多图片和图标,配色都是我自己ps的,算是策划、美术和程序一人承担了。
源码下载
csdn:跑酷游戏
github:parkour
ps:csdn好不爽,无法传大点的gif,所以只好录了个短的gif,而且压缩之后凑合能看
预览
步骤
1 工程结构
开发环境win8.1
vs2013
cocos2dx 3.2
代码目录
游戏组成结构
主要有以下几个场景
预加载场景,用于loading等待
主菜单场景
游戏主场景(背景、移动地图、角色、拾取物)
选项界面
关于界面
2 预加载场景
主要是用于图片资源,音频资源的异步预加载,然后在回调里面跳转到主菜单场景,并且添加了进度条提示void LoadingScene::loadingCallBack(Texture2D *texture) { loadedNum++; //此处的预加载帧动画指示用于实验性质 switch(loadedNum) { case 1: //预加载帧缓存纹理 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("boy.plist",texture); loadProgress->setPercentage((float)loadedNum/totalNum*100); break; case 2: //预加载帧缓存纹理 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("girl.plist",texture); loadProgress->setPercentage((float)loadedNum/totalNum*100); break; default: break; } if(loadedNum==totalNum) { //预加载帧动画 auto boyAnimation=Animation::create(); boyAnimation->setDelayPerUnit(0.1f); for(int i=1;i<=12;i++) { char str[100]={0}; sprintf(str,"boy%d.png",i); boyAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName(str)); } AnimationCache::getInstance()->addAnimation(boyAnimation,"boyAnimation"); //预加载帧动画 auto girlAnimation=Animation::create(); girlAnimation->setDelayPerUnit(0.2f); for(int i=1;i<=8;i++) girlAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("girl"+std::to_string(i)+".png")); AnimationCache::getInstance()->addAnimation(girlAnimation,"girlAnimation"); ////预加载音乐和音效 SimpleAudioEngine::getInstance()->preloadBackgroundMusic("spring_music.wav"); SimpleAudioEngine::getInstance()->preloadBackgroundMusic("winter_music.mp3"); SimpleAudioEngine::getInstance()->preloadEffect("jump.wav"); SimpleAudioEngine::getInstance()->preloadEffect("point.mp3"); SimpleAudioEngine::getInstance()->preloadEffect("gameover.wav"); //加载完毕跳转到游戏场景 auto mainMenu=MainMenu::createScene(); TransitionScene *transition=TransitionFade::create(1.0f,mainMenu); Director::getInstance()->replaceScene(transition); } }
3 主菜单场景
添加一个菜单和对应的回调函数,分别有开始游戏、选项、关于等菜单项//添加菜单 auto newGameItem=MenuItemImage::create("newgameA.png","newgameB.png",CC_CALLBACK_1(MainMenu::menuStartCallback,this)); newGameItem->setPosition(Point(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2)); auto optionItem=MenuItemImage::create("option_btn.png","option_btn.png",CC_CALLBACK_1(MainMenu::menuOptionCallback,this)); optionItem->setPosition(Point(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2-60)); auto aboutItem=MenuItemImage::create("aboutA.png","aboutB.png",CC_CALLBACK_1(MainMenu::menuAboutCallback,this)); aboutItem->setPosition(Point(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2-120)); auto menu=Menu::create(newGameItem,optionItem,aboutItem,NULL); menu->setPosition(Point::ZERO); this->addChild(menu,1);
4 游戏主场景
common头文件这个头文件里面存储了一些定义的全局变量,比如关卡、男女角色选取等
//游戏关卡枚举 enum Level { SPRING, WINTER }; //游戏角色枚举 enum PlayerType { BOY, GIRL }; //游戏角色状态 enum PlayerState { RUN, SLIDE, JUMP }; //地图元素枚举 enum BlockType { LAND, //砖块 NPC, //怪物 STAR, //星星 TOOL, //工具 NONE //空 }; //场景间隔基本单位定为80,分辨率800*480,则排满是10*6个格子,便于作碰撞检测 const float BLOCK_SIZE=80.0f; const float PICKUP_SIZE=40.0f; const float PLAYER_RADIUS=50.0f; const float GRAVITY=-1500.0f; const float PLAYER_SPEED=700.0f;为了便于计算,将屏幕设计分辨率定位800*480,一个砖块或怪物的大小是80*80,一个拾取物的大小是40*40,这样计算坐标时只需要左整数倍数的偏移就可以了。
游戏里面设置了两种关卡,分别对应了春天和冬天的地图,春天的怪物是绿水灵,冬天的怪物是蓝蘑菇,(玩过冒险岛的都知道),同时还可以选择男、女两种角色。
都是用全局变量进行控制。
游戏角色player类
class Player:public Node { public: virtual bool init() override; CREATE_FUNC(Player); public: void run(); //主角奔跑 void jump(); //主角跳跃 void slide(); //主角滑行 PlayerState playerState; //角色状态 private: Sprite *playerSprite; //奔跑的角色精灵 Sprite *playerSpriteSlideJump; //滑行和起跳的角色精灵 Animate *playerAnim; Texture2D *jumpTexture; Texture2D *slideTexture; };游戏角色分男女,具有奔跑、跳跃、滑行三种状态
角色还有奔跑动画,同时还要为角色绑定刚体,需要受物理引擎作用
playerSprite=Sprite::create(playerTextureName); //此处必须初始化一张角色纹理,否则后面无法切换纹理 jumpTexture=Sprite::create(playerJumpTexureName)->getTexture(); //创建跳跃纹理 slideTexture=Sprite::create(playerSlideTextureName)->getTexture(); //创建滑行纹理 playerAnim=Animate::create(playerAnimation); this->addChild(playerSprite); auto playerBody=PhysicsBody::createBox(playerSprite->getContentSize()); //这里要用包围盒,如果用圆形的话会导致滚动 playerBody->setDynamic(true); playerBody->setContactTestBitmask(1); playerBody->setGravityEnable(true); playerBody->getShape(0)->setRestitution(0.0f); //设置刚体回弹力 this->setPhysicsBody(playerBody);
游戏移动地图类
主要是地图中的砖块和怪物等元素的无限滚屏移动
首先,在初始化里面,手动设置初始地图,放置砖块、怪物、拾取物等等
//手动搭建地图,没有加入即时计算~ //1层 for(int i=0;i<10;i++) { if(i!=3&&i!=4&&i!=7&&i!=8) { //添加land auto block=Sprite::create(block_file); block->setPosition(BLOCK_SIZE/2+i*BLOCK_SIZE,BLOCK_SIZE/2+1*BLOCK_SIZE); this->addChild(block); block->setTag(LAND); //设置tag auto blockBody=PhysicsBody::createBox(block->getContentSize()); blockBody->setDynamic(false); blockBody->setContactTestBitmask(1); blockBody->getShape(0)->setRestitution(0); block->setPhysicsBody(blockBody); } } //2层 for(int i=0;i<10;i++) { if(i==2||i==5||i==6) { //添加怪物 auto npc=Sprite::create(npc_file); npc->setTag(NPC); npc->setPosition(BLOCK_SIZE/2+i*BLOCK_SIZE,BLOCK_SIZE/2+2*BLOCK_SIZE); auto npcBody=PhysicsBody::createBox(npc->getContentSize()); npcBody->setDynamic(false); npcBody->setContactTestBitmask(1); npcBody->getShape(0)->setRestitution(0); npc->setPhysicsBody(npcBody); this->addChild(npc); } if(i==3) { //添加land auto block=Sprite::create(block_file); block->setPosition(BLOCK_SIZE/2+i*BLOCK_SIZE,BLOCK_SIZE/2+1*BLOCK_SIZE); this->addChild(block); block->setTag(LAND); //设置tag auto blockBody=PhysicsBody::createBox(block->getContentSize()); blockBody->setDynamic(false); blockBody->setContactTestBitmask(1); blockBody->getShape(0)->setRestitution(0); block->setPhysicsBody(blockBody); } } //3层 for(int i=0;i<10;i++) { if(i!=0&&i!=3&&i!=4) { //添加星星 auto star1=Sprite::create(star_file); star1->setTag(STAR); star1->setPosition(PICKUP_SIZE/2+i*BLOCK_SIZE,BLOCK_SIZE/2+3*BLOCK_SIZE); auto starBody1=PhysicsBody::createBox(star1->getContentSize()); starBody1->setDynamic(false); starBody1->setContactTestBitmask(1); starBody1->getShape(0)->setRestitution(0.0f); star1->setPhysicsBody(starBody1); this->addChild(star1); auto star2=Sprite::create(star_file); star2->setTag(STAR); star2->setPosition(PICKUP_SIZE/2*3+i*BLOCK_SIZE,BLOCK_SIZE/2+3*BLOCK_SIZE); auto starBody2=PhysicsBody::createBox(star2->getContentSize()); starBody2->setDynamic(false); starBody2->setContactTestBitmask(1); starBody2->getShape(0)->setRestitution(0.0f); star2->setPhysicsBody(starBody2); this->addChild(star2); } } //4层 for(int i=0;i<10;i++) { if(i==3||i==4) { //添加land auto block=Sprite::create(block_file); block->setPosition(BLOCK_SIZE/2+i*BLOCK_SIZE,BLOCK_SIZE/2+4*BLOCK_SIZE); this->addChild(block); block->setTag(LAND); //设置tag auto blockBody=PhysicsBody::createBox(block->getContentSize()); blockBody->setDynamic(false); blockBody->setContactTestBitmask(1); blockBody->getShape(0)->setRestitution(0); block->setPhysicsBody(blockBody); } if(i==8) { auto star1=Sprite::create(star_file); star1->setTag(STAR); star1->setPosition(PICKUP_SIZE/2+i*BLOCK_SIZE,BLOCK_SIZE/2+3*BLOCK_SIZE); auto starBody1=PhysicsBody::createBox(star1->getContentSize()); starBody1->setDynamic(false); starBody1->setContactTestBitmask(1); starBody1->getShape(0)->setRestitution(0.0f); star1->setPhysicsBody(starBody1); this->addChild(star1); auto star2=Sprite::create(star_file); star2->setTag(STAR); star2->setPosition(PICKUP_SIZE/2*3+i*BLOCK_SIZE,BLOCK_SIZE/2+3*BLOCK_SIZE); auto starBody2=PhysicsBody::createBox(star2->getContentSize()); starBody2->setDynamic(false); starBody2->setContactTestBitmask(1); starBody2->getShape(0)->setRestitution(0.0f); star2->setPhysicsBody(starBody2); this->addChild(star2); } if(i==6) { //添加道具 auto tool=Sprite::create(tool_file); tool->setTag(TOOL); tool->setPosition(PICKUP_SIZE/2+i*BLOCK_SIZE,BLOCK_SIZE/2+3*BLOCK_SIZE); auto toolBody=PhysicsBody::createBox(tool->getContentSize()); toolBody->setDynamic(false); toolBody->setContactTestBitmask(1); toolBody->getShape(0)->setRestitution(0.0f); tool->setPhysicsBody(toolBody); this->addChild(tool); } }每个物体都绑定了刚体,便于在物理世界做碰撞检测
然后需要启动无限滚屏
//启动调度器,地图滚屏 this->schedule(schedule_selector(GameMap::mapUpdate),0.01f);目前所采用的策略是党某个node消失在左侧是,重新设置其坐标到有边缘从右往左移动,不过,更好的方法是动态的随机生成地图结点,但是算法较复杂。
void GameMap::mapUpdate(float dt) { for(auto &node:this->getChildren()) { node->setPositionX(node->getPositionX()-3.0f); if(node->getPositionX()<=-node->getContentSize().width/2) node->setPositionX(node->getPositionX()+BLOCK_SIZE/2+10*BLOCK_SIZE); } }
游戏主场景类
这里有游戏最重要的逻辑代码。
一开始需要初始化物理引擎
auto *scene=Scene::createWithPhysics(); scene->getPhysicsWorld()->setGravity(Vec2(0,GRAVITY));
添加背景滚屏,这是底层背景,移动速度比地图移动速度慢,这样有层次感,有景深感觉,让画面更加动感
backGround1=Sprite::create(backGroundFile); backGround1->setAnchorPoint(Point::ZERO); backGround1->setPosition(Point::ZERO); this->addChild(backGround1,0); backGround2=Sprite::create(backGroundFile); backGround2->setAnchorPoint(Point::ZERO); backGround2->setPosition(Point::ZERO); this->addChild(backGround2,0);
void GameScene::backGroundUpdate(float dt) { backGround1->setPositionX(backGround1->getPositionX()-1.0f); backGround2->setPositionX(backGround1->getPositionX()+backGround1->getContentSize().width); if(backGround2->getPositionX()<=0.0f) backGround1->setPositionX(0.0f); }
添加角色
//添加player player=Player::create(); player->setPosition(Point(visibleOrigin.x+2*BLOCK_SIZE,visibleOrigin.y+4*BLOCK_SIZE)); this->addChild(player,1);
添加地图
//设置地图,默认锚点在左下角 gameMap=GameMap::create(); gameMap->setPosition(visibleOrigin.x,visibleOrigin.y); this->addChild(gameMap,1);
添加两个控制按钮
//添加滑行和跳跃按钮的事件 score=0; //初始化分数 slideBtn=Sprite::create("slideButton.png"); auto slideBtnTexture1=Sprite::create("slideButton.png")->getTexture(); auto slideBtnTexture2=Sprite::create("slideButtonPress.png")->getTexture(); slideBtnTextures.pushBack(slideBtnTexture1); slideBtnTextures.pushBack(slideBtnTexture2); slideBtn->setScale(0.5); slideBtn->setPosition(Point(visibleOrigin.x+100,visibleOrigin.y+50)); this->addChild(slideBtn,2); jumpBtn=Sprite::create("jumpButton.png"); auto jumpBtnTexture1=Sprite::create("jumpButton.png")->getTexture(); auto jumpBtnTexture2=Sprite::create("jumpButtonPress.png")->getTexture(); jumpBtnTextures.pushBack(jumpBtnTexture1); jumpBtnTextures.pushBack(jumpBtnTexture2); jumpBtn->setScale(0.5); jumpBtn->setPosition(Point(visibleOrigin.x+visibleSize.width-100,visibleOrigin.y+50)); this->addChild(jumpBtn,2);
触摸检测,用于控制按钮
滑行时动态更换纹理
跳跃时给角色一个向上的速度,暂停动画,更换纹理
要注意的是,一开始只能二段跳,接了道具之后可以三段跳
bool GameScene::onTouchBegan(Touch *touch,Event *event) { auto touchPoint=touch->getLocation(); //检测是否触摸在按钮区域 if(slideBtn->getBoundingBox().containsPoint(touchPoint)) { slideBtn->setTexture(slideBtnTextures.at(1)); player->slide(); } if(jumpTimes<jumpTotal&&jumpBtn->getBoundingBox().containsPoint(touchPoint)) { if(isSound) SimpleAudioEngine::getInstance()->playEffect("jump.wav"); //播放跳跃音效 jumpBtn->setTexture(jumpBtnTextures.at(1)); player->jump(); jumpTimes++; } return true; } void GameScene::onTouchEnded(Touch *touch,Event *event) { auto touchPoint=touch->getLocation(); //判断是释放时是否在按钮区域 if(slideBtn->getBoundingBox().containsPoint(touchPoint)) { slideBtn->setTexture(slideBtnTextures.at(0)); player->run(); } if(jumpBtn->getBoundingBox().containsPoint(touchPoint)) { jumpBtn->setTexture(jumpBtnTextures.at(0)); } }
碰撞检测
bool GameScene::onContactBegin(const PhysicsContact &contact) { jumpTimes=0; //落回地面就将已跳跃次数清零 //获得被碰撞物体,getShapeA getShapeB要尝试一下 auto target=contact.getShapeA()->getBody()->getNode(); if(target->getTag()==STAR) { //碰到星星就涨分,星星消失 gameMap->moveNode(target); addScore(100); //拾取星星得100 } else if(target->getTag()==NPC&&target->getPositionY()+target->getContentSize().height/2<player->getPositionY()) //此处要用else if,只有当角色在怪物头上才能踩中 { gameMap->moveNode(target); addScore(150); //踩怪得150 } else if(target->getTag()==NPC&&target->getPositionY()+target->getContentSize().height/2>=player->getPositionY()) //如果角色正面遇到怪物就挂了 gameOver(); else if(target->getTag()==TOOL) { jumpTotal=3; auto toolIcon=Sprite::create("accelerate_state.png"); toolIcon->setPosition(Point(visibleOrigin.x+180,visibleOrigin.y+50)); this->addChild(toolIcon,2); target->removeFromParent(); //道具只出现一次,从parent里面删除 addScore(300); //道具300 } //落回地面恢复跑步状态 if(player->playerState==JUMP) player->run(); return true; }
碰撞检测的逻辑主要是
判断碰撞的是砖块则继续奔跑
判断碰撞的是怪物,并且是踩在投上,则消除怪物,得分
判断碰撞的是拾取物,则消除拾取物,得分,如果是道具的话则获得三段跳的能力
判断碰撞是正面遇到怪物,则游戏结束
游戏结束的逻辑
正面遇到怪物
落到屏幕底下
void GameScene::gameOver() { //游戏结束停止所有的调度器 gameMap->unscheduleAllSelectors(); this->unscheduleAllSelectors(); //播放游戏结束声音 if(isSound) SimpleAudioEngine::getInstance()->playEffect("gameover.wav"); //游戏结束出现菜单 visibleSize=Director::getInstance()->getVisibleSize(); visibleOrigin=Director::getInstance()->getVisibleOrigin(); auto gameOverPanel=Node::create(); auto overLabel=Sprite::create("gameover.png"); overLabel->setPosition(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2+100); gameOverPanel->addChild(overLabel); auto backItem=MenuItemImage::create("back_to_menu.png","back_to_menu_press.png",[](Object *sender) { //用lambda表达式作为菜单函数回调 auto mainMenu=MainMenu::createScene(); TransitionScene *transition=TransitionFade::create(1.0f,mainMenu); Director::getInstance()->replaceScene(transition); }); auto backMenu=Menu::createWithItem(backItem); backMenu->setPosition(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2-50); gameOverPanel->addChild(backMenu); gameOverPanel->setPositionY(visibleOrigin.y+visibleSize.height); this->addChild(gameOverPanel,3); //滑入gameover logo,注意node的锚点在左下角 gameOverPanel->runAction(MoveTo::create(0.5f,Vec2(visibleOrigin.x,visibleOrigin.y))); }
其他逻辑
都放到update这个函数里面进行
角色被阻挡后赶到原来的位置
//当角色被挡道之后跟上原来的位置 float step=2.0f; if(player->getPositionX()<2*BLOCK_SIZE) player->setPositionX(player->getPositionX()+step); if(player->getPositionX()>2*BLOCK_SIZE) player->setPositionX(player->getPositionX()-step);
加分
void GameScene::addScore(float number) { if(isSound) SimpleAudioEngine::getInstance()->playEffect("point.mp3"); //播放得分音效 score+=number; scoreLabel->setString(String::createWithFormat("score: %d",score)->_string); }
5 设置场景
设置游戏关卡、游戏角色和声音,这里懒得加xml配置文件存储。//春天 auto springItem=MenuItemImage::create("spring_icon.png","spring_icon_press.png",[](Object *sender) { //修改关卡 level=SPRING; textLevel->setString("level: Spring"); }); springItem->setPosition(visibleOrigin.x+visibleSize.width/4+35,visibleOrigin.y+visibleSize.height/4*3); //冬天 auto winterItem=MenuItemImage::create("winter_icon.png","winter_icon_press.png",[](Object *sender) { //修改关卡 level=WINTER; textLevel->setString("level: Winter"); }); winterItem->setPosition(visibleOrigin.x+visibleSize.width/4*3-35,visibleOrigin.y+visibleSize.height/4*3); //男生 auto boyItem=MenuItemImage::create("boy1.png","boy_jump.png",[](Object *sender) { //修改角色 playerType=BOY; textPlayer->setString("player: Boy"); }); boyItem->setPosition(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2-100); //女生 auto girlItem=MenuItemImage::create("girl1.png","girl_jump.png",[](Object *sender) { //修改角色 playerType=GIRL; textPlayer->setString("player: Girl"); }); girlItem->setPosition(visibleOrigin.x+visibleSize.width/2+200,visibleOrigin.y+visibleSize.height/2-100); //声音开关 auto soundItem=MenuItemImage::create("sound_on.png","sound_off.png",[](Object *sender) { //修改声音 isSound=!isSound; textSound->setString(isSound?"sound: On":"sound: Off"); }); soundItem->setPosition(visibleOrigin.x+soundItem->getContentSize().width/2,visibleOrigin.y+soundItem->getContentSize().height/2); auto menu=Menu::create(backItem,springItem,winterItem,boyItem,girlItem,soundItem,NULL); menu->setPosition(Point::ZERO); this->addChild(menu);通过全局变量与其他场景交互
6 关于场景
//添加动态背景 auto backGround=Sprite::create("about.jpg"); backGround->setPosition(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2); auto repeatAnim=MoveBy::create(3.0f,Vec2(0,50)); backGround->runAction(RepeatForever::create(Sequence::create(repeatAnim,repeatAnim->reverse(),NULL))); this->addChild(backGround,0); //添加关于告示 auto aboutLabel=Sprite::create("aboutLabel.png"); aboutLabel->setPosition(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2); this->addChild(aboutLabel,1); auto text=LabelBMFont::create("author:tashaxing\nE-mail:tashaxing@163.com\n\nWish you have fun!","bitmapFontChinese.fnt"); text->setPosition(visibleOrigin.x+visibleSize.width/2,visibleOrigin.y+visibleSize.height/2); this->addChild(text,2);一些简介
效果图
整个demo也做了蛮久的,很多图片和图标,配色都是我自己ps的,算是策划、美术和程序一人承担了。
源码下载
csdn:跑酷游戏
github:parkour
相关文章推荐
- cocos2dx骨骼动画Armature源码分析(三)
- cocos2d-x常见的报错(收集整理)
- Cocos2dx引擎笔记——基础概念(场景,层,精灵,调度器)
- Cocos2dx引擎笔记——综述
- cocos:command not found 解决方法
- Cocos2dx引擎笔记——如何让Java和C++接口互相调用:JNI使用指南
- Cocos2dx引擎笔记——Cocos2d-x xml解析
- Cocos2dx引擎笔记——Cocos2d-x 3.0 Json用法
- Cocos2dx引擎笔记——SQLite集成与用法
- Cocos2dx引擎笔记——事件分发机制
- Cocos2dx引擎笔记——内存优化
- cocos2dx 使用jni实现java调用c++
- Cocos2d-x手动绑定C++类到Lua .
- quick-cocos2d-x基于源码加密打包功能的更新策略(3)
- quick-cocos2d-x基于源码加密打包功能的更新策略(2)
- quick-cocos2d-x基于源码加密打包功能的更新策略(1)
- cocos2d-x 用cocos code IDE 打包android mac
- cocos2d-x如何切换横竖屏
- quick-cocos2d-x源文件编译及加密详解
- Android NDK: jni/Android.mk: Cannot find module with tag 'cocos2dx' in import path解决办法