using Cielonos.MainGame.Characters; using UnityEngine; namespace Cielonos.MainGame { public partial class AttackAreaBase { public partial class TraceMoveSubmodule : MoveSubmoduleBase { private const float TerminalMinimumSpeed = 2f; private const float TerminalDistanceMultiplier = 1.5f; private const float TerminalMaximumDistance = 12f; private const float TerminalTurnRadiusFactor = 0.5f; private const float TerminalMinimumTurnRadius = 0.25f; private const float TerminalMaximumAngularSpeed = 3600f; private const float TerminalMinimumClosingSpeed = 4f; public Transform projectile => owner.topParent; public CharacterBase target; public float angularSpeed; public float angularAcceleration; public float moveSpeed; public float moveAcceleration; public bool autoConnect; public bool autoDisconnect; public float detectRadius; public bool keepFlat; private float deltaTime => owner.DeltaTime; private float projectileSpeedMultiplier => owner.creator.attributeSm[CharacterAttribute.ProjectileSpeedMultiplier]; private Vector3 step; private CharacterBase velocitySampleTarget; private Vector3 lastTargetPosition; private Vector3 targetVelocity; private bool hasTargetVelocitySample; public TraceMoveSubmodule(AttackAreaBase attackArea, CharacterBase target, float moveSpeed, float moveAcceleration, float angularSpeed, float angularAcceleration, Vector3 initialDirection, bool autoConnect, bool autoDisconnect, float detectRadius, bool stopWhenHit) : base(attackArea, stopWhenHit) { this.target = target; this.angularSpeed = angularSpeed; this.angularAcceleration = angularAcceleration; this.moveSpeed = moveSpeed; this.moveAcceleration = moveAcceleration; this.autoConnect = autoConnect; this.autoDisconnect = autoDisconnect; this.detectRadius = detectRadius; this.keepFlat = false; projectile.forward = initialDirection; } public TraceMoveSubmodule WithKeepFlat(bool keepFlat) { this.keepFlat = keepFlat; return this; } public override void Update() { if (!canMove) return; float speedMultiplier = projectileSpeedMultiplier; if (target == null || target.statusSm.isDead) { moveSpeed += moveAcceleration * deltaTime; moveSpeed = Mathf.Max(moveSpeed, 0f); unscaledVelocity = projectile.forward * (moveSpeed * speedMultiplier); scaledVelocity = timeScaleCoefficient * unscaledVelocity; projectile.position += scaledVelocity * deltaTime; if (autoConnect) { if (owner.creator == MainGameManager.Player) { CharacterBase detectEnemy = CombatManager.EnemySm.Query(detectRadius).From(owner.transform).Best(); if (detectEnemy != null) { target = detectEnemy; } } } return; } angularSpeed += angularAcceleration * deltaTime; moveSpeed += moveAcceleration * deltaTime; angularSpeed = Mathf.Max(angularSpeed, 0f); moveSpeed = Mathf.Max(moveSpeed, 0f); Vector3 direction = target.CenterPoint.position - projectile.position; if (keepFlat) { direction.y = 0f; } if (direction != Vector3.zero) { Quaternion targetRotation = Quaternion.LookRotation(direction); // RotateTowards 保证恒定转速,不会因为角度小而变慢 Vector3 currentTargetVelocity = GetTargetVelocity(); GetTerminalGuidance(direction.magnitude, direction.normalized, currentTargetVelocity, speedMultiplier, out float guidedMoveSpeed, out float guidedAngularSpeed); projectile.rotation = Quaternion.RotateTowards(projectile.rotation, targetRotation, guidedAngularSpeed * speedMultiplier * deltaTime); unscaledVelocity = projectile.forward * (guidedMoveSpeed * speedMultiplier); scaledVelocity = unscaledVelocity * timeScaleCoefficient; projectile.position += scaledVelocity * deltaTime; } else { unscaledVelocity = projectile.forward * (moveSpeed * speedMultiplier); scaledVelocity = unscaledVelocity * timeScaleCoefficient; projectile.position += scaledVelocity * deltaTime; } if (autoDisconnect) { float distanceToTarget = Vector3.Distance(projectile.position, target.CenterPoint.position); if (distanceToTarget > detectRadius) { target = null; } } } /// /// 追踪物进入自身转弯半径附近后,自动减速并补偿转向速度,避免围绕目标持续盘旋。 /// private void GetTerminalGuidance(float distanceToTarget, Vector3 targetDirection, Vector3 currentTargetVelocity, float speedMultiplier, out float guidedMoveSpeed, out float guidedAngularSpeed) { float baseAngularSpeed = Mathf.Max(angularSpeed, 0f); float angularSpeedRadians = baseAngularSpeed * Mathf.Deg2Rad; float baseTurnRadius = angularSpeedRadians > Mathf.Epsilon ? moveSpeed / angularSpeedRadians : TerminalMaximumDistance; float terminalDistance = Mathf.Clamp(baseTurnRadius * TerminalDistanceMultiplier, TerminalMinimumTurnRadius, TerminalMaximumDistance); float terminalProgress = 1f - Mathf.Clamp01(distanceToTarget / terminalDistance); guidedMoveSpeed = Mathf.Lerp(moveSpeed, Mathf.Min(moveSpeed, TerminalMinimumSpeed), terminalProgress); // 目标远离飞弹时,终端阶段仍需保留足够闭合速度,避免减速后无法追上移动目标。 float targetEscapeSpeed = Mathf.Max(0f, Vector3.Dot(currentTargetVelocity, targetDirection)); float minimumPursuitSpeed = (targetEscapeSpeed + TerminalMinimumClosingSpeed) / Mathf.Max(speedMultiplier, Mathf.Epsilon); guidedMoveSpeed = Mathf.Max(guidedMoveSpeed, minimumPursuitSpeed); float desiredTurnRadius = Mathf.Max(distanceToTarget * TerminalTurnRadiusFactor, TerminalMinimumTurnRadius); float requiredAngularSpeed = guidedMoveSpeed / desiredTurnRadius * Mathf.Rad2Deg; guidedAngularSpeed = Mathf.Min(Mathf.Max(baseAngularSpeed, requiredAngularSpeed), TerminalMaximumAngularSpeed); } /// /// 采样目标真实世界位移,避免依赖不同角色实现中语义不一致的 movement 数据。 /// private Vector3 GetTargetVelocity() { if (target != velocitySampleTarget) { velocitySampleTarget = target; hasTargetVelocitySample = false; targetVelocity = Vector3.zero; } if (target == null || deltaTime <= Mathf.Epsilon) { return targetVelocity; } Vector3 currentTargetPosition = target.transform.position; if (hasTargetVelocitySample) { targetVelocity = (currentTargetPosition - lastTargetPosition) / deltaTime; } lastTargetPosition = currentTargetPosition; hasTargetVelocitySample = true; return targetVelocity; } } } }