CoronaSDK场景管理库:Composer library (上)
Composer是corona官方提供的场景创建和管理系统,我大CoronaSDK果然威武!
1 场景者,何也
corona中的每个场景是以一个lua文件的形式出现,多个.lua文件分散在你的项目中组成众多场景。你编写这些lua文件的时候必须遵循一些规则,以便Composer可以正确理解你的场景代码。
场景代码文件中须得包含两样东西,以使得场景可以被正确地初始化:
- 四个监听函数来处理Composer生成的场景事件
- 四行监听函数声明以及把场景对象返回的语句
具体的例子呆会可以看后面的场景模版。
因为场景都是Lua模块,所以加载的方式首先是require()来加载到内存中,并且只能加载一次。有一件事非常重要,就是放在四个场景监听函数之外的代码,在每次场景显示和隐藏时,并不会被重复执行。
场景对象的创建,主要是通过调用compose.newScene()。这个对象保存着Composer必须访问的重要的场景数据,对于开发者来说,最重要的方面,就是场景的视图(self.view)--这是一个display group对象,场景中所有可视的内容都被添加于其中。例如像image、vector对象这些display object都必须加入这个视图(view),连同交互对象如widgets也都如此。这是关于Composer最重要的概念。
一个场景的self.view这个组对象,位于显示层级的底部。如果你创建一个display object而不加入场景的view组,他们将会处于Composer舞台的前面。
2 生命周期
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" )
一旦加载,场景的生命周期就开始了,它也将遵循下面基本规则:
- 场景文件被加载,并且一个Composer场景对象被创建。
- 场景视图被创建(如果它并不是已经存在的话),并会分发一个创建事件给场景本地的scene:create()函数。
- 如果一个场景已经存在于屏幕上,它会被隐藏、或者视情况回收或删除。
- 新的场景显示出来。
3 场景事件
四个不同的生命周期函数,来处理Composer产生的事件。
- scene:create()
- scene:show()
- scene:hide()
- scene:destory()
scene:create()
当一个场景刚被加载时,还没有一个关联的self.view产生,Composer将会分发一个事件给本地函数scene:create()。如果self.view已经存在,则create事件就会跳过。因为,默认情况下,Composer试图把self.view保持在内存里,假设你在某个时候还会返回这个场景。这样,scene:create()函数将不会在每次场景被显示时都被执行。
local composer = require( "composer" ) local scene = composer.newScene() function scene:create( event ) local sceneGroup = self.view -- Initialize the scene here. -- Example: add display objects to "sceneGroup", add touch listeners, etc. end scene:addEventListener( "create", scene ) return scene
这个scene:create函数对于下面这些事来说是一个恰好的时机:
- 创建用户界面对象
- 创建其他场景需要的显示对象,包括按钮、文字和图像
不过这并不包括native对象,比如文本输入框--这些应该在scene:show()函数里创建。
重要提示:
如果display object应该成为场景的一部分并被Composer所管理--例如当场景被删除时就清除--这些对象就必须被插入到场景的self.view里。注意下面self.view被简单赋给本地变量sceneGroup引用。
local composer = require( "composer" ) local scene = composer.newScene() function scene:create( event ) local sceneGroup = self.view local background = display.newRect( display.contentCenterX, display.contentCenterY, display.contentWidth, display.contentHeight ) background:setFillColor( 1, 0, 0.2 ) sceneGroup:insert( background ) end scene:addEventListener( "create", scene ) return scene
一旦创建完成,场景的视图便被隐藏。根据你的scene transition设置,它将处于当前存在的场景之下或甚至屏幕之外。因为这个场景还不可见的,你这时不应该开始任何timer或激活任何物理对象。同样如果你会用到场景特定的音效,你可以将它加载到内存里,而不是现在就播放它。
scene:show()
这个函数将会在场景每次被显示时被激活两次:一次是在场景正要显示出来之前,另一次在场景完全显示到屏幕上之后。
local composer = require( "composer" ) local scene = composer.newScene() function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Called when the scene is still off screen (but is about to come on screen). elseif ( phase == "did" ) then -- Called when the scene is now on screen. -- Insert code here to make the scene come alive. -- Example: start timers, begin animation, play audio, etc. end end scene:addEventListener( "show", scene ) return scene
正如你所看到,这个传给scene:show()的参数event,包含了一个phase字段--它可以取两个值:“will”或"did"。
- will:发生在场景来到屏幕上之前。这是重新摆放对象的好机会,因为场景前一次显示之后有可能这些对象的位置等属性都发生了改变。(比如重新开始一个关卡)。
- did:发生在场景完全处于屏幕上之后。这是一个开启transition或timer,播放场景音乐(scene:create()时被加载的)的好机会,如果你用到了物理引擎,这时候也适合开启物理模拟器。
scene:hide()
这个函数在每次场景被隐藏时被激活两次:一次在场景正要离开屏幕时,另一次在场景已经完全离开屏幕后。
local composer = require( "composer" ) local scene = composer.newScene() function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( event.phase == "will" ) then -- Called when the scene is on screen (but is about to go off screen). -- Insert code here to "pause" the scene. -- Example: stop timers, stop animation, stop audio, etc. elseif ( phase == "did" ) then -- Called immediately after scene goes off screen. end end scene:addEventListener( "hide", scene ) return scene
类似于show,传给scene:hide()的参数event,也有一个phase字段,分别可以取值:"will"或"did":
will:发生在当场景正要离开屏幕的时候。这是暂停或停止物理系统‘取消timer和transition、停止场景音乐的好机会。
did:这个阶段不需要太多的动作,尽管你可以在这里删除场景self.view或者甚至删除场景。
scene:destrory()
这个函数就是Composer用来清除场景中的display object(任何你插入self.view组的对象)。scene:destroy()在场景被清除前被调用。在这里,你可以undo你在scene:create()里所做的事---没有被关联到场景display object上的东西。例如,dispose()场景音乐等。
function scene:destroy( event ) local sceneGroup = self.view -- Called prior to the removal of scene's view ("sceneGroup"). -- Insert code here to clean up the scene. end
因为Composer出于性能考虑,视图保持场景在内存中,这个函数指挥在下面这些情况被调用:
- 显式调用composer.removeScene()
- Composer被设置为自动删除场景的视图
- app收到操作系统发出的低内存警告
如果你在scene:create()函数里加载音乐文件到内存里,scene:destroy()就是dispose这个音乐的好时机。同理,如果你在scene:create()里建立了数据库连接,那么在这里你可以关闭这个连接。
4 场景模版
下面这个模版就是你创建新场景文件时可以使用的。注意模版中包含的监听事件函数是场景中所有可能的事件,但是你可以只加入你想要处理的。
local composer = require( "composer" ) local scene = composer.newScene() -- ----------------------------------------------------------------------------------------------------------------- -- All code outside of the listener functions will only be executed ONCE unless "composer.removeScene()" is called. -- ----------------------------------------------------------------------------------------------------------------- -- local forward references should go here -- ------------------------------------------------------------------------------- -- "scene:create()" function scene:create( event ) local sceneGroup = self.view -- Initialize the scene here. -- Example: add display objects to "sceneGroup", add touch listeners, etc. end -- "scene:show()" function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Called when the scene is still off screen (but is about to come on screen). elseif ( phase == "did" ) then -- Called when the scene is now on screen. -- Insert code here to make the scene come alive. -- Example: start timers, begin animation, play audio, etc. end end -- "scene:hide()" function scene:hide( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Called when the scene is on screen (but is about to go off screen). -- Insert code here to "pause" the scene. -- Example: stop timers, stop animation, stop audio, etc. elseif ( phase == "did" ) then -- Called immediately after scene goes off screen. end end -- "scene:destroy()" function scene:destroy( event ) local sceneGroup = self.view -- Called prior to the removal of scene's view ("sceneGroup"). -- Insert code here to clean up the scene. -- Example: remove display objects, save state, etc. end -- ------------------------------------------------------------------------------- -- Listener setup scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) scene:addEventListener( "hide", scene ) scene:addEventListener( "destroy", scene ) -- ------------------------------------------------------------------------------- return scene
5 跳转场景
一旦你创建了场景文件,你需要一个方法去访问他们。在Composer中,就是通过composer.gotoScene(sceneName)来完成。这个sceneName参数,对应着lua文件的名字,只是不带.lua扩展名而已。例如,如果你有一个名为menu.lua的场景文件,你访问这个场景的方式就是:
composer.gotoScene( "menu" )
另外,有一些参数,你也可以传给composer.gotoScene()来控制转场动画,或传递数据给这个场景。你可以做如下事:
- 设置转场特效
- 设置转场时间
- 传一个table作为场景的参数数据
local options = { effect = "fade", time = 500, params = { someKey = "someValue", someOtherKey = 10 } } composer.gotoScene( "menu", options )
而在场景中,读取这个参数params则如下:
function scene:create( event ) local sceneGroup = self.view local params = event.params print( params.someKey ) print( params.someOtherKey ) end
重要提示:
因为Composer默认把当前场景视图保存在内存里,所以scene:create()函数只有在场景第一次被创建时被调用。这样,如果你传一个table作为参数给scene:create(),它也只能在第一次被创建时有效。
6 转场特效
"fade"
"crossFade"
"zoomOutIn"
"zoomOutInFade"
"zoomInOut"
"zoomInOutFade"
"flip"
"flipFadeOutIn"
"zoomOutInRotate"
"zoomOutInFadeRotate"
"zoomInOutRotate"
"zoomInOutFadeRotate"
"fromRight"
— over current scene"fromLeft"
— over current scene"fromTop"
— over current scene"fromBottom"
— over current scene"slideLeft"
— pushes current scene off"slideRight"
— pushes current scene off"slideDown"
— pushes current scene off"slideUp"
— pushes current scene off
7 场景管理
Composer的一个重要特性就是,大部分时候它会自动管理display object,当然前提是你把它们插入到场景的view组里:
function scene:create( event ) local sceneGroup = self.view local menuBack = display.newRect( display.contentCenterX, display.contentCenterY, 280, 360 ) sceneGroup:insert( menuBack ) end
一旦这个场景被回收或删除,Composer将会恰当地清理menuBack对象,连同其他被插入view组的对象。被应用到这些对象上的touch、tap、collision一类监听器也将会删除。
重要提示:
正如删除Composer管理之外的display object一样,你还应该在结束一个场景时对下面这些东西负责:
- 删除Runtime listener
- 取消transition和timers
- dispose你所加载的音频
- 删掉和赋nil引用给native对象
- 关闭任何你打开的文件,包括数据库
8 自动回收场景
默认情况下,当改变场景时,composer保持当前场景在内存中。假如你频繁切换场景的话,这样的确可以提升性能。如果你希望当前场景改变到一个新的场景时就回收当前场景,你可以设置composer.recycleOnSceneChange属性为true:
composer.recycleOnSceneChange = true
恢复到默认行为,让view可以在切换时被保存,则可以设置属性为false:
composer.recycleOnSceneChange = false
很重要的一点是,这两种情况都不会把场景从lua内存中卸载。为了显式从内存中删除场景,就需要使用composer.removeScene()。
9 删除场景
如果你彻底用完了一个场景,并再也不打算访问它了,你可以通过下面的API删除之:
composer.removeScene( sceneName [, shouldRecycle])
例如,如果你想删除menu.lua中的view组以及场景对象,那么调用:
composer.removeScene( "menu" )
也可以传入一个参数,把shouldRecycle设置为true。这样将会从显示层次中删除场景的view,而它仍然会保存在lua内存中:
composer.removeScene( "menu", true )
10 重新加载场景
重新加载场景需要一些不同的方式。许多人想在scene:create()函数里创建和定位所有的场景显示对象。然而如果你重新加载场景,这个函数不会再次被调用,因为场景的view已经存在了。已经移动过的对象(比如游戏中的橘色)在你重新加载的时候依然保持原位,监听函数之外定义的变量也将保持他们当前的值。例如,如果你在监听函数之外设置一个分数变量,他在你重新加载场景的时候不会重置到那个初值。
甚至如果你销毁了场景的view,那些变量也还是不会重置。就算scence:create()可以重新执行,但是监听函数以外的代码也无法重新执行。
重新加载场景最好的办法,就是使用一个中间场景。在游戏中,这是经常被用来做一个“过场动画”,也可能显示玩家表现的一个评价,再或者一个菜单选项例如“重玩”和“退出”等等。使用一个中间过度场景,你可以手动删除你想要重新加载的场景,导致你再次加载它的时候有一个新的开始。然而你仍然需要在scene:show()函数的will阶段重新设置变量和重新放置对象。
如果你想跳过过度场景方法,你可以通过调用自己的方法来重新加载一个场景:
local currScene = composer.getSceneName( "current" ) composer.gotoScene( currScene )