详解Unity中的角色控制器

Posted 梦小天幼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解Unity中的角色控制器相关的知识,希望对你有一定的参考价值。

前言

如何让玩家所操纵的角色动起来,这在Unity中很容易实现,但是如何确保玩家所操作的角色不会产生一些非常违反物理常识的情况?而这就需要进行大量的检测,已确保玩家所操纵的角色正常。所幸Unity为我们提供了这样的一个组件——角色控制器(Character Controller)。下面将介绍该组件的基本参数以及使用方法。

目录

前排提醒:本文仅代表个人观点,以供交流学习,若有不同意见请评论留言,笔者一定好好学习,天天向上。

Unity版本[2019.4.10f1] 梦小天幼 & 禁止转载

视频讲解:
详解Unity的角色控制器 | Character Controller_BiLiBiLi


一、Character Controller组件参数介绍

该组件继承自Collider,不受物理系统的影响,但可以发生碰撞。

参数描述
Slope Limit可直接沿着向上移动的最大坡度
Step Offset可直接跨越的最大障碍高度
Skin Width皮肤厚度,值较大可减少抖动,较小可能导致角色卡住,一般设置为半径的10%
Min Move Distance当角色的单次移动距离小于该值时,则被忽略,不生效。可用于减少抖动
Radius & Height所控制角色的半径和高度

关于Skin Width

这个参数官方给的建议是设置为半径的10%,理由是防止角色卡住,但是这样会导致角色挨不着地面,所以这时候可以调整Center的Y轴值,即可使角色接触地面。

二、Character Controller API

有关角色控制器的API 详见下表:

变量/方法描述
isGrounded角色控制器是否接触地面
slopeLimit坡度度数限制
stepOffset可跨越台阶高度,单位米
detectCollisions其他刚体和角色控制器是否能与本角色控制器相撞,默认为真
SimpleMove()以一定速度来移动,移动时自动计算重力因素影响
Move()更复杂的移动,不计算重力影响

1.isGrounded

判断角色控制器是否接触地面

void Update()

    if (controller.isGrounded)
    
        print("正在地面上...");
    

2.SimpleMove

以一定速度来移动,Y轴速度被忽略,因为Y轴是被系统自动施加重力的,速度单位是米/秒。这就意味着,无需乘以Time.deltaTime。返回值为bool,当角色接触地面返回True,反之为False。只要你不做跳跃功能,我们就是好朋友。

    private void Update()
    
        float v = Input.GetAxis("Vertical");
        float h = Input.GetAxis("Horizontal");

        Vector3 dir = Vector3.right * h + Vector3.forward * v;
        characterController.SimpleMove(speed * dir);
    

3.Move

更复杂的移动方式,对一个角色拥有绝对的控制权,不会自动施加重力,需要自己写重力代码,如果需要做跳跃功能,必选这个,因为自由。

    public CharacterController characterController;
    //移动速度
    public float speed = 8.0f;
    //重力
    const float GRAVITY = 9.8f;
    //速度向量
    public Vector3 velocity = Vector3.zero;
    //跳跃高度
    public float jumpHeight = 1.2f;

    private void Update()
    
        //移动更新函数
        Move_Update();
        //高度更新函数
        Height_Update();
        //Move方法
        characterController.Move(velocity * Time.deltaTime);
    

    public void Move_Update()
    
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");

        Vector3 dir = Vector3.right * h + Vector3.forward * v;
        velocity.x = dir.x * speed;
        velocity.z = dir.z * speed;
    

    public void Height_Update()
    
        
        if (characterController.isGrounded)
        
            if (Input.GetButtonDown("Jump"))
               
                //v平方 = 2gh
                velocity.y = Mathf.Sqrt(2 * GRAVITY * jumpHeight);
               
                
            // 如果角色接触地面,则高度置零,这里设置-1而不是0,是因为有时候0无法起跳。
            if (velocity.y < -1)
                velocity.y = -1;
        
        else
        
            // 如果角色不在地面,则不断减去高度。
            //v=gt 自由落体运动
            velocity.y -= GRAVITY * Time.deltaTime;
        
        
    

4.SimpleMove和Move的区别

  • SimpleMove
    * 不受Y轴速度影响,自带重力效果,无法实现跳跃功能
    * 返回值为Bool,当角色接触地面返回True,反之为False。
  • Move
    * 无重力效果,自行实现重力,可做跳跃功能
    * 返回值(CollisionFlags对象),返回角色与物体碰撞的信息

三、总结和参考资料

1.总结

内容较少,不给予总结。hhh

2.参考资料

[1].Unity官方.Unity 官方手册 角色控制器
[2].百度.百度百科 自由落体相关词条

Unity初学1——角色移动控制(2d)

该文来自学习chutianbo老师的笔记,链接b站
在unity中移动角色一般采用控制角色transtion属性中的position其依据的坐标轴,一般就为二元一次方程的xy轴

最简单的移动

public class RubyController : MonoBehaviour

    // 每帧调用一次 Update
    // 让游戏对象每帧右移 0.1
    void Update()
    
        // 创建一个 Vector2 对象 position,用来获取当前对象的位置
        Vector2 position = transform.position;
        // 更改 position 的 x 坐标值,让其 加上 0.05
        position.x = position.x + 0.05f;
        // 更新当前对象的位置到新位置
        transform.position = position;
    

那么这样每一帧我们的角色都会向x的正方向轴移动0.05距离(帧数越高,移动越快)。

用控制来移动

public class RubyController : MonoBehaviour

   // 每帧调用一次 Update
    // 让游戏对象每帧右移 0.1
    void Update()
    
        // 获取水平输入,按向左,会获得 -1.0 f ; 按向右,会获得 1.0 f
        float horizontal = Input.GetAxis("Horizontal");
        // 获取垂直输入,按向下,会获得 -1.0 f ; 按向上,会获得 1.0 f
        float vertical = Input.GetAxis("Vertical");

        // 获取对象当前位置
        Vector2 position = transform.position;
        // 更改位置
        position.x = position.x + 0.1f * horizontal;
        position.y = position.y + 0.1f * vertical;
        // 新位置给游戏对象
        transform.position = position;
    

这似乎是使用unity自带的组件进行移动,通过获取默认的垂直按键和水平案件,即直接对应wasd和方向键。

第二种控制移动方式

public class RubyController : MonoBehaviour

    // Start is called before the first frame update
    void Start()
    
        
    

    // Update is called once per frame
    void Update()
    
    
        Vector2 position = transform.position;

        if (Input.GetKey("w"))
        
            position.y = position.y + 0.05f;
        else if (Input.GetKey("a"))
        
            position.x = position.x - 0.05f;
        else if (Input.GetKey("s"))
        
            position.y = position.y - 0.05f;
        
        else if(Input.GetKey("d"))
            position.x = position.x +0.05f;
        

        
       
        transform.position = position;
    

使用getkey自定义控制方式

但是第一种和第二种中使用的GetKey和GetAxis两个方法的区别在于。GetKey只有0和1的区别,不会存在0.5或者0.05。但是getAxis是一种平滑曲线(移动控制推荐)。

直接使用update移动的弊端

由于update的调用时直接和游戏帧数挂钩的,即一帧调用一次update,所以你帧数越高,角色移动越快
假设游戏30帧,那么0.1f在一秒钟移动就是0.1*30为3个单位。如果是60帧,一秒钟就是6个单位。

解决帧数影响移动速率的方法一:锁帧

  void Start()
    
    //开垂直同步//
        QualitySettings.vSyncCount = 0;
        //帧率设置为50//
        Application.targetFrameRate = 50;
    

但是锁帧会降低游戏画面质量,所以我们使用单位秒来控制Ruby的行动

暂时的最终解决办法

public class RubyController : MonoBehaviour

 public float speed = 0.1f;
 // Start is called before the first frame update
 void Start()
 

 

 // Update is called once per frame
 void Update()
 
     float horizontal = Input.GetAxis("Horizontal");
     float vertical = Input.GetAxis("Vertical");
     Vector2 position = transform.position;
     position.x = position.x + speed * horizontal * Time.deltaTime;
     position.y = position.y + speed * vertical * Time.deltaTime;
     transform.position = position;
 

这样子的话,我们会发现speed=5时才能堪堪获得原本使用帧控制时0.1的速度;

解决碰撞时的bug

如果按照上方最后一份代码控制移动,那么我们会发现在两个刚体碰撞时,我们的角色会出现鬼畜,即我们的位置先进入了碰撞体所占据的位置,然后又被弹了出来,所以这里我们需要引入物体的刚体组件来解决

void Start()
 
     //获得当前游戏对象的刚体组件
     rigidbody2d = GetComponent<Rigidbody2D>();


 
   void Update()
 
    horizontal = Input.GetAxis("Horizontal");
    vertical = Input.GetAxis("Vertical");

 
 //固定时间间隔刷新方法
 private void FixedUpdate()
 
    
     Vector2 position = transform.position;
     position.x = position.x + speed * horizontal * Time.deltaTime;
     position.y = position.y + speed * vertical * Time.deltaTime;
     rigidbody2d.position = position;
 

这里我们需要将获取移动放在update中,因为这样是一帧一调用,会更加平滑,放在FixedUpdate则是约是20ms一调用。

好,常规的bug时间
在上面这套使用物理系统的移动之后,我们在后续加入敌人移动之后,在与其他有力的作用的物体碰撞之后,我们的主角也会获得一个另一个物体移动方向的力(约莫就是机器人向下走,主角在机器人下方被碰撞,主角会一直向下直到碰到其他的碰撞体)
按照解答是刚体移动不能直接使用位置赋值
所以 rigidbody2d.position = position;这一句我们要改成 rigidbody2d.MovePosition(position);

本文仅是个人初步学习unity所用
链接: Ruby’s Adventure:2D 初学者
链接:垂直同步是什么
链接:Time.deltaTime的官方文档

以上是关于详解Unity中的角色控制器的主要内容,如果未能解决你的问题,请参考以下文章

如何使用Unity做游戏中的寻路导航

Unity中的虚拟摇杆,用于触屏游戏的角色移动控制

unity3D如何设计一个游戏角色选择界面?

iOS Unity3D游戏引擎入门③

Unity 中的 AI 角色与 Photon View

详解Unity的移动控制实现