character controller
- Four ways to control character movement
- Directly modify component position
- Go to the resource mall to download the finished character control system
- Character controller component provided by unity
- Do it yourself via physics system
- expand
-
-
- Two commonly used controls are Character and RIgidBody. Their built-in functions are listed below.
- The following is a relatively complete character controller code provided by the boss
- 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)
-
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
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.
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 ( : ps but 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
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
- 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.
- 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:
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!");
}
}
}