using System; using System.Collections.Generic; using System.Linq; using Cielonos.MainGame.Characters; using UnityEngine; namespace Cielonos.MainGame { public partial class CombatManager { public partial class EnemySubmodule { /// /// 索敌评分结果,包含目标引用和各项分数明细。 /// public struct TargetingScore { public Enemy target; /// 最终评分 = baseScore + bonusScore。 public float totalScore; /// 基础加权评分(由 GetScoredEnemies 计算,不受后续修正影响)。 public float baseScore; /// 修正评分累加值(由 ApplyScoreModifier 叠加)。 public float bonusScore; public float distanceScore; public float inputDirScore; public float cameraFacingScore; public float stickyScore; public float lockOnScore; } private CharacterBase _lastHitTarget; private float _lastHitTime; private readonly TargetingScoreConfig _targetingConfig = new(); /// /// 索敌评分配置,允许外部调整权重和参数。 /// public TargetingScoreConfig TargetingConfig => _targetingConfig; /// /// 记录最近命中的目标,用于计算粘滞分。 /// 由攻击系统在命中时调用。 /// public void SetLastHitTarget(CharacterBase target) { _lastHitTarget = target; _lastHitTime = Time.time; } /// /// 综合索敌:在指定半径内对所有敌人评分,返回按分数从高到低排序的列表。 /// /// 索敌半径 /// 索敌原点,默认为玩家位置 public List GetScoredEnemies(float radius, Transform origin = null) { if (origin == null) { origin = Player.transform; } List candidates = GetEnemiesInRadius(origin.position, radius); if (candidates.Count == 0) return new List(); Camera camera = Player.viewSc.playerCamera; Vector2 moveInput = Player.inputSc.Move; bool hasInput = Player.inputSc.IsMoving; // 将摇杆输入转为世界空间方向(相对于摄像机水平朝向) Vector3 inputWorldDir = Vector3.zero; if (hasInput) { float cameraYaw = camera.transform.eulerAngles.y; inputWorldDir = Quaternion.Euler(0f, cameraYaw, 0f) * new Vector3(moveInput.x, 0f, moveInput.y).normalized; } Vector3 cameraForward = camera.transform.forward; cameraForward.y = 0f; cameraForward.Normalize(); // 锁定目标 CharacterBase lockTarget = Player.viewSc.lockTargetModule.isLocking ? Player.viewSc.lockTargetModule.lockTarget : null; // 粘滞衰减 float stickyFactor = 0f; if (_lastHitTarget != null && !_lastHitTarget.statusSm.isDead) { float elapsed = Time.time - _lastHitTime; stickyFactor = Mathf.Clamp01(1f - elapsed / _targetingConfig.stickyDecayTime); } float totalWeight = _targetingConfig.TotalWeight; if (totalWeight <= 0f) totalWeight = 1f; // 预计算最大距离用于归一化 float maxDist = 0f; foreach (CharacterBase enemy in candidates) { float dist = Vector3.Distance(origin.position, enemy.transform.position); if (dist > maxDist) maxDist = dist; } if (maxDist <= 0f) maxDist = 1f; float inputCosThreshold = Mathf.Cos(_targetingConfig.inputConeHalfAngle * Mathf.Deg2Rad); float cameraCosThreshold = Mathf.Cos(_targetingConfig.cameraConeHalfAngle * Mathf.Deg2Rad); List results = new List(candidates.Count); foreach (Enemy enemy in candidates) { if (enemy == null || enemy.statusSm.isDead) continue; Vector3 toEnemy = enemy.transform.position - origin.position; toEnemy.y = 0f; float distance = toEnemy.magnitude; Vector3 toEnemyDir = distance > 0.01f ? toEnemy / distance : Vector3.zero; TargetingScore score = new TargetingScore { target = enemy }; // 1. 距离分:越近越高 [0, 1] score.distanceScore = 1f - Mathf.Clamp01(distance / maxDist); // 2. 输入方向分:有输入时按 dot product 计算 [0, 1],无输入时所有目标得满分 if (hasInput && toEnemyDir.sqrMagnitude > 0f) { float dot = Vector3.Dot(inputWorldDir, toEnemyDir); score.inputDirScore = dot >= inputCosThreshold ? Mathf.InverseLerp(inputCosThreshold, 1f, dot) : 0f; } else { score.inputDirScore = 1f; } // 3. 摄像机朝向分 [0, 1] if (cameraForward.sqrMagnitude > 0f && toEnemyDir.sqrMagnitude > 0f) { float dot = Vector3.Dot(cameraForward, toEnemyDir); score.cameraFacingScore = dot >= cameraCosThreshold ? Mathf.InverseLerp(cameraCosThreshold, 1f, dot) : 0f; } else { score.cameraFacingScore = 0f; } // 4. 粘滞分 [0, 1] score.stickyScore = (enemy == _lastHitTarget) ? stickyFactor : 0f; // 5. 锁定目标分 [0 或 1] score.lockOnScore = (lockTarget != null && enemy == lockTarget) ? 1f : 0f; // 加权总分 score.baseScore = (_targetingConfig.distanceWeight * score.distanceScore + _targetingConfig.inputDirectionWeight * score.inputDirScore + _targetingConfig.cameraFacingWeight * score.cameraFacingScore + _targetingConfig.stickyWeight * score.stickyScore + _targetingConfig.lockOnWeight * score.lockOnScore) / totalWeight; score.bonusScore = 0f; score.totalScore = score.baseScore; results.Add(score); } results.Sort((a, b) => b.totalScore.CompareTo(a.totalScore)); return results; } /// /// 综合索敌的便捷方法:返回评分最高的单个敌人。 /// public Enemy GetBestEnemy(float radius, Transform origin = null, Func overrideCandidate = null) { if (overrideCandidate != null) { Enemy oc = overrideCandidate(); if (oc != null) { return oc; } } List scores = GetScoredEnemies(radius, origin); return scores.Count > 0 ? scores[0].target : null; } /// /// 综合索敌的便捷方法:返回评分前 N 的敌人列表。 /// public List GetBestEnemies(float radius, int count, Transform origin = null) { List scores = GetScoredEnemies(radius, origin); List result = new List(Mathf.Min(count, scores.Count)); for (int i = 0; i < scores.Count && i < count; i++) { result.Add(scores[i].target); } return result; } /// /// 获取最优敌人,优先返回锁定目标(如果在范围内),否则返回综合评分最高的敌人。 /// public CharacterBase GetBestEnemyWithLockonFirst(float radius) { return GetBestEnemy(50f, null, ReturnLockon); Enemy ReturnLockon() { LockTargetSubmodule lockModule = MainGameManager.Player.viewSc.lockTargetModule; if (lockModule.isLocking && lockModule.lockTarget != null) { return lockModule.lockTarget; } return null; } } } public partial class EnemySubmodule { public Enemy GetNearestEnemy(float radius, Transform origin = null) { origin ??= Player.transform; List candidates = GetEnemiesInRadius(origin.position, radius); return candidates.FirstOrDefault(); } public List GetNearestEnemies(float radius, int count, Transform origin = null) { origin ??= Player.transform; List candidates = GetEnemiesInRadius(origin.position, radius); return candidates.Take(count).ToList(); } } } public static class TargetingSystemExtensions { public static List ApplyScoreModifier( this List scores, List boostTargets, float amplifier, float offset = 0) { return scores.ApplyScoreModifier(Predicate, amplifier, offset); bool Predicate(Enemy target) { return boostTargets != null && boostTargets.Contains(target); } } public static List ApplyScoreModifier( this List scores, Predicate match, float amplifier, float offset = 0) { bool changed = false; for (int i = 0; i < scores.Count; i++) { if (!match(scores[i].target)) continue; CombatManager.EnemySubmodule.TargetingScore s = scores[i]; s.bonusScore += s.baseScore * amplifier + offset; s.totalScore = s.baseScore + s.bonusScore; scores[i] = s; changed = true; } if (changed) { scores.Sort((a, b) => b.totalScore.CompareTo(a.totalScore)); } return scores; } public static Enemy BestEnemy(this List scores) { return scores.Count > 0 ? scores[0].target : null; } public static List BestEnemies(this List scores, int count) { List result = new List(Mathf.Min(count, scores.Count)); for (int i = 0; i < scores.Count && i < count; i++) { result.Add(scores[i].target); } return result; } } }