背景:
最近火爆全球的遊戲flappy bird讓筆者歎爲觀止,於是花了一天的時間山寨了一個一模一樣的遊戲,現在把遊戲的思路和源碼分享出來,代碼是基於javascript語言,cocos2d-x遊戲引擎,cocos2d-x editor手遊開發工具完成的,請讀者輕砸;
ps:運行demo必須配置好cocos2d-x editor,暫不支持其他工具。還有demo是跨平臺的,可移植運行android,ios,html5移動系統等;
Android Apk下載:(演示效果)
暫時只移植打包到android系統,可下載運行看看效果;
騰訊微雲:http://share.weiyun.com/cac18d8c58d40bf2401b3fdeeb6bcb2f
代碼下載:
代碼集中營免費下載: http://blog.makeapp.co/?p=306&preview=true
csdn下載:http://download.csdn.net/detail/touchsnow/6912707
百度雲盤:http://pan.baidu.com/s/1pJnWDb9
金山快盤 :http://www.kuaipan.cn/file/id_25348935635745384.htm?source=1
代碼如何移植到各平臺:
Android:http://blog.csdn.net/touchsnow/article/details/19176091
html5: http://blog.makeapp.co/?p=245
效果圖:
cocos2d-x editor開發工具:
cocos2dx editor,它是開發跨平臺的手機遊戲工具,運行window/mac系統上,javascript腳本語言,基於cocos2d-x跨平臺遊戲引擎, 集合代碼編輯,場景設計,動畫製作,字體設計,還有粒子,物理系統,地圖等等的,而且調試方便,和實時模擬;
cocos2dx editor 下載,介紹和教程:http://blog.csdn.net/touchsnow/article/details/19070665;
cocos2dx editor官方博客:http://blog.makeapp.co/;(請持續關注版本更新)
思路和源碼:
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;
}
再次提示代碼下載地址:
csdn下載:http://download.csdn.net/detail/touchsnow/6912707
百度雲盤:http://pan.baidu.com/s/1pJnWDb9
金山快盤 :http://www.kuaipan.cn/file/id_25348935635745384.htm?source=1
flappy博文系列:
flappy bird遊戲源代碼揭祕和下載後續---移植到android真機上
flappy bird遊戲源代碼揭祕和下載後續---移植到html5網頁瀏覽器
flappy bird遊戲源代碼揭祕和下載後續---日進5萬美元的祕訣AdMob廣告
flappy bird遊戲源代碼揭祕和下載後續---移植到蘋果ios上 (未寫)
筆者語:
想了解更多請進入官方博客,最新博客和代碼在官方博客首發;請持續關注,還有更多cocos2dx editor遊戲源碼即將放出;
聯繫筆者:[email protected](郵箱)qq羣:232361142
後言:
爲了鼓勵更多手遊愛好者加入cocos2d-x editor陣營,也爲了讀者堅定對該工具的信心,筆者基於以上的flappy bird代碼和新的圖片資源,往google play和中國應用市場發佈了一個相似的遊戲----騰飛的小鳥(fly bird)。該遊戲還加入了廣告模塊,可以獲得些許的廣告費。也希望讀者通過cocos2d-x editor可以在市場上發佈更多的好遊戲;
google play地址:https://play.google.com/store/apps/details?id=com.makeapp.game.flybird (小祕密:如果應用.apk後綴改爲.rar,你會獲得意想不到的東西)
豌豆莢市場地址:http://www.wandoujia.com/apps/com.makeapp.game.flybird
機鋒市場地址: http://apk.gfan.com/Product/App741996.html
html5在線演示:http://www.makeapp.co/flybird/ (微軟ie瀏覽器存在兼容性問題,請選擇其他瀏覽器;公司內網,小卡,見諒)