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; namespace Cielonos.MainGame.Characters { public partial class LockTargetSubmodule : SubmoduleBase { private Player player => owner.player; private PlayerViewSubcontroller viewSc => owner; private PlayerInputSubcontroller inputSc => player.inputSc; private HUDNavigationSystem navigationSystem => HUDNavigationSystem.Instance; private HUDNavigationCanvas navigationCanvas => HUDNavigationCanvas.Instance; /// /// 通常ACT类武器锁定目标时自动旋转摄像机,即使用LockTargetCamera /// TPS类远程武器不自动旋转,仅在目标上显示锁定标记,不切换摄像机 /// public bool isAutoRotate; /// /// 是否正在锁定目标 /// public bool isLocking; /// /// 是否正在使用锁定目标摄像机 /// 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 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 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(); // 直接强制摄像机在水平和垂直轨道上移动 orbitalFollow.HorizontalAxis.Value = currentYaw; orbitalFollow.VerticalAxis.Value = currentPitch; 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(); 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().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().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(); 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(); orbitalFollow.HorizontalAxis.Value = newYaw; orbitalFollow.VerticalAxis.Value = newPitch; if (lockTarget != null) { lockTarget.navigationElement.showIndicator = false; } this.isLocking = false; this.isAutoRotate = false; viewSc.stateDrivenCamera.GetComponent().SetBool("isLockTarget", false); 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; }); } /// /// 切换锁定目标 /// public void SwitchTarget(float direction) { if (!isLocking || isDuringCameraSwitch) return; if (Time.time - lastTargetSwitchTime < TargetSwitchCooldown) return; List 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().DOScale(1f, 0.3f).From(0.5f).SetEase(Ease.OutQuart).Play(); } } }