2013年5月29日
星期三常见问题解答:定时器和事件
现在是星期三,又到了解答常见问题(FAQ)的时间。以下是一些关于定时器以及 Corona SDK 中如何处理事件的常见问题解答。
1. 我尝试使用 timer.performWithDelay 在我的代码中添加延迟,但它不起作用。
该 API 不会在代码块内进行实际延迟,而是在延迟时间发生后安排回调。这就是所谓的非阻塞调用。
1 2 3 4 5 6 7 |
local function doThis( event ) print( "测试 2" ) end print( "测试 1 " ) timer.performWithDelay( 1000, doThis, 1 ) print( "测试 3" ) |
上述代码将在终端窗口中按以下顺序打印字符串:“测试 1”、“测试 3”、“测试 2”。最后一条消息在延迟安排后 1 秒(1000 毫秒)打印。
如果您希望字符串按正确的顺序打印出来,则必须将 timer.performWithDelay 调用之后的语句移动到回调例程中。
1 2 3 4 5 6 7 |
local function doThis( event ) print( "测试 2" ) print( "测试 3" ) end print( "测试 1 " ) timer.performWithDelay( 1000, doThis, 1 ) |
如果您需要延迟执行代码的某些部分,则需要在定时器回调函数中完成(如上所示)。Corona SDK 没有任何阻塞的 delay() 或 sleep() 函数。
2. 我有一个循环,其中更新屏幕上的对象,但它似乎只更新一次。
1 2 3 4 5 |
local circle = display.newCircle( 10, 20, 25 ) for i = 1, 20 do circle.x = circle.x + i circle.y = circle.y + i end |
上面的代码以非常快的速度将圆向下移动。这不是一个实际的例子,但是查看代码,您会认为圆在每次循环时都在更改位置。实际上,它只更改一次,即在代码块结束时。圆的 x 和 y 属性在每次循环时都会计算,但它的实际位置(屏幕上对象的渲染)仅在代码块执行完成后才会发生。
如果您尝试使用 setReferencePoint 在同一代码块中多次对齐对象,您还需要记住这一点。只有最后一个 setReferencePoint 生效,因为屏幕更新仅在代码块执行后才会发生。
3. 我在我的代码中进行一些长时间的计算,但我的触摸侦听器不起作用。这是怎么回事?
如果您尝试使用 for 循环或在同一代码块中循环很长时间来在代码中添加延迟,则会影响应用程序的性能。所有事件都在代码块结束后触发,因此只要您的代码块正在运行,就不会调用任何侦听器事件。所有触摸、定时器、网络等事件都会在代码块完成后发生。
如果您确实需要在代码中循环很长时间,则应将其分解为多个代码块(使用 timer.performWithDelay)以允许事件发生。
4. 我使用 timer.performWithDelay,但延迟时间似乎不正确。
延迟时间是一个近似时间,并根据 config.lua 文件中设置的每秒帧数 (FPS) 值触发。默认值为 30 FPS 或 33.33 毫秒。您也可以将其设置为 60 FPS(16.166 毫秒)。如问题 3 中所述,事件定时器在代码块末尾触发。如果代码包含超出定时器值的循环,则定时器事件可能会比预期晚发生。有一个定时器侦听器参数 event.time,它将为您提供事件最终触发的时间,您可以使用它来计算真正的延迟。
关于定时器,还有一件事应该提到。您指定的时间值以毫秒为单位,并且与默认帧速率 (30 FPS) 或您在 config.lua 中设置的值相关联。如果设置的值小于帧速率时间(16 或 33 毫秒),则定时器将每帧时间触发一次,而不是您设置的实际时间。因此,如果您将延迟时间设置为 10 毫秒,并且您使用的是默认的 30 FPS,则延迟将每 33.333 毫秒发生一次。
1 2 3 4 5 6 7 8 9 10 |
local function listener( event ) print( "定时器 ID 和时间:", event.source, event.time ) end print( "起始时间:", system.getTimer ) timer.performWithDelay( 1, listener, 1 ) timer.performWithDelay( 10, listener, 1 ) timer.performWithDelay( 25, listener, 1 ) timer.performWithDelay( 40, listener, 1 ) print( "结束时间:", system.getTimer ) |
这会在终端窗口中显示以下内容(时间以毫秒为单位)。
1 2 3 4 5 6 |
起始时间: 29.97 结束时间: 30.343 定时器ID和 时间: table: 0x11fb64b20 61.031 定时器ID和 时间: table: 0x11fb17d80 61.031 定时器ID和 时间: table: 0x1016bbd90 61.031 定时器ID和 时间: table: 0x10e534c50 93.692 |
最重要的是,定时器延迟是近似的。如果需要调整期望延迟时间和实际延迟时间之间的任何差异,可以使用 event.time。
5. 我的定时器调用在完成时不传递参数。怎么回事?
这是 Lua 新手开发者经常遇到的问题。timer.performWithDelay 期望接收一个函数的引用。这与其他期望“监听器”或“完成”引用的所有 API 常见。
1 2 3 4 5 |
local function doThis( value ) print( "Value is ", value ) end timer.performWithDelay( 500, doThis( 25 ),1 ) |
上面的代码会打印 “25”,因为 doThis( 25 ) 在延迟计划之前被调用。延迟会执行,但监听器不会被调用,因为它认为没有提供任何监听器(doThis 返回 nil)。
解决此问题的方法是使用 Lua 闭包。闭包允许您使用参数调用函数,并使其返回一个唯一的本地函数引用,该引用在使用时会使用提供的参数。
1 2 3 4 5 6 7 8 |
local function doThis( value ) return function() print( "Value is " .. value ) end end timer.performWithDelay( 500, doThis( 25 ),1 ) timer.performWithDelay( 1000, doThis( 100 ),1 ) |
从 timer.performWithDelay 调用中调用 doThis 函数会将 value 保存为 Lua upvalue,该 upvalue 与返回的函数引用相关联。 要记住的关键是,doThis( 25 ) 在延迟计划之前执行,并且函数调用返回的值用作定时器触发时监听器的地址。
使用闭包的好处是,可以使用不同的参数值从不同的位置调用它们,并且它们会返回一个使用提供的参数的唯一函数。在上面的示例中,监听器使用值 25 调用,然后稍后使用值 100 调用同一个监听器。这将在终端窗口中打印“25”,然后打印“100”。
今天的提问就到这里。希望你喜欢它们,甚至学到了一些东西。
Mo
发表于 16:51, 5月29日一如既往,精彩!如果可以的话,我有两个关于定时器的问题
1- 我被告知,与其为应用程序中使用的每个定时器提供一个句柄(以便在场景退出时销毁它),我应该只附加到一个对象,例如
local image = display.newImageRect( imagePath..”startScreen.jpg”,1024,768 )
image.timer = timer.performWithDelay( 500, doSomething,1 )
这是否是一种好的方法,因为我希望定时器在图像消失时(在场景退出时销毁)也消失?
2- 如果在本地函数中使用定时器,我真的需要给它一个句柄(以便稍后将其置为 nil)吗?还是定时器会仅仅因为它在本地函数内部而消失?我认为定时器只会在非常短的时间内(如 50 毫秒)消失,但如果我需要启动几秒钟或几分钟的定时器,那么一旦代码退出本地函数,定时器仍然应该是“活动的”,不是吗?
如您所见,经过这么长时间,我仍然对作用域感到困惑!
再次感谢您精彩的周三教程!
Mo