前言:前面两篇文章,已经实现了角色的移动和视角转动,但是角色并没有办法跳跃,有时候还会随着视角移动跑到天上。这是因为缺少重力系统,本篇将实现重力和角色跳跃功能。觉得有帮助的话可以点赞收藏支持一下!
重力模拟和角色跳跃
- 问题-太空人
- 地面检测
- 重力模拟
- 跳跃
- 完整代码
- 效果
- 补充知识
- Property Attributes自定义属性
- Time.fixedDeltaTime
问题-太空人
回到我们第二篇讲角色控制的三种方法中,提到了CharacterController虽然有碰撞但是是没有力的作用的,也就意味着没有重力效果。
地面检测
可以利用CharacterController.isGrounded来判断是否碰到地面。
但是要注意一下函数的功能定义:
isGrounded: Was the CharacterController touching the ground during the last move?
意思是判断的是上一次调用CharacterController.Move时有没有触地。
如果遇到isGrounded如果恒为false可能要注意,判断之前是不是还没有调用过Move。
重力模拟
非常简单,利用学过物理基本的公式a*t=v(加速度*时间=速度)、v*t=s(速度*时间=距离),就可以模拟出来。
新增重力加速度playerGravity和垂直速度verticalVelocity,当碰到地面时候垂直速度是0;当没有碰到地面的时候,垂直速度会随着受力一直变化,速度+=加速度*单位时间。在空中只有重力,对应代码就是verticalVelocity-=playerGravity*Time.fixedDeltaTime,为什么是减号因为重力是向下的,有方向。
最后把因为重力导致位移变化也一起Move就可以了。
public float playerGravity = 9.8F;// 重力加速度
public float verticalVelocity = 0;// 垂直速度
public void PlayerMovement()
{
Vector3 moveDirection = Vector3.zero;
// 获取键盘输入:Horizontal左右移动;Vertical前后移动
// 加入自身tranform方向
moveDirection = Vector3.zero;
moveDirection += transform.forward * Input.GetAxis("Vertical");
moveDirection += transform.right * Input.GetAxis("Horizontal");
moveDirection *= Time.deltaTime * moveSpeed;
// 重力
if (!m_Controller.isGrounded)
verticalVelocity -= playerGravity * Time.fixedDeltaTime;// a*t=v
else
verticalVelocity = 0.0F;
moveDirection.y += verticalVelocity * Time.fixedDeltaTime;// v*t=s
// 移动
m_Controller.Move(moveDirection);
}
由于涉及到了物理模拟,所以时间增量都改用了Time.fixedDeltaTime,并在FixedUpdate更新。这个使用Time.fixedDeltaTime具体原因在补充知识中有详细说明。
跳跃
有了重力,跳跃才有作用。
简单跳跃
跳跃的思路,首先不能多段跳,就是要碰到地面才能响应跳跃按键,跳跃后角色会上升一定高度。
添加一个玩家跳跃高度参数jumpHeight,直接让角色位移到jumpHeight,就能实现一个简单的跳跃效果
public float jumpHeight = 2F;
public void PlayerMovement()
{
Vector3 moveDirection = Vector3.zero;
// 获取键盘输入:Horizontal左右移动;Vertical前后移动
// 加入自身tranform方向
moveDirection = Vector3.zero;
moveDirection += transform.forward * Input.GetAxis("Vertical");
moveDirection += transform.right * Input.GetAxis("Horizontal");
moveDirection *= Time.deltaTime * moveSpeed;
// 重力
if (!m_Controller.isGrounded)
verticalVelocity -= playerGravity * Time.fixedDeltaTime;// a*t=v
else
verticalVelocity = 0.0F;
moveDirection.y += verticalVelocity * Time.fixedDeltaTime;// v*t=s
// 跳跃
if (Input.GetButton("Jump"))
{
// 得在地面才能跳
if (m_Controller.isGrounded)
{
moveDirection.y += jumpHeight;
}
}
// 移动
m_Controller.Move(moveDirection);
}
跳跃优化
上面简单的跳跃,虽然能够实现跳跃功能,但是非常不自然,就是一下子跳到最高,又慢慢下来。
实际游戏中,我们的跳跃真正其实是一个抛物线。
也就向上跳到最顶的时候,也有个速度变化。
跳起来的时候会有一个向上的速度,之后不触地都是只受到重力作用了。所以点击跳跃按钮后,我们只需要算出这个跳起来一瞬间的向上速度是多少就行了。
还是最基础的物理公式推导:
a*t=v(加速度*时间=速度)、v*t=s(速度*时间=距离),设定跳跃的最高的高度h(jumpHeight)。
从最低点到最高点,通过h=0.5*a*t2,可以把总共的时长算出来,由于受力只有重力,通过重力加速度*总时长=初始向上速度。
verticalVelocity = Mathf.Sqrt(jumpHeight * 2 / playerGravity) * playerGravity;// 初始向上速度
于是新的代码就是:
public float jumpHeight = 2F;
public float verticalVelocity = 0;// 垂直速度
public void PlayerMovement()
{
Vector3 moveDirection = Vector3.zero;
// 获取键盘输入:Horizontal左右移动;Vertical前后移动
// 加入自身tranform方向
moveDirection = Vector3.zero;
moveDirection += transform.forward * Input.GetAxis("Vertical");
moveDirection += transform.right * Input.GetAxis("Horizontal");
moveDirection *= Time.fixedDeltaTime * moveSpeed;
// 重力
if (!m_Controller.isGrounded)
verticalVelocity -= playerGravity * Time.fixedDeltaTime;// a*t=v
else
verticalVelocity = 0.0F;
// 跳跃
if (Input.GetButton("Jump"))
{
// 得在地面才能跳
if (m_Controller.isGrounded)
{
verticalVelocity = Mathf.Sqrt(jumpHeight * 2 / playerGravity) * playerGravity;// 初始向上速度
}
}
moveDirection.y += verticalVelocity * Time.fixedDeltaTime;// v*t=s
// 移动
m_Controller.Move(moveDirection);
}
完整代码
具有角色移动、视角、重力和跳跃的完整PlayerController代码。
public class PlayerController : MonoBehaviour
{
[Header("Unity组件")]
public CharacterController m_Controller;
public Transform playerCamera;
[Header("玩家数值")]
public float moveSpeed = 6.0F;
public float mouseSensitivity = 1000.0F;
public float playerGravity = 9.8F;
public float jumpHeight = 2F;
public float verticalVelocity = 0;// 垂直速度
private float x_RotationOffset = 0;// 累计x旋转
private float y_RotationOffset = 0;// 累计y旋转
void Start()
{
m_Controller = this.GetComponent<CharacterController>();
Cursor.lockState = CursorLockMode.Locked;// 鼠标锁定
}
private void FixedUpdate()
{
PlayerRotation();
PlayerMovement();
}
// 控制角色移动
public void PlayerMovement()
{
Vector3 moveDirection = Vector3.zero;
// 获取键盘输入:Horizontal左右移动;Vertical前后移动
// 加入自身tranform方向
moveDirection = Vector3.zero;
moveDirection += transform.forward * Input.GetAxis("Vertical");
moveDirection += transform.right * Input.GetAxis("Horizontal");
moveDirection *= Time.fixedDeltaTime * moveSpeed;
// 重力
if (!m_Controller.isGrounded)
verticalVelocity -= playerGravity * Time.fixedDeltaTime;// a*t=v
else
verticalVelocity = 0.0F;
// 跳跃
if (Input.GetButton("Jump"))
{
// 得在地面才能跳
if (m_Controller.isGrounded)
{
verticalVelocity = Mathf.Sqrt(jumpHeight * 2 / playerGravity) * playerGravity;// 初始向上速度
}
}
moveDirection.y += verticalVelocity * Time.fixedDeltaTime;// v*t=s
// 移动
m_Controller.Move(moveDirection);
}
// 控制角色视角
private void PlayerRotation()
{
// 获取鼠标移动
x_RotationOffset += Input.GetAxis("Mouse X")* Time.fixedDeltaTime * mouseSensitivity;// 水平方向
y_RotationOffset += Input.GetAxis("Mouse Y")* Time.fixedDeltaTime * mouseSensitivity;// 垂直方向
// 限制垂直旋转角度
y_RotationOffset = Mathf.Clamp(y_RotationOffset, -45f, 45f);
// 旋转
transform.rotation = Quaternion.Euler(new Vector3(0, x_RotationOffset, 0));// Player旋转
playerCamera.localRotation= Quaternion.Euler(new Vector3(y_RotationOffset, playerCamera.localEulerAngles.y, playerCamera.localEulerAngles.z));// Camera旋转
}
}
效果
简单跳跃。
优化后的跳跃。
补充知识
Property Attributes自定义属性
我们写代码的时候,会写一些属性在Inspector中也能修改,方便查看不同值的表现效果。
例如我们现在PlayerController中的moveSpeed和playerGravity等,但是要定义为Public。
Property Attributes提供了很多内置属性,方便Inspector查看自定义的属性和修改。
常用的有:
- [Header(“xxx”)]给变量在Inspector中添加标题头
- [Space(xx)]在Inspector中添加间隔
- [Range(xx, xx)]在Inspector中变量为滑动条范围
- [Tooltip(“xxx”)]为变量在Inspector中添加说明(像可以在Inspector查看的注释)
里面的文本文字可以是中文。
有这些能让我们代码更加优雅😀,也方便和别人共事的时候更好阅读使用我们的代码。
Time.fixedDeltaTime
fixedDeltatime是一个固定的时间增量不会随着帧率变换而变化,要在FixedUpdate中使用。
通常来说fixedDeltatime固定是0.02s,在主菜单的Edit→Project Settings→Time可以修改。
之前我们用的帧率相关的增量时间,但是现在开始已经涉及到了物理模拟了,物理更新对时间是非常敏感的,稍微差一点都不行。如果用在按帧率更新,可能由于主机差异帧率不稳定,0.5秒子弹到达敌人,帧率不稳定可能会变成0.8s。