背景:
最近火爆全球的遊戲flappy bird讓筆者歎為觀止,於是花了一天的時間山寨了一個一模一樣的遊戲,現在把遊戲的思路和源碼分享出來,代碼是基於javascript語言,cocos2d-x遊戲引擎,cocos2d-x editor手遊開發工具完成的,請讀者輕砸;
代碼下載:
http://www.kuaipan.cn/file/id_25348935635745384.htm?source=1
:
開發工具:
cocos2dx editor,它是開發跨平台的手機遊戲工具,運行window系統上,javascript指令碼語言,基於cocos2d-x跨平台遊戲引擎, 集合代碼編輯,情境設計,動畫製作,字型設計,還有粒子,物理系統,地圖等等的,而且調試方便,和即時類比;
cocos2dx editor介紹和教程:http://blog.csdn.net/touchsnow/article/details/19070665;
cocos2dx-editor官方部落格:http://blog.makeapp.co/?cat=8;
思路和源碼:
1 情境設計MainLayer.ccbx,如;主要分三層,開始情境、主情境、遊戲結束情境,通過顯示隱藏控制三個情境的切換。
MainLayer.ccbx代碼
<?xml version="1.0" encoding="UTF-8"?><Document jsControlled="true" jsController="MainLayer" resolution="default" > <Resolutions> <Resolution centeredOrigin="false" ext="iphone" height="1280" width="720" name="default" scale="1"/> <Resolution centeredOrigin="false" ext="iphone" height="720" width="1280" name="default1" scale="1"/> </Resolutions> <Animations> <Animation autoPlay="true" id="0" name="Default Timeline" length="10" chainedId="0" offset="0.0" position="0.0" resolution="30" scale="128"> <CallbackChannel> </CallbackChannel> <SoundChannel> </SoundChannel> </Animation> </Animations> <Layer positionX="0" positionY="0.0" sizeType="Percent" width="100" height="100" anchorPointX="0.5" anchorPointY="0.5" ignoreAnchorPoint="true" scaleX="1" scaleY="1" > <Sprite positionType="LeftBottom" width="720.0" height="1280.0" positionX="0" positionY="0" anchorPointX="0" anchorPointY="0" src="Resources/bg.png" name="" var="" target="None" scaleX="1" scaleY="1" visible="true"/> <LayerColor positionType="LeftBottom" width="720" height="1280" positionX="0" positionY="0" anchorPointX="0" anchorPointY="0" color="#fff2e8ff" visible="false"/> <Menu positionType="LeftBottom" width="40" height="40" positionX="356.0" positionY="237.0" anchorPointX="0.5" anchorPointY="0.5" scaleX="2.4" scaleY="1.725"> </Menu> <Sprite positionType="LeftBottom" width="840.0" height="281.0" positionX="0" positionY="0" anchorPointX="0" anchorPointY="0" src="Resources/ground.png" var="ground" target="Doc"/> <Node positionType="LeftBottom" width="40" height="40" positionX="800" positionY="250" anchorPointX="0" anchorPointY="0" var="hoseNode" target="Doc"> <Sprite positionType="LeftBottom" width="86.0" height="60.0" positionX="-500" positionY="400" anchorPointX="0.5" anchorPointY="0.5" src="Resources/flappy_packer.plist/bird3.png" var="test" target="Doc" visible="false"/> <Sprite positionType="LeftBottom" width="86.0" height="60.0" positionX="-550" positionY="500" anchorPointX="0.5" anchorPointY="0.5" src="Resources/flappy_packer.plist/bird1.png" var="bird" target="Doc" scaleX="1" scaleY="1" rotation="0" visible="true"/> </Node> <Node positionType="LeftBottom" width="40" height="40" positionX="303.0" positionY="500" anchorPointX="0.5" anchorPointY="0.5" var="readyNode" target="Doc" visible="true"> <Sprite positionType="LeftBottom" width="508.0" height="158.0" positionX="95.0" positionY="584.0" anchorPointX="0.5" anchorPointY="0.5" src="Resources/flappy_packer.plist/getready.png"/> <Sprite positionType="LeftBottom" width="286.0" height="246.0" positionX="73.0" positionY="236.0" anchorPointX="0.5" anchorPointY="0.5" src="Resources/flappy_packer.plist/click.png"/> </Node> <Node positionType="LeftBottom" width="40" height="40" positionX="300" positionY="500" anchorPointX="0.5" anchorPointY="0.5" var="overNode" target="Doc" visible="true"> <Sprite positionType="LeftBottom" width="590.0" height="298.0" positionX="72.0" positionY="219.0" anchorPointX="0.5" anchorPointY="0.5" src="Resources/flappy_packer.plist/base.png"> <Sprite positionType="LeftBottom" width="508.0" height="158.0" positionX="286.0" positionY="458.0" anchorPointX="0.5" anchorPointY="0.5" src="Resources/flappy_packer.plist/gameover.png"/> </Sprite> <Menu positionType="LeftBottom" width="40" height="40" positionX="0" positionY="0" anchorPointX="0.5" anchorPointY="0.5"> <MenuItem positionType="LeftBottom" width="290" height="176" positionX="-65.0" positionY="-92.0" anchorPointX="0.5" anchorPointY="0.5" normalImage="Resources/flappy_packer.plist/start.png" target="Doc" onClick="onStartClicked"/> <MenuItem positionType="LeftBottom" width="290" height="176" positionX="230.0" positionY="-92.0" anchorPointX="0.5" anchorPointY="0.5" target="Doc" normalImage="Resources/flappy_packer.plist/grade.png" onClick="onGradeClicked"/> </Menu> </Node> </Layer></Document>
2 代碼編寫MainLayer.js
首先,小鳥在向前飛,其實是底部的路和水管在向左移動,相對的你就感覺小鳥在向右飛了;路迴圈行動程式碼:
MainLayer.prototype.groundRun = function (){ var action1 = cc.MoveTo.create(0.5, cc.p(-120, 0)); var action2 = cc.MoveTo.create(0, cc.p(0, 0)); var action = cc.Sequence.create(action1, action2); this.ground.runAction(cc.RepeatForever.create(action));}
初始化高低不同的水管,每一關卡都由上下兩水管和空隙組成。總長度相同,空隙也一定,隨機取下面水管的長度,就形成錯落有致的水管關卡;
MainLayer.prototype.newHose = function (num){ var hoseHeight = 830; var acrossHeight = 300; var downHeight = 100 + getRandom(400); var upHeight = 1100 - downHeight - acrossHeight; var hoseX = 400 * num; var HoseName = FP_MAIN_TEXTURE.HOSE; var ccSpriteDown = cc.Sprite.createWithSpriteFrameName(HoseName[0]); ccSpriteDown.setZOrder(1); ccSpriteDown.setAnchorPoint(cc.p(0, 0)); ccSpriteDown.setPosition(cc.p(hoseX, 0)); ccSpriteDown.setScaleY(downHeight / hoseHeight); var ccSpriteUp = cc.Sprite.createWithSpriteFrameName(HoseName[1]); ccSpriteUp.setZOrder(1); ccSpriteUp.setAnchorPoint(cc.p(0, 0)); ccSpriteUp.setPosition(cc.p(hoseX, downHeight + acrossHeight)); ccSpriteUp.setScaleY(upHeight / hoseHeight); this.hoseNode.addChild(ccSpriteDown); this.hoseNode.addChild(ccSpriteUp); this.hoseSpriteList.push(ccSpriteDown); this.hoseSpriteList.push(ccSpriteUp); return null;}
一開始進入遊戲讓底部路不斷移動,初始化水管,顯示準備遊戲情境;
MainLayer.prototype.onEnter = function (){ cc.AnimationCache.getInstance().addAnimations("Resources/flappy_frame.plist"); this.groundRun(); this.ground.setZOrder(10); this.birdReadyAction(); this.bird.setZOrder(20); this.readyNode.setVisible(true); this.overNode.setVisible(false); for (var i = 0; i < 30; i++) { this.newHose(i); }}
點擊螢幕,小鳥向上飛60dp,然後更快的速度下落(移動動畫),同時閃動翅膀(幀動畫);
MainLayer.prototype.birdRiseAction = function (){ var riseHeight = 60; var birdX = this.bird.getPositionX(); var birdY = this.bird.getPositionY(); var time = birdY / 600; var actionFrame = cc.Animate.create(cc.AnimationCache.getInstance().getAnimation("fly")); var flyAction = cc.Repeat.create(actionFrame, 90000); var riseAction1 = cc.MoveTo.create(0.2, cc.p(birdX, birdY + riseHeight)); var riseAction2 = cc.RotateTo.create(0, -30); var riseAction = cc.Spawn.create(riseAction1, riseAction2); var fallAction1 = cc.MoveTo.create(time, cc.p(birdX, 50)); var fallAction2 = cc.Sequence.create(cc.DelayTime.create(time / 6), cc.RotateTo.create(0, 30)); var fallAction = cc.Spawn.create(fallAction1, fallAction2); this.bird.stopAllActions(); this.bird.runAction(cc.Spawn.create( cc.Sequence.create(riseAction, cc.DelayTime.create(0.1), fallAction), flyAction) );}
檢測碰撞,如果小鳥碰到地面和水管,發生碰撞,這裡碰撞直接用cocos2d-x 裡面的圖片和圖片交叉函數 cc.rectIntersectsRect;
MainLayer.prototype.checkCollision = function (){ if (this.bird.getPositionY() < 60) { cc.log("floor"); this.birdFallAction(); return; } for (var i = 0; i < this.hoseSpriteList.length; i++) { var hose = this.hoseSpriteList[i]; if (!this.isInScreen(hose)) { // continue; } if (cc.rectIntersectsRect(hose.getBoundingBox(), this.bird.getBoundingBox())) { cc.log("hose positionX==" + hose.getBoundingBox().x); cc.log("this.bird positionX==" + this.bird.getBoundingBox().x); cc.log("i==" + i); cc.log("birdFallAction"); this.birdFallAction(); return; } }}
碰撞後,小鳥先下落,遊戲結束情境顯示;
MainLayer.prototype.birdFallAction = function (){ this.gameMode = OVER; this.bird.stopAllActions(); this.ground.stopAllActions(); var birdX = this.bird.getPositionX(); var birdY = this.bird.getPositionY(); var time = birdY / 2000; this.bird.runAction(cc.Sequence.create( cc.DelayTime.create(0.1), cc.Spawn.create(cc.RotateTo.create(time, 90), cc.MoveTo.create(time, cc.p(birdX, 50)))) ); this.overNode.setVisible(true);}
遊戲的難度主要在於多個水管的移動,小鳥觸摸動畫,檢測碰撞,我把Mainlayer.js所有代碼貼出來;
FP_MAIN_TEXTURE = { FRAME_ANIMS: "beanstalk/Resources/bs_main_anims.plist", HOSE: ["holdback1.png", "holdback2.png"]}READY = 1;START = 2;OVER = 3;var MainLayer = function (){ cc.log("MainLayer"); this.bird = this.bird || {}; this.ground = this.ground || {}; this.hoseNode = this.hoseNode || {}; this.readyNode = this.readyNode || {}; this.overNode = this.overNode || {}; this.passTime = 0; this.hoseSpriteList = []; this.isStart = false; this.gameMode = READY;};MainLayer.prototype.onDidLoadFromCCB = function (){ if (sys.platform == 'browser') { this.onEnter(); } else { this.rootNode.onEnter = function () { this.controller.onEnter(); }; } this.rootNode.schedule(function (dt) { this.controller.onUpdate(dt); }); this.rootNode.onExit = function () { this.controller.onExit(); }; this.rootNode.onTouchesBegan = function (touches, event) { this.controller.onTouchesBegan(touches, event); return true; }; this.rootNode.onTouchesMoved = function (touches, event) { this.controller.onTouchesMoved(touches, event); return true; }; this.rootNode.onTouchesEnded = function (touches, event) { this.controller.onTouchesEnded(touches, event); return true; }; this.rootNode.setTouchEnabled(true);};MainLayer.prototype.onEnter = function (){ cc.AnimationCache.getInstance().addAnimations("Resources/flappy_frame.plist"); this.groundRun(); this.ground.setZOrder(10); this.birdReadyAction(); this.bird.setZOrder(20); this.readyNode.setVisible(true); this.overNode.setVisible(false); for (var i = 0; i < 30; i++) { this.newHose(i); }}MainLayer.prototype.newHose = function (num){ var hoseHeight = 830; var acrossHeight = 300; var downHeight = 100 + getRandom(400); var upHeight = 1100 - downHeight - acrossHeight; var hoseX = 400 * num; var HoseName = FP_MAIN_TEXTURE.HOSE; var ccSpriteDown = cc.Sprite.createWithSpriteFrameName(HoseName[0]); ccSpriteDown.setZOrder(1); ccSpriteDown.setAnchorPoint(cc.p(0, 0)); ccSpriteDown.setPosition(cc.p(hoseX, 0)); ccSpriteDown.setScaleY(downHeight / hoseHeight); var ccSpriteUp = cc.Sprite.createWithSpriteFrameName(HoseName[1]); ccSpriteUp.setZOrder(1); ccSpriteUp.setAnchorPoint(cc.p(0, 0)); ccSpriteUp.setPosition(cc.p(hoseX, downHeight + acrossHeight)); ccSpriteUp.setScaleY(upHeight / hoseHeight); this.hoseNode.addChild(ccSpriteDown); this.hoseNode.addChild(ccSpriteUp); this.hoseSpriteList.push(ccSpriteDown); this.hoseSpriteList.push(ccSpriteUp); return null;}MainLayer.prototype.groundRun = function (){ var action1 = cc.MoveTo.create(0.5, cc.p(-120, 0)); var action2 = cc.MoveTo.create(0, cc.p(0, 0)); var action = cc.Sequence.create(action1, action2); this.ground.runAction(cc.RepeatForever.create(action));}MainLayer.prototype.birdReadyAction = function (){ var birdX = this.bird.getPositionX(); var birdY = this.bird.getPositionY(); var time = birdY / 2000; var actionFrame = cc.Animate.create(cc.AnimationCache.getInstance().getAnimation("fly")); var flyAction = cc.Repeat.create(actionFrame, 90000); this.bird.runAction(cc.Sequence.create( flyAction) );}MainLayer.prototype.birdFallAction = function (){ this.gameMode = OVER; this.bird.stopAllActions(); this.ground.stopAllActions(); var birdX = this.bird.getPositionX(); var birdY = this.bird.getPositionY(); var time = birdY / 2000; this.bird.runAction(cc.Sequence.create( cc.DelayTime.create(0.1), cc.Spawn.create(cc.RotateTo.create(time, 90), cc.MoveTo.create(time, cc.p(birdX, 50)))) ); this.overNode.setVisible(true);}MainLayer.prototype.birdRiseAction = function (){ var riseHeight = 60; var birdX = this.bird.getPositionX(); var birdY = this.bird.getPositionY(); var time = birdY / 600; var actionFrame = cc.Animate.create(cc.AnimationCache.getInstance().getAnimation("fly")); var flyAction = cc.Repeat.create(actionFrame, 90000); var riseAction1 = cc.MoveTo.create(0.2, cc.p(birdX, birdY + riseHeight)); var riseAction2 = cc.RotateTo.create(0, -30); var riseAction = cc.Spawn.create(riseAction1, riseAction2); var fallAction1 = cc.MoveTo.create(time, cc.p(birdX, 50)); var fallAction2 = cc.Sequence.create(cc.DelayTime.create(time / 6), cc.RotateTo.create(0, 30)); var fallAction = cc.Spawn.create(fallAction1, fallAction2); this.bird.stopAllActions(); this.bird.runAction(cc.Spawn.create( cc.Sequence.create(riseAction, cc.DelayTime.create(0.1), fallAction), flyAction) );}MainLayer.prototype.onUpdate = function (dt){ if (this.gameMode != START) { return; } this.passTime += dt; this.hoseNode.setPositionX(800 - 200 * this.passTime); this.bird.setPositionX(-500 + 200 * this.passTime); this.checkCollision();}MainLayer.prototype.checkCollision = function (){ if (this.bird.getPositionY() < 60) { cc.log("floor"); this.birdFallAction(); return; } for (var i = 0; i < this.hoseSpriteList.length; i++) { var hose = this.hoseSpriteList[i]; if (!this.isInScreen(hose)) { // continue; } if (cc.rectIntersectsRect(hose.getBoundingBox(), this.bird.getBoundingBox())) { cc.log("hose positionX==" + hose.getBoundingBox().x); cc.log("this.bird positionX==" + this.bird.getBoundingBox().x); cc.log("i==" + i); cc.log("birdFallAction"); this.birdFallAction(); return; } }}MainLayer.prototype.isInScreen = function (sprite){ return (sprite.getPositionX() > 0 && sprite.getPositionX() < 720);}MainLayer.prototype.onExitClicked = function (){}MainLayer.prototype.onStartClicked = function (){ cc.Director.getInstance().resume(); cc.BuilderReader.runScene("", "MainLayer");}MainLayer.prototype.onExit = function (){ cc.log("onExit");}MainLayer.prototype.onTouchesBegan = function (touches, event){ var loc = touches[0].getLocation();}MainLayer.prototype.onTouchesMoved = function (touches, event){}MainLayer.prototype.onTouchesEnded = function (touches, event){ if (this.gameMode == OVER) { return; } if (this.gameMode == READY) { this.gameMode = START; this.readyNode.setVisible(false); } var loc = touches[0].getLocation(); this.birdRiseAction();}function isInRect(ccRect, ccTouchBeganPos){ if (ccTouchBeganPos.x > ccRect.x && ccTouchBeganPos.x < (ccRect.x + ccRect.width)) { if (ccTouchBeganPos.y > ccRect.y && ccTouchBeganPos.y < (ccRect.y + ccRect.height)) { return true; } } return false;}function getRandom(maxSize){ return Math.floor(Math.random() * maxSize) % maxSize;}
再次提示代碼:
http://www.kuaipan.cn/file/id_25348935635745384.htm?source=1