引擎版本:cocos2d-x-3.16
开发工具:vs2015
游戏介绍
StickHero也叫英雄难过棍子关,游戏玩法简单难度却不小,游戏中玩家会成为一位一直前进的勇者,但是在前进的道路上有很多陷阱在阻拦你,这是就需要我们的智慧,靠着游戏中唯一的道具我们手中的那根棍子不断的前进了。
一、玩法介绍
游戏的操作方法很简单,点击屏幕和松开屏幕,当点击屏幕后在游戏人物站立的地方会伸出一根棍子,如果点击屏幕而不松手棍子会一直变长,当松开手指棍子就会停止,然后棍子会向右边倾倒,游戏人物就可以依靠这根棍子通过陷阱。但是棍子只能变长而不能变短,所以需要玩家控制好棍子的长度。
二、游戏资源
链接: https://pan.baidu.com/s/1kU-ey4VwbDiU4kUzTMi3iQ 密码: rpqn
代码托管在github:https://github.com/smiger/StickHero
欢迎界面
欢迎界面主要有背景、标题、开始按钮、平台、英雄,实现在BackgroundLayer层
![QQ截图20180313165738.png](https://img-blog.csdnimg.cn/20181218091706311)
一、背景
资源中有五张背景图片,每次加载时随机选择一张图片作为背景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | Size MyWinSize = Director::getInstance()->getVisibleSize(); int RandomNumber = CCRANDOM_0_1()*1000; int BGI_Number = RandomNumber%5; stage_number = 1; NowStage = stage_number == 0 ? 2:stage_number-1; LastStage = NowStage ==0 ? 2:(NowStage-1); NextStage = NowStage == 2 ? 0:(NowStage+1); // 随机选择游戏欢迎界面时的背景图片 switch (BGI_Number){ case 0: Image_One = Sprite::create( "image/bg/bg1.jpg" ); Image_Two = Sprite::create( "image/bg/bg1.jpg" ); break ; case 1: Image_One = Sprite::create( "image/bg/bg2.jpg" ); Image_Two = Sprite::create( "image/bg/bg2.jpg" ); break ; case 2: Image_One = Sprite::create( "image/bg/bg3.jpg" ); Image_Two = Sprite::create( "image/bg/bg3.jpg" ); break ; case 3: Image_One = Sprite::create( "image/bg/bg4.jpg" ); Image_Two = Sprite::create( "image/bg/bg4.jpg" ); break ; case 4: Image_One = Sprite::create( "image/bg/bg5.jpg" ); Image_Two = Sprite::create( "image/bg/bg5.jpg" ); break ; default : break ; } Image_One->setPosition(MyWinSize.width/2,MyWinSize.height/2); Image_Two->setPosition(MyWinSize.width/2 + Image_Two->getContentSize().width,MyWinSize.height/2); this ->addChild(Image_One,1); this ->addChild(Image_Two,1); |
二、开始按钮
开始菜单会上下浮动,用到RepeatForever重复播放动作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //开始菜单 StartBtn = MenuItemSprite::create( Sprite::create( "image/uires_2.png" ), Sprite::create( "image/uires_2.png" ), NULL, this ,menu_selector(BackgroundLayer::Start)); StartBtn->setPosition(MyWinSize.width/2,MyWinSize.height/2+8); MoveTo* StartBtnMoveDown = MoveTo::create(2,Vec2(MyWinSize.width/2,MyWinSize.height/2-8)); MoveTo* StartBtnMoveUp = MoveTo::create(2,Vec2(MyWinSize.width/2,MyWinSize.height/2+5)); auto StartMoveSeq = Sequence::create(StartBtnMoveDown,StartBtnMoveUp,NULL); auto StartMoveRepeat = RepeatForever::create(StartMoveSeq); StartBtn->runAction(StartMoveRepeat); menu = Menu::create(StartBtn,NULL); menu->setPosition(0,0); this ->addChild(menu,2); |
用MenuItemSprite创建的菜单回调函数menu_selector(BackgroundLayer::Start)实现点击后的逻辑
三、平台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 对所有平台初始化 for ( int i=0;i<3;i++) { stage_sprite[i] = Sprite::create( "image/stage1.png" ); } stage_sprite[0]->setScaleX(30); stage_sprite[0]->setPosition(Vec2(MyWinSize.width/2, stage_sprite[0]->getContentSize().height/4)); // 另两个平台的位置 for ( int i = 1;i<3;i++) { stage_sprite[i]->setPosition(Vec2(MyWinSize.width + stage_sprite[i]->getScaleX()*stage_sprite[i]->getContentSize().width, stage_sprite[i]->getContentSize().height/2)); } for ( int i = 0;i<3;i++) { this ->addChild(stage_sprite[i],3); } |
四、英雄
1 2 3 4 5 | //添加英雄 My_Player.init(); My_Player.SetPosition(Vec2(MyWinSize.width/2,stage_sprite[0]->getContentSize().height/4*3)); this ->addChild(My_Player.getSprite(),5); My_Player.Stay(); |
英雄单独用一个类Player来实现动画和动作,在下一节讲解
英雄主角
英雄用一个单独的类Player来实现
一、加载动画
英雄的动画是做成anim1.plist,在WelcomeScene中已经加入到缓存
1 | SpriteFrameCache::getInstance()->addSpriteFramesWithFile( "image/anim1/anim1.plist" ); |
初始化将停留和行走的动画加到容器中待用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | _player = Sprite::createWithSpriteFrameName( "stay1.png" ); _player->setAnchorPoint(Vec2(0.5,0)); //1 Vector<SpriteFrame*> frameVector; for ( int i = 1;i<=5;i++) { char pngName[260] = {0}; sprintf (pngName, "stay%d.png" ,i); frameVector.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName(pngName)); } StayAnimation = Animation::createWithSpriteFrames(frameVector, 0.1f); StayAnimation->setRestoreOriginalFrame( false ); StayAnimation->setLoops(-1); //无限循环 StayAnimate = Animate::create(StayAnimation); animVector.pushBack(StayAnimate); //将动画装进容器 //2 frameVector.clear(); for ( int i = 1;i<=5;i++) { char pngName[260] = {0}; sprintf (pngName, "walk%d.png" ,i); frameVector.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName(pngName)); } WalkAnimation = Animation::createWithSpriteFrames(frameVector, 1.0f); WalkAnimation->setRestoreOriginalFrame( false ); WalkAnimation->setLoops(-1); //无限循环 WalkAnimate = Animate::create(WalkAnimation); animVector.pushBack(WalkAnimate); //将动画装进容器 |
二、实现方法
英雄主要有停留状态动画、行走动画、移动、停止动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void Player::Stay() { auto animate = animVector.at(0); _player->runAction(animate); } void Player::Stop() { _player->stopAllActions(); } void Player::Walk() { auto animate = animVector.at(1); _player->runAction(animate); } void Player::Start(Vec2 _dec) { MoveTo* move = MoveTo::create(0.2f, _dec); _player->runAction(move); } |
游戏逻辑
一、开始游戏
在欢迎界面点击开始按钮后,进入游戏状态,平台和主角会移动到指定位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void BackgroundLayer::Start(Ref* pSender) { log ( "1\start" ); //移除按钮、标题 this ->removeChild(menu); this ->removeChild(GameName); MoveTo* stageMove1 = MoveTo::create(0.2,Vec2(STAGE_SCALE,stage_sprite[0]->getContentSize().height/2)); MoveTo* playerMove = MoveTo::create(0.2,Vec2(STAGE_SCALE+stage_sprite[NowStage]->getContentSize().width*stage_sprite[NowStage]->getScaleX()/2 - My_Player.getSprite()->getContentSize().width/2 - 5,stage_sprite[0]->getContentSize().height)); stage_sprite[0]->runAction(stageMove1); My_Player.getSprite()->runAction(playerMove); isStart= true ; initStick(); addStage(); } |
初始化棍子的位置并添加下一个平台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void BackgroundLayer::addStage() { log ( "2\addStage" ); Size MyWinSize = Director::getInstance()->getVisibleSize(); stage_sprite[stage_number]->setScaleX(10+CCRANDOM_0_1()*40); stage_sprite[stage_number]->setPosition(Vec2(MyWinSize.width + stage_sprite[stage_number]->getScaleX()*stage_sprite[stage_number]->getContentSize().width, stage_sprite[stage_number]->getContentSize().height/2)); MoveTo* stageMove = MoveTo::create(0.2, Vec2(MyWinSize.width/2 + CCRANDOM_0_1()*MyWinSize.width/3, stage_sprite[stage_number]->getContentSize().height/2)); stage_sprite[stage_number]->runAction(stageMove); log ( "stage_number=%d" ,stage_number); if (stage_number+1<=2) { stage_number+=1; } else { stage_number = 0; } //NowStage的编号是[0,2]对应stage_number的编号是[1,3] NowStage = stage_number == 0 ? 2:stage_number-1; LastStage = NowStage ==0 ? 2:(NowStage-1); NextStage = NowStage == 2 ? 0:(NowStage+1); } |
二、游戏中
1、游戏是通过触摸屏幕进行交互的,所以在初始化时添加单点触摸事件
1 2 3 4 5 6 7 8 | // 单点触摸事件 touch_listener = EventListenerTouchOneByOne::create(); touch_listener->setSwallowTouches( true ); touch_listener->onTouchBegan = CC_CALLBACK_2(BackgroundLayer::onTouchBegan, this ); touch_listener->onTouchMoved = CC_CALLBACK_2(BackgroundLayer::onTouchMoved, this ); touch_listener->onTouchEnded = CC_CALLBACK_2(BackgroundLayer::onTouchEnded, this ); touch_listener->onTouchCancelled = CC_CALLBACK_2(BackgroundLayer::onTouchCancelled, this ); _eventDispatcher->addEventListenerWithSceneGraphPriority(touch_listener, this ); |
触摸开始时,使棍子变长,触摸结束时停止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | bool BackgroundLayer::onTouchBegan(Touch* pTouch, Event* pEvent) { log ( "4\onTouchBegan" ); if (isStart){ addStick(); } else { return false ; } isStart = false ; return true ; } void BackgroundLayer::onTouchEnded(Touch* pTouch, Event* pEvent) { log ( "5\onTouchEnded" ); StopStick(); RotateStickAndGo(); } |
2、棍子变长使用了schedule定时器,触摸开始棍子一直变长直到触摸结束
1 2 3 4 5 6 7 8 9 10 11 | void BackgroundLayer::addStick() { log ( "6\addStick" ); // 棍子设置在平台的右上角那个点上 stick->setPosition(StickPoint); this ->schedule(schedule_selector(BackgroundLayer::StickLength)); } void BackgroundLayer::StickLength( float ) { stick->setScaleY(stick->getScaleY()+5); } |
当结束触摸时,记录下棍子的长度并结束定时器
1 2 3 4 5 6 | void BackgroundLayer::StopStick() { // 存储棍子的长度 TouchLength = stick->getContentSize().height*stick->getScaleY(); this ->unschedule(schedule_selector(BackgroundLayer::StickLength)); } |
3、判断是否成功到下一个平台还是掉落失败
只要棍子末端在下个平台之上则进入下个平台,否则游戏结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | void BackgroundLayer::RotateStickAndGo() { log ( "7\RotateStickAndGo" ); // 算出棍子到下个平台的最小距离、最大距离 DestLengthMin = abs (stage_sprite[LastStage]->getPositionX() - stage_sprite[NowStage]->getPositionX()) - stage_sprite[LastStage]->getContentSize().width*stage_sprite[LastStage]->getScaleX()/2 - stage_sprite[NowStage]->getContentSize().width*stage_sprite[NowStage]->getScaleX()/2; DestLengthMax = DestLengthMin + stage_sprite[NowStage]->getContentSize().width*stage_sprite[NowStage]->getScaleX(); CallFunc* MoveToNext = CallFunc::create(CC_CALLBACK_0(BackgroundLayer::PlayerMoveToNextStage, this )); RotateTo* Ro_Stick = RotateTo::create(0.5, 90); //旋转90度 Sequence* GOGO = Sequence::create(Ro_Stick,MoveToNext, NULL); CallFunc* GoCallBack = CallFunc::create(CC_CALLBACK_0(BackgroundLayer::PlayerMove, this )); Sequence* StickDown = Sequence::create(Ro_Stick,GoCallBack,NULL); log ( "TouchLength=%d,DestLengthMin=%d,DestLengthMax=%d" , TouchLength,DestLengthMin,DestLengthMax); log ( "LastStage=%d,NowStage=%d" , LastStage,NowStage); // 判断计算出的棍子长度是不是在我们最大距离和最小距离之间 if (TouchLength<DestLengthMin || TouchLength > DestLengthMax) { stick->runAction(StickDown); } else if (TouchLength >= DestLengthMin && TouchLength <=DestLengthMax) { stick->runAction(GOGO); } } |
安全到达下个平台,英雄动画变为行走状态移动到下个平台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void BackgroundLayer::StageAndPlayerMove() { log ( "8\StageAndPlayerMove" ); Vec2 dest; dest.x = stage_sprite[NowStage]->getPosition().x + stage_sprite[NowStage]->getContentSize().width*stage_sprite[NowStage]->getScaleX()/2 - My_Player.getSprite()->getContentSize().width/2 - 5; dest.y = stage_sprite[NowStage]->getContentSize().height; float dist = dest.x - My_Player.getSprite()->getPosition().x; log ( "dist=%f" , dist); MoveTo* playermove = MoveTo::create(dist / MOVE_SPEED, dest); My_Player.Stop(); My_Player.Walk(); CallFunc* moveCallBack = CallFunc::create(CC_CALLBACK_0(BackgroundLayer::stageMove, this )); CallFunc* moveCallBack1 = CallFunc::create([&]{isStart = true ;}); CallFunc* moveCallBack2 = CallFunc::create([&]{ My_Player.Stay(); }); Sequence* Move = Sequence::create(playermove,moveCallBack,moveCallBack1,moveCallBack2,NULL); My_Player.getSprite()->runAction(Move); } |
如果失败,则执行掉落动作,进入游戏结束界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void BackgroundLayer::PlayerDown() { log ( "11\PlayerDown" ); MoveBy* Down = MoveBy::create(0.1,Vec2(0,-800)); My_Player.getSprite()->runAction(Down); RotateTo* Ro_Stick = RotateTo::create(0.5, 180); //旋转180度 Sequence* StickDown = Sequence::create(Ro_Stick, NULL); stick->runAction(StickDown); over = GameOverLayer::create(); this ->addChild(over,8); } void BackgroundLayer::PlayerMove() { log ( "12\PlayerMove" ); MoveBy* GO = MoveBy::create(( float )TouchLength/MOVE_SPEED,Vec2(TouchLength,0)); CallFunc* DownCallBack = CallFunc::create(CC_CALLBACK_0(BackgroundLayer::PlayerDown, this )); Sequence* Goon = Sequence::create(GO,DownCallBack,NULL); My_Player.getSprite()->runAction(Goon); } |
结束语
本实例开发教程到这里就结束了,代码来源于网上,该实例仅供学习参考!
代码托管在github:https://github.com/smiger/StickHero