Files
Cielonos/Assets/Scripts/MainGame/AttackArea/Submodules/TraceMoveSubmodule.cs
SoulliesOfficial 39b43680a9 爆更
2026-07-18 03:16:20 -04:00

189 lines
8.6 KiB
C#

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;
}
}
}
/// <summary>
/// 追踪物进入自身转弯半径附近后,自动减速并补偿转向速度,避免围绕目标持续盘旋。
/// </summary>
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);
}
/// <summary>
/// 采样目标真实世界位移,避免依赖不同角色实现中语义不一致的 movement 数据。
/// </summary>
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;
}
}
}
}