碰撞检测
概念:碰撞就是游戏中的元素是否碰到一起,比如射击游戏,如果击中靶心就算碰撞,要检测出来,做一些其他的操作。比如射中靶心需要做一些特效等。
在Unity中,游戏物体的碰撞我们可以通过刚体组件和碰撞器组件来进行检测。
首先 新建Plane Wall
在Wall的上方,放置一个Cube
在Cube立方体添加刚体组件(Rigidbody)
运行游戏
这时候我们可以看到立方体掉在了Plane上了
Cube立方体和面板产生了碰撞,并且静止在了面板上,
这时候我们可以通过脚本来进行检测,
比如说立方体在掉落在Plane上的时候我们可以将其销毁
给cube立方体添加一个检测碰撞的脚本组件CubeCollision
31 // 碰撞开始
32 void OnCollisionEnter(Collision collision) {
33 // 销毁当前游戏物体
34 Destroy(this.gameObject);
35 }
36
37 // 碰撞结束
38 void OnCollisionExit(Collision collision) {
39
40 }
41
42 // 碰撞持续中
43 void OnCollisionStay(Collision collision) {
44
45 }
在MonoBehaviour类中,OnCollisionEnter,Exit,Stay是碰撞的回调方法,我们可以在CubeCollision类中重载它们。
当绑定了CubeCollision脚本组件的游戏物体发生碰撞时,
OnCollisionEnter便会被触发被调用一次,
OnCollisionStay这个方法会在整个碰撞过程中会被持续调用
如果碰撞接触被解除时,OnCollisionExit就会被触发一次
这是一个完整的碰撞接触过程,这三个方法都有一个Collision类型的参数,用于保存碰撞信息。
1 // 碰撞开始2 void OnCollisionEnter(Collision collision) { 3 var name = collision.collider.name; 45 Debug.Log("Name is " + name); 6 }
这样就可以得到被碰撞游戏物体的名称了
使用碰撞检测,发生碰撞的游戏物体之间会有碰撞模拟,比如撞到东西会反弹或者停顿一下之类的。有时候我们只是想要检测物体与物体之间是否发生接触,但是不要产生碰撞的效果,这时候我们可以使用触发器来进行接触检
除了碰撞之外,Unity还支持接触检测,因为
首先,我们把立方体Cube的碰撞器设置为触发类型:
像这样,我们在Cube的检查视图中找到碰撞器组件,将该组件的IsTrigger属性勾选上,这样在游戏物体发生接触的时候就不会有碰撞的效果了,而是会直接穿过去。Unity的碰撞器有很多类型,Cube的碰撞器类型是盒子碰撞器(BoxCollider),另外还有球形碰撞器、胶囊体碰撞器等。
然后,我们在CubeCollision脚本组件里面重载以下三个方法:
1 // 开始接触
2 void OnTriggerEnter(Collider collider) {
3 Debug.Log("开始接触");
4 }
5
6 // 接触结束
7 void OnTriggerExit(Collider collider) {
8 Debug.Log("接触结束");
9 }
10
11 // 接触持续中
12 void OnTriggerStay(Collider collider) {
13 Debug.Log("接触持续中");
14 }
MonoBehaviour类的OnTriggerEnter、OnTriggerExit和OnTriggerStay是触发检测的三个回调方法,OnTriggerEnter在游戏物体发生接触时调用一次,OnTriggerExit在游戏物体完全分离时调用一次,而OnTriggerStay在游戏物体接触过程中持续调用。
重新运行游戏,我们可以在控制台看到调试信息
这时候Cube会直接从Plane中穿下去,不会停在面板上。
刚体(Rigidbody)
在unity要使游戏中对象受物理引擎控制,那就需要给它加个刚体(Rigidbody)。Component->Physics->Rigidbody.
刚体是模拟物理效果的对象。添加了刚体组件的游戏物体,会受重力影响,可以被玩家四处推动,或者直接用脚本添加力给对象来让他四处移动。
通常情况下,对同一物体,要么通过刚体操纵,要么通过变换(transform)操纵。但添加刚体组件之后直接通过Transform组件更改物体位置,它和其他对象的碰撞可能出问题(除非Is Kinematic 选项被选中,选中该选项,则没法通过物理模拟来改变物体运动状态)。应避免同时使用两种方式。正确地方法应该是通过施加力或者扭矩的方式来移动刚体。
刚体属性
名称 | |
---|---|
Mass质量 | 学过物理的都知道,质量越大,惯性越大,里的单位可以自己统一规定,但是官方给出的建议是场景中的物体质量最好不要相差100倍率以上,估计是防止两个质量相差太大的物体碰撞后会产生过大的速度,从而影响游戏性能吧,。 |
Drag阻力 | 这里指的是空气阻力,当游戏物体收到某个作用力的时候,这个值越大越难被移动,当通过力移动的时候,有多少空气阻力影响物体,0表示没有空气阻力,阻力越大物体越难被移动,当阻力无穷大的时候,会让物体立刻停下来 |
Angular Drag(角阻力) | 同样指的是空气阻力,只不过是用来阻碍物体旋转的。如果设置成无限的话,物体会立即停止旋转。 |
Use Gravity(使用重力) | 勾选了这个项,游戏对象就会受到重力影响。 |
Is Kinematic(是否动态) | 勾选这个选项会使游戏对象不受物理引擎的影响,但这不等同于没有刚体组件。这通常用于需要用动画控制的刚体,这样就不会因为惯性而影响动画了。 |
Interplate(差值类型) | 如果看到刚体移动的时候运动的不是很平滑,可以选择一种平滑方式: None(无差值):不使用差值平滑。 Interpolate(差值):根据上一帧来平滑移动。 |
Extrapolate(推算) | 根据推算下一帧物体的位置来平滑移动。 |
Collision Detection(碰撞检测方式) | Discrete(离散):默认的碰撞检测方式。但若当物体A运动很快的时候,有可能前一帧还在B物体的前面,后一帧就在B物体后面了,这种情况下不会触发碰撞事件,所以如果需要检测这种情况,那就必须使用后两种检测方式。 Continuous(连续):连续碰撞检测。使用离散碰撞检测来检测与动态碰撞器(刚体)的碰撞,使用连续碰撞检测来检测与静态网格(非刚体)的碰撞检测。采用连续动态碰撞检测模式的刚体碰见这类物体也将采用连续碰撞检测模式。而与其他刚体将采用离散碰撞检测模式。这种模式适用于那些采用动态连续碰撞模式的物体碰撞的物体。(这对物理表现有很大的影响,如果你不关心与高速物体的碰撞,那么就让其为默认的离散模式。)。 Continuous Dynamic(动态连续):连续动态碰撞检测。使用连续动态碰撞检测模式来检测与连续模式和连续动态模式的物体间的碰撞。也适用于与静态网格(非刚体)的碰撞检测。而与之碰撞的其他模式的物体,采用的是离散动态碰撞检测模式。适用于高速物体。。 |
Freeze Position/Rotation(冻结位置/旋转) | 可以对物体在X、Y、Z三个轴上的位置/旋转进行锁定,即使受到相应的力也不会改变,但可以通过脚本来修改。 |
了解完刚体组的设置参数后,我们游戏场景中,在cube立方体的上方新建一个球体,附上刚体。
点击运行游戏按钮
碰撞器
这个是一个有趣的物理引擎控件。
碰撞器,顾名思义就是为了物体之间的碰撞而添加的组件,要使两个物体之间发生碰撞,就一定要为这两个物体添加碰撞器。
而刚体和碰撞器之间的关系则是:刚体是纯物理引擎的,可以为物体提供一些常见的物理效果。而碰撞器他可以独立于物理引擎之外来工作。即带有刚体的游戏对象如果没有带碰撞器,就不能与其他的物体发生碰撞,刚体要发生碰撞一定要有碰撞器!。而如果游戏对象只带有碰撞器而没有刚体,一样可以发生碰撞效果。
接下来看一个例子:用之前的场景,有一个Plane(带有Mesh Collider网格碰撞器但是没有刚体组件),一个Cube(带有Box Collider盒子碰撞器和一个刚体组件)。这时候选中Cube的Use Gravity,播放场景,可以看到Cube在落下之后会与Plane发生碰撞而停在Plane上。这说明了两个物体之间发生了碰撞。可以看到Plane在没有刚体组件的情况下一样可以与Cube发生碰撞,所以要发生碰撞刚体组件并不是必须的。
这时候如果让我们删除Cube或者Plane中的碰撞器组件,播放场景,可以看到Cube和Plane不再发生碰撞,而是穿过了Plane。这也就说明了两个物体之间需要发生碰撞,必须要都带有碰撞器组件。
碰撞器(Collider)的类型有好几种:
1、Box Collider(盒子碰撞器)——外形是一个立方体。
2、Sphere Collider(球形碰撞器)——外形是一个球体。
3、Cupsule Collider(胶囊体碰撞器)——外形是一个胶囊体。
4、Mesh Collider(网格碰撞器)——网格碰撞器中有一个Mesh属性,可以给他引用不同模型的物体作为网格碰撞器的形状。
5、Wheel Collider(车轮碰撞器)——专门轿车或者其他行驶车辆使用
6、Terrain Collidder(地形碰撞器)—— 这是一个基于高度图的碰撞器
有时候,在刚体发生碰撞的时候,我们想要获取到一些消息。这时候可以使用Unity提供的函数来获取刚体之间的碰撞消息。碰撞时主要可以传递三种消息:1、谁传递了消息。2、在消息传递过程中传递了哪些参数。3、在两个刚体发生碰撞时,用户应该在哪个刚体重使用消息捕获函数。
接下来看一个例子:检测与立方体发生碰撞的是什么物体,并且给与它碰撞之后的刚体施加一个刚体力。代码如下:
public int collisionForce=50;
void OnCollisionEnter(Collision collision)
{
print(collision.gameObject);
if(collision.rigidbody)
{
collision.rigidbody.AddForce(collisionForce,0,0);
}
}
将脚本添加给Cube物体。当游戏运行的时候,Cube会在场景中向下掉落。这时候Cube移动,与屏幕上的名称为Wall的游戏对象发生碰撞。这时候因为 OnCollisionEnter()函数,这个函数发生在开始发生碰撞的时候,参数是Collision类型的变量。输出了被Cube碰撞物体的信息。并且判断被碰撞的名称为Wall的游戏对象有没有刚体,如果有刚体则为这个名称为Wall的游戏对象的刚体添加一个X轴上的力。如果没有刚体,则返回null,这条语句不会执行。
可以看到名称为Wall的游戏对象产生了X轴上的力位置发生了变化,并且在Console视图中,输出了被Cube碰撞的Capsule的信息。
另外呢,我们还可以复合碰撞器是原始碰撞器的组合,集体作为一个 单一的碰撞器使用。当你有一个复杂的网格使用碰撞时,这就派上用场了,但不能使用网格碰撞器。要创建复合碰撞器,创建碰撞物体的子物体,然后添加原始碰撞 器到每个子物体,这就容易来定位、旋转和缩放每个碰撞器,它们是各自独立的。
建立父子关系
将需要的组合的物体标记成BoxCollider
把刚体添加进父对象(子对象不添加刚体)
这样就可以了,现在它们就是一个整体一了
这个模型附加了刚体碰撞器,并且有多个原始碰撞器作为子游戏物体。
当刚体父级通过力移动,那么子碰撞器也会随着移动,父刚体通过力改变移动
他的子碰撞器在场景中也会和场景中的其他碰撞器交互。
恒力(Constant Force)
指的是持续不断的应用一个力
这是一个小的物理工具类,用于应用一个恒力给物体,恒力组件添加会绑定RigidBody,改变刚体里的Angular Drag角阻力。比如秋千不停的转
属性:
名称 | 解释 |
---|---|
Force力 | 每一帧在世界空间应用给刚体的向量力 |
Relative Force 相对力 | 每帧应用于该力相对于刚体坐标 |
Torque扭矩 | 施加在世界坐标中的扭矩矢量。对象将绕着该矢量转动。矢量越长,转速越快。 |
Relative Torque 相对扭矩 | 施加在本地坐标中的扭矩矢量。对象将绕着该矢量转动。矢量越长,转速越快。 |
Rigidbody.AddForce只在一帧中应用一个力给Rigidbody,因此你必须持续地调用这个函数。 另一方面ConstantForce将在每一帧中应用这个力,直到你改变这个力或力矩到一个新的值。
public class ExampleClass : MonoBehaviour {
public float thrust;
public Rigidbody rb;
void Start() {
rb = GetComponent();
}
void FixedUpdate() {
rb.AddForce(transform.forward * thrust);
}
}
public void AddForce(Vector3 force, ForceMode mode = ForceMode.Force);
force:世界坐标空间里的向量。
mode:力模式,选择如何使用Rigidbody.AddForce施加一个力
变量名 | |
---|---|
Force | 添加一个rigidbody持续力,使用它的质量 |
Acceleration | 添加一个持续的加速度到刚体,忽视他的质量 |
Impulse | 添加一个即时Rigidbody力脉冲,利用其质量 |
VelocityChange | 添加一个Rigidbody瞬时速度变化忽略了它的质量 |
public void AddForce(float x, float y, float z, ForceMode mode = ForceMode.Force);
x:在世界坐标空间x轴力的大小。
y:在世界坐标空间y轴力的大小。
不过这个力仅应用于被激活的刚体,如果这个游戏对象没有被激活,即使添加力也会没有效果。
默认刚体是被唤醒的,如果力的大小为0,那么刚体不被唤醒
物理材质(Physic Material)
物理材质是用来调节碰撞物体的摩擦力和弹力效果
创建
然后将从项目视图中拖拽物理材质到场景的一个碰撞器上。
变量名称 | |
---|---|
dynamicFriction | 已经移动时摩擦力被使用。该值在0到1之间。 |
staticFriction | 当对象横卧在表面时,摩擦力系数被使用。 |
bounciness | 表面是多么有弹性的?值为0将不会有弹性。值为1将是没有任何能量损耗的弹力。 |
frictionCombine | 明确摩擦力如何结合 |
bounceCombine | 明确弹力如何组合 |
铰链关节(Hinge Joint)
铰链关节由两个刚体组成,约束它们像连在一个铰链上一样运动
可以用于门的打开,或者一些钟表动作的实现
在本节课程中我们可以通过该组件实现门与墙的连接功能,以此来实现玩家打开门时,对门施加一个力,通过这个力与铰链关节内部力的作用,使得门有一种被推开的表现。
新建Wall,Door Person 设置位置和缩放,
加Rigidbody 并且都把Use Gravity的勾去掉。
其中,Wall与Person的RigidBody组件的Is Kinematic属性打上勾(设为true,使其不受物理引擎驱动,Wall是为了防止其移动,Person是为了防止其受到力不断旋转。
在Door物体上添加Hinge Joint组件,
接下来我看来为Hinge Joint组件设置值:
- Connected Body 连接的物体保留为None。
这里有的朋友可能就会问了,为什么不是将Wall赋值到这里来,使得门与墙连接呢?因为留空的话,会默认与世界连接,而这一点,在手册上也有提到:
单独的铰链关节要连在游戏对象上。铰链会绕着Anchor 属性指定的点,沿着 指定的Axis 属性方向移动。不需要给关节的Connected Body 属性分配游戏对象。只有希望关节的Transform依赖附加对象的Transform时,才需要分配游戏对象给Connected Body属性。 - Anchor 锚点位置,该值按照门与墙的相对位置来设置,如下图:
这里的Wall在Door的x轴的负方向上,所以将Anchor设为0,0,-0.5。Anchor的x y z值对锚点的影响为:z为0时,z轴方向上锚点在原点,z=0.5时,z轴方向上锚点在物体的边缘,大家多设置几个值就明白了。在这,我们需要将锚点设置到门与墙连接的位置。 - Axis 坐标轴,有点类似与Rotation对物体的影响一样,这里我们需要门以与墙连接的位置为轴进行旋转,所以设置Axis = 0,1,0
设置完毕,运行项目
点击Person物体,直接在场景视图中移动,让他撞上门,就可以看到门被推开了,行走离开门这个物体后,门过一会就会旋转回原位。
弹簧关节(Spring Joint)
1: 将两个刚体束缚在一起, 相对位置保持不变,永远不会变化;
2: 弹簧关节属性
connect Body:目标连接的刚体;
Anchor 本体锚点,连接目标旋转时围绕的中心点;
Auto Configure Connected Anchor: 勾选时,仅给出锚点的坐标,系统将自动计算出目标锚点坐标;
Connect Anchor 连接目标的锚点,本体选择时围绕的中心点;
Sprint 弹力;
Damper: 阻尼,物体移动受到阻碍的大小,越大物体越慢;
Min distance 弹簧两端最小距离
Max distance 弹簧两端最大距离
Break Force 给出一个力的限值,当关节受到的力超过这个,关节损坏;
Break Torque 给出一个力矩的极限,当关节受到力矩超过此值时关节损坏;
Enable collision 允许碰撞检测;
Enable Preprocess 允许进行预处理;
创建一个空节点Spring_joint
创建一个正方体Cube为它的子节点,创建一个球体Sphere为它的子节点,为了区分,给Sphere一个材质,颜色为红色
给正方体Cube和球体Sphere都增加刚体组件Rigidbody,都去掉重力,
因为我们要做成和弹簧一般,当一个力施加给球体,然后球体和弹簧一般上下跳动。而不是随意往哪个方向弹跳,那么我们就必须要约束这两个物体的位置和旋转轴了。
设置正方体Cube的Constraint约束Freeze PositionX,Y,Z,RotationX,Y,Z,设置球体Sphere的Constraint约束Freeze RotationX,Y,Z。
给球体Cube增加spring_joint组件,里面的Connected Body属性设置为Sphere
创建一个脚本test_spring_joint挂载到Sphere上,要给它一个力
Rigidbody body;
// Use this for initialization
void Start()
{
this.body = this.GetComponent();
this.body.AddForce(new Vector3(200, 0, 0));//给球体一个x轴方向的力,它会像弹簧一样在正方体面前弹来弹去
}
固定关节
固定关节基于另一个物体来限制一个物体的运动。效果类似于父子关系,但是不是通过层级变换,而是通过物理实现的
1: 将两个刚体束缚在一起, 相对位置保持不变,永远不会变化;
2: 固定关节属性
connect Body:目标连接的刚体;
Break Force 给出一个力的限值,当关节受到的力超过这个,关节损坏;
Break Torque 给出一个力矩的极限,当关节受到力矩超过此值时关节损坏;
Enable collision 允许碰撞检测;
Enable Preprocess 允许进行预处理;
创建一个空节点fixed_joint
创建一个立方体cube为它的子节点,创建一个球体Sphere为它的子节点,为了区分,给Sphere2一个材质,颜色为红色
给立方体和球体都增加刚体组件Rigidbody,都去掉重力
给立方体增加fixed_joint组件,里面的Connected Body属性设置为Sphere球体
创建一个脚本test_fix_joint挂载到Sphere球体上,要给它一个力
Rigidbody body;
// Use this for initialization
void Start() {
this.body = this.GetComponent();
this.body.AddForce(new Vector3(0, 0, 100));//给它一个z轴方向的力
}
两者开始旋转,但是彼此之间的位置距离都没有变化,你到哪里我到哪里
如果对两者的旋转和位置需要约束可以在刚体的Freeze RotationX,Y,Z进行约束
可配置关节
创建一个空节点config_joint
创建一个正方体Cube为它的子节点,创建一个球体Sphere为它的子节点,为了区分,给Sphere一个材质,颜色为红色
给正方体Cube和球体Sphere都增加刚体组件Rigidbody,都保留重力,设置正方体Cube的Constraint约束Freeze PositionX,Y,Z,设置球体Sphere的Constraint约束Freeze PositionX,Y,Z。
给球体Cube增加config_joint组件,修改X Motion, Y Motion, Z Motion 为Locked,里面的Connected Body属性设置为Sphere,
创建一个脚本test_spring_joint挂载到Sphere上,杂重力的作用下两个物体像单摆一样运动。