【Unity游戏开发教程】零基础带你从小白到超神16——四种方法控制人物移动之角色控制器

控制人物移动的四种方式

  • 直接修改组件位置
  • 去资源商城下载角色控制系统成品直接拿来用
  • unity提供的角色控制器组件
  • 通过物理系统自己做

直接修改组件位置

最基础的方式,是改变物体位置的最直接的方式
适用于既没有物理系统,也对移动没有特殊要求的情况
demo

public float speed;

void Update()
{
    
    
    Move();
}

void Move()
{
    
    
    float MoveX = Input.GetAxisRaw("Horizontal");
    float MoveZ = Input.GetAxisRaw("Vertical");
    Vector3 dir = new Vector3(MoveX, 0, MoveZ).normalized;

    //1、Translate
    transform.Translate(dir * Time.deltaTime * speed);

    //2、修改positon
    //transform.position += dir * Time.deltaTime * speed;

    //3、TransformPoint
    //transform.TransformPoint(dir * Time.deltaTime * speed);
}

去资源商城下载角色控制系统成品

比如去商城搜索 3rd person
bir

具体使用可以参考文章:
https://zhuanlan.zhihu.com/p/31662004?mode=light

unity提供的角色控制器组件

1.我们要先向人物添加Character Controller组件
在这里插入图片描述

参数 描述
斜度限制(Slope Limit) 可直接沿着向上移动的最大坡度
每步偏移量(Step Offset) 可直接跨越的最大障碍高度
蒙皮厚度(Skin Width) 皮肤厚度,值较大可减少抖动,较小可能导致角色卡住,一般设置为半径的10%(ps:但是这样会导致角色挨不着地面,所以这时候可以调整Center的Y轴值,即可使角色接触地面。)
最小移动距离(Min Move Distance) 当角色的单次移动距离小于该值时,则被忽略,不生效。可用于减少抖动
半径和高度(Radius & Height) 所控制角色的半径和高度

2.新建脚本MoveText,挂在在人物上,编写控制脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveTest : MonoBehaviour
{
    
    
    private CharacterController player;//创建角色控制器
    void Start()
    {
    
    
        player = GetComponent<CharacterController>();

    }

    void Update()
    {
    
    
        //获取水平轴
        float horizontal = Input.GetAxis("Horizontal");
        //获取垂直轴
        float vertical = Input.GetAxis("Vertical");
        //创建成一个方向向量
        Vector3 dir = new Vector3(horizontal,0,vertical);
        //让物体朝该方向移动
        //player.Move(dir);   	//更复杂的移动,不计算重力影响
        player.SimpleMove(dir);	//以一定速度来移动,移动时自动计算重力因素影响
    }
}

3.运行调试
在这里插入图片描述

Character Controller API

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

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

void Update()
{
    
    
	if (controller.isGrounded)
	{
    
    
		print("正在地面上...");
	}
}

SimpleMove和Move的区别

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

代码优化,更加复杂的角色控制

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveTest : MonoBehaviour
{
    
    
    public float speed = 1; //移动速度 默认值1
    public float gravity = 1;//掉落速度(重力) 默认值1

    private CharacterController player;//创建角色控制器

    bool isGrounded = false;
    float fallSpeed = 0; //掉落速度 会随着滞空时间不断增加

    // Start is called before the first frame update
    void Start()
    {
    
    
        player = GetComponent<CharacterController>();
    }

    // Update is called once per frame
    void Update()
    {
    
    
        Movement();
    }

    void Movement()
    {
    
    
        //player提供了isGrounded接口用于检测是否落地
        isGrounded = player.isGrounded;

        //不在地面时,下坠速度不断增加
        if (!isGrounded)
        {
    
    
            fallSpeed -= gravity;
        }
        else
        {
    
    
            if (fallSpeed < 0)
            {
    
    
                fallSpeed = 0;
            }
        }

        //平移
        float MoveX = Input.GetAxisRaw("Horizontal");
        float MoveZ = Input.GetAxisRaw("Vertical");
        //Vector3 dir = new Vector3(MoveX, 0, MoveZ).normalized;

        //平移加上掉落 
        Vector3 v = (transform.forward * MoveZ + transform.right * MoveX).normalized * speed + transform.up * fallSpeed;
        // Debug.Log(v);
        // Debug.Log(Time.deltaTime);
        //1、simpleMove 带重力的移动
        // player.SimpleMove(dir * speed * Time.deltaTime);
        //2、Move 不带重力 需要自己设置重力
        player.Move(v * Time.deltaTime);
    }
}

通过物理系统自己做

物理方式通过对物体施加力,改变物体运动状态的方式让物体移动,其中最核心的组件就是Rigibody

简单介绍Rigibody

参数 含义 功能
Mass 质量 物体的质量(任意单位)。建议一个物体的质量不要与其他物体 相差100倍
Drag 阻力 当受力移动时物体受到的空气阻力。0表示没有空气阻力,极 大时使物体立即停止运动
Angular Drag 角阻力 当受扭力旋转时物体受到的空气阻力。0表示没有空气阻力, 极大时使物体立即停止旋转
Use Gravity 使用重力 该物体是否受重力影响,若激活,则物体受重力影响
Is Kinematic 是否是运动学 游戏对象是否遵循运动学物理定律,若激活,该物体不再受物理引擎驱动,而只能通过变换来操作。适用于模拟运动的平台或者模拟由铰链关节连接的刚体
Interpolate 插值 物体运动插值模式。当发现刚体运动时抖动,可以尝试下面的 选项:None(无),不应用插值;Interpolate(内插值),基于上一帧变换来平滑本帧变换;Extrapolate(外插值),基于下一帧变换来 平滑本帧变换
Collision Detection 碰撞检测 碰撞检测模式。用于避免高速物体穿过其他物体却未触发碰 撞。碰撞模式包括Discrete (不连续)、Continuous (连续)、 Continuous Dynamic (动态连续〉3种。其中,Discrete模式用来 检测与场景中其他碰撞器或其他物体的碰撞;Continuous模式 用来检测与动态碰撞器(刚体)的碰撞;Continuous Dynamic模 式用来检测与连续模式和连续动态模式的物体的碰撞,适用于 高速物体
Constraints 约束 对刚体运动的约束。其中,Freeze Position(冻结位置)表示刚体 在世界中沿所选轴的移动将无效,Freeze Rotation(冻结旋转)表示刚体在世界中沿所选的X、Y、Z轴的旋转将无效

demo

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class RigibodyMove : MonoBehaviour
{
    
    
    Rigidbody rb;
    public float speed;

    void Start()
    {
    
    
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
    
    
        Move();
    }

    void Move()
    {
    
    
        float MoveX = Input.GetAxisRaw("Horizontal");
        float MoveZ = Input.GetAxisRaw("Vertical");
        Vector3 dir = new Vector3(MoveX, 0, MoveZ).normalized;

        //1、AddForce
        //rb.AddForce(dir * speed * Time.deltaTime);
        //2、修改velocity
        //rb.velocity += dir * speed * Time.deltaTime;
        //3、MovePosition
        rb.MovePosition(transform.position + dir * speed * Time.deltaTime);
    }
}

拓展

事实上,一个完整的角色控制器需要考虑的问题有很多。像重力、跳跃、阻力、空中移动、 处理斜坡、处理台阶、冲刺、蹲伏等等,我们需要考虑制作的游戏类型来选择合适的方案

常用的两种控制Character和RIgidBody,以下列出了其自带的功能

1.CharacterController

  • 处理斜坡
  • 处理台阶
  • 不会卡墙

2.RigidBody

  • 自带重力
  • 提供阻力
  • 可以和物理对象交互

下面是大佬提供的较为完整的角色控制器代码

首先是一个移动脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveTest:MonoBehaviour
{
    
    
    private CharacterController cc;//创建角色控制器
    public float speed = 12f;
    public float gravity = -9.81f;
    public float jumpHeight = 3f; //跳跃高度

    public Transform groundCheck;//用来获取生成球体的位置
    public float groundDistance = 0.4f;
    public LayerMask groundMask;//值判断为地面的层

    Vector3 velocity;
    bool isGrounded; //检测是否在地面


    
    void Start()
    {
    
    
        cc = GetComponent<CharacterController>();
    }

    // Update is called once per frame
    void Update()
    {
    
    
        MoveMent();
    }

    void MoveMent()
    {
    
    
        //CheckSphere 检验球,这段代码看起来很懵,其实这只是创建一个看不见的球用来检测,碰到就是true没有碰到就是false
        //第一个值是生产的位置,第二个值是圆的半径,第三个值是可筛选的层
        isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);//检测是否在地面

        if(isGrounded && velocity.y < 0)
        {
    
    
            //防止在真正掉落到地面前 停止掉落
            velocity.y = -2;
        }

        float MoveX = Input.GetAxisRaw("Horizontal");
        float MoveZ = Input.GetAxisRaw("Vertical");

        Vector3 dir = (transform.right * MoveX + transform.forward * MoveZ).normalized;

        //平移
        cc.Move(dir * speed * Time.deltaTime);

        if (Input.GetButtonDown("Jump") && isGrounded)
        {
    
    
            //物理公式 v=sqrt(v*-2*g)
            velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
        }

        velocity.y += gravity * Time.deltaTime;
        //再乘一个Time.deltaTime是由物理决定的 1/2gt^2
        cc.Move(velocity * Time.deltaTime);
    }
}

然后是一个视角控制器脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MouseLook : MonoBehaviour
{
    
    
    public float mouseSensitivity = 100f;

    public Transform playerBody;

    float xRotation = 0f;
    // Start is called before the first frame update
    void Start()
    {
    
    
        Cursor.lockState = CursorLockMode.Locked;
    }

    // Update is called once per frame
    void Update()
    {
    
    
        FreeLook();
    }

    void FreeLook()
    {
    
    
        float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;

        xRotation -= mouseY;
        xRotation = Mathf.Clamp(xRotation, -90f, 90f);

        //摄像头旋转x轴
        transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);

        //角色旋转y轴
        playerBody.Rotate(Vector3.up * mouseX);
    }
}

参数设置如下:
在这里插入图片描述

更加全面的角色控制器,考虑了滑坡、高处掉下受伤、下落判断、奔跑、兔子跳等(没有包含视角控制)

// From: https://wiki.unity3d.com/index.php/FPSWalkerEnhanced
// Modified:
// 1. Namespace to prevent conflicts.
// 2. Only checks Horizontal and Vertical inputs.

namespace PixelCrushers.SceneStreamer.Example
{
    
    
    using UnityEngine;

    [RequireComponent(typeof(CharacterController))]
    public class FPSWalkerEnhanced : MonoBehaviour
    {
    
    
        [Tooltip("How fast the player moves when walking (default move speed).")]
        [SerializeField]
        private float m_WalkSpeed = 6.0f;

        [Tooltip("How fast the player moves when running.")]
        [SerializeField]
        private float m_RunSpeed = 11.0f;

        [Tooltip("If true, diagonal speed (when strafing + moving forward or back) can't exceed normal move speed; otherwise it's about 1.4 times faster.")]
        [SerializeField]
        public bool m_LimitDiagonalSpeed = true;

        [Tooltip("If checked, the run key toggles between running and walking. Otherwise player runs if the key is held down.")]
        [SerializeField]
        private bool m_ToggleRun = false;

        //[Tooltip("How high the player jumps when hitting the jump button.")]
        //[SerializeField]
        //private float m_JumpSpeed = 8.0f;

        [Tooltip("How fast the player falls when not standing on anything.")]
        [SerializeField]
        private float m_Gravity = 20.0f;

        [Tooltip("Units that player can fall before a falling function is run. To disable, type \"infinity\" in the inspector.")]
        [SerializeField]
        private float m_FallingThreshold = 10.0f;

        [Tooltip("If the player ends up on a slope which is at least the Slope Limit as set on the character controller, then he will slide down.")]
        [SerializeField]
        private bool m_SlideWhenOverSlopeLimit = false;

        [Tooltip("If checked and the player is on an object tagged \"Slide\", he will slide down it regardless of the slope limit.")]
        [SerializeField]
        private bool m_SlideOnTaggedObjects = false;

        [Tooltip("How fast the player slides when on slopes as defined above.")]
        [SerializeField]
        private float m_SlideSpeed = 12.0f;

        [Tooltip("If checked, then the player can change direction while in the air.")]
        [SerializeField]
        private bool m_AirControl = false;

        [Tooltip("Small amounts of this results in bumping when walking down slopes, but large amounts results in falling too fast.")]
        [SerializeField]
        private float m_AntiBumpFactor = .75f;

        //[Tooltip("Player must be grounded for at least this many physics frames before being able to jump again; set to 0 to allow bunny hopping.")]
        //[SerializeField]
        //private int m_AntiBunnyHopFactor = 1;

        private Vector3 m_MoveDirection = Vector3.zero;
        private bool m_Grounded = false;
        private CharacterController m_Controller;
        private Transform m_Transform;
        private float m_Speed;
        private RaycastHit m_Hit;
        private float m_FallStartLevel;
        private bool m_Falling;
        private float m_SlideLimit;
        private float m_RayDistance;
        private Vector3 m_ContactPoint;
        private bool m_PlayerControl = false;
        //private int m_JumpTimer;


        private void Start()
        {
    
    
            // Saving component references to improve performance.
            m_Transform = GetComponent<Transform>();
            m_Controller = GetComponent<CharacterController>();

            // Setting initial values.
            m_Speed = m_WalkSpeed;
            m_RayDistance = m_Controller.height * .5f + m_Controller.radius;
            m_SlideLimit = m_Controller.slopeLimit - .1f;
            //m_JumpTimer = m_AntiBunnyHopFactor;
        }


        //private void Update()
        //{
    
    
        //    // If the run button is set to toggle, then switch between walk/run speed. (We use Update for this...
        //    // FixedUpdate is a poor place to use GetButtonDown, since it doesn't necessarily run every frame and can miss the event)
        //    if (m_ToggleRun && m_Grounded && Input.GetButtonDown("Run"))
        //    {
    
    
        //        m_Speed = (m_Speed == m_WalkSpeed ? m_RunSpeed : m_WalkSpeed);
        //    }
        //}


        private void FixedUpdate()
        {
    
    
            float inputX = Input.GetAxis("Horizontal");
            float inputY = Input.GetAxis("Vertical");

            // If both horizontal and vertical are used simultaneously, limit speed (if allowed), so the total doesn't exceed normal move speed
            float inputModifyFactor = (inputX != 0.0f && inputY != 0.0f && m_LimitDiagonalSpeed) ? .7071f : 1.0f;

            if (m_Grounded)
            {
    
    
                bool sliding = false;
                // See if surface immediately below should be slid down. We use this normally rather than a ControllerColliderHit point,
                // because that interferes with step climbing amongst other annoyances
                if (Physics.Raycast(m_Transform.position, -Vector3.up, out m_Hit, m_RayDistance))
                {
    
    
                    if (Vector3.Angle(m_Hit.normal, Vector3.up) > m_SlideLimit)
                    {
    
    
                        sliding = true;
                    }
                }
                // However, just raycasting straight down from the center can fail when on steep slopes
                // So if the above raycast didn't catch anything, raycast down from the stored ControllerColliderHit point instead
                else
                {
    
    
                    Physics.Raycast(m_ContactPoint + Vector3.up, -Vector3.up, out m_Hit);
                    if (Vector3.Angle(m_Hit.normal, Vector3.up) > m_SlideLimit)
                    {
    
    
                        sliding = true;
                    }
                }

                // If we were falling, and we fell a vertical distance greater than the threshold, run a falling damage routine
                if (m_Falling)
                {
    
    
                    m_Falling = false;
                    if (m_Transform.position.y < m_FallStartLevel - m_FallingThreshold)
                    {
    
    
                        OnFell(m_FallStartLevel - m_Transform.position.y);
                    }
                }

                // If running isn't on a toggle, then use the appropriate speed depending on whether the run button is down
                if (!m_ToggleRun)
                {
    
    
                    m_Speed = Input.GetKey(KeyCode.LeftShift) ? m_RunSpeed : m_WalkSpeed;
                }

                // If sliding (and it's allowed), or if we're on an object tagged "Slide", get a vector pointing down the slope we're on
                if ((sliding && m_SlideWhenOverSlopeLimit) || (m_SlideOnTaggedObjects && m_Hit.collider.tag == "Slide"))
                {
    
    
                    Vector3 hitNormal = m_Hit.normal;
                    m_MoveDirection = new Vector3(hitNormal.x, -hitNormal.y, hitNormal.z);
                    Vector3.OrthoNormalize(ref hitNormal, ref m_MoveDirection);
                    m_MoveDirection *= m_SlideSpeed;
                    m_PlayerControl = false;
                }
                // Otherwise recalculate moveDirection directly from axes, adding a bit of -y to avoid bumping down inclines
                else
                {
    
    
                    m_MoveDirection = new Vector3(inputX * inputModifyFactor, -m_AntiBumpFactor, inputY * inputModifyFactor);
                    m_MoveDirection = m_Transform.TransformDirection(m_MoveDirection) * m_Speed;
                    m_PlayerControl = true;
                }

                // Jump! But only if the jump button has been released and player has been grounded for a given number of frames
                //if (!Input.GetButton("Jump"))
                //{
    
    
                //    m_JumpTimer++;
                //}
                //else if (m_JumpTimer >= m_AntiBunnyHopFactor)
                //{
    
    
                //    m_MoveDirection.y = m_JumpSpeed;
                //    m_JumpTimer = 0;
                //}
            }
            else
            {
    
    
                // If we stepped over a cliff or something, set the height at which we started falling
                if (!m_Falling)
                {
    
    
                    m_Falling = true;
                    m_FallStartLevel = m_Transform.position.y;
                }

                // If air control is allowed, check movement but don't touch the y component
                if (m_AirControl && m_PlayerControl)
                {
    
    
                    m_MoveDirection.x = inputX * m_Speed * inputModifyFactor;
                    m_MoveDirection.z = inputY * m_Speed * inputModifyFactor;
                    m_MoveDirection = m_Transform.TransformDirection(m_MoveDirection);
                }
            }

            // Apply gravity
            m_MoveDirection.y -= m_Gravity * Time.deltaTime;

            // Move the controller, and set grounded true or false depending on whether we're standing on something
            m_Grounded = (m_Controller.Move(m_MoveDirection * Time.deltaTime) & CollisionFlags.Below) != 0;
        }


        // Store point that we're in contact with for use in FixedUpdate if needed
        private void OnControllerColliderHit(ControllerColliderHit hit)
        {
    
    
            m_ContactPoint = hit.point;
        }


        // This is the place to apply things like fall damage. You can give the player hitpoints and remove some
        // of them based on the distance fallen, play sound effects, etc.
        private void OnFell(float fallDistance)
        {
    
    
            print("Ouch! Fell " + fallDistance + " units!");
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_36303853/article/details/129721957