主角有了,下面應該參照原作讓它上下微微的起伏。
在MenuScene:ctor()方法裡面繼續添加代碼:
local function titleBirdMove(dt)
local sequence = transition.sequence({
CCMoveTo:create(0.5, CCPoint(display.cx, display.cy + 55)),
-- CCDelayTime:create(0.5),
CCMoveTo:create(0.5, CCPoint(display.cx, display.cy + 45)),
})
sprite:runAction(sequence)
end
local scheduler = require(cc.PACKAGE_NAME .. ".scheduler")
local birdUpDown = scheduler.scheduleGlobal(titleBirdMove, 0.5)
其中需要注意的是scheduler這個對象的使用是需要添加引用的,也就是local scheduler = require(cc.PACKAGE_NAME .. ".scheduler")這行代碼。
這裡很想吐槽一下quick-x的wiki文檔是在是太簡陋了。好多具體用法都不是太詳細或者幹脆沒有,好多時候都需要看源碼或者上百度查。
F5運作一下player,發現bird是上下移動了,但是沒有向前的感覺。
為什麼呢?地闆沒有移動!沒有參照物!該添加地闆,并且讓地闆不停的向左移動了。
我最開始的做法是設定一個循環方法,添加兩個地闆的對象,第二個地闆對象排列在第一個地闆對象的後面,然後兩個地闆圖檔同時向左移動,移動一個圖檔的距離之後,充值兩個圖檔的初始坐标重新開始向左移動,後來運作的時候覺得有點卡頓的感覺。上網查了些資料改成了根據逐幀的方法來改變圖檔的位置。這樣看起來效果比較平滑,圖檔切的好的話是無縫的,看不出來圖檔的切換。
local floor = display.newSprite("#move_bottom.png", display.cx, display.bottom + 30)
local floor2 = display.newSprite("#move_bottom.png", display.cx + 336, display.bottom + 30)
local function floorMove(dt) -- 地闆循環移動造成前進的感覺
if floor:getPositionX() <= -floor:getContentSize().width*0.5 then
floor:setPosition(floor:getContentSize().width*1.5 - 2, display.bottom + 30)
else floor:setPosition(floor:getPositionX() - 2, display.bottom + 30) end
if floor2:getPositionX() <= -floor2:getContentSize().width*0.5 then
floor2:setPosition(floor2:getContentSize().width*1.5 - 2, display.bottom + 30)
else floor2:setPosition(floor2:getPositionX() - 2, display.bottom + 30)
end
end
local FLOOR_MOVE = scheduler.scheduleUpdateGlobal(floorMove)
self:addChild(floor)
self:addChild(floor2)
然後加入一個按鈕,用來開始遊戲,跳轉到MainScene界面。
local function onBtnStartClicked(tag)
app:EnterMainScene()
scheduler.unscheduleGlobal(birdUpDown)
scheduler.unscheduleGlobal(FLOOR_MOVE)
end
local btnStart = ui.newImageMenuItem({
image = "#bird_start_btn.png",
imageSelected = "#brid_start_btn_pressed.png",
x = display.cx,
y = display.cy - 20,
listener = onBtnStartClicked
})
local menu = ui.newMenu({btnStart})
self:addChild(menu)
app:EnterMainScene()這個方法是寫在MyApp.lua裡面的,還有一個EnterMenuScene()方法,是用來回到Menu頁面的,一起寫在這裡了:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX90TUNdXQqJGasdkYxw2RlZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TNyETNwUDMxADNwQDM0EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
最終的Menu頁面效果如圖,點選按鈕就可以開始遊戲了
終于來到主界面了。是不是有點小激動了呢?
加入背景圖檔,這次我們選夜晚那張,和白天那張差別開來
function MainScene:ctor()
self.bg = display.newSprite("#bird_bg_night.png", display.cx, display.cy)
self:addChild(self.bg)
end
下面參照MenuScene,把界面畫好
self.getReady = display.newSprite("#get_ready.png", display.cx, display.top - 125)
self:addChild(self.getReady)
local frames = display.newFrames("bird%01d.png", 1, 3)
self.sprite = display.newSprite(frames[1])
self.sprite:zorder(3)
local animation = display.newAnimation(frames, 1 / 7) --1秒播放3幀
self.HERO_ACTION = self.sprite:playAnimationForever(animation) -- 循環播放動畫
self.sprite:setPosition(display.cx - 55, display.cy)
-- sprite:setScale(1) -- 設定精靈大小
local function titleBirdMove(dt)
local sequence = transition.sequence({
CCMoveTo:create(0.5, CCPoint(display.cx - 55, display.cy + 5)),
-- CCDelayTime:create(0.5),
CCMoveTo:create(0.5, CCPoint(display.cx - 55, display.cy - 5)),
})
self.sprite:runAction(sequence)
end
self.birdUpDown = scheduler.scheduleGlobal(titleBirdMove, 1) -- 精靈上下微動
self:addChild(self.sprite)
self.tip = display.newSprite("#bird_tip.png", display.cx, display.cy - 10)
self.tip:addTo(self)
self.floor = display.newSprite("#move_bottom.png")
self.floor:setPosition(self.floor:getContentSize().width* 0.5, display.bottom + 30)
self.floor2 = display.newSprite("#move_bottom.png")
self.floor2:setPosition(self.floor2:getContentSize().width* 1.5, display.bottom + 30)
local function floorMove(dt) -- 地闆循環移動造成前進的感覺
local sp1 = self.floor
if sp1:getPositionX() <= -sp1:getContentSize().width*0.5 then
sp1:setPosition(sp1:getContentSize().width*1.5 - 2, display.bottom + 30)
else sp1:setPosition(sp1:getPositionX() - 2, display.bottom + 30) end
local sp2 = self.floor2
if sp2:getPositionX() <= -sp2:getContentSize().width*0.5 then
sp2:setPosition(sp2:getContentSize().width*1.5 - 2, display.bottom + 30)
else sp2:setPosition(sp2:getPositionX() - 2, display.bottom + 30)
end
end
self.FLOOR_MOVE = scheduler.scheduleUpdateGlobal(floorMove)
self:addChild(self.floor, 2)
self:addChild(self.floor2, 2)
F5看一下效果:
根據原作的邏輯,在螢幕上任意地方點選一下,遊戲就開始了。那麼我們首先要添加對螢幕的觸摸監聽事件:
在MainScene:ctor()裡面添加touchLayer
-- touchLayer 用于接收觸摸事件
self.touchLayer = display.newLayer()
self:addChild(self.touchLayer)
并且添加MainScene的onEnter事件和onTouch事件
function MainScene:onEnter()
-- 注冊touch事件處理函數
self.touchLayer:addTouchEventListener(function(event, x, y)
return self:onTouch(event, x, y)
end)
self.touchLayer:setTouchEnabled(true)
end
function MainScene:onTouch(event, x, y)
if self.FIRST_TAP then
self:InitGame()
self.CollisionDetection = scheduler.scheduleUpdateGlobal(function() self:UpdateGame() end)
end
if STATE_GAME_OVER == true then
return false
end
GRAVITY = -2 -- 重力恢複
-- audio.playEffect(MUSIC.wing, false) -- 播放揮動翅膀音效
scheduler.unscheduleGlobal(self.birdUpDown)
transition.stopTarget(self.sprite)
transition.execute(self.sprite, CCRotateTo:create(0.2, -30), {
-- delay = 0.1,
easing = "backout",
onComplete = function()
local sequence = transition.sequence({
CCDelayTime:create(0.4),
CCRotateTo:create(0.6, 90)
})
self.sprite:runAction(sequence)
end,
})
self.HERO_ACTION = self.sprite:playAnimationForever(display.newAnimation(display.newFrames("bird%01d.png", 1, 3), 1 / 7))
if self.sprite:getPositionY() > display.height then
self.sprite:setPositionY(display.height + 20)
end
end
在onTouch方法裡面需要判斷是否是第一次點選螢幕,如果是的話,需要初始化一些遊戲的内容,開始添加障礙物,并且開始碰撞檢測。字型檔案是我在sample裡面随便找的,需要拷到res檔案夾裡面,一共兩個檔案,futura-48.fnt、futura-48.png。本來想用原版的字型,沒去找字型制作的軟體。虧我還把資源裡面的原版字型一個個摳出來了……
function MainScene:InitGame()
if self.FIRST_TAP ~= true then return false end
self.tip:zorder(-1)
self.getReady:zorder(-1)
self.passNum = ui.newBMFontLabel({
text = "0",
font = "futura-48.fnt",
x = display.cx,
y = display.top - 60,
})
self:addChild(self.passNum, 4)
function Gravity() -- HERO持續受到的重力
GRAVITY = GRAVITY - 0.15
self.sprite:setPositionY(self.sprite:getPositionY() + GRAVITY)
if self.sprite:getPositionY() <= 105 then -- 如果飛的過低,遊戲結束
STATE_GAME_OVER = true
end
end
self.heroGravity = scheduler.scheduleUpdateGlobal(Gravity)
local function addPipes() -- 每1.5秒在一定範圍内随機加入一對水管,但水管之間的距離不變
self.pipes_up = display.newSprite("#obstacle_up.png", display.right + 50, display.cy + 140 + math.random(0, 200))
self.pipes_down = display.newSprite("#obstacle_down.png", display.right + 50, self.pipes_up:getPositionY() - 400)
self.pipes_up:addTo(self)
self.top_pipes[#self.top_pipes + 1] = self.pipes_up
self.pipes_down:addTo(self, 1)
self.bottom_pipes[#self.bottom_pipes + 1] = self.pipes_down
end
self.ADD_PIPES = scheduler.scheduleGlobal(addPipes, 1.5)
self.FIRST_TAP = false
end
UpdateGame()方法裡面東西比較多,在裡面有很重要的方法getBoundingBox(),這個方法傳回的對象通過intersectsRect(target)方法就能實作碰撞檢測了。
實作小鳥的點選跳躍沒有使用MoveTo,而是在UpdateGame()裡面給小鳥一個持續向下的重力
GRAVITY = GRAVITY - 0.15
同時讓小鳥有一個 持續向上的移動速度,這個速度保持不變
self.sprite:setPositionY(self.sprite:getPositionY() + 5.1)
由于這個方法是逐幀實行的,是以很快向下的速度就會超過向上的速度。每次點選螢幕的時候,就初始化小鳥的重力,始向下的速度小于向上的速度,小鳥就會猛的向上飛一段距離,然後快速向下墜。
之是以沒有使用MoveTo動畫是因為這樣看起來動畫效果更平滑,而且現實中重力是遞增的,符合實體規律。
function MainScene:UpdateGame()
GRAVITY = GRAVITY - 0.15 -- HERO持續受到的重力
self.sprite:setPositionY(self.sprite:getPositionY() + GRAVITY)
if self.sprite:getPositionY() <= 105 then -- 如果飛的過低,遊戲結束
STATE_GAME_OVER = true
end
self.sprite:setPositionY(self.sprite:getPositionY() + 5.1)
local rHero = self.sprite:getBoundingBox()
for k,v in pairs(self.top_pipes) do
v:setPositionX(v:getPositionX() - 2)
local rtop_Pipes = v:getBoundingBox()
local bump = rHero:intersectsRect(rtop_Pipes)
if bump then
STATE_GAME_OVER = true
end
if v:getPositionX() < -30 then
v:removeSelf()
table.remove(self.top_pipes, k)
end
end
for k,v in pairs(self.bottom_pipes) do
v:setPositionX(v:getPositionX() - 2)
local rbottom_Pipes = v:getBoundingBox()
local bump = rHero:intersectsRect(rbottom_Pipes)
if v:getPositionX() + 20 < self.sprite:getPositionX() then
if PASS_NUMBER < 10 then
self.passNum:setString(string.format("%01d", PASS_NUMBER))
elseif PASS_NUMBER < 100 then
self.passNum:setString(string.format("%02d", PASS_NUMBER))
else
self.passNum:setString(string.format("%03d", PASS_NUMBER))
end
end
if bump then
STATE_GAME_OVER = true
end
if v:getPositionX() < -30 then
v:removeSelf()
table.remove(self.bottom_pipes, k)
PASS_NUMBER = PASS_NUMBER + 1
-- audio.playEffect(MUSIC.point, false) -- 播放得分音效
end
end
if STATE_GAME_OVER then
echoInfo("-----GAME OVER-----")
-- audio.playEffect(MUSIC.hit, false) -- 播放撞擊音效
transition.removeAction(self.HERO_ACTION) -- 停止小鳥揮動翅膀
transition.pauseTarget(self.floor) -- 停止地表移動
transition.pauseTarget(self.floor2)
transition.removeAction(self.FLAPPY) -- 停止小鳥向上飛的動畫
scheduler.unscheduleGlobal(self.ADD_PIPES) -- 停止添加水管
transition.pauseTarget(self.pipes_up) -- 停止上水管移動的動畫
transition.pauseTarget(self.pipes_down) -- 停止下水管移動的動畫
transition.pauseTarget(self.top_pipes[#self.top_pipes -1])
transition.pauseTarget(self.bottom_pipes[#self.bottom_pipes -1])
transition.moveTo(self.sprite,{y = 100, time = self.sprite:getPositionY() / 270})
scheduler.unscheduleGlobal(self.CollisionDetection)
scheduler.unscheduleGlobal(self.FLOOR_MOVE)
PASS_NUMBER = 1
function restart( )
STATE_GAME_OVER = false
app:EnterMainScene()
end
scheduler.performWithDelayGlobal(restart, 2)
end
end
其中有些動畫細節,比如每次點選螢幕,小鳥的向上旋轉,等一小段時間如果不繼續點選螢幕,小鳥向下旋轉90°什麼的也在代碼中做了處理。看代碼應該能夠明白,就不一一解釋了。
另外一些效果音播放,還有如何打包成手機上可用的apk程式下次再說吧。
遊戲難度還是挺高的……你們能得多少分呢?我最多10幾分:)