2014 年 1 月 7 日
教程:沿路径移动对象
在 Corona SDK 中移动对象有很多方法。 如果你想将对象从 A 点移动到 B 点,最简单的方法是使用 transition.to()。 但是,如果您需要沿着具有多个分段的路径移动它,例如在国际象棋棋盘上以其独特的“L”模式移动骑士呢? 本教程概述了如何通过一系列排队的过渡来实现连续移动。
初始设置
此过程的基础是一个 x
和 y
坐标对的表,用于按顺序沿对象移动。 以其最简单的格式,该表可能如下所示
1 2 3 4 5 6 7 8 9 10 |
local movePath = {} movePath[1] = { x=200, y=0 } movePath[2] = { x=200, y=200 } movePath[3] = { x=0, y=200 } movePath[4] = { x=200, y=300 } movePath[5] = { x=150, y=200 } movePath[6] = { x=200, y=100 } movePath[7] = { x=100, y=200 } movePath[8] = { x=0, y=0 } |
这只是声明了一系列移动点,从索引 1 开始,并根据需要跨越多个点。 默认情况下,这些点等同于屏幕上的特定坐标,而不是相对于对象起始位置的点,但我们将包含一个设置,让您选择最适合您应用程序的选项。
自定义路径参数
除了基本的 x
和 y
坐标设置外,我们还允许路径中每个“段”的两个附加参数:time
和 easingMethod
。 例如,我们可以像这样扩展我们的表格
1 2 3 4 5 6 7 8 9 |
local movePath = {} movePath[1] = { x=200, y=0 } movePath[2] = { x=200, y=200 } movePath[3] = { x=0, y=200, time=500 } movePath[4] = { x=200, y=300, time=500 } movePath[5] = { x=150, y=200, time=250, easingMethod=easing.inOutExpo } movePath[6] = { x=200, y=100, time=2000 } movePath[7] = { x=100, y=200, time=500 } movePath[8] = { x=0, y=0, time=500, easingMethod=easing.outQuad } |
如果指定了自定义 time
参数,它将覆盖任何其他时间设置,并且对象将在该确切持续时间内移动到该点。 同样,如果 easingMethod
被定义并设置为 Corona 的 缓动方法之一,则对象将使用该缓动方法移动到该点,而不是默认的线性插值。
距离函数
为了计算两点之间的距离,我们需要包含一个快速函数。此函数的目的将在稍后解释,但我们现在先添加它
1 2 3 4 5 6 |
local function distBetween( x1, y1, x2, y2 ) local xFactor = x2 - x1 local yFactor = y2 - y1 local dist = math.sqrt( (xFactor*xFactor) + (yFactor*yFactor) ) return dist end |
创建对象
显然,我们需要一个或多个对象沿着我们创建的路径移动。现在让我们创建两个基本圆形
1 2 3 4 5 |
local circle1 = display.newCircle( 60, 100, 15 ) circle1:setFillColor( 1, 0, 0.4 ) local circle2 = display.newCircle( 120, 100, 15 ) circle2:setFillColor( 1, 0.8, 0.4 ) |
setPath() 函数
现在我们已经完成了基本设置,让我们来探索一下将为对象排队所有过渡的函数。我们称该函数为setPath()
,并为其提供三个参数:要移动的object
,要沿着移动的path
,以及一个可用于自定义移动的 params
表。
1 2 3 4 5 6 7 8 9 10 |
local function setPath( object, path, params ) local delta = params.useDelta or nil local deltaX = 0 local deltaY = 0 local constant = params.constantTime or nil local ease = params.easingMethod or easing.linear local tag = params.tag or nil local delay = params.delay or 0 local speedFactor = 1 |
在函数的前几行中,我们设置了一些局部变量,其中大多数用于检查是否通过 params
表传入了各种参数。当我们逐步完成本教程时,将解释每个参数。
Delta
这使我们可以通过布尔值 useDelta
(通过 params
表传入)使用delta位置。如果设置为 true
,则对象的路径将相对于其起始位置。如果设置为 false
或省略,则路径点将等同于明确的屏幕坐标。
是否使用 params.useDelta = true
取决于具体情况。对于在棋盘上移动骑士,delta 是合乎逻辑的选择,因为骑士可能位于棋盘的任何方格上,我们需要将其从当前方格以“L”形模式移动到另一个有效方格。
如果 useDelta
在 params
表中传入,则我们将 deltaX
和 deltaY
设置为对象的起始位置,而不是默认值 0
。设置过渡时,这将用于将沿路径的每个点偏移对象的起始位置。它还将用于重构恒定的移动速率,稍后将讨论。
1 2 3 4 |
if ( delta ) then deltaX = object.x deltaY = object.y end |
恒定移动速率
我们将添加的另一个选项是设置“恒定移动速率”的能力。例如,我们可能希望对象以稳定的恒定速率沿整个路径移动,即使起点和第二个点之间的距离是 100 像素,而第二个点和第三个点之间的距离是 280 像素。为了实现这一点,我们只需要基于通过 params.constantTime
传入的“时间”值计算速度因子即可。
1 2 3 4 |
if ( constant ) then local dist = distBetween( object.x, object.y, deltaX+path[1].x, deltaY+path[1].y ) speedFactor = constant/dist end |
应传递给 params.constantTime
的值只是一个整数“时间”值,speedFactor
是通过**起始点和第二个点之间的距离**计算的。换句话说,如果我们使用值 1200,则对象将在 1200 毫秒内从起始点移动到第二个点,并且沿该段建立的速度将应用于路径中的所有其他段,无论它们之间的距离如何。
默认缓动方法
如果我们希望为路径中的所有线段使用非线性缓动方法,我们可以告诉模块使用任何缓动方法。例如,要使用 quadratic-in-out 方法,我们可以在 params
表中传递 easingMethod = easing.inOutQuad
。此缓动方法将应用于路径上的所有过渡,但在路径表中具有特定 easingMethod
设置的过渡除外。
过渡标记
过渡库中的一个功能是能够标记(命名)任意数量的过渡,然后 取消、暂停 或 恢复 所有共享同一标记的过渡。由于我们将构建由多个相关过渡组成的路径,因此如果您想在路径从一个点移动到另一个点时暂停或恢复路径移动,则标记至关重要。要标记将共同构成路径的所有过渡,只需在 params
表中传递 tag
参数(字符串值)。
循环遍历点
现在,让我们循环遍历我们的点并设置过渡队列。为简单起见,我们将首先声明所有过渡,并在每个过渡上应用一个 delay
参数,以便每个过渡在前一个过渡完成后开始。
首先,我们将开始循环并立即设置默认的 segmentTime
为 500 毫秒。然后,我们将检查是否提供了 params.constantTime
,如果是,我们将使用经过重构的时间覆盖 segmentTime
,具体来说,就是点之间的距离乘以我们上面计算的 speedFactor
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
for i = 1,#path do local segmentTime = 500 --如果定义了 "constant",则根据点之间的距离重构过渡时间 if ( constant ) then local dist if ( i == 1 ) then dist = distBetween( object.x, object.y, deltaX+path[i].x, deltaY+path[i].y ) else dist = distBetween( path[i-1].x, path[i-1].y, path[i].x, path[i].y ) end segmentTime = dist*speedFactor |
如上所述,我们可以选择在路径中的任何特定线段上设置自定义时间。如果提供此值,它将覆盖 500 毫秒的默认值和 “恒定速率” 值(如果正在使用)。让我们检查是否为此线段指定了自定义时间
1 2 3 4 |
else --如果此路径线段有自定义时间,则使用它 if ( path[i].time ) then segmentTime = path[i].time end end |
此外,让我们检查是否在此特定路径线段上设置了自定义 easingMethod
参数。如果声明了此参数,它将覆盖应用于整个路径的可选默认缓动方法。
1 2 |
--如果此线段具有自定义缓动,则覆盖默认方法(如果有) if ( path[i].easingMethod ) then ease = path[i].easingMethod end |
最后,整个过程的核心 — 为路径创建每个过渡
1 2 3 4 |
transition.to( object, { tag=tag, time=segmentTime, x=deltaX+path[i].x, y=deltaY+path[i].y, delay=delay, transition=ease } ) delay = delay + segmentTime end end |
请注意,每个实际过渡的参数表是根据我们在上面的行中收集或计算的值填充的。此外,请注意第 66 行 — 在这里,我们为每个新过渡按顺序添加到总 delay
值,从而使每个过渡在前一个过渡完成后开始。
设置对象运动
让我们开始让我们的圆圈在路径上移动!在最基本的级别上,setPath()
函数可以像这样调用
1 2 |
setPath( circle1, movePath, { tag="moveObject" } ) setPath( circle2, movePath, { tag="moveObject" } ) |
但是,这不包括任何像 useDelta
或 constantTime
这样的选项,所以让我们扩展一下它
1 2 |
setPath( circle1, movePath, { useDelta=true, constantTime=1200, easingMethod=easing.inOutQuad, delay=200, tag="moveObject" } ) setPath( circle2, movePath, { useDelta=true, constantTime=1200, easingMethod=easing.inOutQuad, tag="moveObject" } ) |
就是这样 — 这两个圆形都将沿着相同的路径 movePath
移动,并且由于我们使用了 delta 设置,路径会根据每个对象的起始位置进行偏移。每个线段上的移动速度将是恒定的,并且所有线段都将应用缓入缓出二次缓动。
暂停、恢复、取消
由于我们在序列中为每个过渡都标记了 "moveObject"
标签,因此暂停、恢复或取消操作非常简单 — 只需将标签名称传递给其中一个过渡控制 API 即可。
1 2 3 4 5 6 7 |
--暂停序列 transition.pause( "moveObject" ) --过一段时间后,恢复它 transition.resume( "moveObject" ) --全部完成... 取消它! transition.cancel( "moveObject" ) |
总结
希望本教程能帮助您开始在 Corona 中进行基于路径的移动。最后,请注意以下几点:
- 函数式格式允许您创建和重用多个独特的路径“模式”,并通过简单地将适当的路径表以及对象和可选参数传递给
setPath()
函数,将它们应用于各种对象。通过这种方式,例如,您可以为棋盘游戏中的棋子创建几种不同的移动路径,并根据玩家的移动需要重复使用这些路径。 - 本教程仅概述了线性的基于路径的移动。如果您需要基于曲线的路径方法,请参考使用曲线路径教程。
fuhongxue
发布于 18:12, 01 月 07 日如何让物体沿着贝塞尔曲线路径移动?
有没有什么简单的方法?
juf jannie
发布于 10:10, 01 月 09 日或者是一条曲线。
手动为曲线添加每个坐标真的不好玩。
Brent
发布于 11:34, 01 月 09 日本教程不针对曲线路径。如果有足够多的请求,我可能会编写另一个涉及贝塞尔曲线路径的教程。
Brent
Peter Rich
发布于 21:12, 01 月 09 日+1
Kawika
发布于 05:25, 01 月 12 日我投票支持曲线路径教程。谢谢 Brent!
Fernker
发布于 12:36, 03 月 13 日我很想看到一个关于曲线路径的教程。
Nathan Harper
发布于 21:07, 05 月 13 日请出曲线教程!
Joe Colling
发布于 09:54, 09 月 04 日是的,请!+1 曲线路径。
Chris
发布于 02:00, 01 月 28 日再加一个 +1,请出贝塞尔曲线路径教程。
Noah (Chunky Apps)
发布于 07:25, 02 月 16 日+1 曲线路径。我已经等了好几年了!
Evandro
发布于 16:21, 03 月 04 日+1
Charley
发布于 06:04, 11 月 01 日曲线路径,赞成!
Erich Grüttner Díaz
发布于 19:35, 01 月 07 日很棒的教程,一如既往!
或许可以添加一个“循环”参数(true 或 false)。你觉得怎么样?
谢谢你,Brent!!!
Tony
发布于 04:34, 01 月 12 日请做一个关于沿着曲线路径移动物体的教程!谢谢!
Sid
发布于 08:45, 01 月 13 日关于贝塞尔曲线,请查看 Level Director(第三方工具),我一直在我的项目中使用它,它可以让你绘制路径和贝塞尔曲线,并允许你使物体自动跟随它们,非常酷。
Dave Baxter
发布于 05:18, 01 月 15 日我用这个让物体沿着贝塞尔曲线移动 –
http://developer.coronalabs.com/code/bezier-curve-corona-sdk
Dave
Erik
发布于 20:29, 04 月 28 日这个教程太棒了!非常感谢您的这份指南,它非常有用。这个神奇的程序唯一缺少的是一个选项,可以将其重置回第一个点,并通过一个 true 或 false 参数永远循环遍历所有点。非常感谢这样的参数 🙂
Nick
发布于 16:02, 05 月 25 日投票支持曲线教程
谢谢!!
Olivier Romanetti
发布于 07:16, 05 月 30 日也投票支持曲线教程 🙂
Nick
发布于 11:34, 06 月 03 日曲线!
keenan
发布于 06:49, 06 月 25 日很棒的东西,我们可以在我们的项目中使用它吗?
Joe
发布于 15:24, 09 月 04 日很棒的文章。不过,有一个问题……
在文章顶部的“初始设置”下,第一组代码下面说 –
“默认情况下,这些点等同于屏幕上的特定坐标,而不是相对于对象起始位置的点,但我们将包含一个设置,让您可以选择最适合您应用程序的选项。”
现在,我仔细阅读了几遍这篇文章,但没有找到显示此设置的位置。是我错过了吗,还是它不在那里???什么是“设置让我选择最适合我的应用程序的选项”?
请告知。非常感谢。
Brent
发布于 21:46, 09 月 04 日嗨,Joe,
您所说的设置是“delta”设置。仔细阅读那个小节,尝试两个选项,并观察差异。
Brent
Joe
发布于 11:21, 09 月 06 日啊。我现在明白了。*戴上眼镜*
好吧。我还有一个问题。在“SetPath”部分下,代码开始是这样的 –
local function setPath( object, path, params )
对于“params”部分 – 这是否意味着我必须在我将在过渡语句中使用的每个参数都放进去?它还谈到了一个 params 表。这里教程的哪里写了它?我不明白 Params 是从哪里来的……也许只是“params”这个词让我感到困惑……无论如何,我理解“object”和“path” – 但是,我不明白为什么会有“params”……
似乎“params”是先被调用,然后才被定义的……这不可能是正确的,对吗?还是我错了?您能否解释一下代码片段中的“params”是干什么的,以及为什么它在那里?我就是不明白。
先谢谢您,先生。
-Joe
Brent
发布于 14:04, 09 月 06 日嗨,Joe,
“params”只是作为第三个参数传递给“setPath”函数的表。它在代码中实际上不叫“params”,所以请认为我指的是那个表为“params”。
Mikel
发布于 10:40, 02 月 27 日非常好的教程。
花了我 1 个小时,现在我的敌人都在跟随我的路径了。
非常好。谢谢。
Brian
发布于 08:53, 02 月 28 日我认为代码可能存在一个小问题。您将“constantTime”作为参数传入,但在您的 setPath() 函数中,您指的是“constantRate”。因此,setPath() 函数中的变量“constant”将始终为 nil。
Brent Sorrentino
发布于 14:04, 02 月 29 日嗨,Brian,
感谢您指出这一点……名称确实不正确。
– Brent