本教程讨论射线投射和反射,分别通过 physics.rayCast() 和 physics.reflectRay() API 实现。它还附带一个“真实世界”演示项目,可在此处下载 此处。
在最基本的层面上,射线投射涉及从一个点到另一个点发射
Corona 还提供了一个方便的 API,用于从射线击中的任何物体反射射线。对于任何注册命中的射线投射,physics.reflectRay() 会返回一个表示反射方向的向量,其大小(长度)为 1
。本教程演示了实际使用中的射线投射和射线反射。
如果您还没有下载 演示项目,请下载并跟随代码进行操作。
此演示中的一个重要步骤是配置x
、y
和 rotation
值的表
-- Set up mirror positions relative to the center of the content area local mirrorSet = { { 0, -125, 90 }, -- top { 105, -60, -35 }, -- right-upper { 105, 60, 35 }, -- right-lower { 0, 125, 90 }, -- bottom { -105, -60, 35 }, -- left-upper { -105, 60, -35 } -- left-lower }
炮塔本身是一个带有径向物理实体的标准显示对象——是的,射线投射检测/反射也适用于径向实体!
-- Create turret turret = display.newImageRect( mirrorGroup, "turret.png", 48, 48 ) physics.addBody( turret, "dynamic", { radius=18 } ) turret.x, turret.y = display.contentCenterX, display.contentCenterY
接下来,我们通过将炮塔的角速度设置为示例代码开头附近的 turretSpeed
变量来开始旋转炮塔。然后,我们启动一个重复计时器,每 2000 毫秒发射一次激光。
-- Start rotating turret turret.angularVelocity = turretSpeed -- Start repeating timer to fire beams timer.performWithDelay( 2000, fireOnTimer, 0 )
投射实际射线很简单。找到 castRay()
函数并检查突出显示的行
local function castRay( startX, startY, endX, endY ) -- Perform ray cast local hits = physics.rayCast( startX, startY, endX, endY, "closest" )
physics.rayCast() 的前四个参数指示x 和 y 起始位置,以及 x 和 y 目标位置。这里,这些值作为 startX
、startY
、endX
和 endY
分别传递给 castRay()
函数。
第五个参数虽然是可选的,但值得注意,因为它指示您希望从射线投射返回的结果“类型”。当前可用以下选项
"any"
— 返回第一个有效结果,但不一定是距离起点最近的结果。"closest"
— 返回距离起点最近的结果(如果没有指定其他选项,则为默认返回值)。"sorted"
— 返回所有结果,按距离起点的距离从近到远排序。"unsorted"
— 返回所有结果,不应用任何排序算法。physics.rayCast() API 返回一个描述每次命中的 数组(表),但由于我们只对射线投射注册的第一次命中感兴趣(在本例中是距离射线起点最近的),我们将只处理数组中的第一个表
local function castRay( startX, startY, endX, endY ) -- Perform ray cast local hits = physics.rayCast( startX, startY, endX, endY, "closest" ) -- There is a hit; calculate the entire ray sequence (initial ray and reflections) if ( hits and beamGroup.numChildren <= maxBeams ) then -- Store first hit to variable (just the "closest" hit was requested, so use "hits[1]") local hitFirst = hits[1] -- Store the hit X and Y position to local variables local hitX, hitY = hitFirst.position.x, hitFirst.position.y
从表示第一次命中的表hits[1]
,设置为 hitFirst
)
object
— 与射线碰撞的物理对象。position.x
— 与 object
的 x 碰撞点,在内容空间中。position.y
— 与 object
的 y 碰撞点,在内容空间中。normal.x
— 命中的 object
表面法线的 x 分量,在局部空间中。normal.y
— 命中的 object
表面法线的 y 分量,在局部空间中。使用此信息,我们可以从起点到命中点绘制一组分层线
local function drawBeam( startX, startY, endX, endY ) -- Draw a series of overlapping lines to represent the beam local beam1 = display.newLine( beamGroup, startX, startY, endX, endY ) beam1.strokeWidth = 2 ; beam1:setStrokeColor( 1, 0.312, 0.157, 1 ) ; beam1.blendMode = "add" ; beam1:toBack() local beam2 = display.newLine( beamGroup, startX, startY, endX, endY ) beam2.strokeWidth = 4 ; beam2:setStrokeColor( 1, 0.312, 0.157, 0.706 ) ; beam2.blendMode = "add" ; beam2:toBack() local beam3 = display.newLine( beamGroup, startX, startY, endX, endY ) beam3.strokeWidth = 6 ; beam3:setStrokeColor( 1, 0.196, 0.157, 0.392 ) ; beam3.blendMode = "add" ; beam3:toBack() end
请注意,此示例使用了三条宽度不同的线,彼此分层,混合模式 为 "add"
。这将创建一个具有更亮中心区域的激光器的良好视觉外观,该中心区域向边缘逐渐淡化为红色/橙色。
如果您希望将射线从其击中的表面反射回来,Corona 提供了方便的 physics.reflectRay() API。如前所述,这将返回一个表示反射方向的向量,其大小(长度)为 1
。
调用此函数需要三个参数
from_x
— 原始射线投射的起始 x 位置。from_y
— 原始射线投射的起始 y 位置。hit
— 由投射射线返回的 hits
数组中的一个条目。在我们的示例中,physics.reflectRay() 调用如下所示
local reflectX, reflectY = physics.reflectRay( startX, startY, hitFirst )
请注意,from_x
和 from_y
参数基于原始射线投射的起始位置startX
和 startY
)endX
和 endY
的命中位置。换句话说,physics.reflectRay() 只处理一次反射,而不是一系列递归反射。为了使激光继续在镜子之间反弹/反射,我们需要每次都进行另一次射线投射,如下所示。
要拉伸反射光线并为其设置目标点,只需考虑您选择的向量长度并将其与命中点相加即可
local reflectX, reflectY = physics.reflectRay( startX, startY, hitFirst ) local reflectLen = 1600 local reflectEndX = ( hitX + ( reflectX * reflectLen ) ) local reflectEndY = ( hitY + ( reflectY * reflectLen ) )
在本例中,我们使用 1600
作为反射向量长度,但可以根据需要进行设置。事实上,这个值有些随意,除非您有特定需要限制反射光线的长度,否则通常最好将此长度设置为远大于其在击中另一个物体之前可能传播的距离的值。
使用此新信息,我们可以再次调用 castRay()
函数(延迟 40 毫秒后)以从命中点到下一个目标点绘制一组新线
local reflectX, reflectY = physics.reflectRay( startX, startY, hitFirst ) local reflectLen = 1600 local reflectEndX = ( hitX + ( reflectX * reflectLen ) ) local reflectEndY = ( hitY + ( reflectY * reflectLen ) ) -- If the ray is reflected, cast another ray if ( reflectX and reflectY ) then timer.performWithDelay( 40, function() castRay( hitX, hitY, reflectEndX, reflectEndY ); end ) end
在演示项目中,在 castRay()
函数内,检查以下行
if ( hits and beamGroup.numChildren <= maxBeams ) then
基本上,此条件检查可确保 castRay()
函数仅在没有更多命中或达到 maxBeams
值时才重复。这对于在特定光束开始在两个表面之间以类似的模式来回反弹时停止反射过程非常有用,因为它可能会几乎无限期地重复。
当满足任一条件并且过程终止时,我们绘制最后一道光束,然后调用一个基本过渡以淡出父显示组
-- Draw the final beam drawBeam( startX, startY, endX, endY ) -- Fade out entire beam group after a short delay transition.to( beamGroup, { time=800, delay=400, alpha=0, onComplete=resetBeams } )
过渡完成后,我们调用 resetBeams()
函数来清除/重置组
local function resetBeams() -- Clear all beams/bursts from display for i = beamGroup.numChildren,1,-1 do local child = beamGroup[i] display.remove( child ) child = nil end -- Reset beam group alpha beamGroup.alpha = 1 -- Restart turret rotating after firing is finished turret.angularVelocity = turretSpeed end
这基本上完成了演示演练——在一个重复的计时器上,从炮塔进行射线投射并绘制一组激光线。如果射线击中物理世界中的另一个物体,则计算射线的反射并将其拉伸到另一个点。根据该数据,执行另一次射线投射,并且该过程继续进行,直到没有更多命中或达到光束阈值为止。
如您所见,射线投射可以成为您物理工具集中有用的补充。从查询物理世界到计算移动物体的潜在路径,射线投射快速、简单,并且只需几行代码即可完成。