Files
Cielonos/Assets/Scripts/MainGame/Characters/Player/View/LockTargetSubmodule.cs
SoulliesOfficial ddd387ef35 做不出来
2026-06-30 01:48:58 -04:00

338 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using DG.Tweening;
using SickscoreGames.HUDNavigationSystem;
using UniRx;
using UnityEngine;
using UnityEngine.UI;
using SLSUtilities.General;
using Unity.Cinemachine;
using Ease = DG.Tweening.Ease;
using Cielonos.MainGame.Effects.Feedback;
namespace Cielonos.MainGame.Characters
{
public partial class LockTargetSubmodule : SubmoduleBase<PlayerViewSubcontroller>
{
private Player player => owner.player;
private PlayerViewSubcontroller viewSc => owner;
private PlayerInputSubcontroller inputSc => player.inputSc;
private HUDNavigationSystem navigationSystem => HUDNavigationSystem.Instance;
private HUDNavigationCanvas navigationCanvas => HUDNavigationCanvas.Instance;
/// <summary>
/// 通常ACT类武器锁定目标时自动旋转摄像机即使用LockTargetCamera
/// TPS类远程武器不自动旋转仅在目标上显示锁定标记不切换摄像机
/// </summary>
public bool isAutoRotate;
/// <summary>
/// 是否正在锁定目标
/// </summary>
public bool isLocking;
/// <summary>
/// 是否正在使用锁定目标摄像机
/// </summary>
public bool isUsingLockTargetCamera => isLocking && isAutoRotate;
public bool isDuringCameraSwitch;
private const float CameraSwitchCooldown = 0.5f;
public Enemy lockTarget;
private float lastTargetSwitchTime;
private const float TargetSwitchCooldown = 0.5f;
public Transform targetPoint;
// 用于存储独立的摄像机平滑旋转状态,防止跟随 Transform 的跳变
private float currentYaw;
private float currentPitch;
private bool wasShakingLastFrame;
private Tweener iconTween;
public LockTargetSubmodule(PlayerViewSubcontroller owner) : base(owner)
{
isLocking = false;
isAutoRotate = false;
isDuringCameraSwitch = false;
lockTarget = null;
targetPoint = null;
}
public void Update()
{
if (isUsingLockTargetCamera)
{
if (targetPoint != null)
{
Vector3 actualTargetPos = targetPoint.position;
Vector3 playerPos = viewSc.cameraRoot.position;
var camLockData = viewSc.currentCameraLockData;
// 1. 根据距离动态计算怪物权重
float horizontalDistance = Vector2.Distance(new Vector2(playerPos.x, playerPos.z), new Vector2(actualTargetPos.x, actualTargetPos.z));
float closeRangeThreshold = camLockData.weightDistanceRange.x;
float farRangeThreshold = camLockData.weightDistanceRange.y;
float dynamicWeight = camLockData.weightCurve.Evaluate((horizontalDistance - closeRangeThreshold) / (farRangeThreshold - closeRangeThreshold));
// 2. 高度差硬性限制 (解决 Boss 跳跃问题)
float heightDiff = actualTargetPos.y - playerPos.y;
float maxHeightDifference = 2f;
if (heightDiff > maxHeightDifference)
{
// 确保 TargetGroup 中心点的 Y 坐标最高不超过 player.y + maxHeightDifference
float heightWeightLimit = maxHeightDifference / (heightDiff - maxHeightDifference);
dynamicWeight = Mathf.Min(dynamicWeight, heightWeightLimit);
}
// 3. 将动态权重应用到 TargetGroup 中的目标
List<CinemachineTargetGroup.Target> targets = viewSc.targetGroup.Targets;
targets[0].Weight = 1 - dynamicWeight; //玩家中心点权重
for (int i = 1; i < targets.Count; i++)
{
if (targets[i].Object == targetPoint)
{
var t = targets[i];
t.Weight = dynamicWeight;
targets[i] = t;
break;
}
}
// 4. 重新计算虚拟中心点,用于驱动 OrbitalFollow 的轨道 Pitch
// 注意Yaw 依旧严格瞄准敌人的真实 XZ 位置,以免水平方向跑偏
Vector3 horizontalDirection = actualTargetPos - playerPos;
float targetYaw = Mathf.Atan2(horizontalDirection.x, horizontalDirection.z) * Mathf.Rad2Deg;
float groupCenterY = (playerPos.y * 1f + actualTargetPos.y * dynamicWeight) / (1f + dynamicWeight);
float virtualYDiff = groupCenterY - playerPos.y;
float targetPitch = Mathf.Atan2(-virtualYDiff, horizontalDistance) * Mathf.Rad2Deg;
// 独立平滑 Yaw (保证在水平面上环绕旋转) 和 Pitch使用内部状态而非容易受动画影响的 transform
float smoothFactor = 1f - Mathf.Exp(-10f * Time.deltaTime);
currentYaw = Mathf.LerpAngle(currentYaw, targetYaw, smoothFactor);
currentPitch = Mathf.LerpAngle(currentPitch, targetPitch, smoothFactor);
// 限制硬锁定时的最大俯仰角,防止在极近距离或穿模时摄像机垂直朝地/朝天
currentPitch = Mathf.Clamp(currentPitch, -20f, 60f);
viewSc.cameraRoot.rotation = Quaternion.Euler(currentPitch, currentYaw, 0f);
// 【2】接管并强制控制 OrbitalFollow 的轨道位置
var orbitalFollow = viewSc.lockingTargetCamera.GetComponent<CinemachineOrbitalFollow>();
float horizontalOffset = 0f;
float verticalOffset = 0f;
var orbitShaker = viewSc.lockingTargetCamera.GetComponent<CameraOrbitShaker>();
bool isOrbitActive = orbitShaker != null && orbitShaker.enabled && orbitShaker.HasActiveShakes;
if (isOrbitActive)
{
horizontalOffset = orbitShaker.CurrentHorizontalOffset;
verticalOffset = orbitShaker.CurrentVerticalOffset;
wasShakingLastFrame = true;
}
else if (wasShakingLastFrame)
{
// 刚结束大招回旋的帧:将相机的最终物理轴值写入内部状态,防止数值突变
currentYaw = orbitalFollow.HorizontalAxis.Value;
currentPitch = orbitalFollow.VerticalAxis.Value;
wasShakingLastFrame = false;
}
// 直接强制摄像机在水平和垂直轨道上移动(包含大招/反馈的 Orbit 回旋偏差量)
orbitalFollow.HorizontalAxis.Value = currentYaw + horizontalOffset;
orbitalFollow.VerticalAxis.Value = currentPitch + verticalOffset;
float OF_Fade = camLockData.orbitalFollowFadeCurve.Evaluate(Mathf.Clamp01((horizontalDistance - camLockData.orbitalFollowFadeDistanceRange.x) /
(camLockData.orbitalFollowFadeDistanceRange.y - camLockData.orbitalFollowFadeDistanceRange.x)));
orbitalFollow.TargetOffset.y = MathExtensions.Lerp(camLockData.orbitalFollowTargetYRange, OF_Fade);
var rotationComposer = viewSc.lockingTargetCamera.GetComponent<CinemachineRotationComposer>();
float RC_Fade = camLockData.rotationComposerFadeCurve.Evaluate(Mathf.Clamp01((horizontalDistance - camLockData.rotationComposerFadeDistanceRange.x) /
(camLockData.rotationComposerFadeDistanceRange.y - camLockData.rotationComposerFadeDistanceRange.x)));
rotationComposer.TargetOffset.y = MathExtensions.Lerp(camLockData.rotationComposerTargetYRange, RC_Fade);
}
}
}
}
public partial class LockTargetSubmodule
{
public void SwitchLockState()
{
if (isLocking)
{
UnlockTarget();
}
else
{
LockTarget(true);
}
}
public void LockTarget(bool isAutoRotate)
{
if(isDuringCameraSwitch) return;
Enemy target = CombatManager.EnemySm.GetNearestEnemy(50f);
if (target != null)
{
this.isLocking = true;
this.isAutoRotate = isAutoRotate;
this.lockTarget = target;
this.isDuringCameraSwitch = true;
// 初始化内部旋转状态,防止切入时画面突变
Vector3 camEuler = viewSc.playerCamera.transform.eulerAngles;
currentYaw = camEuler.y;
currentPitch = camEuler.x;
if (currentPitch > 180f) currentPitch -= 360f;
if (isAutoRotate)
{
targetPoint = target.bodyPartsSc.cameraLockingPoint ?? target.bodyPartsSc.staticCenterPoint;
// 给怪物赋予基础权重 1以及 1.5 的包围盒半径,防止 Group size is zero
viewSc.targetGroup.AddMember(targetPoint, 1f, 1.5f);
viewSc.currentCamera = viewSc.lockingTargetCamera;
viewSc.lockingTargetCamera.LookAt = viewSc.targetGroup.transform;
viewSc.stateDrivenCamera.GetComponent<Animator>().SetBool("isLockTarget", true);
Observable.Timer(TimeSpan.FromSeconds(CameraSwitchCooldown)).First().Subscribe(_ =>
{
isDuringCameraSwitch = false;
});
}
else
{
Observable.Timer(TimeSpan.FromSeconds(CameraSwitchCooldown)).First().Subscribe(_ =>
{
isDuringCameraSwitch = false;
});
}
lockTarget.navigationElement.showIndicator = true;
Image icon = lockTarget.navigationElement.Indicator.OnscreenIcon;
iconTween?.Kill(true);
iconTween = icon.GetComponent<RectTransform>().DOScale(1f, 0.5f).From(0f).SetEase(Ease.OutQuart).Play();
}
}
public void UnlockTarget()
{
if(isDuringCameraSwitch) return;
Vector3 currentEuler = viewSc.playerCamera.transform.rotation.eulerAngles;
var inputController = viewSc.freeLookCamera.GetComponent<CinemachineInputAxisController>();
if (inputController == null) return;
float newYaw = currentEuler.y;
float newPitch = currentEuler.x;
if (newPitch > 180f) newPitch -= 360f;
float minPitch = -20f;
float maxPitch = 70f;
newPitch = Mathf.Clamp(newPitch, minPitch, maxPitch);
CinemachineOrbitalFollow orbitalFollow = viewSc.freeLookCamera.GetComponent<CinemachineOrbitalFollow>();
orbitalFollow.HorizontalAxis.Value = newYaw;
orbitalFollow.VerticalAxis.Value = newPitch;
if (lockTarget != null)
{
lockTarget.navigationElement.showIndicator = false;
}
this.isLocking = false;
this.isAutoRotate = false;
viewSc.stateDrivenCamera.GetComponent<Animator>().SetBool("isLockTarget", false);
viewSc.currentCamera = viewSc.freeLookCamera; // 核心修复:更新当前相机引用为自由相机
Transform oldTargetPoint = targetPoint;
this.lockTarget = null;
this.targetPoint = null;
this.isDuringCameraSwitch = true;
// 延迟移除目标组的成员,直到摄像机混合完成(通常为 0.5s)。
// 否则退出的相机在混合的第一帧就会因为失去目标而瞬间位移,导致画面抖动。
Observable.Timer(TimeSpan.FromSeconds(CameraSwitchCooldown)).First().Subscribe(_ =>
{
if (oldTargetPoint != null)
{
viewSc.targetGroup.RemoveMember(oldTargetPoint);
}
isDuringCameraSwitch = false;
});
}
/// <summary>
/// 切换锁定目标
/// </summary>
public void SwitchTarget(float direction)
{
if (!isLocking || isDuringCameraSwitch) return;
if (Time.time - lastTargetSwitchTime < TargetSwitchCooldown) return;
List<Enemy> sortedEnemies = CombatManager.EnemySm.GetVisibleEnemiesSortedByScreenX();
if (sortedEnemies.Count <= 1) return;
int currentIndex = sortedEnemies.IndexOf(lockTarget);
if (currentIndex < 0)
{
currentIndex = 0;
}
int dir = direction > 0 ? -1 : 1;
int newIndex = currentIndex + dir;
// 边界检查(无循环)
if (newIndex < 0 || newIndex >= sortedEnemies.Count)
{
return;
}
CharacterBase newTarget = sortedEnemies[newIndex];
// 目标相同检查
if (newTarget == lockTarget)
{
return;
}
lastTargetSwitchTime = Time.time;
SetNewTarget(sortedEnemies[newIndex]);
}
private void SetNewTarget(Enemy newTarget)
{
if (lockTarget != null)
{
lockTarget.navigationElement.showIndicator = false;
}
lockTarget = newTarget;
Transform oldTargetPoint = targetPoint;
targetPoint = newTarget.bodyPartsSc.cameraLockingPoint ?? newTarget.bodyPartsSc.staticCenterPoint;
if (isUsingLockTargetCamera)
{
viewSc.lockingTargetCamera.LookAt = viewSc.targetGroup.transform;
// 同步更新 TargetGroup替换新旧目标点
if (oldTargetPoint != null && oldTargetPoint != targetPoint)
{
viewSc.targetGroup.RemoveMember(oldTargetPoint);
}
viewSc.targetGroup.AddMember(targetPoint, 1f, 1.5f);
}
lockTarget.navigationElement.showIndicator = true;
Image icon = lockTarget.navigationElement.Indicator.OnscreenIcon;
iconTween?.Kill(true);
iconTween = icon.GetComponent<RectTransform>().DOScale(1f, 0.3f).From(0.5f).SetEase(Ease.OutQuart).Play();
}
}
}