在我们之前的章节中,我们已经了解了碰撞体和刚体。但是,对于刚体这个组件来讲,有两种使用方式。其一就是用它来模拟现实世界的移动或碰撞效果(例如,门的开关);其二就是使用代码来控制物体移动或碰撞(例如,角色的行走)。两种方式使用场景不同而已。
本章节要讲的角色控制器 Character Controller也是一个组件,它用来控制游戏对象的运行。需要注意的是,该组件具有碰撞特性(胶囊碰撞体),而没有刚体特性(不接受刚体的运动控制)。如果我们使用一个具有刚体组件的游戏对象A去碰撞具有角色控制器的游戏对象B的时候,我们的游戏对象B并不会因为碰撞而运动。角色控制器组件提供了Move和SimpleMove等方法进行移动控制。
接下来,我们就来创建一个“SampleScene6.unity”场景,向其中添加一个球体和胶囊体。
其中球体我们添加球体碰撞体组件和刚体组件,以及一个X轴正方向的力
然后就是我们的胶囊体,添加一个CharacterController组件。这里,我们需要注意的是,默认情况下,我们添加的胶囊体会自带“Capsule Collider”碰撞体组件,我们取消它即可。
然后我们Play运行我们整个工程,如下所示:
我们发现,当球体碰撞到胶囊体的时候,两者都停止了运行,并且没有穿透模型。这说明我们的角色控制器组件具备碰撞体特性,但不具备刚体特性(如果具有刚体属性的话,它应该受到碰撞影响,向右边移动)。即使我们加上刚体,也不会有刚体特性的。角色控制器是专门用来控制角色移动的组件,区别于直接用Transform或者刚体,CharacterController有着更好的效果。接下来,我们就来简单介绍这个组件。
Slope Limit:坡度限制,设置角色可以走上的最大斜坡角度值(以度为单位)。
Step Offset:台阶高度,设置角色可以迈上的最大台阶高度值。
Skin width:皮肤厚度,决定两个碰撞体碰撞后相互渗透的程度。较大的皮肤宽度可减少抖动。较小的皮肤宽度可能导致角色卡住。合理设置是将此值设为半径的 10%。
Min Move Distance:最小移动距离,设置角色对象最小移动值。
Center:设置胶囊碰撞体在世界坐标中的位置。
Radius:设置胶囊碰撞体的横截面半径。
Height:设置胶囊碰撞体的高度。
前四个参数主要解决移动中遇到障碍时候的设置;后面三个参数则是设置碰撞形状的设置。CharacterController为我们提供了两种移动方法:Move 和 SimpleMove,两者都需要一个Vector3类型的参数。首先我们先试一试Move方法。我们取消Sphere游戏对象,增加一个平面来充当斜坡,如下所示:
接下来,我们创建一个“CharacterMove.cs”脚本来控制胶囊体移动。想到移动,大家肯定会联想到Translate方法,这个方法是我们之前学的,就是用来移动的。我们先使用它进行移动操作,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterMove : MonoBehaviour
{
// 移动速度
private float speed = 10.0f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.A))
{
transform.Translate(Vector3.left * speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.D))
{
transform.Translate(Vector3.right * speed * Time.deltaTime);
}
}
}
上面的代码非常简单,就是按下“A”和“D”键的时候,让胶囊体左右移动。
我们发现,胶囊体确实可以移动,但是它穿过了斜坡,并没有爬上斜坡。接下来,我们就是用CharacterController的Move方法来试试,代码修改如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterMove : MonoBehaviour
{
// 移动速度
private float speed = 10.0f;
// 角色控制器组件
private CharacterController controller;
// Start is called before the first frame update
void Start()
{
controller = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.A))
{
//transform.Translate(Vector3.left * speed * Time.deltaTime);
controller.Move(Vector3.left * speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.D))
{
//transform.Translate(Vector3.right * speed * Time.deltaTime);
controller.Move(Vector3.right * speed * Time.deltaTime);
}
}
}
然后我们重新Play运行工程,如下:
我们发现,胶囊体可以顺利爬上斜坡,但是往回移动的时候,就会停留在空中继续移动。也就是说,Move方法并没有考虑重力的效果。那么SimpleMove方法呢?
if (Input.GetKey(KeyCode.A))
{
//transform.Translate(Vector3.left * speed * Time.deltaTime);
//controller.Move(Vector3.left * speed * Time.deltaTime);
controller.SimpleMove(Vector3.left * speed);
}
if (Input.GetKey(KeyCode.D))
{
//transform.Translate(Vector3.right * speed * Time.deltaTime);
//controller.Move(Vector3.right * speed * Time.deltaTime);
controller.SimpleMove(Vector3.right * speed);
}
请注意,SimpleMove方法的不需要Time.deltaTime时间了,简化了我们的代码书写。接下来,我们重新Play当前工程,效果如下:
这次我们发现,这次在进行移动的时候,就会受到重力的影响了。通过对比,我们可以很清楚的知道Translate,Move和SimpleMove三者的区别了。毫无疑问,我们肯定最终选择SimpleMove方法,它提供了碰撞检测以及重力效果。
另外,CharacterController由于没有直接的碰撞组件和刚体组件,因此OnCollisionXXX和OnTriggerXXX方法就不能使用了,但角色控制器专门提供了OnControllerColliderHit方法。接下来,我们就试试这个方法,它的参数为ControllerColliderHit对象,该对象有诸如gameObject,collider,rigidbody,controller等属性可以使用。
void OnControllerColliderHit(ControllerColliderHit hit)
{
Debug.Log("碰撞物体为:" + hit.gameObject.name);
}
增加以上代码后,重新运行当前工程。
请注意,“Plane”是我们的地面,而“Plane(1)”是我们的斜坡。由于碰撞检测非常快,会输出大量的Log信息,因此我们可以点击“Collapse”将重复内容折叠显示。另外,角色控制器Character controller 为我们提供了一个isGrounded属性,我们可以再脚本章通过该属性判断当前角色是否碰撞到地面。
接下来,我们总结刚体,碰撞体以及角色控制器三者的使用场景。
首先,刚体多用于场景中的“道具物体”,他们的运动通过Unity的物理系统来控制,我们要做的的就是使用代码给这些“道具物体”施加一个力即可,Rigidbody的AddForce()方法。
接下来就是碰撞体,它定义了游戏对象的“轮廓外形”,碰撞检测就是根据这个“轮廓外形”进行计算的。几乎场景中所有需要交互的游戏对象都需要碰撞体,即便是场景中“静止”的游戏物体,为了不发生“穿模”现象,我们也需要增加碰撞体(例如场景中的花草树木,建筑物等等)。碰撞体一般联合刚体一起使用,场景中能够“运动”的游戏对象,一般都会同时添加碰撞体和刚体。如果想让Unity的物理系统来控制“运动物体”的移动,就按照刚体默认属性即可(例如我们上面说的“道具物体”)。如果想要使用代码来控制“运动物体”的移动,则需要勾选刚体的Is Kinematic属性。两者的区别还体现在OnCollisionEnter 和 OnTriggerEnter 方法上面。另外,刚体的Is Kinematic属性还可以通过脚本来控制,从而可以在物理系统和脚本代码两者之间切换控制。
最后是角色控制器,多用于游戏中的“角色”,因为游戏角色要进行大量的交互控制,它的移动和旋转相对来说,比较复杂。他们的运动要么是玩家控制,要么是脚本控制,所以一般不需要由物理系统来控制。这个时候,我们使用CharacterController组件来控制“角色”就非常方便了。CharacterController组件的本质是“胶囊碰撞体”+“移动代码封装”,它没有刚体特性,不会受到力的作用效果,也不会产生力的作用效果。请注意,使用了CharacterController组件就不需要使用刚体和碰撞体了,也不要使用Translate()来移动了,要用它自己提供的SimpleMove ()方法来移动物体。如果需要使用CharacterController进行碰撞检测的话,使用它的OnControllerColliderHit即可。
本课程涉及的内容已经共享到百度网盘:https://pan.baidu.com/s/1e1jClK3MnN66GlxBmqoJWA?pwd=b2id