目录编程 |
1 加速区域数组 1.1 Zone 组件微信 1.2 阻止检测地面编辑器 1.3 持续加速ide 1.4 任意方向函数 2 意识到存在flex 2.1 检测区域优化 2.2 材质选择动画 2.3 最开始进入和最后退出ui 2.4 检测忽然出现和消失的物体 2.5 热重载 2.6 更复杂的行为 3 简单运动 3.1 自动滑动条 3.2 位置插值 3.3 自动倒置 3.4 平滑步长 3.5 更多控制 3.6 压碎的碰撞体 3.7 局部插值 |
本文重点内容:
一、经过加速区域建立跳板和浮空
二、制做一个多功能区域
三、不一样材质的交互以及关闭或者激活对象
四、经过事件触发简单对象插值运动
这是关于控制角色移动的教程系列的第十期。它让环境能够以各类方式和对象运动产生交互。
本教程是CatLikeCoding系列的一部分, 原文地址见文章底部。
本教程使用Unity 2019.4.4f1制做。它还使用ProBuilder软件包。
(和环境交互)
1 加速区域
一个活跃的环境比一个静态的环境更有意思,特别是它们还能对正在发生的行为作出反应的时候。这个行为表示能够对任何事情作出反应,也能够作任何事情,可是一个简单的例子是相似于跳板的东西:每当有东西落在跳板上时,它就会向上弹起。这能够是咱们运动的球体,也能够是其余掉落或被推到跳板上的物体。所以,该行为在逻辑上属于跳板。其余物体不须要意识到它的存在,它们只是忽然被弹飞起来了。
1.1 Zone 组件
描述跳板行为的最通用方法是,它是一个区域,可加速进入区域的任何物体。所以,咱们将建立AccelerationZone组件类型,其可配置的速度不能为负。
区域能够经过添加一个带有触发器碰撞器的对象到场景中来建立,而后将 zone behavior 附加到它上。你也能够添加可视化的跳板对象,可是我只是用半透明的黄色材质使区域可见。
(Acceleration zone 组件)
当具备刚体的物体进入区域时,咱们应该对其进行加速。为此添加一个OnTriggerEnter方法,该方法将触发并调用新的Accelerate方法。进入该区域的全部物体都被执行,可是若是须要的话,可使用Layer来防止检测到不须要的处理的物体。
(在区域中的物体被推开)
1.2 阻止检测地面
这种简单的方法在发射常规物体时效果很好,可是咱们的球体却没有正确发射。相反,它进入该区域时彷佛得到了很大的前进速度。发生这种状况是由于咱们将其压在了地面上。在这种状况下,能够经过下降“Max Snap Speed ”来解决,但这种方法不适用于设置为低速的加速区域。一般,为了防止被地面捕捉,咱们必须指示MovingSphere暂时不要执行捕捉。为此,咱们能够向其添加一个公共的PreventSnapToGround方法,该方法将stepsSinceLastJump设置为-1。
如今,若是物体具备MovingSphere组件,则AccelerationZone.Accelerate能够调用此方法,咱们能够经过使用Sphere做为输出参数调用TryGetComponent来进行检查和检索。
(发射)
请注意,这种方法不会重置跳跃阶段,所以在没有着陆的状况下弹跳跳板不会刷新空气跳跃。
1.3 持续加速
瞬时速度变化对于跳板很合适,可是咱们也可使用该区域建立其余连续的加速度现象,例如悬浮区域。咱们能够经过简单地添加一个与OnTriggerEnter相同的OnTriggerStay方法来支持这个特性。
若是效果持续时间较长,那么经过适当的加速度来实现速度变化会更好一些,所以让咱们向该区域添加一个可配置的加速度,最小仍是为零。若是将其设置为零,咱们将当即进行更改,不然将应用加速。
(升空区域 air加速度为1)
也能够施加力,这样质量较大的物体最终加速得较慢,可是固定的加速度使关卡设计变得更容易,所以我使用这个方式。
1.4 任意方向
最后,为了使其能够在任何方向上加速,请在“Accelerate”开始时将体速度转换为区域的局部空间,并在应用时将其转换回世界空间。使用InverseTransformDirection和TransformDirection进行此操做,以便区域的比例不会对其产生影响。如今能够经过旋转区域来控制加速度方向。
(跳跃区域之间的弹跳)
2 意识到存在
加速区域只是如何建立具备特定行为的触发区域的一个示例。若是你须要一个作其余事情的区域,你将不得不为它编写新的代码。可是,检测和响应某个地方出现的某些东西的简单行为是如此广泛,咱们理想状况下只想编写一次。有不少行为很是简单,好比只是激活一个对象,就为它建立一个专用的组件类型可能就有些设计过渡了。更复杂的行为一般只是几个简单动做的组合。若是关卡设计师能够经过简单的对象来建立它,那会是很是方便的。
2.1 检测区域
让咱们首先建立一个DetectionZone组件,该组件检测其区域中是否存在某些东西,并在有物体进入或退出时通知感兴趣的模块。咱们经过从UnityEngine.Events命名空间为它提供类型为UnityEvent的onEnter和onExit字段进行配置来实现。
只需让它在OnTriggerEnter和OnTriggerExit中的适当事件上调用Invoke方法。这将触发对事件注册的全部内容的方法调用。
检查器会将组件的事件做为名为On Enter()和On Exit()的列表公开,这些列表最初是空的。名称后面的括号中没有任何内容,表示这些事件没有参数。
(没有事件)
2.2 材质选择
为了演示其工做原理,咱们将建立一个简单的MaterialSelector组件类型,该组件类型具备可配置的材质数组和MeshRenderer参考。它具备一个带有索引参数的公共Select方法,该方法将有效的材质分配给渲染器(若是有效的话)。
建立一个带有红色非活动区域和绿色活动区域的材质选择器组件,这将用于更改检测区域的可视化。虽然不须要将其添加到受影响的游戏对象中,但这仍然是有意义的。
(材质选择器)
如今,经过按项目的+按钮将其添加到检测区域组件的输入事件列表中。经过材质选择器的左下角字段将游戏对象连接到该项目。以后,能够选择MaterialSelector.Select方法。因为此方法具备整数参数,所以其值将显示在方法名称下方。默认状况下,它设置为零,表示不活动状态,所以将其设置为1。而后对退出事件执行相同的操做,此次将参数保留为零。
(设置材质)
区域对象默认使用不活动的红色材质。只要有物体进入区域,将切换材质到绿色。当有东西离开这个区域时,它又会变成红色。
(和检测区域的交互)
2.3 最开始进入和最后退出
该检测区域能够工做,并确实能够完成其编程的目的,即每次进入时调用一次进入,每次离开时调用一次退出。所以,咱们能够混合使用enter和exit事件(例如enter,enter,exit,enter,exit,exit),而且当其中仍然有东西时,最终会出现视觉上无效的区域。在区域中保持活动状态时,使区域保持活动状态更加直观。使用保证进入和退出事件将严格交替的区域进行设计也更加容易。所以,它仅应在第一件东西进入时和最后一件东西离开时发出信号。重构事件重命名为onFirstEnter和onLastExit可使这一点变得清晰,这将须要再次链接事件。
(重命名事件)
为了使这种行为成为可能,咱们必须跟踪区域中当前的碰撞体。经过为DetectionZone提供一个List
该列表如何工做?
请参阅“对象管理”系列的“持久对象”教程。
在OnTriggerEnter中,只有在列表为空时才调用enter事件,而后始终将碰撞器添加到列表中以跟踪它。
在OnTriggerExit中,咱们从列表中移除碰撞器,而且只有在列表为空时才调用退出事件 列表的Remove方法返回删除是否成功 这应该老是这样的,由于不然咱们就没法追踪碰撞器。
(只要有物体在区域就保持激活状态)
2.4 检测忽然出现和消失的物体
不幸的是,OnTriggerExit不可靠,由于在停用,禁用或销毁游戏对象或其碰撞器时便不会再调用它。不该该单独禁用碰撞器,由于那样会致使物体掉落到几何体中,所以咱们将不支持这种方法。可是咱们应该可以处理整个游戏对象在区域内时被禁用或销毁的状况。
在每个物理步长中,咱们都要检查区域内的碰撞器是否仍然有效。添加一个在碰撞器列表中循环的FixedUpdate方法。若是一个碰撞器计算为false,这意味着它或它的游戏对象已经被销毁。若是不是的话,咱们就须要检查它的游戏对象是否被禁用了,这一点咱们能够经过它的游戏对象的active属性来发现。若是碰撞器再也不有效,则将其从列表中删除并递减循环迭代器。若是列表为空,则调用退出事件。
大多数状况下,检测区域中没有物体。为了不没必要要地连续调用FixedUpdate,咱们能够在组件唤醒时和最后一个碰撞器退出后禁用该组件。而后咱们只有在有东西进入后才启用它。之因此这样有效,是由于不管是否启用行为,老是会触发触发器方法。
接下来,咱们还应该处理区域对象自身被停用或销毁的状况,由于当事件仍在区域中时发生时,调用退出事件是有意义的。咱们均可以经过添加一个OnDisable方法来完成这两项工做,该方法清除列表并在列表不为空时调用exit事件。
请注意,检测区的组件不该由其余代码禁用,由于它能够管理本身的状态。通常规则是不要禁用检测区域组件,也不要禁用任何可能影响该区域的碰撞器。这些游戏对象应所有停用或销毁。
2.5 热重载
由于热重载(在编辑器播放模式下从新编译)将调用OnDisable,因此它违反了咱们刚刚声明的规则。这将致使退出事件被调用以响应热重载,此后已经在区域中的对象会被忽略。幸运的是,咱们能够在OnDisable中检测到热重载。若是同时启用了该组件而且游戏对象处于活动状态,则咱们将进行热重载,而且什么也不作。当游戏对象没有被销毁而组件被销毁时,状况也是如此,可是咱们仍然什么都不作。
咱们只须要在编辑器中播放时进行检查,就能够将代码包装在#if UNITY_EDITOR和#endif中。
OnDisable中有哪些相关状态组合?
若是禁用了该组件,仅仅是禁用或反激活游戏对象,则应该继续进行。不然,若是游戏对象未处于活动状态,则该游戏对象将被停用或销毁,应该继续。不然,要么是热重载,要么是仅组件被销毁,则将其忽略。
2.6 更复杂的行为
这只是经过事件能够完成的简单演示。你能够经过将更多条目添加到事件列表来建立更复杂的行为。甚至没必要为此建立新方法,直接使用现有方法。而限制则是它必须是与事件的参数列表匹配的无效方法或属性设置器,或者最多具备一个可序列化的参数。例如,我进行了一些设置,以便在更改检测区域自己的可视化效果的同时,在检测区域内有东西时关闭悬浮区域。
(切换悬浮区域)
您必老是对全部事件都响应。有时候可能只有在进入或退出时才触发某些事件。例如,在进入区域时激活某些内容。而后退出并不会取消激活它,而从新进入则会再次激活它,虽然二级激活实际上没有任何用处。
这种基于事件的方法能够用于整个游戏吗?
从理论上讲,是的,它对于快速原型制做很是有用,可是却很麻烦。一旦发现本身重复了复杂的模式,便有必要为其建立专用的方法或行为,这种方法或方法应该更容易使用,并在之后必要时进行优化。
3 简单运动
咱们将在本教程中介绍的最后一种状况是移动环境对象。复杂的运动能够经过动画来完成,能够经过检测区域触发。可是一般两点之间的简单线性插值就足够了,例如,对于门,电梯或浮动平台。如今,让咱们添加对此的支持。
3.1 自动滑动条
不管插值什么,它在概念上都由从0到1的滑块控制。如何更改值是与插值自己不一样的问题。保持滑块分离还能够将其用于多个插值。所以,咱们将建立一个专用于该值的AutomaticSlider组件。它的可配置持续时间必须为正。当咱们使用它为物理对象设置动画时,咱们将使其在FixedUpdate方法中增长其值,并确保它不会溢出。一旦值达到1,咱们就能够完成并能够禁用滑块。
再一次,咱们将使用Unity事件使它可以附加行为到滑动条。在本例中,咱们须要一个随值变化的事件,咱们将使用它来传递滑块的当前值。因此咱们的事件须要一个浮点参数,可使用UnityEvent
可是,Unity没法序列化通用事件类型,所以该事件不会显示在检查器中。咱们必须建立本身的具体可序列化事件类型,该事件类型只是扩展UnityEvent
进入播放模式时,滑块将当即开始增长。若是你不但愿这样作,请在默认状况下将其禁用。而后,你能够将其链接到检??测区域,以在之后启用它。
(禁用具备值更改事件的滑块)
请注意,在这种状况下,事件的名称后跟(Single),表示它具备一个参数。单精度是指浮点类型,它是单精度浮点数。
3.2 位置插值
接下来,建立一个PositionInterpolator组件类型,该类型经过带有float参数的公共Interpolate方法在两个可配置位置之间插值可配置刚体的位置。使用Vector3.LerpUnclamped,以使提供的值不会被钳位,而是由调用者决定。咱们须要经过其MovePosition方法更改身体的位置,以便将其解释为运动,不然将成为闪现。
(位置插值和滑块相链接)
经过将sider和interpolator都添加到同一平台对象,我建立了一个简单的移动平台。插值器的Interpolate方法的动态版本绑定到滑块的事件,这就是为何其值没有字段的缘由。而后,我将滑块链接到检测区域,以便在有物体进入该区域时激活平台。请注意,插值点在世界空间中。
(激活移动的平台)
3.3 自动倒置
咱们能够经过向AutomaticSlider添加可配置的自动反向切换来使插值来回移动。这须要咱们跟踪它是否反转,并在FixedUpdate中加倍代码,同时必须支持双向。一样,当自动反转激活时,咱们必须跳动而不是钳制该值。在持续时间极短的状况下,这可能会致使溢出,所以反弹后咱们仍然会钳住。
(自动升降的平台)
3.4 平滑步长
线性插值的运动是刚性的,反转时速度会忽然变化。经过将值的平滑变体传递给事件,可使其加速和减速。经过对其应用smoothstep函数来实现。并使它成为可配置的选项。
(线性VS平滑)
(开启了平滑步长的平台)
3.5 更多控制
能够经过检测区域事件,并禁用滑块组件来暂停动画,但让咱们也能够控制其方向。最简单的方法是经过公共属性提供其反转状态。将反向字段替换为自动反向属性,调整其余代码的大小写以使其匹配。
让咱们对自动反转选项执行相同的操做。在这种状况下,咱们必须保留序列化字段,所以添加一个显式属性。
(更复杂的平台控制)
请注意,方向反转是忽然的,由于它仍然是简单的插值。若是要在任什么时候候平稳中止和反转,则须要建立使用加速度和速度的更复杂的逻辑。
3.6 压碎的碰撞体
移动场景的危险在于,物体最终可能会陷入两个接近的碰撞器之间。当碰撞器之间的缝隙关闭时,身体要么被弹出,要么最终被压入碰撞器或穿过碰撞器。若是碰撞表面成必定角度,则存在清晰的逃生路径,物体将朝该方向被推进。若是不是这样,或者若是没有足够的时间逃脱,则物体最终会被压碎,穿透碰撞体。若是一个物体卡在两个足够厚的简单碰撞器之间,那么它能够留在它们内部,一旦有一条清晰的道路就弹出。不然会掉下去。
(物体被压入地表内了)
若是碰撞表面成必定角度,则物体会被推到一边,而且颇有可能逃脱。所以,经过在表面之间留出足够的空间或经过引入倾斜的碰撞器(不管是否可见)来设计这样的配置是一个好主意。此外,将box碰撞器隐藏在地板上可使它更牢固,以避免物体被推入。或者,添加一个区域,在适当的时候触发该区域的销毁,表示它被压碎了。
(带有角度的碰撞器,而且地表下面隐藏了盒碰撞器)
3.7 局部插值
世界空间中的配置可能会带来不便,由于它没法在多个位置用于同一动画。所以,让咱们经过在PositionInterpolator中添加一个局部空间选项进行总结。为此,咱们添加了一个可选的可配置的Transform,该插值相对于应该发生的插值。一般用插值器引用对象,但这不是必需的。
(相对插值让复用成为可能)
下一章节,滚动。
本文翻译自 Jasper Flick的系列教程
原文地址:
https://catlikecoding.com/unity/tutorials

本文分享自微信公众号 - 壹种念头(OneDay1Idea)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。