注意 :本教程面向高级和有经验的用户,不涉及诸如添加组件,创建新的GameObject脚本和C#语法之类的主题。 如果您需要提高Unity技能,请查看我们的Unity入门和Unity脚本简介教程。
在本教程的
第一部分中 ,我们学习了如何使用将绳索缠绕在障碍物上的机制来创建猫钩。 但是,我们还需要更多:绳索可以在水平面上缠绕对象,但返回时不会分离。
开始工作
从Unity的第一部分中打开完成的项目,或下载本部分教程的
草稿 ,然后打开
2DGrapplingHook-Part2-Starter 。 与第一部分一样,我们将使用Unity版本2017.1或更高版本。
在“
场景”项目文件夹中的编辑器中打开“
游戏”场景。
启动游戏场景,尝试将猫钩钩在角色上方的石头上,然后摆动以将绳子缠绕在一对石头边缘上。
当您返回时,您会注意到绳索用来转过的石头的点不会再次脱钩。
考虑绳索应展开的位置。 为了简化任务,最好使用绳索缠绕边缘的情况。
如果嵌在其头部上方的石头上的子弹向右摆动,则绳索将在阈值之后弯曲,在该阈值处,该子弹与当前子弹所连接的肋骨交叉180度角点。 在下图中,它以突出显示的绿点显示。
当子弹向另一个方向摆动时,绳索应再次在同一点解钩(上图中以红色突出显示):
松开的逻辑
要计算需要在绳索较早缠绕的点上解开绳索的力矩,我们需要了解几何形状。 特别是,我们将使用角度的比较来确定绳索何时应从边缘分离。
这项任务似乎有些令人生畏。 即使在最勇敢的时候,数学也会激发恐惧和绝望。
幸运的是,Unity具有一些出色的数学助手功能,可以使我们的生活更加轻松。
在IDE中打开
RopeSystem脚本,并创建一个名为
HandleRopeUnwrap()
的新方法。
private void HandleRopeUnwrap() { }
转到
Update()
并在最后添加对我们的新方法的调用。
HandleRopeUnwrap();
尽管
HandleRopeUnwrap()
什么也不做,但是现在我们可以处理与从边缘分离的整个过程相关的逻辑。
正如您在本教程的第一部分中所记得的那样,我们将绳索缠绕位置存储在一个名为
ropePositions
的集合中,该集合是一个
List<Vector2>
集合。 每次绳索缠绕一条边缘时,我们都会在该集合中保留此包裹点的位置。
为了使过程更高效,如果集合中存储的位置数等于或小于1,我们将不会在
HandleRopeUnwrap()
执行任何逻辑。
换句话说,当
ropePositions
钩到起点并且其绳索尚未缠绕在边缘上时,
ropePositions
的数量将为1,并且我们将不会遵循
ropePositions
的处理逻辑。
将此简单的
return
添加到
HandleRopeUnwrap()
的顶部,以节省宝贵的CPU周期,因为每秒从
Update()
多次调用此方法。
if (ropePositions.Count <= 1) { return; }
添加新变量
在此新测试下,我们将添加一些尺寸和参考,以实现实现扭曲逻辑基础所需的不同角度。 将以下代码添加到
HandleRopeUnwrap()
:
这里有很多变量,因此我将解释它们中的每一个,并添加一个方便的插图以帮助理解它们的目的。
anchorIndex
是ropePositions
集合中位于集合末尾两个位置的索引。 我们可以将其视为从子弹位置到绳索上两个位置的点。 在下图中,这是将挂钩连接到表面的第一个点。 当ropePositions
新的包裹点填充ropePositions
集合ropePositions
,该点将始终保持包裹点ropePositions
相距两个位置。- 铰链索引是存储当前铰链点的集合的索引; 换句话说,绳子当前缠绕在从条塞最接近绳子末端的点周围的位置。 它总是与
ropePositions.Count - 1
相距一个位置,这就是为什么我们使用ropePositions.Count - 1
。 anchorPosition
通过引用ropePositions
集合中的anchorIndex
位置anchorPosition
计算的,并且是该位置的简单Vector2值。hingePosition
通过引用ropePositions
集合中hingeIndex
索引的位置hingePosition
计算的,并且是该位置的简单Vector2值。hingeDir
是从anchorPosition
定向到hingePosition
的向量。 在以下变量中使用它来获取角度。- 铰链角度-有用的辅助函数
Vector2.Angle()
在此处用于计算anchorPosition
和铰链点之间的角度。 playerDir
是从anchorPosition
指向playerDir
当前位置(playerPosition)的向量- 然后,使用锚点和玩家(子弹)之间的角度,计算玩家角度。
所有这些变量都是使用存储在
ropePositions
集合中的Vector2值存储的位置并将这些位置与其他位置或播放器(子弹)的当前位置进行比较来计算的。
用于比较的两个重要变量是
hingeAngle
和
hingeAngle
。
存储在
hingeAngle
角度中的值必须保持静态,因为它始终是从
hingeAngle
的两个“绳索折叠”处的点到
hingeAngle
的当前“绳索的折叠”之间的点之间恒定的角度,直到松开绳索或折叠后该点才移动将添加一个新的弯曲点。
当
playerAngle
改变。 通过将这个角度与
hingeAngle
角度进行
hingeAngle
,并检查
hingeAngle
是在该角的左侧还是右侧,我们可以确定是否应分离最靠近
hingeAngle
的当前折叠点。
在本教程的第一部分中,我们将折叠位置保存在名为
wrapPointsLookup
的字典中。 每次保存弯曲点时,我们都将弯曲点添加到字典中,位置为键,值为0。 但是,这个0值相当神秘,对吧?
我们将使用该值存储弹头相对于其与铰链点(当前折叠点最接近弹头)的角度的位置。
如果您将值指定为
-1 ,则
hingeAngle
的角度(
hingeAngle
)小于铰链的角度(
hingeAngle
),并且值为
1时, hingeAngle
的角度大于
hingeAngle
的角度。
由于我们将值保存在字典中,因此,每次将
hingeAngle
与
hingeAngle
进行比较时,我们都可以了解到弹头是否刚刚超过了限制,然后就可以解开绳索了。
可以用不同的方式解释:如果刚检查了弹头的角度并且该角度小于铰链角度,但是最后一次将其保存在折弯点字典中时,将其标记为一个值,指示该点位于该拐角的另一侧,则应立即移除该点。 !
解开绳
请看下面的屏幕截图,并附上注释。 我们的弹头紧紧抓住岩石,向上摇摆,在上升的过程中将一根绳子缠绕在岩石的边缘。
您可能会注意到,在
wrapPointsLookup
不透明的最高挥杆位置,其当前最近的折叠点(标有白点)将存储在
wrapPointsLookup
词典中,值为
1 。
在向下的过程中,当
playerAngle
变得小于
hingeAngle
(两条绿色的虚线)时,如蓝色箭头所示,将执行检查,如果折弯点的最后一个(当前)值为
1 ,则应删除折弯点。
现在,让我们在代码中实现此逻辑。 但是在开始之前,让我们创建一个将用于展开的方法的空白。 因此,创建逻辑后,不会导致错误。
通过插入以下行来添加新的
UnwrapRopePosition(anchorIndex, hingeIndex)
方法
UnwrapRopePosition(anchorIndex, hingeIndex)
:
private void UnwrapRopePosition(int anchorIndex, int hingeIndex) { }
完成此操作后,回到
HandleRopeUnwrap()
。 在新添加的变量下,添加以下逻辑,该逻辑将处理两种情况:
hingeAngle
小于
hingeAngle
以及
hingeAngle
大于
hingeAngle
:
if (playerAngle < hingeAngle) {
此代码应对应于上述第一种情况的逻辑说明(当
hingeAngle
<
hingeAngle
),但也应处理第二种情况(当
hingeAngle
>
hingeAngle
)。
- 如果最接近
hingeAngle
的当前折叠点在hingeAngle
< hingeAngle
角度的点处的值为1 ,则我们删除该点并执行返回操作,以便该方法的其余部分不执行。 - 否则,如果弯曲点最后未标记为值1 ,但
hingeAngle
小于hingeAngle
,则分配-1 。 - 如果当前最靠近
hingeAngle
折叠点在hingeAngle
> hingeAngle
的点处为-1 ,则移除该点并返回。 - 否则,我们将折点字典中位于铰链位置的条目分配为1 。
此代码确保
wrapPointsLookup
字典始终处于更新状态,以确保当前折弯点的值(最接近
wrapPointsLookup
块)与当前相对于折弯点的
wrapPointsLookup
块角度匹配。
不要忘记,当凸角小于铰链角(相对于参考点)时,该值为-1;而当凸角大于铰链角时,该值为1。
现在,我们将在
RopeSystem脚本中
UnwrapRopePosition()
代码,
该代码将直接进行解耦,移动参考位置并为绳索距离值DistanceJoint2D分配新的距离值。 将以下行添加到以前创建的方法光盘中:
- 当前锚点的索引(绳索从子弹的第二位置)变为铰链的新位置,并且铰链的旧位置被移除(以前最靠近子弹的位置,现在我们“解开”)。 变量
newAnchorPosition
在绳索位置列表中分配了值anchorIndex
。 然后将其用于定位锚点的更新位置。 - 绳索接头RigidBody2D(DivistJoint2D绳索连接到该绳索接头)将其位置更改为锚点的新位置。 这确保了将子弹连接到DistanceJoint2D时,它在绳索上的平稳连续运动,并且此连接应允许它相对于新位置继续摆动,该新位置成为参考-换句话说,相对于绳索从其位置向下的下一个点。
- 然后,您需要更新distanceJoint2D距离值,以考虑从弹头到新参考点的距离的急剧变化。 如果尚未完成此操作,则将对
distanceSet
标志进行快速检查,并为该距离分配弹头与锚点的新位置之间的计算距离值。
保存脚本并返回到编辑器。 再次开始游戏,并观察当弹头超过每个弯曲点的阈值时绳索如何从边缘脱离!
尽管逻辑已经准备好了,但我们还是在将
hingeAngle
与
hingeAngle
(
if (playerAngle < hingeAngle)
)进行比较之前,向
HandleRopeUnwrap()
添加了一些辅助代码。
if (!wrapPointsLookup.ContainsKey(hingePosition)) { Debug.LogError("We were not tracking hingePosition (" + hingePosition + ") in the look up dictionary."); return; }
实际上,这不应该发生,因为当猫钩两次缠绕一条肋骨时,我们会重新定义并断开其连接,但是如果仍然发生这种情况,我们可以通过简单的
return
和错误消息轻松退出此方法。在控制台中。
此外,由于此,我们将更方便地处理这种限制情况; 此外,如果发生不必要的事情,我们会收到错误消息。
接下来要去哪里?
这里是本教程第二部分和最后一部分
的完成项目的链接 。
祝贺您完成本教程系列! 当比较角度和位置时,一切都变得相当复杂,但是我们幸免于难,现在我们有了一个很棒的钩猫和绳索系统,可以将游戏中的物体缠绕起来。
您知道我们的Unity开发团队写过书吗? 如果不是,请查看
Unity Games By Tutorials 。 该游戏将教您如何从头开始创建四个现成的游戏:
- 两杆射手
- 第一人称射击游戏
- 塔防游戏(具有VR支持!)
- 2D平台游戏
阅读本书之后,您将学习如何为Windows,macOS,iOS和其他平台创建自己的游戏!
本书既适合初学者,又适合希望将Unity技能提升到专业水平的人。 要掌握这本书,您需要具有编程经验(任何语言)。