这一次我们在上一篇自由视角的基础上加上锁定视角的功能,没有看过上一篇的点下方链接。
Unity第三人称的实现思路(一)
锁定视角动画设置
锁定视角的动画分为两种情况,一种是不带方向的一种是带方向的:
单一方向:
8方向:
比如黑魂里的锁定视角,只推左摇杆正常移动时模型永远面朝目标,但如果按住x奔跑时模型就会朝向移动的方向,但摄像机依旧是对准目标的。而在这个Demo中我用的是拔出剑时模型永远面朝目标,剑收起来时模型朝向移动方向。 锁定视角只需要理清 角色碰撞器,模型,目标 三者之间的相对位置就能知道融合树中的数值应该怎么给。
锁定视角摄像机
Cinemachine组件很多参数都是公开的,只需要引用Cinemachine命名空间然后获取组件就能修改其中的参数。锁定视角需要更改的参数其实只有两三处,可以在锁定视角时用代码修改,这里为了减少代码量没有采用这种方法,毕竟现在需要修改的只有两三处,但以后就不一定了。在上一篇中提到的Priority(优先级)参数就起到作用了,主摄像机会渲染处于激活状态的更高优先级的虚拟相机,我们只需要复制一份自由视角的相机然后修改其中的参数,再用代码动态激活和关闭这个相机即可。
Axis Control中两个轴的speed设置成0是为了让鼠标两个轴的输入变为0,相当于禁止移动视角。
Binding Mode原本是World Space,如果不移动视角的话位置就不会改变,锁定视角时会出现相机在目标与角色中间,看不到角色的问题。设置成Lock To Target后相机位置会锁定在角色身后,只要让角色时刻朝向目标,摄像机就永远能看见角色并对准目标。
角色朝向控制
//如果按下锁定键
if(Input.GetKeyDown(keys.Lock))
{
//锁定状态改变
state.isLocking = !state.isLocking;
//锁定为真打开锁定视角摄像机,锁定为假时关闭
lockCamera.SetActive(state.isLocking);
//设置动画状态机锁定状态的参数
animController.SetBool("LockState", state.isLocking);
}
//锁定状态时每一帧调用
void LockLook()
{
if (!state.isLocking)
return;
//不移动时不旋转角色
if (moveDir.sqrMagnitude < 0.1f)
return;
//相机朝向目标,旋转值赋给角色后角色也朝向目标
Vector3 cameraEuler = Camera.main.transform.eulerAngles;
transform.rotation = Quaternion.Euler(0, cameraEuler.y, 0);
//非战斗状态(空手)
if(!state.isEquiped)
{
//键盘输入的方向
Vector3 dir = moveDir.normalized;
float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg;
Quaternion targetDir = Quaternion.Euler(0, angle, 0);
//模型朝向键盘输入方向
model.localRotation = Quaternion.Lerp(model.localRotation, targetDir, 12 * Time.deltaTime);
}
//战斗状态(拔剑)
else
{
//模型本地旋转设置为0
model.localRotation = Quaternion.Lerp(model.localRotation, Quaternion.Euler(0, 0, 0), 12 * Time.deltaTime);
}
}
上面修改模型朝向时用的是本地旋转值。空手状态时,因为模型的父级朝向目标,移动时应该是相对于父级的本地方向而不是真实的方向。拔剑状态时模型应该朝向目标,因为父级朝向目标,只需要让模型旋转归零就能与父级同样朝向。
主要代码
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public Transform target;
/// <summary>
/// 角色控制器
/// </summary>
CharacterController characterController;
/// <summary>
/// 动画控制器
/// </summary>
PlayerAnimController animController;
/// <summary>
/// 角色状态
/// </summary>
CharacterState state;
/// <summary>
/// 按键
/// </summary>
[SerializeField] InputKeys keys;
/// <summary>
/// 模型
/// </summary>
Transform model;
/// <summary>
/// 锁定状态下的摄像机
/// </summary>
GameObject lockCamera;
/// <summary>
/// 移动欲望
/// </summary>
Vector3 moveDir;
private void Awake()
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
characterController = GetComponent<CharacterController>();
animController = GetComponentInChildren<PlayerAnimController>();
animController.PlayerController = this;
state = new CharacterState();
animController.State = state;
model = transform.Find("Model");
lockCamera = GameObject.Find("Virtual Camera Group").transform.Find("LockLook").gameObject;
}
private void Update()
{
InputCheck();
}
private void FixedUpdate()
{
FreeLook();
LockLook();
Vector2 movement = SquareToCircle(moveDir);
if(state.isLocking && state.isEquiped)
{
animController.SetFloat("SpeedZ", movement.y);
animController.SetFloat("SpeedX", movement.x);
}
else
{
animController.SetFloat("SpeedZ", movement.sqrMagnitude);
}
}
/// <summary>
/// 自由视角
/// </summary>
void FreeLook()
{
if (state.isLocking)
return;
if (moveDir.sqrMagnitude < 0.1f)
return;
model.localRotation = Quaternion.Lerp(model.localRotation, Quaternion.Euler(0 ,0 ,0), 12 * Time.deltaTime);
Vector3 dir = moveDir.normalized;
float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg + Camera.main.transform.eulerAngles.y;
Quaternion targetDir = Quaternion.Euler(0, angle, 0);
transform.rotation = Quaternion.Lerp(transform.rotation, targetDir, 12 * Time.deltaTime);
}
/// <summary>
/// 锁定视角
/// </summary>
void LockLook()
{
if (!state.isLocking)
return;
if (moveDir.sqrMagnitude < 0.1f)
return;
Vector3 cameraEuler = Camera.main.transform.eulerAngles;
transform.rotation = Quaternion.Euler(0, cameraEuler.y, 0);
//非战斗状态
if(!state.isEquiped)
{
Vector3 dir = moveDir.normalized;
float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg;
Quaternion targetDir = Quaternion.Euler(0, angle, 0);
model.localRotation = Quaternion.Lerp(model.localRotation, targetDir, 12 * Time.deltaTime);
}
//战斗状态
else
{
model.localRotation = Quaternion.Lerp(model.localRotation, Quaternion.Euler(0, 0, 0), 12 * Time.deltaTime);
}
}
/// <summary>
/// 输入检测
/// </summary>
void InputCheck()
{
float fwd = 0;
float right = 0;
fwd = Input.GetKey(keys.fwd) ? 1 : fwd;
fwd = Input.GetKey(keys.bwd) ? -1 : fwd;
right = Input.GetKey(keys.right) ? 1 : right;
right = Input.GetKey(keys.left) ? -1 : right;
moveDir.z = Mathf.Lerp(moveDir.z, fwd, 24 * Time.deltaTime);
moveDir.x = Mathf.Lerp(moveDir.x, right, 24 * Time.deltaTime);
//拔出武器
if (Input.GetKeyDown(keys.equip))
{
state.isEquiped = !state.isEquiped;
animController.SetTrigger("Equip");
}
//锁定视角
if(Input.GetKeyDown(keys.Lock))
{
state.isLocking = !state.isLocking;
lockCamera.SetActive(state.isLocking);
animController.SetBool("LockState", state.isLocking);
}
}
/// <summary>
/// 椭圆映射
/// </summary>
/// <param name="oldVec"></param>
/// <returns></returns>
Vector2 SquareToCircle(Vector3 oldVec)
{
Vector2 newVec;
newVec.x = oldVec.x * Mathf.Sqrt(1 - (oldVec.z * oldVec.z) / 2);
newVec.y = oldVec.z * Mathf.Sqrt(1 - (oldVec.x * oldVec.x) / 2);
return newVec;
}
/// <summary>
/// 动画根节点速度作为移动速度
/// </summary>
/// <param name="velocity"></param>
public void SetCharacterVelocity(Vector3 velocity)
{
characterController.SimpleMove(new Vector3(velocity.x, characterController.velocity.y, velocity.z));
}
}
using UnityEngine;
public class PlayerAnimController : MonoBehaviour
{
/// <summary>
/// 角色逻辑控制器
/// </summary>
PlayerController playerController;
public PlayerController PlayerController { set { playerController = value; } }
/// <summary>
/// 角色状态
/// </summary>
CharacterState state;
public CharacterState State { set { state = value; } }
/// <summary>
/// 动画状态机
/// </summary>
Animator anim;
private void Awake()
{
anim = GetComponent<Animator>();
}
/// <summary>
/// 设置Float参数
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void SetFloat(string name, float value)
{
anim.SetFloat(name, value);
}
/// <summary>
/// 触发器
/// </summary>
/// <param name="name"></param>
public void SetTrigger(string name)
{
anim.SetTrigger(name);
}
public void SetBool(string name, bool value)
{
anim.SetBool(name, value);
}
/// <summary>
/// 动画执行时每一帧调用
/// </summary>
private void OnAnimatorMove()
{
playerController.SetCharacterVelocity(anim.velocity);
}
#region 帧事件
/// <summary>
/// 拔出武器调用
/// </summary>
public void OnEquipStateEnter()
{
SetBool("IsEquiped", true);
}
/// <summary>
/// 收起武器调用
/// </summary>
public void OnUnEquipStateEnter()
{
SetBool("IsEquiped", false);
}
#endregion
}