Composer 库

Composer 库是 Corona 中官方的场景(屏幕)创建和管理系统。该库为开发者提供了一种创建和转换各个场景的简便方法。

理解场景

Composer 生命周期始于 main.lua。但是,main.lua 本身**不是** Composer 场景 — 它仅用于初始化代码,然后通过 composer.gotoScene() 启动第一个场景。在此调用中,指定要加载的场景(文件)的名称,不带 .lua 扩展名。

local composer = require( "composer" )

-- Code to initialize your app

-- Assumes that "scene1.lua" exists and is configured as a Composer scene
composer.gotoScene( "scene1" )

场景基本上只是 Lua 文件,作为单独的 .lua 文件存在于您的项目中。但是,您必须遵循一些额外的结构设置,以便 Composer 可以将它们视为场景。这包括两行初始化场景的代码、四个用于处理 Composer 生成的事件的侦听器函数、四行初始化这些侦听器函数的代码,以及一个基本的 return 语句将场景与 Composer 关联起来。请参阅下面的示例 场景模板

场景对象本身是通过调用 composer.newScene() 创建的。此对象保存 Composer 必须访问的场景的重要数据。对于开发者来说,最重要的方面是场景的 self.view 显示组 — 这是应将所有场景的可视内容添加到其中的显示组。

重要

请记住,您**必须**将场景显示对象插入到场景的 self.view 组中。如果您创建了显示对象但**未**将它们插入到此组中,它们将位于 Composer 舞台 的前面,并且不会被视为场景的一部分。对于应作为场景的一部分并由 Composer“管理”的显示对象(例如,在移除场景时清理),您必须将它们插入到场景的 self.view 组中,例如:

function scene:create( event )

    -- Assign "self.view" to local variable "sceneGroup" for easy reference
    local sceneGroup = self.view

    local rect = display.newRect( 160, 240, 200, 200 )
    -- Insert rectangle into "sceneGroup"
    sceneGroup:insert( rect )
end

场景流程/事件

四个不同的生命周期函数处理 Composer 生成的事件:

函数 生命周期点
scene:create() 在首次创建场景但尚未出现在屏幕上时发生。
scene:show() 在场景出现在屏幕上之前和/或之后立即发生。
scene:hide() 在场景退出屏幕之前和/或之后立即发生。
scene:destroy() 在场景销毁时发生。

调用 composer.gotoScene() 后,场景的生命周期开始,并遵循以下基本流程:

  1. 加载场景的 .lua 文件,并通过 composer.newScene() 创建 Composer 场景对象。此时,尚未初始化场景的视图 (self.view)。

  2. 初始化场景的视图,如果该视图**不存在**,则会将 create 事件分派到场景的 scene:create() 函数。此时,场景仍然位于“屏幕外”,因此这是创建场景所需的用户界面对象和其他显示对象的绝佳时机,包括按钮、文本、图形以及其他应在场景出现在屏幕上时显示的对象。

  3. 在场景过渡到“屏幕上”之前,会将 show 事件分派到场景的 scene:show() 函数,其中 phase 参数等于 "will"。这是重置变量值或重新定位自上次显示场景以来可能已从其预期起始位置移动的对象(如重新启动游戏关卡)的绝佳机会。

  4. 场景完全显示在屏幕上后,另一个 show 事件将分派到 scene:show() 函数,其中 phase 参数等于 "did",并且此场景现在被视为活动场景。这是启动过渡/计时器、播放特定于场景的scene:create() 中加载的音乐,以及如果您正在使用 Corona 的 physics 引擎,则启动物理模拟的好地方。

  5. 例如,如果通过转到另一个场景来退出此活动场景,则会将 hide 事件分派到场景的 scene:hide() 函数,其中 phase 参数等于 "will"。这是暂停或停止物理、取消计时器和过渡,以及停止特定于场景的scene:show() 中播放的音频的绝佳机会。

  6. 场景完全从屏幕上消失后,另一个 hide 事件将分派到 scene:hide() 函数,其中 phase 参数等于 "did"。此时,场景的视图保持初始化状态,因为默认情况下,Composer 会将隐藏/非活动场景保留在内存中,因为它假设它们可能会重新显示定期。

  7. 从这时起,如果场景被销毁,无论是通过 命令 还是由于 自动回收,都会将 destroy 事件分派到场景的 scene:destroy() 函数。这是 Composer 清理场景的显示对象(您插入到场景的 self.view 组或其子组中的任何显示对象)的时候。这也是“撤消”您在 scene:create() 中执行的操作(这些操作与场景的显示对象无关)的好时机,例如,dispose特定于场景的音频。

场景模板

以下模板可用于创建新的场景文件。请注意,此模板包含场景中所有潜在事件的侦听器函数,但您只需要包含要处理的事件的侦听器。

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()"
-- -----------------------------------------------------------------------------------




-- -----------------------------------------------------------------------------------
-- 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

end


-- 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


-- 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)

    elseif ( phase == "did" ) then
        -- Code here runs immediately after the scene goes entirely off screen

    end
end


-- destroy()
function scene:destroy( event )

    local sceneGroup = self.view
    -- Code here runs prior to the removal of scene's view

end


-- -----------------------------------------------------------------------------------
-- Scene event function listeners
-- -----------------------------------------------------------------------------------
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )
-- -----------------------------------------------------------------------------------

return scene

第一行本地化场景的 Composer 库,下一行创建场景的视图。在此之后,您应该包含任何场景范围的函数和前向声明将在场景中使用的局部变量。由于 Lua 文件只加载一次,因此除非您采取显式步骤卸载该文件,否则您在此处设置的变量将**不会**在您重新访问场景时重置。

之后是四个用于处理为场景生成的事件的函数。然后,声明事件侦听器以处理场景事件,最后,将 scene 对象返回给 Composer。

请注意,在侦听器函数中,self.view 被分配给局部变量 sceneGroup 以便于引用。如果您需要在这些函数之外引用场景的视图,只需使用 scene.view

跳转到场景

创建场景 .lua 文件后,您需要一种方法来访问它们。在 Composer 中,这是通过 composer.gotoScene() 完成的。

composer.gotoScene( sceneName )

sceneName 参数对应于 Lua 文件的名称,不带 .lua 扩展名。例如,如果您有一个名为 menu.lua 的场景文件,请使用以下命令访问该场景:

composer.gotoScene( "menu" )

此外,您可以选择将几个参数传递给 composer.gotoScene() 以控制过渡并向场景提供数据。您可以执行以下操作:

local options = {
    effect = "fade",
    time = 500,
    params = {
        someKey = "someValue",
        someOtherKey = 10
    }
}
composer.gotoScene( "menu", options )

在本例中,menu.lua 场景将使用“淡入”效果在 500 毫秒内过渡到屏幕上。还传入了一个可选的参数表。要从 menu.lua 场景访问此数据,请在目标场景的 scene:create()scene:show() 函数中访问 event.params 表。

function scene:create( event )
    local sceneGroup = self.view
    local params = event.params

    print( params.someKey )
    print( params.someOtherKey )
end
重要

由于 Composer 默认将当前场景的视图保留在内存中,因此只有在首次创建场景时才会调用 scene:create() 函数。因此,如果您传递一个参数表并尝试在 scene:create() 函数中以 event.params 的形式访问它,则它只会在首次创建场景时可用。

过渡效果

options 表的 effect 键支持以下字符串值:

  • "fade"(淡入)
  • "crossFade"(交叉淡入淡出)
  • "zoomOutIn"(缩小淡入)
  • "zoomOutInFade"(缩小淡入淡出)
  • "zoomInOut"(放大淡出)
  • "zoomInOutFade"(放大淡出淡入)
  • "flip"(翻转)
  • "flipFadeOutIn"(翻转淡出淡入)
  • "zoomOutInRotate"(缩小淡入旋转)
  • "zoomOutInFadeRotate"(缩小淡入淡出旋转)
  • "zoomInOutRotate"(放大淡出旋转)
  • "zoomInOutFadeRotate"(放大淡出淡入旋转)
  • "fromRight" — 覆盖当前场景(从右侧进入)
  • "fromLeft" — 覆盖当前场景(从左侧进入)
  • "fromTop" — 覆盖当前场景(从顶部进入)
  • "fromBottom" — 覆盖当前场景(从底部进入)
  • "slideLeft" — 将当前场景推出(向左滑动)
  • "slideRight" — 将当前场景推出(向右滑动)
  • "slideDown" — 将当前场景推出(向下滑动)
  • "slideUp" — 将当前场景推出(向上滑动)

场景管理

Composer 的一个主要特性是,在大多数情况下,它会自动管理显示对象,前提是您将它们插入到场景的视图组中。

function scene:create( event )

    -- Assign "self.view" to local variable "sceneGroup" for easy reference
    local sceneGroup = self.view

    local menuBack = display.newRect( display.contentCenterX, display.contentCenterY, 280, 360 )
    -- Insert object into "sceneGroup"
    sceneGroup:insert( menuBack )
end

在此场景被回收或移除后,Composer 将处理 menuBack 对象以及插入到场景的视图组或其子组中的其他对象的正确处置。应用于这些对象的触摸、点击和碰撞侦听器也将被移除。

重要

就像移除 Composer 之外的显示对象一样,在完成场景后,您仍然负责以下操作:

自动回收场景

默认情况下,更改场景时,Composer 会将当前场景的视图保留在内存中,如果您经常访问相同的场景,这可以提高性能。如果您希望在更改为新场景时**回收**当前场景(移除当前场景的 self.view),可以将 composer.recycleOnSceneChange 属性设置为 true

composer.recycleOnSceneChange = true

要恢复到保留场景视图(但从视图中隐藏)的默认行为,请将该属性设置为 false

composer.recycleOnSceneChange = false

务必理解,这两种情况都不会将场景从 Lua 内存中**卸载**。要从内存中显式移除场景,请使用 composer.removeScene() 调用。有关详细信息,请参阅下一节。

移除场景

如果您完全完成了场景并且不打算再次访问它们,则可以调用以下函数

例如,如果您要移除 menu.lua 的视图和场景对象,请调用

composer.removeScene( "menu" )

(可选)您可以将 shouldRecycle 的值传递为 true。这将从显示层次结构中移除场景的视图,但它将保留在 Lua 内存中。

composer.removeScene( "menu", true )

重新加载场景

重新加载场景需要稍微不同的方法。许多人希望在 scene:create() 函数中创建和定位所有场景显示对象。但是,如果您从自身重新加载场景,则**不会**再次调用此函数,因为场景的视图仍然存在。当您重新加载场景时,可能已移动的对象(即游戏中的角色)将保留在原位,并且在侦听器函数外部定义的变量将保持其当前值。例如,如果您在侦听器函数外部将 score 变量设置为 0,则在重新加载场景时它不会重置为该值。

即使您销毁场景的视图,某些变量也不会重置。虽然 scene:create()重新执行但在这种情况下,在侦听器函数外部执行的任何代码都将**不会**重新执行。

重新加载场景的最佳实践是使用中间场景。在游戏中,这通常被称为“过场动画”,它可能会向用户显示其表现的摘要、“重播”和“退出”等选项菜单。使用中间过场动画,您可以手动移除要重新加载的场景,从而在再次加载时重新开始。但是,您可能仍然需要在 scene:show() 函数的 "will" 阶段重置变量并重新定位对象。

如果您想跳过过场动画方法,可以通过调用以下命令从自身重新加载场景

local currScene = composer.getSceneName( "current" )
composer.gotoScene( currScene )

叠加场景

Composer 允许您拥有一个**叠加**场景。这是一个加载在活动场景(父场景)顶部的场景。叠加场景的构造方式与任何其他 Composer 场景一样。

显示叠加层

要显示叠加层,请通过 composer.showOverlay() 函数调用它。由于叠加场景可能不会覆盖整个屏幕,因此用户可能会与下面的父场景进行交互。为了防止这种情况,请在 options 表中将 isModal 参数设置为 true。这可以防止触摸/点击事件从叠加场景传递到父场景。

local options = {
    effect = "fade",
    time = 500,
    isModal = true
}
composer.showOverlay( "inventory", options )

加载叠加层后,它在事件处理方面的行为与任何其他场景完全相同。也就是说,scene:create()scene:show()scene:hide()scene:destroy() 将以与其他场景相同的规则被调用。

隐藏叠加层

要隐藏叠加层并返回父场景,请使用 composer.hideOverlay() 调用,例如

composer.hideOverlay( "fromBottom", 400 )

这可以从叠加场景、父场景或某些事件处理程序(如 Android 的“后退”键处理程序)中调用。尝试通过 composer.gotoScene() 转到另一个场景也会自动隐藏叠加层。

访问父场景

显示或隐藏叠加层时,您可能需要在父场景中执行操作。例如,您可能需要从叠加层收集一些输入或选择,并更新父场景的某些方面。在 Composer 中,叠加场景可以通过 event.parent 访问父场景对象。这允许您访问父场景中的函数/方法,并在显示或隐藏叠加场景时与父场景进行通信。例如

------------------------------------------------------------------------------
-- In "scene1.lua" (parent scene)
------------------------------------------------------------------------------
local composer = require( "composer" )

local scene = composer.newScene()

-- Custom function for resuming the game (from pause state)
function scene:resumeGame()
    --code to resume game
end

-- Options table for the overlay scene "pause.lua"
local options = {
    isModal = true,
    effect = "fade",
    time = 400,
    params = {
        sampleVar = "my sample variable"
    }
}

-- By some method (a pause button, for example), show the overlay
composer.showOverlay( "pause", options )

return scene

------------------------------------------------------------------------------
-- In "pause.lua"
------------------------------------------------------------------------------
local composer = require( "composer" )

local scene = composer.newScene()

function scene:hide( event )
    local sceneGroup = self.view
    local phase = event.phase
    local parent = event.parent  --reference to the parent scene object

    if ( phase == "will" ) then
        -- Call the "resumeGame()" function in the parent scene
        parent:resumeGame()
    end
end

-- By some method (a "resume" button, for example), hide the overlay
composer.hideOverlay( "fade", 400 )

scene:addEventListener( "hide", scene )
return scene

其他方法/属性

Composer 提供了一些便捷的方法和属性来帮助您进行场景管理。

设置/获取变量

您可以在 Composer 模块内部存储键值对,这应该有助于在场景之间访问数据。要设置可以从另一个 Composer 场景访问的变量,请使用 composer.setVariable() 函数,例如

composer.setVariable( "gameLevel", 1 )
composer.setVariable( "playerName", "Zorron" )

然后,要从另一个场景检索变量

local playerName = composer.getVariable( "playerName" )

获取场景名称

要获取场景的名称,请使用 composer.getSceneName() 函数并传入以下字符串值之一作为唯一必需参数

  • "current" — 获取当前场景名称
  • "previous" — 获取先前显示的场景的名称
  • "overlay" — 获取叠加场景的名称(如果它正在显示)
local currScene = composer.getSceneName( "current" )
local prevScene = composer.getSceneName( "previous" )
local overlayScene = composer.getSceneName( "overlay" )

Composer 舞台对象

composer.stage 属性返回对顶级Composer 显示组的引用,所有场景视图都插入到该组中。这可以被视为“Composer 场景层”,如果您需要在所有 Composer 场景的上方或下方放置显示对象(即使在过渡效果期间),它也很有用。例如

  • 在所有 Composer 场景后面显示并在场景转换期间保持静态的背景图像。
  • 出现在所有 Composer 场景上方的选项卡或 UI 栏。
  • HUD(平视显示器)元素,例如生命值、得分等。

调试

composer.isDebug 属性切换“Composer 调试模式”,如果设置为 true,则会在某些情况下将有用的调试信息打印到 Corona Simulator 控制台。在构建用于部署的项目之前,应将其设置为 false(默认值)。