189 lines
8.6 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|