由于之前期末了一直在装备考试,随手记已经很久没有写了。今天开始用Unity整一个第一人称射击游戏出来。
今天实现的内容:
-
基于CharacterController的第一人称移动功能,了解了一下CharacterController.Move和CharacterController.SimpleMove的区别CharacterController中Move和SimpleMove的区别
-
重力效果(不拟真)
-
跳跃效果跳跃受当前运动状态影响
-
冲刺效果,更快的移动速度
-
下蹲效果,通过改变CharacterController.Height来实现,拥有独立的下蹲行走速度和下蹲冲刺速度。
BUG以及缺陷:
我这个写法标准嘛?我也不知道,但是能用了。
值得注意的:
冲刺判断时考虑到了移动情况,避免在原地不动时按下冲刺键也能跳到冲刺高度。
要注意将得到的movementDirection的模长设置为1,可以使用normalize。这样做是为了避免在Horizontal和Vertical都有输入时导致的模长大于1位移变大,也就是斜着走时速度变快的情况。注意不能在模长小于0时设置。
代码:
基于CharacterController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FPCharacterControllerMovement : MonoBehaviour
{
//-----------------------------------------------------运动-----------------------------------------------------------------//
// 走路的速度
public float walkSpeed = 10f;
// 奔跑的速度
public float sprintingSpeed = 20f;
// 重力
public float grivaty = 9.8f;
// 跳跃高度
public float jumpHeight = 2f;
// 下蹲之后的高度
public float crouchHeight = 1f;
// 站立时的高度
[HideInInspector]
public float originHeight;
// 下蹲时的冲刺的速度
public float crouchWalkSpeed = 5f;
public float crouchSprintSpeed = 5f;
// 当前在冲刺
[HideInInspector]
public bool m_isSprinting;
// 当前是否蹲下了
[HideInInspector]
public bool m_isCrouched;
// 当前是否正在蹲下/起立 用于协程控制
[HideInInspector]
public bool m_isDoCrouching;
// 对CharacterController的引用
private CharacterController m_characterController;
// 对Transform的引用
private Transform m_characterTransform;
// 移动的方向
private Vector3 m_movementDirection;
// 当前使用哪个速度(Walk or Sprint)
private float m_currentSpeed;
//------------------------------------------------------动画----------------------------------------------------------------//
private Animator m_characterAnimator;
private float m_velocity;
private void Start()
{
// 运动
m_currentSpeed = walkSpeed;
m_characterController = GetComponent<CharacterController>();
m_characterTransform = transform;
m_isCrouched = false;
m_isDoCrouching = false;
originHeight = m_characterController.height;
// 动画
m_characterAnimator = GetComponentInChildren<Animator>();
}
private void Update()
{
//触地判断
if (m_characterController.isGrounded)
{
//------------------------------------------运动--------------------------------------------------------//
// 获得输入
float temp_Horizontal = Input.GetAxis("Horizontal");
float temp_Vertical = Input.GetAxis("Vertical");
// 将方向从对象的自身坐标系转换为世界坐标系
m_movementDirection = m_characterTransform.TransformDirection(
new Vector3(temp_Horizontal, 0, temp_Vertical));
// 这是为了防止Horizontal和Vertical都有输入时
// 出现movementDirection模长大于1的情况
// 保证速度的恒定
if(m_movementDirection.sqrMagnitude > 1)
{
m_movementDirection = m_movementDirection.normalized;
}
// SimpleMove是带有重力效果的 目前不使用
//characterController.SimpleMove(temp_MovementDirection * Time.deltaTime * speed);
// 是否按着冲刺键并且拥有运动
// 运动不考虑y轴的跳跃
if (Input.GetKey(KeyCode.LeftShift) &&
m_movementDirection.x != 0 &&
m_movementDirection.z != 0)
{
// 如果条件符合 m_isSprinting设置为true 状态为冲刺
m_isSprinting = true;
}
else
{
m_isSprinting = false;
}
// 是否按下跳跃键
if (Input.GetButtonDown("Jump"))
{
m_movementDirection.y = jumpHeight;
}
// 是否按下下蹲键
if (Input.GetKeyDown(KeyCode.C))
{
// 正在下蹲或者起立时 不能更新状态 防止BUG
if(!m_isDoCrouching)
{
//当前是否已经蹲下了
float temp_currentHeight =
(m_isCrouched) ? originHeight : crouchHeight;
// 正在下蹲/起立
m_isDoCrouching = true;
// 因为下蹲(起立)是一个过程 所以用协程
StartCoroutine(DoCrouch(temp_currentHeight));
// 将下蹲状态设置为反状态
m_isCrouched = !m_isCrouched;
}
}
// 是否是下蹲状态
if(m_isCrouched)
{
m_currentSpeed = (m_isSprinting) ? crouchSprintSpeed : crouchWalkSpeed;
}
else
{
m_currentSpeed = (m_isSprinting) ? sprintingSpeed : walkSpeed;
}
//------------------------------------------------------------------------------------------------------//
//------------------------------------------动画--------------------------------------------------------//
Vector3 temp_velocity = m_characterController.velocity;
temp_velocity.y = 0; //过滤掉跳跃带来的影响
m_velocity = temp_velocity.magnitude;
m_characterAnimator.SetFloat("Velocity", m_velocity, 0.1f, Time.deltaTime);
//------------------------------------------------------------------------------------------------------//
}
// 重力效果
m_movementDirection.y -= grivaty * Time.deltaTime;
// 实际的移动
// 这样写有一些问题
// 比如重力受移动速度的影响
m_characterController.Move(m_movementDirection * Time.deltaTime * m_currentSpeed);
}
// 下蹲/站立的协程
private IEnumerator DoCrouch(float target)
{
while(Mathf.Abs(m_characterController.height - target) > 0.0001f)
{
float temp_currentHeight = 0;
yield return null;
m_characterController.height = Mathf.SmoothDamp(
m_characterController.height,
target,
ref temp_currentHeight,
Time.deltaTime * 3f);
}
m_isDoCrouching = false; //对吗?目前看来没问题
}
}
基于Rigidbody
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FPMovement_Rigidbody : MonoBehaviour
{
public float speed;
public float gravity;
public float jumpHeight;
private Rigidbody m_rigidbody;
private bool m_isGrounded;
private bool isJumping;
void Start()
{
m_rigidbody = this.GetComponent<Rigidbody>();
}
private void Update()
{
// 只有在地面上时才能起跳
if(m_isGrounded)
{
// 跳跃逻辑判断
if (Input.GetButtonDown("Jump"))
{
Debug.Log("Jump!");
isJumping = true;
}
}
else
{
// 在空中时,要将isJumping设置为false
isJumping = false;
}
}
void FixedUpdate()
{
// 玩家只有触地时才能移动和跳跃
if (m_isGrounded)
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 currentDirection = new Vector3(horizontal, 0, vertical); //这个坐标系是局部的,需要转换到世界坐标后再使用
currentDirection = this.transform.TransformDirection(currentDirection.normalized); //normalized是为了保证速度大小的一致
currentDirection *= speed;
Vector3 currentVelocity = m_rigidbody.velocity; //获取当前的速度
Vector3 velocityChange = currentDirection - currentVelocity; //用户输入的速度减去当前速度来获取实际需要的速度
velocityChange.y = 0; //暂时不计算这个值
m_rigidbody.AddForce(velocityChange, ForceMode.VelocityChange);
// 跳跃
if(isJumping)
{
m_rigidbody.velocity = new Vector3(velocityChange.x, CalculateJumpHeightSpeed(), velocityChange.z);
}
}
// 重力
m_rigidbody.AddForce(Vector3.down * gravity);
}
// 计算要达到跳跃高度需要的y轴速度
private float CalculateJumpHeightSpeed()
{
return Mathf.Sqrt(2 * gravity * jumpHeight);
}
// 这只是一个相当简单的方法
private void OnCollisionEnter(Collision collision)
{
m_isGrounded = true;
}
//private void OnCollisionStay(Collision collision)
//{
// m_isGrounded = true;
//}
private void OnCollisionExit(Collision collision)
{
m_isGrounded = false;
}
}