Corona中文站

强大、易学的跨平台(iOS/Android)开发框架。QQ群1:74390406(满) 群2:221929599

导航

五分钟学会Corona(七) - 基本运动与变换
关于Corona SDK最强大的一件事,就是任何display object,都可以动画之。这足以证明Corona的图像模型的超级灵活性。

动画允许你创建一个视觉丰富,引人入胜的使用体验。动画是通过生成一个帧序列来完成的,在帧到帧之间平滑的演变。术语“补间”(简称 inbetween)就是说这种中间帧的生成过程的。它通常用来略称,一个对象在动画期间一个属性的改变,比如位置补间。

变换

transition允许你很容易的只用一行代码来创建动画,这通过允许你补间一个display object的一个或多个属性来实现。例如你可以淡出一个display object,通过补间其alpha属性(这个alpha属性从1.0到0)。

做到这一点最简单的方法是,用 transition.to 方法,第一个参数是一个display object,第二个参数是一个table包含控制参数。这个控制参数指定了动画的时间,动画开始的延时(可选),这个display object最终的属性值。一个属性的中间值,是由一个指定的变化函数作为控制参数产生的。默认是一个线性函数。

下面是一些关于如何让一个矩形动画化的例子。(see Transition2 in the sample code):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16
local square = display.newRect( 0, 0, 100, 100 )

square:setFillColor( 255,255,255 )



local w,h = display.contentWidth, display.contentHeight



local square = display.newRect( 0, 0, 100, 100 )

square:setFillColor( 255,255,255 )



local w,h = display.contentWidth, display.contentHeight



-- (1) 移动square到右下角;大小减半

-- 以square的中心为本地原点; 淡出

transition.to( square, { time=1500, alpha=0, x=(w-50), y=(h-50) } )



-- (2) 在2.5秒后, square 淡入回来

transition.to( square, { time=500, delay=2500, alpha=1.0 } )


在第一个补间中,请注意多个值会改变:位置和alpha值。这是因为在控制参数中我们指明,x、y和alpha属性的最终值。对控制参数中指定的每个属性,这个库会读取当前值,并且在指定的时间内(这里是1.5秒)逐步改变属性直到最终值。在最后一个补间里,我们使用delay控制参数,让之前淡出补间完成之后,再开始后一个补间动画。

注意 transition 库的操作是基于时间轴的规则。

transition.to( target, params )

随时间,返回target显示对象的动画属性的补间。最终属性值再参数table中指定了。为了自定义补间,你可以可选的使用下列非动画属性参数:

• params.time 指定 transition的时间周期(毫秒)。默认,这个值是500ms(0.5秒)

• params.transition 默认值是 easing.linear . 参看 Easing 来获取更多信息。

• params.delay 指定在补间动画开始前的延时 (没默认值)

• params.delta 是一个布尔值,指明是否非控制参数,被解释为最终值,或作为值的改变。默认是nil,意味着false。

• params.onStart i是一个函数或者table listener(在补间动画开始前被调用)。table Listener们必须有一个onStart方法。当被调用时,这个listener作为目标,而不是一个event。

• params.onComplete 是一个函数或者table listener,在补间动画完成后被调用,异步的。


transition.from( target, params )

和 transition.to 相似,不同在于属性值的开始值,在参数table中被指定,而最终值是在调用时相应的属性值。

这个 transition 库,消除了手动操作动画对象的必要性。比较下面两个例子代码,都是让一个白色矩形在一秒钟淡出:

The transition library eliminates the need for manually animating objects. Compare the following code that causes a white rectangle to fade out in 1 second:

Manual animation
Using transition

local rect = display.newRect(

0, 0, 100, 100 )

rect:setFillColor(255,255,255)



local tMax =1000+system.getTimer()

local function fadeOut(event)

local t = event.time

local rect = event.target

if t < tMax then

rect.alpha = 1 - t/tMax

else

rect.alpha = 0

-- 完成,则删除 listener

Runtime:removeEventListener ("enterFrame", fadeOut )

end

end



-- Add listener to begin animation

Runtime:addEventListener(

"enterFrame", fadeOut )
local rect = display.newRect(

0, 0, 100, 100 )

rect:setFillColor(255,255,255)



transition.to(

rect, {time=1000, alpha=0})



transition.cancel( tween )

取消这个补间动画。

transition.dissolve( src, dst, duration, delayDuration )


Easing

Easing 库是一个中值函数的集合,被 transition库使用:

easing.linear( t, tMax, start, delta )


easing.inQuad( t, tMax, start, delta )


easing.outQuad( t, tMax, start, delta )


easing.inOutQuad( t, tMax, start, delta )


easing.inExpo( t, tMax, start, delta )


easing.outExpo( t, tMax, start, delta )


easing.inOutExpo( t, tMax, start, delta )


Y你可以创建你自己的easing函数,来在开始和最终值之间,生成中间值。这个函数必须具备下列参数:

t

从 transition 开始的瞬间,到现在经历过的时间

tMax

transition所用的时间周期

start

开始时的值

delta

改变的变化量。(最终值 = start + delta)


Movieclips

扩展的 movieclip库允许你从image序列,创建sprite动画(有时候叫“movieclips”),它可以使用和其他任何Corona display object同样的奇数,在屏幕上移动。有一些函数可以用来播放这些动画帧,或者这些帧当中的部分序列;比如向前或向后播放;跳到指定帧;向前或向后跳过帧;在序列完成后自动删除动画;让动画可以被拖放,通过press,drag和release事件来完成。

这个框架提供一个快速轻量级的方法,来创建动画,并有助于移植现有的flash内容到Corona中来。

这个 movieclip库是一个扩展模块,比需要在你的工程里包括 movieclip.lua文件,并且使用require命令。(参看 Loading external libraries 来获取关于require的细节)

注意,这个库是基于旧的sprite库。这个库的名字被改成了“movieclip”,因为“sprite”这个名字被sprite-sheet特性所保留,参看 Corona Game Edition. 后面讨论两者的不同。

使用 movieclip 的范例工程,见 Graphics/Movieclip project,或在 Ansca Mobile网站上代码。


Movieclip functions

movieclip.newAnim( frames )

用一个image文件名数组提供帧table,来创建一个动画sprite:

myAnim = movieclip.newAnim{ "img1.png", "img2.png", "img3.png", "img4.png" }


object:play()

开始向后播放一个animated sprite。当到达image列表的终点,它将会循环回来重新播放第一个image,并继续播放。如果一个特定帧序列已经被构造,调用play()而不带参数,将会简单地用那个序列恢复播放。

object:play{ startFrame=a, endFrame=b, loop=c, remove=shouldRemove }

开始向后播放一个animated sprite。当endFrame数字指定的帧到达时,它将会循环回 startFrame数字指定的帧来继续播放。

myAnim:play{ startFrame=1, endFrame=6, loop=3, remove=true }


loop参数接收一个数字,表示序列被循环的次数。0(默认值)表示永远循环下去。

The remove 参数时一个布尔标志,如果设置为true,这个 movieclip将会在指定序列播放完成后自动删除自己。这是件很有用的事,比如你要用一个爆炸动画或者一次性的动画。注意,这个object只有在你的lua代码没有任何引用指向它时,才会被垃圾手机。默认值是false。

所有参数都是可选的,但是 startFrame和 endFrame 默认被设置为第一和最后一个image上。所以显式地提供这两个值,来避免未预期的行为,也是个很好的实践。

穿参数给play()表示一个新序列将被构造,并且所有当前的序列信息将被重置。如果你只是想恢复播放当前序列,可以不带参数调用play()。

object:reverse()

反方向播放这个 animated sprite。当image集合的开始到达,它将会循环回最后的image,继续反向播放。如果一个指定的帧序列已经被构造,无参数调用reverse(),将只简单恢复向前播放这个序列。

object:reverse{ startFrame=a, endFrame=b, loop=c, remove=shouldRemove }



开始向前播放一个animated sprite。当endFrame数字指定的帧到达时,它将会循环回 startFrame数字指定的帧来继续播放。

loop参数接收一个数字,表示序列被循环的次数。0(默认值)表示永远循环下去。

The remove 参数时一个布尔标志,如果设置为true,这个 movieclip将会在指定序列播放完成后自动删除自己。这是件很有用的事,比如你要用一个爆炸动画或者一次性的动画。注意,这个object只有在你的lua代码没有任何引用指向它时,才会被垃圾手机。默认值是false。

所有参数都是可选的,但是 startFrame和 endFrame 默认被设置为最后和第一个image上。所以显式地提供这两个值,来避免未预期的行为,也是个很好的实践。

穿参数给reverse()表示一个新序列将被构造,并且所有当前的序列信息将被重置。如果你只是想恢复播放当前序列,可以不带参数调用reverse()。

object:nextFrame()

在进程中重置任何动画序列,移动动画到整个序列中下一个image,并停止。这可以被一个2帧movieclip使用,来使一个image在2个状态间切换。

object:previousFrame()

在进程中重置任何动画序列,移动动画到整个序列中前一个image,并停止。

object:stop()

在当前帧中,停止sprite动画。

object:stopAtFrame( frame )


把动画跳到指定帧,给一个帧的数字编号,或者一个可选的帧标签(参看后面的 setLabels函数)。

注意:从一个特定帧实现“go to and play”,可以简单发出一个 stopAtFrame命令,之后跟一个play或reverse命令。两个命令都将在显示更新前执行,所以这个变换将会立刻开始:

myAnim:stopAtFrame(4)

myAnim:play()


再比如:



myAnim:stopAtFrame("label")

myAnim:reverse()


object:setLabels( labels )

为之前创建的Anim对象添加一个可选的标签,用一个table来附加标签名到选好的帧号上:(firstLabel和anotherLabel是任意字符串)

myAnim:setLabels{ firstLabel=1, anotherLabel=3 }


object:setDrag

当 drag 被设置为true,任何 movieclip 就变成了一个可拖放的对象。 limitX和 limitY参数限制是否可以在x和y轴方向上拖动,还有bounds参数可以用来指定对象的拖放范围 {left, top, width, height}。onPress和onDrag,还有onRelease参数都需要一个当那些事件发生后会调用的函数名。所有参数都是可选的。

myAnim:setDrag{ drag=true, limitX=false, limitY=false, onPress=myPressFunction, onDrag=myDragFunction,

onRelease=myReleaseFunction, bounds={ 10, 10, 200, 50 }}


为了再关闭可拖放属性,设置"drag" to false:

myAnim:setDrag{ drag=false }


注意上面的例子,大括号是lua中的简略用法,就是直接通过一个名值组合的table穿参数给函数。换句话说,这个函数实际上收到一个单值,实际就是一个lua table,比如:funtion({values})。这个简略用法允许小括号被省略,以提高代码的可读性。

movieclips 和 sprite sheets的区别

Corona Game Edition 包括了一个 “sprite sheet”特性,即通过从内存中一个大纹理图片中拷贝小的矩形区块,来构造animated sprite。

Sprite sheets对纹理内存的使用是非常高效率的,因为无论源图片有多大尺寸,OpenGL-ES为所有纹理分配的内存大小,都是其向上到2的N次方对齐的。例如,一个大小80X300的单个图片,将需要一个128X512像素的纹理内存的分配。

因此, sprite sheets被推荐用于制作复杂的角色动画,或者任何有大量动画状态的程序。

然而, sprite sheets需要更多编码和更多设置;例如,你必须构造一个动画帧的很大的sheet。 当然movieclip库更容易使用,也可以更快速的移植flash内容,因为 movieclip的帧可以从flash中,作为PNG序列被导出。

为获取更多关于 sprite sheets的信息,参看 Game Edition: Sprite Sheets.

自定义/可编程动画

通常你需要创建你自己的动画,这时候可能没法使用transition库。这时候你就不得不取编写自定义的代码来产生动画序列,这就是常说的可编程动画。


为了创建这样的动画,你需要随时间变化而改变屏幕上的内容。某些情况下,在例如一个for或while循环中修改对象属性,这是很自然的。然而在Corona里,你不可能用这样的循环生产动画,因为在你的代码块执行完成之前,屏幕是绝对不会刷新的(参看 Screen Updates)。


相反,动画的产生,是借助重复的调用listener。这些listener修改屏幕上的display object,然后退出,然后允许屏幕刷新。这样的listener例如“enterFrame”,你可以注册你的listener到“enterFrame”事件上。在绘图循环中,“enterFrame”事件在屏幕刷新之前被分发,使得你的代码有机会修改屏幕上的内容(参见 Drawing Cycle)。通过“enterFrame”事件,你可以产生各种动画。事实上,transition库也是用这些事件构建的。


下面是一个如何制作弹球动画的例子:





listener函数animate每当一次“enterFrame”事件发生就会被调用一次。它负责在小球碰到屏幕边缘的时候,让其反弹以及改变其位置。


因为“enterFrame”事件发生在全局级别,所以你需要向全局运行时对象注册listener。




帧频

"enterFrame"事件发生有一个固定的时间间隔,这就是我们说的帧频。所以你的listener将会在每个帧频时间开始时被调用。然而,如果你的listener消耗太久的时间而没有退出的话,那么实际帧频将小于所需的帧频。


基于时间 vs 基于帧

在上面的例子中,动画是基于帧的方式。如果实际帧频变慢下来,因为中间帧的渲染也是在每次事件调用发生的,所以小球会看起来移动的越来越慢;也没有中间帧会跳过。如果你打算让声音和动画同步,这样就变得有点糟糕了。


解决这个问题的方案是采用基于时间的动画。通过计算我们listener每次被调用之间的时间,和适当的改变速度,我们把上面的例子,改为基于时间的方式。这将导致下面的改变:





注意我们是如何利用“enterFrame”事件包含一个存储毫秒数的参数属性的。我们通过之前保存的时间来确定小球应该运动到多远处。另外,我们上一个例子中的x,y方向的速度(2.8和2.2)显式的假设是用帧来测量的。默认情况下,它会被设置成30fps,或33.3毫秒。所以我们可以用原来的速度(30/1000)来获得新的基于时间的速度。


Lost or Missing Time


看上去,基于时间的动画有一个问题,就是当设备挂起app以后,你需要计算“丢失”的时间。在模拟器中,你可以模拟挂起,通过使用键盘上的 ⌘↓ (command-down arrow),这表示硬件挂起/恢复你的app。(注意你需要知道,通过菜单导致一个app挂起,就好像被挂起但是并不产生挂起事件)


在弹球的动画里,如果app被挂起半秒钟,球就会看起来象在屏幕上跳跃一样。在上面的例子里,解决的方案就是调节 tPrevious,把丢失的时间算上。


<< 五分钟学会Corona(六) - Bitmap Mask五分钟学会Corona(八) - Sprite高级动画 >>

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

最近发表

Powered By Z-Blog 1.8 Walle Build 100427 Copyright 2011-2015 BuildApp.Net. All Rights Reserved.