添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
力能扛鼎的键盘  ·  Error in ...·  2 月前    · 
长情的刺猬  ·  expected bytes, str ...·  4 月前    · 
豪爽的麦片  ·  Capture core crash ...·  10 月前    · 
飘逸的企鹅  ·  基于Unity 关于SteamVR中 ...·  1 年前    · 
独立的电影票  ·  Requests limits and ...·  1 年前    · 
精彩文章免费看

Unity 物理系列三 角色控制器

参考
https://docs.unity.cn/cn/2019.4/Manual/CharacterControllers.html

第一人称或第三人称游戏中的角色通常需要一些基于碰撞的物理效果,这样角色就不会跌穿地板或穿过墙壁。但是,通常情况下,角色的加速度和移动在物理上并不真实,因此角色可以不受动量影响而几乎瞬间加速、制动和改变方向。
在 3D 物理中,可以使用 角色控制器 创建此类行为。该组件为角色提供了一个始终处于直立状态的简单胶囊碰撞体。控制器有自己的特殊函数来设置对象的速度和方向,但与真正的碰撞体不同,控制器不需要刚体,动量效果也不真实。
角色控制器无法穿过场景中的静态碰撞体,因此将紧贴地板并被墙壁阻挡。控制器可以在移动时将刚体对象推到一边,但不会被接近的碰撞加速。这意味着可以使用标准 3D 碰撞体来创建供控制器行走的场景,但您不受角色本身的真实物理行为的限制。

一、参考B站 视频 【简明Unity教程】漫谈Unity中的各种移动方法

直接修改Postion简单粗暴,无法处理复杂情况比如物理碰撞,障碍物。比如控制一个小球移动:

void Update(){
   float x = Input.GetAxis("Horizontal");
   transform.Translate(x*Time.deltaTime*turnSpeed,0,speed*Time.deltaTime);
   //上面的Translate等价于这样写:
   transform.position += new Vector3(x*Time.deltaTime*turnSpeed,0,speed*Time.deltaTime);

关于position和Translate的区别,可以参考Unity基础篇:Unity中的世界坐标和局部坐标,Transform和Translate等问题的讨论。

对于translate(a,b)函数,如果省略b,系统将把它缺省为space.self,意思是在自身坐标系进行a向量方向的移动,如果b为space.world,那么物体就在世界坐标系上进行a向量方向的移动。
transform.position 本身就是世界坐标。transform.position+= vector3.forward等于是在世界坐标的z轴前进。
Vector3.forward和transform.forward的区别,Vector3.forward的值永远是(0,0,1)(这里的(0,0,1)是世界坐标的(0,0,1)),而transform.forward我们可以理解为其对应物体的z轴方向,是一个向量,而不是一个坐标,但是我们应当把它看成世界坐标系内的,而不是局部坐标系内的。

二、参考【移动专题】20分钟学会使用3D模型和动画
1.去Store下载并导入这两个资源
2.给模型添加动画和脚本

这一步可以参考视频,使用的知识点可以参考之前的动画系列:Unity 动画系列三 Animator Controller

脚本就简单粗暴移动position:

using UnityEngine;
public class CharMove : MonoBehaviour
    public float speed = 3;
    Animator anim;
    Vector3 move;
    private void Start()
        anim = GetComponent<Animator>();
    void Update()
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        move = new Vector3(x, 0, z);
        updateAnim();
        //往哪边走就往哪看
        transform.LookAt(transform.position + new Vector3(x, 0, z));
        transform.position += new Vector3(x, 0, z) * speed * Time.deltaTime;
    void updateAnim()
        anim.SetFloat("speed", move.magnitude);

这里视频中能看到如何解决以下两个问题:

  • 小人移动过快,这是因为使用的是RM动作,即root motion,动画播放时也进行了位移,需要暂时取消root motion。
  • 小人刚开始移动时,会在待机动作下移动一段距离后,才开始跑动。这是因为动作切换时,没有强行打断。需要在translation中,把has exit time取消勾选。这样就可以随走随停了。
  • 三、参考【移动专题】实用的物理刚体角色控制器
    1.给角色添加rigidbody和胶囊体,在场景中添加Cube进行测试
  • 角色撞到Cube会停下来,然后摔倒。这个修改rigidbody的freeze rotation,勾选即可解决。
  • 角色撞到Cube后,会发生抖动。这个需要将transfrom移动方式,改为刚体移动。
  • Unity 脚本生命周期中,详细解释了update和FixedUpdate区别,输入事件获取放在update中,刚体的移动操作则应该放在FixedUpdate:

    using UnityEngine;
    public class CharMove : MonoBehaviour
        public float speed = 3;
        Animator anim;
        Rigidbody rigid;
        Vector3 move;
        private void Start()
            anim = GetComponent<Animator>();
            rigid = GetComponent<Rigidbody>();
        void Update()
            float x = Input.GetAxis("Horizontal");
            float z = Input.GetAxis("Vertical");
            move = new Vector3(x, 0, z);
            //往哪边走就往哪看
            //transform.LookAt(transform.position + new Vector3(x, 0, z));
            //transform.position += new Vector3(x, 0, z) * speed * Time.deltaTime;
            updateAnim();
        private void FixedUpdate()
            rigid.velocity = move * speed;
        void updateAnim()
            anim.SetFloat("speed", move.magnitude);
    
    2.新的转向方式
    using UnityEngine;
    public class CharMove : MonoBehaviour
        public float speed = 3;
        public float turnspeed = 10;
        Animator anim;
        Rigidbody rigid;
        Vector3 move;
        float forwardAmount;//前进量
        float turnAmount;//转向量
        private void Start()
            anim = GetComponent<Animator>();
            rigid = GetComponent<Rigidbody>();
        void Update()
            float x = Input.GetAxis("Horizontal");
            float z = Input.GetAxis("Vertical");
            move = new Vector3(x, 0, z);
            //往哪边走就往哪看
            //transform.LookAt(transform.position + new Vector3(x, 0, z));
            //transform.position += new Vector3(x, 0, z) * speed * Time.deltaTime;
            //世界坐标转本地坐标
            Vector3 localMove = transform.InverseTransformVector(move);
            //本地坐标的Z永远是角色的正前方
            forwardAmount = localMove.z;
            //转向量则是两个分量的比值,x越大z越小就转得越快
            turnAmount = Mathf.Atan2(localMove.x, localMove.z);
            updateAnim();
        private void FixedUpdate()
            //rigid.velocity = move * speed;
            rigid.velocity = forwardAmount * transform.forward * speed;
            //转向,只需要转水平方向的。新朝向=老朝向*一个角度
            //turnAmout本身有正负性,已经能表示是左转还是右转
            rigid.MoveRotation(rigid.rotation * Quaternion.Euler(0, turnAmount * turnspeed, 0));
        void updateAnim()
            anim.SetFloat("speed", move.magnitude);
    
    3.对动画进行融合

    使用的知识点可以参考Unity 动画系列六 BlendTree混合树

    在游戏动画中一个常见的任务是将两个或多个相似的动作混合在一起。也许最著名的例子就是根据角色的速度混合行走和跑步的动画。另一个例子是一个角色在跑步时向左或向右倾斜,就是根据参数来混合,决定当前播放的是哪个动画。

    1.blend type 选2D Freeform Cartesian

    2D Freeform Cartesian(2D自由笛卡儿):当混合的2个参数不代表不同的方向时使用。使用Freeform Cartesian,参数X和Y可以表示不同的概念类型,例如角速度和线速度。

    先增加两个参数Forward,Turn。然后Add Motion Field:

    //anim.SetFloat("speed", move.magnitude); anim.SetFloat("Forward", forwardAmount); anim.SetFloat("Turn", turnAmount);
    4.给予角色走的能力
        void Update()
            float x = Input.GetAxis("Horizontal");
            float z = Input.GetAxis("Vertical");
            move = new Vector3(x, 0, z);
            //往哪边走就往哪看
            //transform.LookAt(transform.position + new Vector3(x, 0, z));
            //transform.position += new Vector3(x, 0, z) * speed * Time.deltaTime;
            //世界坐标转本地坐标
            Vector3 localMove = transform.InverseTransformVector(move);
            //本地坐标的Z永远是角色的正前方
            forwardAmount = localMove.z;
            //转向量则是两个分量的比值,x越大z越小就转得越快
            turnAmount = Mathf.Atan2(localMove.x, localMove.z);
            //按下左SHIFT键,进入走路状态
            if (Input.GetButton("Fire3"))
                forwardAmount *= 0.3f;
            updateAnim();
    

    不过现在这样,角色虽然走起来了,动画播放速度有点快。可以改下播放速度:

    // read inputs float h = CrossPlatformInputManager.GetAxis("Horizontal"); float v = CrossPlatformInputManager.GetAxis("Vertical"); bool crouch = Input.GetKey(KeyCode.C); // calculate move direction to pass to character if (m_Cam != null) // calculate camera relative direction to move: m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized; m_Move = v*m_CamForward + h*m_Cam.right; // we use world-relative directions in the case of no main camera m_Move = v*Vector3.forward + h*Vector3.right; #if !MOBILE_INPUT // walk speed multiplier if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f; #endif // pass all parameters to the character control script m_Character.Move(m_Move, crouch, m_Jump); m_Jump = false;

    这里角色按W向前时,是根据摄像头来计算的。按C键即crouch,是蹲下走路。

    4.ThirdPersonUserControl.cs
    public void Move(Vector3 move, bool crouch, bool jump)
        // convert the world relative moveInput vector into a local-relative
        // turn amount and forward amount required to head in the desired
        // direction.
        if (move.magnitude > 1f) move.Normalize();
        move = transform.InverseTransformDirection(move);
        CheckGroundStatus();
        move = Vector3.ProjectOnPlane(move, m_GroundNormal);
        m_TurnAmount = Mathf.Atan2(move.x, move.z);
        m_ForwardAmount = move.z;
        ApplyExtraTurnRotation();
        // control and velocity handling is different when grounded and airborne:
        if (m_IsGrounded)
            HandleGroundedMovement(crouch, jump);
            HandleAirborneMovement();
        ScaleCapsuleForCrouching(crouch);
        PreventStandingInLowHeadroom();
        // send input and other state parameters to the animator
        UpdateAnimator(move);
    

    if (move.magnitude > 1f) move.Normalize();当同时按下WD时,处理斜向移动过快。其它逻辑和上一部分的处理类似。

    5.blend tree
    6.Root Motion

    知识点可以参考Unity 动画系列四 代码控制动画实例 和 RootMotion
    这里自己实现了OnAnimatorMove,对速度可以自由控制,Root Motion显示为Handled by Script。

    7.AICharacterControl.cs

    这个是点击一个位置,自动导航的。

    agent.SetDestination(target.position); if (agent.remainingDistance > agent.stoppingDistance) character.Move(agent.desiredVelocity, false, false); character.Move(Vector3.zero, false, false);

    结合点就是agent.desiredVelocity
    在unity学习项目中,也有类似处理:

    传统末日风格的第一人称控制在现实中并不真实。该角色每小时能跑 90 英里,可以立即停止并急转弯。因为该角色非常不真实,所以使用刚体和物理组件来创造这种行为有点不切实际,并会让玩家产生错觉。解决方案是使用专门的角色控制器。角色控制器只是一个胶囊形状的碰撞体,可以通过脚本来命令这个碰撞体向某个方向移动。然后,控制器将执行运动,但会受到碰撞的约束。控制器将沿着墙壁滑动,走上楼梯(如果低于 Step Offset 值),并走上 Slope Limit 设置范围内的斜坡。
    控制器本身不会对力作出反应,也不会自动推开刚体。
    如果要通过角色控制器来推动刚体或对象,可以编写脚本通过 OnControllerColliderHit() 函数对与控制器碰撞的任何对象施力。
    另一方面,如果希望玩家角色受到物理组件的影响,那么可能更适合使用刚体,而不是角色控制器。

  • Slope Limit 将碰撞体限制为爬坡的斜率不超过指示值(以度为单位)。
  • Step Offset 仅当角色比指示值更接近地面时,角色才会升高一个台阶。该值不应该大于角色控制器的高度,否则会产生错误。
  • Skin width 两个碰撞体可以穿透彼此且穿透深度最多为皮肤宽度 (Skin Width)。较大的皮肤宽度可减少抖动。较小的皮肤宽度可能导致角色卡住。合理设置是将此值设为半径的 10%。
  • Min Move Distance 如果角色试图移动到指示值以下,根本移动不了。此设置可以用来减少抖动。在大多数情况下,此值应保留为 0。
  • Center 此设置将使胶囊碰撞体在世界空间中偏移,并且不会影响角色的枢转方式。
  • Radius 胶囊碰撞体的半径长度。此值本质上是碰撞体的宽度。
  • Height 角色的胶囊碰撞体高度。更改此设置将在正方向和负方向沿 Y 轴缩放碰撞体。
  • 使用示例可以参考
    [Unity3D]最简单的最详细的第一人称角色控制器
    Physx CharacterController源码解析