本章节我们继续研究碰撞体,并且探索一下碰撞体与刚体之间的联系。我们回到之前的工程,然后给我们的紫色球体Sphere1也添加一个刚体组件。如下所示
此时,两个球体都具备了碰撞体和刚体组件。接下来,我们Play运行查看效果
我们发现,黄球碰撞紫球之后,两者都向右移动了,并且黄色还发出的角度变化。之所以这样,肯定是刚体组件作用的原因。应该还是依据动能公式进行各种计算,其中影响的因素包括双方的质量,以及运动方的移动速度等等。如果指向让两个球体在X轴上移动的话,我们可以借助刚体的Constraints下的Freeze Position来实现,我们要做的就是勾选Y轴和Z轴,这样球体在受到刚体影响进行移动的时候,就只会在X轴上发生移动了。
大家可以将两个球体像上图那样设置,然后运行一下,查看效果,这里就不演示了。
有时候,我们需要在两个球体发生碰撞的时候做一些处理,比如播放一个碰撞火花效果。我们应该怎么办呢?这里要给大家介绍一个方法。脚本系统可以使用 OnCollisionEnter 方法检测何时发生碰撞。接下来,我们给紫球添加一个C#脚本(CollisionScript.cs),在这个脚本中我们只打印相互碰撞的两个游戏对象的名称即可。如下所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CollisionScript : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
// 打印碰撞游戏对象的名称
Debug.Log("当前对象 " + gameObject.name + " 与 " + collision.gameObject.name + " 发生碰撞");
}
}
方法OnCollisionEnter 被传入的参数为 Collision 类,该类中的gameObject变量可以获取到参与碰撞的游戏对象,进而可以获取到碰撞游戏对象的基本信息。同时,我们还可以通过Collision类的GetContact 或 GetContacts方法来获取碰撞点,该方法会返回ContactPoint对象,这个ContactPoint对象中point属性就是一个Vector3类型的位置点。接下来,我们将上面的脚本附加到紫球上面,然后Play运行当前工程。
由于我们的地面Plane也存在碰撞体组件,因此工程运行后就打印了紫球Sphere1与地面Plane的碰撞日志信息,然后就是当黄球Sphere2碰撞紫球Sphere1的时候,打印出紫球与黄球的碰撞日志信息。请注意,紫球是被碰撞的游戏物体哦。接下来,我们在写一个脚本(CollisionScript2.cs),并将该脚本附加到黄球上面,如下所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CollisionScript2 : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
// 打印碰撞游戏对象的名称
Debug.Log("碰撞游戏对象的名称 " + collision.gameObject.name + " 发生碰撞");
}
}
为了区分日志信息,我们上面的代码只打印了碰撞目标的名称,也就是紫球Sphere1 。然后,我们将这个脚本附加到黄球Sphere2上面,运行当前工程,如下所示:
首先两个球体Sphere1和Sphere2都会与地面Plane发生碰撞,这个不是我们关注的。我们主要关注的是当黄球Sphere2与紫球Sphere1 发生碰撞的时候,两个脚本中的OnCollisionEnter都被触发了。而方法的参数Collision对象都代表了碰撞的对方。上图中第一个红色框日志信息就是黄球Sphere2输出的,它的碰撞对方自然就是紫球Sphere1了。而下面的红色框日志就是紫球Sphere1输出的,它直接输出自己与Sphere2发生碰撞。其实,除了OnCollisionEnter方法之外,还有OnCollisionStay和OnCollisionExit两个方法,从名称上面来看,一个是碰撞中的调用方法(碰撞可能会持续发生),另一个是碰撞结束的调用方法(一次性)。而我们上面的OnCollisionEnter方法则是碰撞开始调用的方法(一次性)。我们可以根据我们要实现的效果来选择使用其中任意方法。
使用刚体组件能够帮助我们完成游戏对象的运动效果,然后配合碰撞体组件可以帮助我们完成碰撞后的运动效果。这两个组件基本上可以帮助我们模拟现实世界中物体的物理碰撞和移动。但是,有时候,我们还是希望通过代码来完成移动或者碰撞后的移动。比如上面中两个球体碰撞后,黄球弹了起来,这个可能不是我们想要的效果。因此我们不得不做轴向的限制。但是,如果使用代码控制的话,我们就可以简简单单的让球体在X轴上移动即可。如何使用代码控制呢?我们之前也讲过,我们可以启动刚体的“Is Kinematic”属性,使用代码来控制游戏对象的移动或旋转。因此,我们就先启用紫球Sphere1的“Is Kinematic”属性。这样的话,紫球应该就不能受物理引擎而发生运动了,运行工程如下:
接下来,我们开始Play运行工程
正如我们预想的那样,紫球不在向右运动,而且黄球由于紫球碰撞体的阻挡,也停止了移动。接下来,我们继续启动黄球Sphere2的“Is Kinematic”属性。
这样的话,我们之前施加的力Constant Force组件就不能其作用了,我们应该使用脚本控制黄球向右移动,我们需要向“CollisionScript2.cs”脚本中添加如下代码:
void Update()
{
if (Input.GetKeyDown(KeyCode.D))
transform.Translate(new Vector3(0.5f, 0, 0));
}
我们使用按键“D”来控制黄球向右移动去碰撞紫球,运行工程如下所示:
我们发现,黄球可以穿透紫球,并且紫球也没有任何的反应。并且我们的控制台也没有任何的日志输出。这说明,当我们启用刚体的“Is Kinematic”属性,而由代码去控制游戏对象进行移动或旋转的时候,现实世界中的碰撞效果全部消失了,并且OnCollisionEnter方法也没有被触发。那我们应该怎么办呢?首先,我们应该明白一点的是,我们启用刚体的“Is Kinematic”属性之后,就不能够让Unity物理引擎来控制游戏对象的移动或旋转了。正如我们使用按键“D”来控制黄球移动一个道理。同样的,紫球在受到碰撞的时候,也需要我们使用代码来控制它的移动和旋转。但是,我们怎么知道两球碰撞的时间点呢,OnCollisionEnter方法也失效了啊。我们需要通过另一个方法来执行碰撞后的运动效果,就是碰撞体里面的“Is Trigger”属性。接下来,我们就开启紫球Sphere1碰撞体里面的“Is Trigger”属性,截图如下:
然后,我们需要在紫球的“CollisionScript.cs”脚本中添加“OnTriggerEnter”方法。该方法的参数为Collider 对象(与OnCollisionEnter方法参数Collision不一样哦)。这里的Collider 对象同样可以通过gameObject来获取碰撞对象。具体代码如下:
void OnTriggerEnter(Collider other)
{
Debug.Log("当前对象被" + other.gameObject.name + "碰撞到了!");
}
接下来,我们Play运行当前工程。
我们终于看到OnTriggerEnter方法被触发了,打印出了黄球Sphere2了。那么,我们继续给黄球添加OnTriggerEnter方法也试一试,如下所示:
void OnTriggerEnter(Collider other)
{
Debug.Log("碰撞到了 " + other.gameObject.name + " 对象!");
}
接下来,我们就Play运行当前工程
我们发现两球的OnTriggerEnter方法都被触发了,正确打印出彼此对方的名称。除了OnTriggerEnter方法之前,还有OnTriggerExit和OnTriggerStay两个方法。看名称就知道,一个是碰撞结束方法,另一个是碰撞中的方法(碰撞也是可持续的哦)。接下来,我们就借助这三个方法来控制黄球的运行。例如,当发生碰撞后,黄球就停止移动。我们可以在“CollisionScript2.cs”脚本中设置一个碰撞进行时的变量isTriggerFlag=false,在OnTriggerEnter方法中设置为true,在OnTriggerExit方法中设置为false,当我们使用按键D进行移动的时候,需要参考isTriggerFlag来进行移动,具体完整代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CollisionScript2 : MonoBehaviour
{
// 碰撞进行时变量
private bool isTriggerFlag = false;
void Update()
{
// 移动加入isTriggerFlag条件
if (Input.GetKeyDown(KeyCode.D) && !isTriggerFlag)
transform.Translate(new Vector3(0.5f, 0, 0));
}
void OnCollisionEnter(Collision collision)
{
// 打印碰撞游戏对象的名称
Debug.Log("碰撞游戏对象的名称 " + collision.gameObject.name + " 发生碰撞");
}
void OnTriggerEnter(Collider other)
{
isTriggerFlag = true;
Debug.Log("碰撞到了 " + other.gameObject.name + " 对象!");
}
void OnTriggerExit(Collider other)
{
isTriggerFlag = false;
}
}
其实在上面的代码中,OnCollisionEnter方法已经失去了意义,可以删除掉了。
我们运行当前工程,当按下按键D移动黄球的时候,碰到紫球就不在移动了。
那么紫球该如何移动呢,我们可以在紫球的OnTriggerStay方法中进行移动,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CollisionScript : MonoBehaviour
{
void OnCollisionEnter(Collision collision)
{
// 打印碰撞游戏对象的名称
Debug.Log("当前对象 " + gameObject.name + " 与 " + collision.gameObject.name + " 发生碰撞");
}
void OnTriggerEnter(Collider other)
{
Debug.Log("当前对象被" + other.gameObject.name + "碰撞到了!");
}
void OnTriggerStay(Collider other)
{
// 去掉地面碰撞的情况
if (other.gameObject.name == "Sphere2")
transform.Translate(0.1f, 0, 0);
}
}
其实在上面的代码中,OnCollisionEnter方法已经失去了意义,可以删除掉了。
我们运行当前工程,当按下按键D移动黄球的时候,碰到紫球后,黄球停止移动且紫球向右缓慢移动,两者碰撞结束,黄球又可以移动……
静态碰撞体和动态碰撞体:
我们可以将碰撞体添加到没有刚体组件的游戏对象,从而创建场景的地板、墙壁和其他静止物体。这些被称为静态碰撞体(Static Collider)。相反,具有刚体的游戏对象上的碰撞体称为动态碰撞体(Rigidbody Collider)。如果我们在动态碰撞体上开启了IsKinematic 属性,那么就是运动碰撞体 (Kinematic Rigidbody Collider)。在游戏场景中,以上三种碰撞体都会出现,如何判断三者之间的碰撞关系呢?重点在于OnTriggerEnter和OnCollisionEnter方法的选择。我们可以这样的理解,刚体是基于碰撞体之上的运动组件。刚体可以帮助我们完成游戏物体的运动以及碰撞后的运动效果,同时提供OnCollisionEnter方法让我们可以处理碰撞后的游戏代码逻辑。如果我们不想让刚体来完成游戏的运动,也就是启用刚体的“Is Kinematic”属性,那么我们则需要使用代码来控制游戏物体的运动。为了能够获取碰撞发生,我们需要启用碰撞体的“Is Trigger”属性,Unity会在碰撞后调用OnTriggerEnter方法。因此,我们对游戏对象碰撞后的移动和旋转操作应该放置到OnTriggerEnter方法中。
但是OnTriggerEnter方法执行是有条件的,如下所示:
1、两个物体都必须有碰撞体组件;
2、其中一个物体的碰撞体的IsTrigger属性必须勾上;
3、最重要的一点,其中一个物体必须有刚体组件。如果是一个运动的物体去碰撞一个静止的物体,则刚体组件必须加在运动的物体上,否则无法触发OnOnTriggerEnter方法。
最后介绍一下Layer层和碰撞体的关系,层与层之间是否发生碰撞。我们点击菜单栏Edit->Project Setting->Physics,然后找到Layer Collision Matrix(层碰撞矩阵)选项。在Layer Collision Matrix下就会显示所有层,层与层之间是否可以发生碰撞检测。默认是都发生碰撞检测的,也就是勾选状态。如果不想让两个层进行碰撞检测,取消两层交叉的勾选框即可。
本课程涉及的内容已经共享到百度网盘:https://pan.baidu.com/s/1e1jClK3MnN66GlxBmqoJWA?pwd=b2id