第 5 章 — 将游戏转换为 Composer

上一页 | 下一页

在上一章中,我们学习了场景管理的基础知识。在本章中,我们将转换原始游戏文件,现在是 main_original.lua,到我们的 game.lua 场景。

场景结构

正如您在上一章中所了解到的,在一个启用 Composer 的Lua 文件中有专门的位置来放置程序的不同方面。在我们最初版本的 Star Explorer 中,我们可以按照线性顺序编写代码,例如创建一个对象,将其放置在屏幕上,可能添加一个物理体,链接事件侦听器,然后继续下一个项目。

Composer 要求您以略微不同的方式思考。正确使用它需要您考虑场景生命周期函数 — scene:create()scene:show()scene:hide()scene:destroy()并且您根据场景是在屏幕上还是屏幕外运行命令。

将此概念视为电影场景:如果导演正在过渡到一个场景 — 淡入、将摄像机平移到一个点等 — 场景中的演员通常不会开始表演,直到场景“准备好”并聚焦。同样的方法也适用于 Composer 场景。例如,我们已经添加了生成小行星并使其运动的命令,但在我们将要创建的 game.lua 场景中,这些命令只会在场景完全显示在屏幕上后才会运行。

可访问代码

让我们开始吧!第一个攻击点是场景可访问的文件区域。

  1. 复制标准的scene-template.lua文件,包含在本章的 源文件 中。

  2. 将此**副本**重命名为 game.lua 并将其放在您的 StarExplorer 项目文件夹中。

  3. 在单独的编辑器窗口/选项卡中打开 main_original.luagame.lua。您将从 main_original.lua 复制几个代码块到 game.lua,因此同时打开这两个 Lua 文件会很方便。

物理设置

由于此游戏显然仍然会利用物理引擎,因此没有理由将该设置推迟到以后。将您的物理设置命令从 main_original.lua 复制并粘贴到场景可访问的game.lua 的 空间中,紧跟在场景初始化之后

local composer = require( "composer" )

local scene = composer.newScene()

-- -----------------------------------------------------------------------------------
-- Code outside of the scene event functions below will only be executed ONCE unless
-- the scene is removed entirely (not recycled) via "composer.removeScene()"
-- -----------------------------------------------------------------------------------

local physics = require( "physics" )
physics.start()
physics.setGravity( 0, 0 )
注意

之前,我们在这些命令之后使用了 math.randomseed() 命令。但是,您可能还记得我们在修改后的 main.lua 文件中重新声明了该命令,因此没有理由将其复制到 game.lua

图像表单

接下来我们需要图像表单配置。让我们将其粘贴在物理命令下方

local physics = require( "physics" )
physics.start()
physics.setGravity( 0, 0 )

-- Configure image sheet
local sheetOptions =
{
    frames =
    {
        {   -- 1) asteroid 1
            x = 0,
            y = 0,
            width = 102,
            height = 85
        },
        {   -- 2) asteroid 2
            x = 0,
            y = 85,
            width = 90,
            height = 83
        },
        {   -- 3) asteroid 3
            x = 0,
            y = 168,
            width = 100,
            height = 97
        },
        {   -- 4) ship
            x = 0,
            y = 265,
            width = 98,
            height = 79
        },
        {   -- 5) laser
            x = 98,
            y = 265,
            width = 14,
            height = 40
        },
    }
}
local objectSheet = graphics.newImageSheet( "gameObjects.png", sheetOptions )

初始变量

在图像表单设置之后,粘贴来自 main_original.lua 的以下局部变量

local objectSheet = graphics.newImageSheet( "gameObjects.png", sheetOptions )

-- Initialize variables
local lives = 3
local score = 0
local died = false

local asteroidsTable = {}

local ship
local gameLoopTimer
local livesText
local scoreText

显示组

在我们最初的版本中,我们创建了三个显示组来对我们的游戏对象进行排序和分层:backGroupmainGroupuiGroup。我们将继续使用它们,但由于我们使用的是 Composer,因此需要进行一些小的修改。

在上一章中,您学习了如何将场景对象插入到场景的**视图**组 (sceneGroup) 中。现在,一个需要理解的重要概念是显示组实际上可以插入到其他显示组中!因此,我们可以维护原始游戏中的三个显示组,**并将**它们放入场景的视图组中。

为了方便这一点,我们将推迟实际创建我们的三个组,直到我们创建场景。但是,我们现在仍然需要使用您之前学到的**前向声明**方法来定义变量。因此,不要将每个变量与 display.newGroup() 关联,暂时让它们未定义

local ship
local gameLoopTimer
local livesText
local scoreText

local backGroup
local mainGroup
local uiGroup
注意

在我们最初的版本中,我们在初始化显示组后立即创建了背景、飞船、生命文本和分数文本。现在,因为我们只为显示组创建了局部引用,所以我们必须将这些操作推迟到稍后,在 scene:create() 函数内部。

游戏函数

当我们继续 melewati titik ini di `main_original.lua`, kita sampai pada fungsi lokal yang memberdayakan fungsionalitas inti game kita. Pada dasarnya, ini dapat disalin langsung ke file `game.lua` Anda, ditempel langsung di bawah variabel yang baru saja kita definisikan.

Pertama, salin fungsi `updateText()`

local backGroup
local mainGroup
local uiGroup


local function updateText()
    livesText.text = "Lives: " .. lives
    scoreText.text = "Score: " .. score
end

Ikuti ini dengan fungsi `createAsteroid()`

local function updateText()
    livesText.text = "Lives: " .. lives
    scoreText.text = "Score: " .. score
end


local function createAsteroid()

    local newAsteroid = display.newImageRect( mainGroup, objectSheet, 1, 102, 85 )
    table.insert( asteroidsTable, newAsteroid )
    physics.addBody( newAsteroid, "dynamic", { radius=40, bounce=0.8 } )
    newAsteroid.myName = "asteroid"

    local whereFrom = math.random( 3 )

    if ( whereFrom == 1 ) then
        -- From the left
        newAsteroid.x = -60
        newAsteroid.y = math.random( 500 )
        newAsteroid:setLinearVelocity( math.random( 40,120 ), math.random( 20,60 ) )
    elseif ( whereFrom == 2 ) then
        -- From the top
        newAsteroid.x = math.random( display.contentWidth )
        newAsteroid.y = -60
        newAsteroid:setLinearVelocity( math.random( -40,40 ), math.random( 40,120 ) )
    elseif ( whereFrom == 3 ) then
        -- From the right
        newAsteroid.x = display.contentWidth + 60
        newAsteroid.y = math.random( 500 )
        newAsteroid:setLinearVelocity( math.random( -120,-40 ), math.random( 20,60 ) )
    end

    newAsteroid:applyTorque( math.random( -6,6 ) )
end

Fungsi berikutnya, `fireLaser()`, harus mengikuti

    newAsteroid:applyTorque( math.random( -6,6 ) )
end


local function fireLaser()

    local newLaser = display.newImageRect( mainGroup, objectSheet, 5, 14, 40 )
    physics.addBody( newLaser, "dynamic", { isSensor=true } )
    newLaser.isBullet = true
    newLaser.myName = "laser"

    newLaser.x = ship.x
    newLaser.y = ship.y
    newLaser:toBack()

    transition.to( newLaser, { y=-40, time=500,
        onComplete = function() display.remove( newLaser ) end
    } )
end
Penting

Sekarang amati baris tepat setelah fungsi `fireLaser()` di `main_original.lua`

ship:addEventListener( "tap", fireLaser )

Ini menciptakan fungsi event listener untuk `ship`, tetapi kita belum menciptakan `ship`! Jadi, kita harus menunda perintah ini sampai nanti, setelah pembuatan objek kapal yang sebenarnya.

Saat Anda menyalin/menempelkan beberapa fungsi berikutnya, lewati perintah yang segera mengikutinya. Secara khusus, **hilangkan baris berikut** saat Anda menyalin kode dari `main_original.lua` ke `game.lua`

ship:addEventListener( "tap", fireLaser )
ship:addEventListener( "touch", dragShip )
gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 )
Runtime:addEventListener( "collision", onCollision )

Pada dasarnya, sisa dari场景可访问的ruang Anda harus diisi sebagai berikut

local function dragShip( event )

    local ship = event.target
    local phase = event.phase

    if ( "began" == phase ) then
        -- Set touch focus on the ship
        display.currentStage:setFocus( ship )
        -- Store initial offset position
        ship.touchOffsetX = event.x - ship.x

    elseif ( "moved" == phase ) then
        -- Move the ship to the new touch position
        ship.x = event.x - ship.touchOffsetX

    elseif ( "ended" == phase or "cancelled" == phase ) then
        -- Release touch focus on the ship
        display.currentStage:setFocus( nil )
    end

    return true  -- Prevents touch propagation to underlying objects
end


local function gameLoop()

    -- Create new asteroid
    createAsteroid()

    -- Remove asteroids which have drifted off screen
    for i = #asteroidsTable, 1, -1 do
        local thisAsteroid = asteroidsTable[i]

        if ( thisAsteroid.x < -100 or
             thisAsteroid.x > display.contentWidth + 100 or
             thisAsteroid.y < -100 or
             thisAsteroid.y > display.contentHeight + 100 )
        then
            display.remove( thisAsteroid )
            table.remove( asteroidsTable, i )
        end
    end
end


local function restoreShip()

    ship.isBodyActive = false
    ship.x = display.contentCenterX
    ship.y = display.contentHeight - 100

    -- Fade in the ship
    transition.to( ship, { alpha=1, time=4000,
        onComplete = function()
            ship.isBodyActive = true
            died = false
        end
    } )
end


local function onCollision( event )

    if ( event.phase == "began" ) then

        local obj1 = event.object1
        local obj2 = event.object2

        if ( ( obj1.myName == "laser" and obj2.myName == "asteroid" ) or
             ( obj1.myName == "asteroid" and obj2.myName == "laser" ) )
        then
            -- Remove both the laser and asteroid
            display.remove( obj1 )
            display.remove( obj2 )

            for i = #asteroidsTable, 1, -1 do
                if ( asteroidsTable[i] == obj1 or asteroidsTable[i] == obj2 ) then
                    table.remove( asteroidsTable, i )
                    break
                end
            end

            -- Increase score
            score = score + 100
            scoreText.text = "Score: " .. score

        elseif ( ( obj1.myName == "ship" and obj2.myName == "asteroid" ) or
                 ( obj1.myName == "asteroid" and obj2.myName == "ship" ) )
        then
            if ( died == false ) then
                died = true

                -- Update lives
                lives = lives - 1
                livesText.text = "Lives: " .. lives

                if ( lives == 0 ) then
                    display.remove( ship )
                else
                    ship.alpha = 0
                    timer.performWithDelay( 1000, restoreShip )
                end
            end
        end
    end
end


-- -----------------------------------------------------------------------------------
-- Scene event functions
-- -----------------------------------------------------------------------------------

Kerja bagus! Sekarang kita memiliki semua场景可访问的kode yang disalin. Di beberapa bagian berikutnya, kita akan mengilustrasikan bagaimana fungsi `scene:` Composer menyatukan game kita.

创建场景

Sekarang kita perlu benar-benar **membuat** adegan itu. Untuk adegan menu di bab sebelumnya, kita membuat latar belakang, judul, dan dua tombol dalam `scene:create()`. Untuk adegan ini, kita akan membuat latar belakang, kapal, dan dua objek teks. Selain itu, kita akan menjeda mesin fisika dan benar-benar membuat tiga grup tampilan yang diperlukan untuk melapisi objek game kita.

Mari kita mulai dengan fisika. Di dalam fungsi `scene:create()`, tambahkan perintah `physics.pause()` sebagai berikut

-- -----------------------------------------------------------------------------------
-- Scene event functions
-- -----------------------------------------------------------------------------------

-- create()
function scene:create( event )

    local sceneGroup = self.view
    -- Code here runs when the scene is first created but has not yet appeared on screen

    physics.pause()  -- Temporarily pause the physics engine
end

Apa tujuan dari perintah ini pada titik ini dalam siklus hidup adegan? Ingatlah bahwa adegan permainan kita tidak benar-benar ada di layar pada saat ini dan, karena kita tidak ingin permainan dimulai dulu, kita akan segera menjeda mesin fisika. Ini memungkinkan kita untuk membuat objek, menetapkan badan fisik mereka, dan memposisikan mereka, tetapi mereka tidak akan terpengaruh secara fisik sampai kitame-restartmesin fisika.

Selanjutnya kita perlu membuat tiga grup tampilan yang sebelumnya hanya kita definisikan sebagai referensi penerusan. Tambahkan baris yang disorot berikut ini

function scene:create( event )

    local sceneGroup = self.view
    -- Code here runs when the scene is first created but has not yet appeared on screen

    physics.pause()  -- Temporarily pause the physics engine

    -- Set up display groups
    backGroup = display.newGroup()  -- Display group for the background image
    sceneGroup:insert( backGroup )  -- Insert into the scene's view group

    mainGroup = display.newGroup()  -- Display group for the ship, asteroids, lasers, etc.
    sceneGroup:insert( mainGroup )  -- Insert into the scene's view group

    uiGroup = display.newGroup()    -- Display group for UI objects like the score
    sceneGroup:insert( uiGroup )    -- Insert into the scene's view group
end

Di sini, selain membuat grup, kita juga **menyisipkan** setiap grup ke dalam grup **tampilan** adegan Composer (`sceneGroup`) menggunakan `sceneGroup:insert()`. Ini adalah bagaimana kita menanamkan grup tampilan asli kita ke dalam adegan.

Seperti yang Anda ingat, sebagian besar Solar2D menampilkan API objek menerima variabel grup tampilan yang valid sebagai pintasan sebaris yang mudah untuk memasukkan objek ke dalam grup itu. Namun, display.newGroup() adalah salah satu pengecualian untuk pintasan ini — Anda tidak bisa begitu saja memberikan referensi grup sebaris untuk memasukkan grup tampilan baru ke dalam grup yang sudah ada. Sebagai gantinya, Anda harus menggunakan perintah `object:insert()`.

Dengan grup di tempatnya, mari kita buat latar belakangnya

    -- Set up display groups
    backGroup = display.newGroup()  -- Display group for the background image
    sceneGroup:insert( backGroup )  -- Insert into the scene's view group

    mainGroup = display.newGroup()  -- Display group for the ship, asteroids, lasers, etc.
    sceneGroup:insert( mainGroup )  -- Insert into the scene's view group

    uiGroup = display.newGroup()    -- Display group for UI objects like the score
    sceneGroup:insert( uiGroup )    -- Insert into the scene's view group

    -- Load the background
    local background = display.newImageRect( backGroup, "background.png", 800, 1400 )
    background.x = display.contentCenterX
    background.y = display.contentCenterY
end
注意

Jika Anda memeriksa area kode yang dapat diakses adegan di dekat bagian atas `game.lua`, Anda akan melihat bahwa kita **tidak** menyertakan referensi penerusan ke `latar belakang` melaluilatar belakang lokal. Ini karena, setelah kita membuat dan memasukkan objek latar belakang ke dalam tampilan adegan, kita tidak perlu mengaksesnya di tempat lain dalam kode. Jadi, kita cukup membuatnya sebagai objek `lokal` di dalam fungsi `scene:create()`.

Sekarang mari kita buat kapal dan kedua objek teks

    -- Load the background
    local background = display.newImageRect( backGroup, "background.png", 800, 1400 )
    background.x = display.contentCenterX
    background.y = display.contentCenterY

    ship = display.newImageRect( mainGroup, objectSheet, 4, 98, 79 )
    ship.x = display.contentCenterX
    ship.y = display.contentHeight - 100
    physics.addBody( ship, { radius=30, isSensor=true } )
    ship.myName = "ship"

    -- Display lives and score
    livesText = display.newText( uiGroup, "Lives: " .. lives, 200, 80, native.systemFont, 36 )
    scoreText = display.newText( uiGroup, "Score: " .. score, 400, 80, native.systemFont, 36 )
end

Tidak seperti latar belakang, kita membuat tiga objek ini menggunakan **referensi penerusan** variabel — `ship`, `livesText`, dan `scoreText` — yang kita sertakan sebelumnya di场景可访问的bagian kode. Ini karena fungsi lain perlu mengetahui objek-objek ini saat permainan berjalan.

Pada dasarnya, Anda dapat membuat referensi penerusan di场景可访问的area , menetapkan objek aktual ke referensi itu di dalam fungsi `scene:`, dan kemudian fungsi lain akan mengaitkan referensi dengan objek baru.

Perhatikan bahwa kita masih memasukkan objek ke dalam grup tampilan yang tepat seperti `backGroup`, `mainGroup`, dan `uiGroup`. Ini adalah prosedur yang benar karena semua grup tersebut dimasukkan ke dalam grup **tampilan** adegan dan sekarang menjadi anak dari grup induk tersebut.

Bergerak maju — ingat bagaimana kita menunda penambahan event listener `ship` "tap" dan ` "touch"` di场景可访问的bagian karena `ship` belum ada sebagai objek yang sebenarnya? Sekarang setelah kapal **memang** ada, mari tambahkan event listener-nya

    -- Display lives and score
    livesText = display.newText( uiGroup, "Lives: " .. lives, 200, 80, native.systemFont, 36 )
    scoreText = display.newText( uiGroup, "Score: " .. score, 400, 80, native.systemFont, 36 )

    ship:addEventListener( "tap", fireLaser )
    ship:addEventListener( "touch", dragShip )
end

Itu dia! Objek adegan awal kita sekarang akan dibuat — meskipun di luar layar — tepat sebelum Composer melanjutkan ke fungsi berikutnya dalam siklus hidup adegan, `scene:show()`.

显示场景

Di adegan menu, kita tidak perlu menggunakan `scene:show()` atau fungsi pendampingnya `scene:hide()`, tetapi di adegan permainan ini kita perlu. Pada titik ini, masih ada beberapa aspek penting yang belum kita salin dari `main_original.lua` — terutama, kita belum mengaktifkan deteksi tabrakan atau memulai loop permainan untuk menelurkan asteroid. Untungnya, `scene:show()` dapat digunakan untuk menggerakkan semuanya!

过渡效果

Meskipun kita tidak menggunakannya untuk adegan menu, perintah composer.gotoScene() memungkinkan Anda untuk menentukan **efek transisi** seperti memudar, meluncur masuk dari tepi layar,cross-fadingdari adegan sebelumnya, dll. Tentu, ada durasi **waktu** yang terkait dengan awal dan akhir transisi adegan, dan di sinilah **fase adegan** ikut berperan.

场景阶段

Faktor penting untuk dipahami(berbeda dengan `scene:create()`)adalah bahwa Composer memanggil fungsi `scene:show()` **dua kali**. Tentu saja penting untuk mengetahui **kapan** masing-masing panggilan ini terjadi sehingga kita dapat mengambil tindakan yang tepat pada waktu yang tepat. Perbedaan ini diberikan melalui `event.phase` dalam `scene:show()`. Pada dasarnya, panggilan/fase `scene:show()` bekerja seperti ini

  1. Panggilan pertama terjadi ketika adegan siap untuk ditampilkan, pada dasarnya setelah setiap perintah dalam `scene:create()` telah dieksekusi. Dalam hal ini, `event.phase` adalah ` "will"`, yang secara efektif menunjukkan bahwa adegan "akan ditampilkan" dan efek transisi akan segera terjadi.

  2. Panggilan kedua terjadi segera **setelah** adegan ditampilkan — pada dasarnya, ketika transisi adegan telah selesai. Dalam hal ini, `event.phase` adalah ` "did"`, yang berarti adegan "telah ditampilkan" dan efek transisi selesai.

Perhatikan bahwa template adegan sudah berisi pernyataan kondisional untuk memeriksa setiap fase `scene:show()`

-- show()
function scene:show( event )

    local sceneGroup = self.view
    local phase = event.phase

    if ( phase == "will" ) then
        -- Code here runs when the scene is still off screen (but is about to come on screen)

    elseif ( phase == "did" ) then
        -- Code here runs when the scene is entirely on screen

    end
end

Sekarang setelah Anda memahami konsep ini, cukup tempel kode dari file `main_original.lua` Anda ke dalam klausa kondisional yang tepat. Untuk game ini, pada dasarnya kita ingin memulai permainan — menelurkan asteroid, mendeteksi tabrakan, dll. — setelah adegan sepenuhnya ditampilkan di layar(fase ` "did"`). Jadi, di file `game.lua` Anda, di dalam fungsi `scene:show()`, tambahkan tiga perintah yang disorot

-- show()
function scene:show( event )

    local sceneGroup = self.view
    local phase = event.phase

    if ( phase == "will" ) then
        -- Code here runs when the scene is still off screen (but is about to come on screen)

    elseif ( phase == "did" ) then
        -- Code here runs when the scene is entirely on screen
        physics.start()
        Runtime:addEventListener( "collision", onCollision )
        gameLoopTimer = timer.performWithDelay( 500, gameLoop, 0 )
    end
end

Pada dasarnya, perintah-perintah ini menyelesaikan hal berikut:

  1. 使用 physics.start() 重新启动物理引擎(记住我们在 scene:create() 中暂停了它)。
  2. 启动碰撞检测。
  3. 使用我们原来的 timer.performWithDelay() 命令启动游戏循环。

让我们检查一下代码的结果!保存修改后的 game.lua 文件,然后重新启动模拟器。正如预期的那样,您将看到菜单屏幕,但现在我们可以继续了。点击/单击**播放**按钮,假设您到目前为止所做的一切都正确,Composer 应该会进入 game.lua 场景,该场景的播放方式与我们最初版本的游戏相同。

隐藏场景

目前游戏中存在一个重大缺陷。当玩家的生命值耗尽时,小行星将继续积聚,并且无法重新开始游戏。这意味着我们需要调整我们的 game.lua 代码,以便当玩家的生命值耗尽时 Composer 退出场景。

当我们打算退出游戏场景时,请记住 gameLoopTimer 计时器仍在运行,会生成小行星并移除屏幕外的小行星。此外,物理引擎仍将移动小行星。所有这些都应该在 scene:hide() 函数内停止。

scene:show() 类似,scene:hide() 将被调用**两次**,并且区别再次通过 event.phase 提供。基本上,scene:hide() 调用/阶段的工作方式如下

  1. 第一次调用发生在场景即将隐藏时(过渡到屏幕外)。在这种情况下,event.phase"will",有效地指示场景“将隐藏”并且过渡效果即将发生。

  2. 第二次调用发生在场景完全离开屏幕**之后**。在这种情况下,event.phase"did",表示场景“已隐藏”并且过渡效果已完成。

对于此场景,在 scene:hide()"will""did" 阶段条件中,我们将通过添加三个命令来“撤消”一些操作

-- hide()
function scene:hide( event )

    local sceneGroup = self.view
    local phase = event.phase

    if ( phase == "will" ) then
        -- Code here runs when the scene is on screen (but is about to go off screen)
        timer.cancel( gameLoopTimer )

    elseif ( phase == "did" ) then
        -- Code here runs immediately after the scene goes entirely off screen
        Runtime:removeEventListener( "collision", onCollision )
        physics.pause()
    end
end

这些命令本质上与我们在 scene:show() 函数中所做的相反

  1. 通过取消与 gameLoopTimer 关联的计时器来停止游戏循环。
  2. 通过移除运行时事件侦听器来停止碰撞检测。
  3. 使用 physics.pause() 暂停物理引擎。

请注意我们是如何有意使用 scene:hide() 的**两个**阶段的。第一个命令可以在场景开始隐藏之前发生phase == "will"因为一旦游戏结束,我们就不需要生成新的小行星。对于其他两个命令,我们将它们推迟到场景完全离开屏幕之后,因为我们不希望剩余的小行星在场景过渡开始时突然停止检测碰撞或停止移动(physics.pause())。

场景清理

希望玩家们想再次玩游戏!默认情况下,Composer 会将场景缓存在内存中,以便在重新访问场景时节省处理能力。因此,即使此时隐藏了您的游戏场景,它也基本保持您离开时的状态。如果您再次玩游戏,场景将重新出现,并开始生成新的小行星。不幸的是,存在一些问题

根据游戏的不同,清理场景以重新开始可能需要一些工作。在这个游戏中,我们需要“撤消”我们在 scene:create() 中所做的一些事情,并删除 asteroidsTable 表中包含的对旧小行星的引用。我们还需要在 scene:show() 中重置 scorelives 和飞船的可见性。这些都不是特别复杂,但是有没有更简单的方法来重置场景呢?幸运的是,Composer 提供了一种方法

composer.removeScene( "game" )

本质上,此命令会移除并销毁 game.lua 场景,就好像它从未存在过一样。这样做,您将失去上面提到的缓存优势,但对于大多数场景来说,以编程方式单独重置每个方面是不值得的。

使用这种简化的方法,让我们修改我们的 scene:hide() 函数

-- hide()
function scene:hide( event )

    local sceneGroup = self.view
    local phase = event.phase

    if ( phase == "will" ) then
        -- Code here runs when the scene is on screen (but is about to go off screen)
        timer.cancel( gameLoopTimer )

    elseif ( phase == "did" ) then
        -- Code here runs immediately after the scene goes entirely off screen
        Runtime:removeEventListener( "collision", onCollision )
        physics.pause()
        composer.removeScene( "game" )
    end
end

此添加应该很清楚 - 我们只需调用composer.removeScene( "game" )scene:hide()"did" 阶段内,在场景完全过渡到屏幕外后有效地销毁场景。

是时候测试我们的更改了!保存您修改后的 `game.lua` 文件,然后重新启动模拟器。现在您应该能够重复玩游戏,并且每次都能获得干净的重置。

本章更加详细,需要将许多内容复制到 `game.lua` 中非常具体的位置。如果您的代码未按预期工作,请将其与本章源文件捆绑的 `game.lua` 文件进行比较。

额外补充

在本章的前面,您了解了场景**过渡效果**。我们已经将一个应用于 `endGame()` 函数中的 `composer.gotoScene()` 命令,所以现在让我们在游戏场景出现时应用一个。

如果您还记得,游戏场景只能通过菜单场景中的**播放**按钮访问,因此我们将修改该文件

  1. 在您的 `StarExplorer` 文件夹中打开 `menu.lua` 文件。

  2. 找到 `gotoGame()` 函数,并将持续时间为 800 毫秒的 `“crossFade”` 效果添加到 composer.gotoScene() 命令中

local function gotoGame()
    composer.gotoScene( "game", { time=800, effect="crossFade" } )
end
  1. 虽然我们还没有创建它,但 `highscores.lua` 场景也应该以过渡效果出现。由于 `menu.lua` 已经打开,请修改紧跟在 `gotoGame()` 之后的 `gotoHighScores()` 函数中的 composer.gotoScene() 命令
local function gotoGame()
    composer.gotoScene( "game", { time=800, effect="crossFade" } )
end

local function gotoHighScores()
    composer.gotoScene( "highscores", { time=800, effect="crossFade" } )
end
  1. 保存您修改后的 `menu.lua` 文件。

太好了!现在游戏场景 - 以及高分场景,一旦我们创建它 - 将以一个漂亮的交叉淡入淡出效果进行过渡。

章节概念

以下是我们在本章中涵盖的概念的摘要

命令/属性 描述
physics.pause() 暂停物理引擎。
object:insert() 将对象插入到组中。
timer.cancel() 取消使用 timer.performWithDelay() 启动的计时器操作。
object:removeEventListener() 从对象中移除事件侦听器。
composer.removeScene() 移除特定的 Composer 场景。