[Unity Game Development Tutorial] Zero basics will take you from novice to Super God 16 - Four ways to control character movement using character controllers

Four ways to control character movement

  • Directly modify component position
  • Go to the resource mall to download the finished character control system and use it directly
  • Character controller component provided by unity
  • Do it yourself via physics system

Directly modify component position

The most basic way is the most direct way to change the position of an object. It
is suitable for
demo situations where there is neither a physical system nor special requirements for movement.

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);
}

Go to the resource mall to download the finished character control system

For example, go to the mall to search for 3rd person
One

For specific usage, please refer to the article:
https://zhuanlan.zhihu.com/p/31662004?mode=light

Character controller component provided by unity

1. We need to add the Character Controller component to the character first.
Insert image description here

parameter describe
Slope Limit Maximum slope that can be moved directly up
Step Offset Maximum obstacle height that can be directly crossed
Skin Width Skin thickness, a larger value can reduce jitter, a smaller value may cause the character to get stuck, generally set to 10% of the radius ( : psbut this will cause the character to not touch the ground, so at this time you can adjust the Y-axis value of Center to make it The character touches the ground.)
Min Move Distance When the character's single movement distance is less than this value, it will be ignored and will not take effect. Can be used to reduce jitter
Radius & Height The radius and height of the controlled character

2. Create a new script MoveText, hang it on the character, and write the control script

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. Run debugging
Insert image description here

Character Controller API

Variables/Methods describe
isGrounded Whether the character controller is touching the ground
slopeLimit Slope degree limit
stepOffset Height that can be crossed over steps, in meters
detectCollisions Whether other rigid bodies and character controllers can collide with this character controller. The default is true.
SimpleMove() Move at a certain speed and automatically calculate the influence of gravity factors when moving
Move() More complex movements that do not account for gravity effects

Determine whether the character controller is touching the ground

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

The difference between SimpleMove and Move

  1. SimpleMove
  • It is not affected by the Y-axis speed and has its own gravity effect.无法实现跳跃功能
  • The return value is Bool. When the character touches the ground, it returns True, otherwise it returns False.
  1. Move
  • No gravity effect, realize gravity by yourself,可做跳跃功能
  • Return value (CollisionFlags object), returns information about the collision between the character and the object

Code optimization, more complex character control

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);
    }
}

Do it yourself via physics system

The physical method 施加力makes the object move by changing the motion state of the object. The core component is the Rigibody.

A brief introduction to Rigibody

parameter meaning Function
Mass quality The mass of an object (in arbitrary units). It is recommended that the mass of an object should not differ by 100 times from other objects
Drag resistance The air resistance experienced by an object when it is moved by a force. 0 means there is no air resistance. When it is extremely large, the object will stop moving immediately.
Angular Drag Angular resistance The air resistance experienced by an object when it is rotated by torque. 0 means there is no air resistance. When it is extremely high, the object will stop rotating immediately.
Use Gravity Use gravity Whether the object is affected by gravity. If activated, the object is affected by gravity.
Is Kinematic Is it kinesiology? Whether the game object obeys the laws of kinematic physics. If activated, the object is no longer driven by the physics engine and can only be manipulated through transformations. Suitable for simulating a moving platform or simulating rigid bodies connected by hinge joints
Interpolate interpolation Object motion interpolation mode. When jitter is found when rigid body motion occurs, you can try the following options: None, no interpolation is applied; Interpolate, which smoothes the transformation of this frame based on the transformation of the previous frame; Extrapolate, which uses the transformation of the next frame. to smooth the transformation of this frame
Collision Detection Impact checking Collision detection mode. Used to prevent high-speed objects from passing through other objects without triggering a collision. Collision modes include Discrete (discontinuous), Continuous (continuous), and Continuous Dynamic (dynamic continuous). Among them, Discrete mode is used to detect collisions with other colliders or other objects in the scene; Continuous mode is used to detect collisions with dynamic Collision of objects (rigid bodies); Continuous Dynamic mode is used to detect collisions with objects in continuous mode and continuous dynamic mode, and is suitable for high-speed objects
Constraints constraint Constraints on rigid body motion. Among them, Freeze Position means that the movement of the rigid body along the selected axis in the world will be invalid, and Freeze Rotation means that the rotation of the rigid body along the selected X, Y, and Z axes in the world will be invalid.

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);
    }
}

expand

In fact, there are many issues that need to be considered for a complete character controller. Like gravity, jumping, resistance, air movement, handling slopes, handling steps, sprinting, crouching, etc., we need to consider the type of game we are making to choose the appropriate solution

Two commonly used controls are Character and RIgidBody. Their built-in functions are listed below.

1.CharacterController

  • handling slopes
  • handling steps
  • Will not get stuck in the wall

2.RigidBody

  • Comes with gravity
  • provide resistance
  • Can interact with physical objects

The following is a relatively complete character controller code provided by the boss

First a mobile script

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);
    }
}

Then a perspective controller script

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);
    }
}

The parameter settings are as follows:
Insert image description here

A more comprehensive character controller that takes into account landslides, injuries caused by falling from heights, whereabouts judgment, running, bunny hopping, etc. (perspective control is not included)

// 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!");
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_36303853/article/details/129721957