Skip to content

简介

角色控制器(Character Controller)组件(Component)可用于创建和配置角色的物理属性和控制角色。令角色能被控制移动且具备一些基于碰撞的物理原理。

组件逻辑

在普遍的 3D 游戏中,角色通常需要一些有支持碰撞的物理系统,这样它就不会从地板上掉下来或穿过墙壁。在许多游戏中,角色的加速和运动不符合物理现实,这样的设计是为了让角色可以立即加速、制动和改变方向,而不受惯性的影响。

在 SandMod 编辑器中,可以使用 角色控制器(Character Controller)组件(Component)管理此种行为。该组件为角色提供:

  • 一个始终处于直立状态的 胶囊碰撞器(Capsule Collider)。
  • 有属于该组件特有的函数来设置对象的速度和方向的控制器。

带有角色控制器的 游戏对象(Game Object)无法穿过场景中的静态碰撞体,其能站在地板上移动并且会被墙壁阻隔。带有角色控制器组件的游戏对象可以在移动时推动带有刚体组件的游戏对象,但不会在碰撞时被弹开。简而言之,您可以使用碰撞器和刚体来创建可被控制、移动的角色,但仅使用角色控制器控制的角色却不完全受真实的物理规则影响。

【Character_Controller001.mp4】

创建方法

创建 角色控制器(Character Controller)组件(Component)前,需要在 场景编辑器(Scene Editor)的 层级窗口(Hierarchy)或者 场景预览窗口(Scene)中,选中囊括角色外观、骨骼等要素的根节点 游戏对象(Game Object)。

方法一:

选中对应的游戏对象后,在 检视窗口(Inspector)窗口中用鼠标左键点击 添加组件(Add Component)> 物理(Physics) > 角色控制器(Character Controller) 进行创建。

【Character_Controller002.png】

方法二:

选中对应的游戏对象后,在 菜单栏(Menu Bar)中点击 组件(Component)> 物理(Physics) > 角色控制器(Character Controller) 进行创建。

【Character_Controller003.png】

属性

角色控制器(Character Controller)组件(Component)包含以下属性:

【Character_Controller004.png】

名称描述
斜率限制(Slope Limit)设置角色可以行走的最大坡度。单位是角度,默认值是 45 度。
步距偏移(Step Offset)用于设置最大自动爬台阶高度。该值不应该大于角色控制器的高度,否则会产生错误。
蒙皮宽度(Skin Width)用于设置角色控制器和其他碰撞盒之间发生碰撞时,可以彼此穿透的深度最大值。轻微的穿透来避免数字精度的问题,可以避免碰撞导致角色抖动或者卡住。
该属性值通常为一个较小且为正的浮点数。较大的皮肤宽度可减少抖动。较小的皮肤宽度可能导致角色卡住。推荐将此属性值设为半径的 10%。
最小移动距离(Min Move Distance)角色控制器的最小移动距离,此设置可以用来减少角色非常规移动导致的抖动效果。每次调用 CharacterController.Move(value:Vector3) 时,如果低于这个距离则不会移动。
中心(Center)此设置将使胶囊碰撞盒中心的偏移,设置此值并不会影响角色的枢转方式。
半径(Radius)胶囊碰撞盒的半径长度。此值本质上是碰撞盒的宽度。
高度(Height)角色的胶囊碰撞盒高度,属性值表示胶囊体末端两个球心的距离。

角色移动控制逻辑

如果要驱动 角色控制器(Character Controller)移动,您可以使用 CharacterController.Move 方法,该方法会考量行进路线中的碰撞体,内部使用了模拟物理系统的算法去检测,检测碰到物体后,系统会做出以下判断:

  • 基于 斜率限制(Slope Limit)属性值,判断被碰撞的碰撞器坡度能否被攀爬。
  • 基于 步距偏移(Step Offset)属性值,判断被碰撞的碰撞器高度能否被攀爬。
  • 判断被碰撞的碰撞器无法被攀爬,控制器会停止移动。

如示例中您需要角色每帧朝着 X 轴正方向,以每帧移动 0.1 个单位的速度移动,代码示例如下:

typescript
class PlayerMove extends Component {

  //定义公共变量随时调整角色移动的方向和速度;
    public Movement:Vector3 = new Vector3(0.1,0,0);
  
  //定义私有变量保存 角色控制器(Character Controller);
    private _characterController:CharacterController;

    /*
    编辑运行时事件;
    */
    OnStart(): void {
      //获取 角色控制器(Character Controller)并将其保存为 _characterController 私有变量;
        this._characterController = this.gameObject.GetComponent(CharacterController);
    }

    /*
    编辑此脚本中每帧执行的逻辑;
    */
    OnUpdate(): void {
      //每帧控制角色根据公共变量 Movement 设定的速度和方向移动;
        this._characterController.Move(this.Movement);
    }

}

角色控制器(Character Controller)的 斜率限制(Slope Limit)属性值为 46,步距偏移(Step Offset)属性值为 0.6,移动经过 45 度的斜坡和高度为 0.5 的台阶运行效果如下:

【Character_Controller004.mp4】

由运行效果视频所示,角色没有模拟重力移动,如果需要角色移动模拟重力,您可以在 Y 轴方向添加移动的值,或者使用 CharacterController.SimpleMove 方法。

值得注意的是,为了简便计算 ,CharacterController.SimpleMove  方法有以下特点:

  • 该方法会为角色计算重力。
  • 该方法的参数为角色每秒移动的速度,而 CharacterController.Move 方法的参数为移动的值。

角色碰撞

碰撞事件

当角色控制器和碰撞器、其他角色控制器发生碰撞时,可以用 OnControllerColliderHit 方法检测角色的碰撞事件。

角色控制器碰撞事件的代码格式如下:

typescript
  OnControllerColliderHit(controllerColliderHit:ControllerColliderHit):void{
      //这里编辑点角色碰撞后发生的事件;
  };
  1. OnControllerColliderHit 是 角色控制器(Character Controller)组件用于检测碰撞的函数,挂载脚本的 游戏对象(Game Object)如果同时挂载了 角色控制器(Character Controller)组件可以直接调用此函数。
  2. controllerColliderHit 是角色碰撞事件的返回值,其包含了被碰撞的游戏对象、碰撞器等信息,请参阅 API 文档关于 ControllerColliderHit 的相关内容。

例如角色与其他带有碰撞器的 游戏对象(Game Object)发生了碰撞,需要在 控制台(Console)打印被碰撞游戏对象的名字,代码示例如下:

typescript
class PlayerConllision extends Component {

    //定义私有变量保存 角色控制器(Character Controller);
    private characterController:CharacterController;

    /*
    编辑运行时事件;
    */
    OnStart(): void {
        //获取 角色控制器(Character Controller)并将其保存为 _characterController 私有变量;
        this.characterController = this.gameObject.GetComponent(CharacterController);
    }
    
    /*
    角色控制器碰撞检测事件;
    -- controllerColliderHit 是碰撞事件 ControllerColliderHit 类型的返回值;
    */
    OnControllerColliderHit(controllerColliderHit:ControllerColliderHit):void{
        //通过 ControllerColliderHit 类型的回调获得被碰撞的 游戏对象(Game Object);
        let collidedGO:GameObject = controllerColliderHit.gameObject;
        //获得被碰撞的游戏对象的名字,并储存在命名为 collidedGOName 的变量中;
        let collidedGOName:string = collidedGO.name;
        //输出被碰撞的游戏对象的名字;
        Debug.Log("Collided object is:",collidedGOName);
    }

    
    OnUpdate(): void {
    }
}

逐帧碰撞检测

除了碰撞事件检测 角色控制器(Character Controller)是否发生碰撞,SandMod 也提供了 CharacterController.collisionFlagsCharacterController.isGrounded 等方法判断角色当前帧的碰撞状态。关于该 API 的具体内容请参阅 API 文档关于 CharacterController.collisionFlagsCharacterController.isGrounded 的内容。

需要注意的是,这些方法仅用于检测角色控制器当前帧的碰撞状态,如果需要实时得知角色控制器的碰撞状态,需要将该方法编辑于 OnUpdate 事件中。

例如需要检测角色是否在地面上以及角色身体是否会碰撞到其他碰撞器,代码示例如下:

typescript
class PlayerConllision extends Component {

  //定义私有变量保存 角色控制器(Character Controller);
    private characterController:CharacterController;

    /*
    编辑运行时事件;
    */
    OnStart(): void {
      //获取 角色控制器(Character Controller)并将其保存为 _characterController 私有变量;
        this.characterController = this.gameObject.GetComponent(CharacterController);
    }

    /*
    编辑此脚本中每帧执行的逻辑;
    */
    OnUpdate(): void {
      //判断角色当前帧与其他碰撞器碰撞的位置是否为角色控制器胶囊体的侧面;
        if(this.characterController.collisionFlags == CollisionFlags.Sides){
            //这里编辑角色的身体被碰撞时执行的事件;
        };
      //判断角色是否在地面;
        if(this.characterController.isGrounded){
            //这里编辑角色在地面时执行的事件;
        };
    }
}