实现一个小的锁定敌人功能:
在人物的后方创建一个子物体(Camera Holder),并创建 子物体Lock Camera(锁定状态的虚拟相机),锁定状态的相机脚本如下:提供了Init函数供我们进行初始化看向的敌人。
public class ThirdLockEnemyCamera : MonoBehaviour
{
private CinemachineVirtualCamera cinemachine;
private EnemyController lockedEnemy;
private void Awake()
{
cinemachine = GetComponent<CinemachineVirtualCamera>();
}
public void Init(EnemyController currentEnemy)
{
lockedEnemy = currentEnemy;
cinemachine.LookAt = lockedEnemy.transform;
}
}
在人物控制部分:规定鼠标中键进行锁定敌人,并且调用相机的变化。
//第三人称切换锁定状态
private void SwitchLockState(InputAction.CallbackContext obj)
{
if (CameraManager.Instance.currentCameraStyle == CameraStyle.FirstPerson) return;
IsLock = !IsLock;
if (IsLock)
{
//选择一个敌人进行锁定
//curLockedEnemy = SelectLockEnemyByMousePos();
curLockedEnemy = SelectLockEnemy();
if (curLockedEnemy != null)
{
CameraManager.Instance.SwitchToThirdPersonLockEnemy(curLockedEnemy);
GlobalEvent.CallEnterFocusOnEnemy(curLockedEnemy);
}
else IsLock = false;
}
else
{
curLockedEnemy = null;
//取消聚焦点 相机转换
GlobalEvent.CallExitFocusOnEnemy();
CameraManager.Instance.SwitchCameraStyle(CameraStyle.ThirdPersonCombat);
}
}
选择敌人的函数:
方法一:根据人物的朝向进行选择
//在指定的集合中根据当前人物的朝向选择一个敌人
private EnemyController SelectEnemyCloseToPlayer(HashSet<EnemyController> enemiese, float minDistance,float faceingAngle)
{
//具体要求:距离小于某个值 + 角度为人物的前方 (根据角度选择最佳的目标)
EnemyController currentEnemy = null;
float minAngle = faceingAngle / Mathf.PI * 180;
foreach (var enemy in enemiese)
{
if (Vector3.Distance(transform.position, enemy.transform.position) < minDistance)
{
//enemy满足要求 比较得到更好的敌人
Vector3 targetPos = enemy.transform.position - transform.position;
float curAngle = Vector3.Angle(transform.forward, targetPos);
if (curAngle < minAngle)
{
minAngle = curAngle;
currentEnemy = enemy;
}
}
}
return currentEnemy;
}
方法二:根据鼠标的位置进行选择
//screenPoint表示屏幕的位置,z轴坐标为摄像机到人物的距离(转换到人物所在的平面上)
//通过Camera.main.ScreenToWorldPoint(screenPoint) 得到屏幕点在世界坐标(z轴为人物)上的位置,通过这个位置进行判断锁定敌人。
最终方法:想复杂了,直接使用摄像机Transform的位置和摄像机的forward即可。
private EnemyController SelectEnemyCloseToMouse(HashSet<EnemyController> enemiese, float minDistance, float faceingAngle)
{
//具体要求:距离小于某个值 + 角度为人物的前方 (根据角度选择最佳的目标)
EnemyController currentEnemy = null;
//Vector3 screenPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, Vector3.Distance(Camera.main.transform.position, transform.position));
//Vector3 startPos = Camera.main.ScreenToWorldPoint(screenPoint);
Transform startTransform = Camera.main.transform;
float minAngle = faceingAngle / Mathf.PI * 180;
foreach (var enemy in enemiese)
{
if (Vector3.Distance(transform.position, enemy.transform.position) < minDistance)
{
//enemy满足要求 比较得到更好的敌人
Vector3 targetPos = enemy.transform.position - startTransform.position;
float curAngle = Vector3.Angle(startTransform.forward, targetPos);
if (curAngle < minAngle)
{
minAngle = curAngle;
currentEnemy = enemy;
}
}
}
return currentEnemy;
}
在敌人死亡的时候,要判断死亡的敌人是否就是死亡的敌人。如果是的话,则需要取消锁定。(可以利用敌人的死亡事件来实现,添加一个判定即可)
对于锁定状态下的移动和旋转,和第一人称的方式差不多。
移动:根据面朝方向和输入控制移动。
//第三人称 锁定视角 移动人物
private void MovePlayerInThirdPersonLock()
{
if (playerAnimationInf.IsWalk == false) return;
//人物的实际移动:同时根据人物的朝向 和 人物的WASD输入
moveDirection = transform.forward * inputDirection.y + transform.right * inputDirection.x;
moveDirection.y = 0;
if (physicalCheck.haveBarrierInMoveDirectino(transform.position + transform.up * 0.5f, moveDirection, moveDirection.normalized.magnitude * curSpeed * Time.deltaTime * 2)) return;
characterController.Move(moveDirection.normalized * curSpeed * Time.deltaTime);
}
旋转:始终面朝敌人即可。
//第三人称 锁定视角方式的旋转
private void RotatePlayerInThirdPersonLockEnemy()
{
//人物的forward永远是敌人,人物的目标面向:敌人坐标 - 自己坐标
moveDirection = new Vector3(curLockedEnemy.transform.position.x - transform.position.x,transform.forward.y, curLockedEnemy.transform.position.z - transform.position.z);
transform.forward = Vector3.Slerp(transform.forward, moveDirection, rotationSpeed * Time.deltaTime);
}
实现格挡功能,并假定规则如下:
(1)按E键开始格挡进入Enter Guard,按着不放会进入Loop Guard状态,松开退出。
(2)与动画联动,在Enter Guard状态下受到攻击会触发完美格挡(不掉血,不掉体力),并且进入Perfect Parry状态,在Loop Guard状态下受到攻击会触发普通格挡(减少掉血,但消耗体力),并且进入 Normal Parry状态。
在以上规则下,通过合理设置Enter Guard的退出时间达到预设的格挡效果。
按键绑定函数如下:暂定只要当前不在攻击(/翻滚等)状态,就可以进行防御。
//进行格挡
private void StartParry(InputAction.CallbackContext obj)
{
if (CanParry)
{
playerAnimationInf.IsGuard = true;
CancelRun();
}
}
//取消格挡
private void StopParry(InputAction.CallbackContext obj)
{
playerAnimationInf.IsGuard = false;
}
对于完美格挡的判定函数:利用动画名字判断
public bool IsPerfectParry()
{
return anim.GetCurrentAnimatorStateInfo(animatorCombatLayer).IsName("Enter Guard");
}
格挡要与角色的TakeDamage函数联系,普通格挡和完美格挡分别造成不同的(自定义)影响。