第三人称移动,主要两个部分,一是人物,二是相机。
先说人物,unity其实提供了一个CharacteController组件可以方便地用于控制人物移动,但是这个组件会与刚体冲突。如果使用CharacterController,人物将不会受到力的作用(包括重力),有碰撞效果,但碰撞后不会对其他物体施加力,也就是不会把被碰撞的物体挤开,感觉不是很符合现实,所以我仍然使用rigidbody+碰撞体的组合。
首先给人物添加这两个组件,设置好碰撞体大小。 然后创建一个PlayerController脚本,用于控制人物操作,脚本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private Transform cam;
public float speed = 10f;
public float turnSmoothTime = 0.1f;
float turnSmoothVelocity;
float horizontal;
float vertical;
public float jumpForce;
private bool canJump = true;
Animator ani;
private Rigidbody rb;
void Start()
{
cam = Camera.main.transform;
ani = this.GetComponent<Animator>();
rb = this.GetComponent<Rigidbody>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && canJump)
{
ani.SetTrigger("jump");
this.rb.AddForce(Vector3.up * jumpForce);
canJump = false;
}
}
void FixedUpdate()
{
horizontal = Input.GetAxisRaw("Horizontal");
vertical = Input.GetAxisRaw("Vertical");
Vector3 dir = new Vector3(horizontal, 0f, vertical).normalized;
if ((dir.magnitude >= 0.1f))
{
float targetAngle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg + cam.eulerAngles.y;
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, turnSmoothTime);
transform.rotation = Quaternion.Euler(0f, angle, 0f);
Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
this.rb.velocity = this.rb.velocity.y * Vector3.up + moveDir * speed;
}
else
{
this.rb.velocity = new Vector3(0, rb.velocity.y, 0);
}
playAni();
}
private void LateUpdate()
{
this.transform.position = this.rb.transform.position;
}
void playAni()
{
ani.SetFloat("horizontal", Mathf.Abs(horizontal));
ani.SetFloat("vertical", Mathf.Abs(vertical));
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
canJump = true;
}
}
}
说一下里面的点,变量声明中,cam是主相机的位置,speed是人物移动速度,turnSmoothTime用于转向花的时间,turnSmoothVelocity是后面调用函数的参数,horizontal和vertical是用户输入,jumpForce跳跃力,canJump用于标记能否跳跃。
物理相关的运动应当放在FixedUpdate中,但是我把跳跃逻辑放在了Update里,具体原因移步:【Unity Tips】备忘录(扫盲篇)中的第4点。跳跃逻辑如果放在FixedUpdate里,会使得跳跃不能及时检测。检测按下空格键时,给刚体一个向上的力来跳跃。然后为了防止连续跳跃,在起跳后canJump设为false,在接触地面后回归true。
在FixedUpdate中,检测按键输入,用dir来存储这个输入,dir是一个三维向量,因为人物在平面移动只需要x和z方向,所以y值为0。判断当dir ≥ 0.1f时,执行移动相关的逻辑。计算一个targetAngle,通过Quaternion.Euler对人物坐标进行旋转,但是在游戏中人物不可能瞬间转向,因此使用Mathf.SmoothDampAngle将人物逐渐转向,转向时间则由turnSmoothTime决定。然后计算移动方向,根据这个方向设置刚体的移动速度。在每一帧的最后,将人物位置与刚体同步。playAni()用来设置animator中的参数,控制人物动画。
将这个脚本挂载到人物身上,人物的移动就完成了,接下来是相机控制的部分。关于第三人称相机控制,unity也给了一个Cinemachine,其中的FreeLook Camera比较适合第三人称,但是各种设置调整起来也挺麻烦的,我直接用代码来实现了。
创建一个CameraController脚本,挂载到相机上,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
public Vector3 m_Camera;
public Transform target;
public float targetHeight = 1.8f;
public float targetSide = 0.1f;
public float distance = 4;
public float maxDistance = 8;
public float minDistance = 2.2f;
public float xSpeed = 250;
public float ySpeed = 125;
public float yMinLimit = -10;
public float yMaxLimit = 72;
public float zoomRate = 80;
private float x = 20;
private float y = 0;
void Start()
{
}
void Update()
{
m_Camera.Set(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"), Input.GetAxis("Mouse ScrollWheel"));
x += m_Camera.x * xSpeed * Time.deltaTime;
y -= m_Camera.y * ySpeed * Time.deltaTime;
y = clampAngle(y, yMinLimit, yMaxLimit);
Quaternion rotation = Quaternion.Euler(y, x, 0);
transform.rotation = rotation;
distance -= (m_Camera.z * Time.deltaTime) * zoomRate * Mathf.Abs(distance);
distance = Mathf.Clamp(distance, minDistance, maxDistance);
transform.position = target.position + new Vector3(0, targetHeight, 0) + rotation * (new Vector3(targetSide, 0, -1) * distance);
}
float clampAngle(float angle, float min, float max)
{
if (angle < -360)
{
angle += 360;
}
if (angle > 360)
{
angle -= 360;
}
return Mathf.Clamp(angle, min, max);
}
}
m_Camera是鼠标输入,target是目标人物,targetHeight,targetSide控制相机的上下左右位置,distance控制相机与人物的距离,maxDistance和minDistance则是距离的上下限,防止相机靠得太近或离得太远,xSpeed是左右移动视角时的旋转速度,ySpeed是上下移动视角时的旋转速度,yMinLimit和yMaxLimit是控制上下移动视角的界限,zoomRate是用滚轮控制相机距离时的调整速度。
每帧先获取鼠标的输入,存进m_Camera中,根据m_Camera和xSpeed、ySpeed来计算xy值,y再通过clampAngle函数将它的值限定在设定的limit范围内,然后计算相机的旋转、与目标的距离,再结合目标位置和设置的偏移量计算相机位置。
将目标人物赋给相机的target,再简单做个人物的动画状态机,一个第三人称控制就完成了。做了个简单demo可以参考:
https://github.com/zwjzwk/Unity-Third-Person-Control
|