主角有了,下面应该参照原作让它上下微微的起伏。
在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页面的,一起写在这里了:
最终的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程序下次再说吧。