几何抛物线
cs
using UnityEngine;
namespace GameLogic
{
/// <summary>
/// 几何抛物线
/// </summary>
public class GeometryParabola : MonoBehaviour
{
public Vector3 target = Vector3.zero;
[Range(1, 256)]
[SerializeField] int resolution = 50; // 轨迹分辨率
[Range(3.25f, 6.8f)]
[SerializeField] float height = 3.25f; // 抛物线高度
[Range(0.5f, 40f)]
[SerializeField] float moveSpeed = 23.4f; // 移动速度
private Vector3[] parabolaPoints;
private float currentDistance = 0f;
private float totalDistance = 0f;
private bool isMoving = false;
private float startTime = 0f; // 开始移动的时间
[Header("超时回收设置")]
[SerializeField] private float maxLifetime = 15f; // 最大生存时间(秒)
private float currentLifetime = 0f;
/// <summary>
/// UFO到达终点时的回调(由对象池调用者设置)
/// </summary>
public System.Action OnComplete;
private const float MinHeight = 3.25f;
private const float MaxHeight = 6.8f;
private void Start()
{
// 不再自动启动,由Initialize方法控制
}
private void Update()
{
if (isMoving)
{
// 更新生存时间
currentLifetime += Time.deltaTime;
// 检查是否超时
if (currentLifetime >= maxLifetime)
{
Debug.LogWarning($"[UFO超时回收] UFO已存活{currentLifetime:F2}秒,超过最大生存时间{maxLifetime}秒,强制回收");
isMoving = false;
ReturnToPool();
return;
}
MoveAlongParabola();
}
}
public void SetData(float distance,float axisAngle,float _height,float _moveSpeed)
{
target = TargetPosition(distance, axisAngle);
height = RangeMapper.MapRangeClamped(_height, 1.5f, 3.0f, MinHeight, MaxHeight);
moveSpeed = _moveSpeed;
}
private Vector3 TargetPosition(float distance,float angleDegrees)
{
// 当前位置
Vector3 currentPosition = transform.position;
// 通过四元数旋转forward向量
Quaternion qua = Quaternion.Euler(0, angleDegrees, 0);
Vector3 forwardDir = qua * transform.forward;
Vector3 targetPosition = currentPosition + forwardDir * distance;
// 可视化
Debug.DrawLine(currentPosition, targetPosition, Color.red, 2f);
return targetPosition;
}
public void StartMovement()
{
currentDistance = 0f;
currentLifetime = 0f; // 重置生存时间计时器
isMoving = true;
startTime = Time.time;
// 生成抛物线点
GenerateParabolaPoints();
Debug.Log($"[UFO启动] UFO开始移动,最大生存时间: {maxLifetime}秒");
}
/// <summary>
/// 预测飞盘在指定时间后的位置(用于碰撞预判)
/// </summary>
public Vector3 PredictPositionAtTime(float time)
{
if (!isMoving || parabolaPoints == null || parabolaPoints.Length < 2)
return transform.position;
// 计算从开始到指定时间的距离
float predictedDistance = currentDistance + (moveSpeed * time);
// 计算进度百分比
float t = Mathf.Clamp01(predictedDistance / totalDistance);
// 返回预测位置
return GetPositionOnParabola(t);
}
/// <summary>
/// 获取飞盘当前速度(基于轨迹切线方向)
/// </summary>
public Vector3 GetCurrentVelocity()
{
if (!isMoving || parabolaPoints == null || parabolaPoints.Length < 2)
return Vector3.zero;
float t = Mathf.Clamp01(currentDistance / totalDistance);
// 获取当前位置和稍后位置来计算速度方向
Vector3 currentPos = GetPositionOnParabola(t);
Vector3 nextPos = GetPositionOnParabola(Mathf.Min(t + 0.01f, 1f));
Vector3 direction = (nextPos - currentPos).normalized;
return direction * moveSpeed;
}
/// <summary>
/// 获取飞盘半径(用于碰撞检测)
/// </summary>
public float GetDiscRadius()
{
SphereCollider sphereCol = GetComponent<SphereCollider>();
if (sphereCol != null)
return sphereCol.radius * transform.localScale.x;
// 默认半径
return 0.15f;
}
/// <summary>
/// 是否正在移动
/// </summary>
public bool IsMoving()
{
return isMoving;
}
/// <summary>
/// 获取轨迹点数组(用于高级预测)
/// </summary>
public Vector3[] GetParabolaPoints()
{
return parabolaPoints;
}
/// <summary>
/// 获取当前进度(0-1)
/// </summary>
public float GetProgress()
{
if (totalDistance <= 0) return 0;
return Mathf.Clamp01(currentDistance / totalDistance);
}
private void GenerateParabolaPoints()
{
if (target == Vector3.zero) return;
Vector3 pos0 = transform.position;
Vector3 pos1 = target;
float deltaX = (pos1.x - pos0.x) / resolution;
float deltaZ = (pos1.z - pos0.z) / resolution;
float p, a, b, c = pos1.y - pos0.y;
float h = c <= height ? height : c;
parabolaPoints = new Vector3[resolution + 1];
if (Mathf.Approximately(c, 0))
{
a = (pos0.x - pos1.x) * 0.5f;
b = -a;
p = a * a / (-4 * h);
if (Mathf.Approximately(p, 0))
{
// 直线情况
GenerateStraightLine();
return;
p = 0.1f; // 设置一个小的p值
}
}
else
{
p = pos0.x - pos1.x;
p /= Mathf.Sqrt(4 * h) + Mathf.Sqrt(4 * (h - c));
p *= -p;
if (Mathf.Approximately(p, 0))
{
// 直线情况
GenerateStraightLine();
return;
p = 0.1f; //
}
a = deltaX >= 0 ? -Mathf.Sqrt(4 * p * -h) : Mathf.Sqrt(4 * p * -h);
b = deltaX >= 0 ? Mathf.Sqrt(4 * p * (c - h)) : -Mathf.Sqrt(4 * p * (c - h));
}
// 生成抛物线点
parabolaPoints[0] = pos0;
parabolaPoints[resolution] = pos1;
float currentDistance = 0f;
for (int i = 1; i < resolution; i++)
{
float x = a + (deltaX * i);
float y = x * x / (4 * p) + h;
float normalizedX = pos0.x + x - a;
float normalizedZ = pos0.z + (deltaZ * i);
float normalizedY = pos0.y + y;
parabolaPoints[i] = new Vector3(normalizedX, normalizedY, normalizedZ);
#region 10米处的高度
// 计算当前点到前一个点的距离
float segmentDistance = Vector3.Distance(parabolaPoints[i - 1], parabolaPoints[i]);
currentDistance += segmentDistance;
//Debug.Log($"currentDistance : {currentDistance}");
// 在10米处打印高度
if (Mathf.Abs(currentDistance - 10f) < 1f) // 使用更精确的范围
{
Debug.Log($"在10米处: 点{i}, 世界高度={normalizedY:F2}米, 局部高度={y:F2}, 累计距离={currentDistance:F2}米");
// 如果想要更精确的10米点,可以插值计算
float exact10mDistance = 10f;
if (i > 0)
{
float overshoot = currentDistance - exact10mDistance;
if (overshoot > 0 && overshoot < segmentDistance)
{
float t = 1f - (overshoot / segmentDistance);
Vector3 exact10mPoint = Vector3.Lerp(parabolaPoints[i - 1], parabolaPoints[i], t);
Debug.Log($"精确10米点: 位置={exact10mPoint}, 高度={exact10mPoint.y:F2}米");
}
}
}
#endregion
}
// 计算总距离
totalDistance = 0f;
for (int i = 1; i < parabolaPoints.Length; i++)
{
totalDistance += Vector3.Distance(parabolaPoints[i - 1], parabolaPoints[i]);
}
}
/* private void GenerateParabolaPoints()
{
if (target == Vector3.zero) return;
Vector3 pos0 = transform.position;
Vector3 pos1 = target;
parabolaPoints = new Vector3[resolution + 1];
// 计算水平距离
Vector3 horizontalDir = new Vector3(pos1.x - pos0.x, 0, pos1.z - pos0.z);
float horizontalDistance = horizontalDir.magnitude;
horizontalDir.Normalize();
// 控制点(抛物线顶点)
Vector3 controlPoint = (pos0 + pos1) * 0.5f;
controlPoint.y = Mathf.Max(pos0.y, pos1.y, height) + 1.0f;
// 生成抛物线点(贝塞尔曲线)
for (int i = 0; i <= resolution; i++)
{
float t = i / (float)resolution;
// 二次贝塞尔曲线
float u = 1 - t;
float tt = t * t;
float uu = u * u;
Vector3 point = uu * pos0;
point += 2 * u * t * controlPoint;
point += tt * pos1;
parabolaPoints[i] = point;
}
// 计算总距离
totalDistance = 0f;
for (int i = 1; i < parabolaPoints.Length; i++)
{
totalDistance += Vector3.Distance(parabolaPoints[i - 1], parabolaPoints[i]);
}
}*/
private void GenerateStraightLine()
{
parabolaPoints = new Vector3[2];
parabolaPoints[0] = transform.position;
parabolaPoints[1] = target;
totalDistance = Vector3.Distance(parabolaPoints[0], parabolaPoints[1]);
}
private void MoveAlongParabola()
{
if (parabolaPoints == null || parabolaPoints.Length < 2) return;
// 更新当前距离
currentDistance += moveSpeed * Time.deltaTime;
// 计算进度百分比
float t = Mathf.Clamp01(currentDistance / totalDistance);
// 根据进度获取抛物线上的位置
Vector3 newPosition = GetPositionOnParabola(t);
// 更新飞碟位置
transform.position = newPosition;
// 计算并应用朝向
if (t < 0.99f)
{
Vector3 nextPosition = GetPositionOnParabola(t + 0.01f);
Vector3 direction = (nextPosition - newPosition).normalized;
if (direction != Vector3.zero)
{
transform.rotation = Quaternion.LookRotation(direction);
}
}
// 检查是否到达终点
if (t >= 1f)
{
isMoving = false;
transform.position = target;
ReturnToPool();
Debug.Log("Reached destination!");
}
}
/// <summary>
/// 回收UFO到对象池(通过回调)
/// </summary>
private void ReturnToPool()
{
OnComplete?.Invoke();
OnComplete = null;
}
private Vector3 GetPositionOnParabola(float t)
{
if (parabolaPoints == null || parabolaPoints.Length < 2)
return transform.position;
// 将t值映射到轨迹点的索引
float segmentLength = 1f / (parabolaPoints.Length - 1);
int segmentIndex = Mathf.FloorToInt(t / segmentLength);
if (segmentIndex >= parabolaPoints.Length - 1)
return parabolaPoints[parabolaPoints.Length - 1];
// 计算在段内的插值参数
float segmentT = (t - (segmentIndex * segmentLength)) / segmentLength;
// 线性插值
return Vector3.Lerp(parabolaPoints[segmentIndex], parabolaPoints[segmentIndex + 1], segmentT);
}
// 可视化轨迹点(用于调试)
private void OnDrawGizmos()
{
if (parabolaPoints == null || parabolaPoints.Length < 2) return;
Gizmos.color = Color.green;
for (int i = 0; i < parabolaPoints.Length - 1; i++)
{
Gizmos.DrawSphere(parabolaPoints[i], 0.1f);
Gizmos.DrawLine(parabolaPoints[i], parabolaPoints[i + 1]);
}
Gizmos.DrawSphere(parabolaPoints[parabolaPoints.Length - 1], 0.1f);
}
}
public class RangeMapper
{
/// <summary>
/// 将一个值从一个范围映射到另一个范围
/// </summary>
public static float MapRange(float value, float inMin, float inMax, float outMin, float outMax)
{
// 先将输入值标准化到0-1范围
float normalized = Mathf.InverseLerp(inMin, inMax, value);
// 再映射到输出范围
float result = Mathf.Lerp(outMin, outMax, normalized);
// 四舍五入到小数点后一位
return Mathf.Round(result * 100f) / 100f;
}
/// <summary>
/// 带自动限制的版本
/// </summary>
public static float MapRangeClamped(float value, float inMin, float inMax, float outMin, float outMax)
{
// 限制输入值在原始范围内
float clampedValue = Mathf.Clamp(value, inMin, inMax);
float mappedValue = MapRange(clampedValue, inMin, inMax, outMin, outMax);
// 四舍五入到小数点后一位
return Mathf.Round(mappedValue * 100f) / 100f;
}
}
}
物理抛物线
cs
using UnityEngine;
namespace GameLogic
{
public class MultipleDirLauncher : DicsusLauncherBase
{
[Header("目标参数")]
[SerializeField] private float targetDistance = 10f; // 10米处检测高度
[SerializeField] private float minHeight = 1.5f; // 最小高度
[SerializeField] private float maxHeight = 3.0f; // 最大高度
[SerializeField] private float heightTolerance = 0.15f; // 高度允许误差
[SerializeField] private float targetRange = 76f; // 目标射程
[SerializeField] private float rangeTolerance = 1f; // 射程允许误差
[Header("物理参数")]
[SerializeField] private float customGravity = 9.81f; // 自定义重力加速度
[Header("发射控制")]
[SerializeField] private float launchHeight = 12f; // 垂直发射角度(度)
[SerializeField] private float horizontalAngle = 0f; // 水平发射方向角(度),正值向右,负值向左
[SerializeField] private float initialVelocity = 0f; // 计算出的初速度
[SerializeField] private float configuredSpeed = 0f; // 外部设置的初速度
[Header("飞行姿态控制")]
[SerializeField] private float flightPitchAngle = 0f; // 飞行过程中的俯仰角度(相对于水平面,-45° 到 45°)
[SerializeField] private float rotationSmoothness = 5f; // 旋转平滑度
[SerializeField] private bool maintainFlightPitch = true; // 是否保持飞行俯仰角
[Header("回收控制")]
[SerializeField] private float groundHeight = 0f; // 地面高度
[SerializeField] private float recycleDelay = 0.5f; // 落地后延迟回收时间
[Header("调试显示")]
[SerializeField] private bool showTrajectory = true;
[SerializeField] private int trajectoryPoints = 50;
[SerializeField] private Color trajectoryColor = Color.yellow;
private Rigidbody rb;
private Vector3 lastVelocity = Vector3.zero;
private float originalCustomGravity;
private bool hasSlowMotionGravity;
private Vector3 launchStartPosition = Vector3.zero;
private bool isLaunched = false;
private float launchTime = 0f; // 发射时间
bool isInit = false;
private const float MinHeight = 8f;
private const float MaxHeight = 20f;
// 记录外部设置的目标高度(10米处)
private float targetHeightAt10m = 2.25f; // 默认中间值
void Start()
{
Initialize();
//LaunchDisc();
}
void Initialize()
{
if (isInit)
return;
isInit = true;
rb = GetComponent<Rigidbody>();
if (rb == null)
{
rb = gameObject.AddComponent<Rigidbody>();
}
SetupPhysics();
launchStartPosition = transform.position;
}
void FixedUpdate()
{
// 应用自定义重力
if (isLaunched)
{
rb.AddForce(Vector3.down * customGravity, ForceMode.Acceleration);
RecordTrajectoryFrame(rb);
if (maintainFlightPitch)
{
MaintainFlightPitch();
}
}
}
/// <summary>
/// 重置飞碟状态
/// </summary>
public void ResetDisc()
{
isLaunched = false;
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
transform.position = launchStartPosition;
transform.rotation = Quaternion.identity;
}
/// <summary>
/// 保持飞行过程中的俯仰角
/// 限制:飞碟前向量与速度方向的夹角必须在 -45° 到 45° 范围内
/// </summary>
private void MaintainFlightPitch()
{
if (rb.velocity.magnitude > 0.1f)
{
// 计算目标方向(速度方向 + 俯仰角调整)
Vector3 velocityDir = rb.velocity.normalized;
// 获取水平方向
Vector3 horizontalVelocity = new Vector3(rb.velocity.x, 0, rb.velocity.z);
if (horizontalVelocity.magnitude > 0.1f)
{
// 计算水平朝向
Vector3 horizontalDir = horizontalVelocity.normalized;
// 应用俯仰角调整
Quaternion horizontalRotation = Quaternion.LookRotation(horizontalDir);
Quaternion desiredRotation = horizontalRotation * Quaternion.Euler(flightPitchAngle, 0f, 0f);
// 计算期望的前向量
Vector3 desiredForward = desiredRotation * Vector3.forward;
// 计算前向量与速度方向的夹角
float angleToVelocity = Vector3.SignedAngle(
new Vector3(velocityDir.x, 0, velocityDir.z).normalized, // 速度的水平分量
new Vector3(desiredForward.x, 0, desiredForward.z).normalized, // 前向量的水平分量
Vector3.up
);
// 限制夹角在 -45° 到 45° 之间
if (Mathf.Abs(angleToVelocity) > 45f)
{
// 如果超出范围,调整俯仰角使夹角不超过45°
float correction = Mathf.Sign(angleToVelocity) * (Mathf.Abs(angleToVelocity) - 45f);
float adjustedPitch = flightPitchAngle - correction;
desiredRotation = horizontalRotation * Quaternion.Euler(adjustedPitch, 0f, 0f);
if (Time.frameCount % 30 == 0)
{
Debug.Log($"[FlyingDiscLauncher] 夹角超限:{angleToVelocity:F1}° -> 调整俯仰角:{flightPitchAngle:F1}° -> {adjustedPitch:F1}°");
}
}
// 平滑旋转到目标方向
rb.rotation = Quaternion.Slerp(rb.rotation, desiredRotation,
rotationSmoothness * Time.fixedDeltaTime);
}
}
}
void SetupPhysics()
{
rb.useGravity = false; // 关闭Unity重力,使用自定义重力
rb.drag = 0f;
rb.angularDrag = 0.05f;
}
/// <summary>
/// 计算满足76米射程所需初速度
/// </summary>
float CalculateVelocityForRange(float angleDeg, float range)
{
float angleRad = angleDeg * Mathf.Deg2Rad;
float sin2Theta = Mathf.Sin(2f * angleRad);
if (sin2Theta <= 0.001f)
{
Debug.LogWarning("角度太小或接近0/90度,射程公式不适用");
return 0f;
}
float originalVelocity = Mathf.Sqrt((customGravity * range) / sin2Theta);
return originalVelocity;
}
/// <summary>
/// 计算在10米处的高度
/// </summary>
float CalculateHeightAt10m(float angleDeg, float velocity)
{
float angleRad = angleDeg * Mathf.Deg2Rad;
float x = 10f;
float term1 = x * Mathf.Tan(angleRad);
float term2 = (customGravity * x * x) / (2f * velocity * velocity * Mathf.Cos(angleRad) * Mathf.Cos(angleRad));
return term1 - term2;
}
/// <summary>
/// 自动选择合适角度满足高度约束
/// </summary>
float FindOptimalAngle(float desiredHeight = 2.25f)
{
// 使用公式: y = tanθ * (10 - 100/R)
float R = targetRange;
float k = 10f - 100f / R;
// 所需tanθ
float tanTheta = desiredHeight / k;
float angle = Mathf.Atan(tanTheta) * Mathf.Rad2Deg;
return angle;
}
/// <summary>
/// 验证参数是否在允许范围内
/// </summary>
bool ValidateParameters(float angleDeg, float velocity)
{
// 计算10米处高度
float heightAt10m = CalculateHeightAt10m(angleDeg, velocity);
// 计算射程
float angleRad = angleDeg * Mathf.Deg2Rad;
float range = (velocity * velocity * Mathf.Sin(2f * angleRad)) / customGravity;
// 检查高度约束
float minAllowedHeight = minHeight - heightTolerance;
float maxAllowedHeight = maxHeight + heightTolerance;
bool heightValid = (heightAt10m >= minAllowedHeight) && (heightAt10m <= maxAllowedHeight);
// 检查射程约束
float minRange = targetRange - rangeTolerance;
float maxRange = targetRange + rangeTolerance;
bool rangeValid = (range >= minRange) && (range <= maxRange);
/* Debug.Log($"角度: {angleDeg:F2}°, 速度: {velocity:F2} m/s");
Debug.Log($"10米处高度: {heightAt10m:F2}m, 允许范围: [{minAllowedHeight:F2}, {maxAllowedHeight:F2}] - {(heightValid ? "通过" : "不通过")}");
Debug.Log($"射程: {range:F2}m, 允许范围: [{minRange:F2}, {maxRange:F2}] - {(rangeValid ? "通过" : "不通过")}");*/
return heightValid && rangeValid;
}
/// <summary>
/// 获取飞盘当前速度
/// </summary>
public Vector3 GetCurrentVelocity()
{
return rb != null ? rb.velocity : Vector3.zero;
}
/// <summary>
/// 获取飞盘半径(用于碰撞检测)
/// </summary>
public float GetDiscRadius()
{
// 假设飞盘是圆形,返回碰撞体半径
SphereCollider sphereCol = GetComponent<SphereCollider>();
if (sphereCol != null)
return sphereCol.radius * transform.localScale.x;
// 默认半径
return 0.15f;
}
/// <summary>
/// 是否已发射
/// </summary>
public bool IsLaunched()
{
return isLaunched;
}
/// <summary>
/// 发射飞碟
/// </summary>
public override void LaunchDisc()
{
Initialize();
isLaunched = true;
launchTime = Time.time;
launchStartPosition = transform.position;
ApplyConfiguredSpeed();
// 设置速度
float verticalAngleRad = launchHeight * Mathf.Deg2Rad;
float horizontalAngleRad = horizontalAngle * Mathf.Deg2Rad;
// 计算速度分量
float verticalSpeed = initialVelocity * Mathf.Sin(verticalAngleRad);
float horizontalSpeed = initialVelocity * Mathf.Cos(verticalAngleRad);
// 根据水平角度计算X和Z方向速度
lastVelocity = new Vector3(
horizontalSpeed * Mathf.Sin(horizontalAngleRad), // X方向:左右
verticalSpeed, // Y方向:上下
horizontalSpeed * Mathf.Cos(horizontalAngleRad) // Z方向:前后
);
rb.velocity = lastVelocity;
SetTrajectoryMotionParams(p =>
{
p.initialVelocity = lastVelocity;
p.initialSpeed = lastVelocity.magnitude;
p.horizontalSpeed = horizontalSpeed;
p.verticalSpeed = verticalSpeed;
p.customGravity = customGravity;
p.drag = rb.drag;
p.angularDrag = rb.angularDrag;
p.launchAngle = launchHeight;
p.horizontalAngle = horizontalAngle;
p.pitchAngle = flightPitchAngle;
p.calculatedGravity = customGravity;
});
// 计算并打印实际的10米处高度
float actualHeightAt10m = CalculateHeightAt10m(launchHeight, initialVelocity);
// 计算理论飞行时间和射程
float flightTime = (2f * verticalSpeed) / customGravity;
float theoreticalRange = horizontalSpeed * flightTime;
/*Debug.Log($"[PhysicsParabola] 发射参数:");
Debug.Log($" - 垂直角度: {launchHeight:F2}°, 水平角度: {horizontalAngle:F2}°");
Debug.Log($" - 初速度: {initialVelocity:F2} m/s (水平: {horizontalSpeed:F2} m/s, 垂直: {verticalSpeed:F2} m/s)");
Debug.Log($" - 重力: {customGravity:F2} m/s²");
Debug.Log($" - 10米处高度: {actualHeightAt10m:F2}m (目标: {targetHeightAt10m:F2}m)");
Debug.Log($" - 理论飞行时间: {flightTime:F2}s");
Debug.Log($" - 理论射程: {theoreticalRange:F2}m (目标: {targetRange:F2}m)");*/
// 验证最终参数(仅用于调试,不再自动调整)
ValidateParameters(launchHeight, initialVelocity);
}
/// <summary>
/// 设置高度
/// </summary>
public override void SetHeight(float height)
{
// 记录目标高度(10米处)
targetHeightAt10m = height;
// 根据目标高度计算发射角度
launchHeight = MapRangeClamped(height, 1.5f, 3.0f, MinHeight, MaxHeight);
// 重要:根据新角度重新计算所需的重力值,确保射程为76米
// 使用迭代法找到满足两个约束的重力值:
// 1. 射程 = 76米
// 2. 10米处高度 = targetHeightAt10m
float bestGravity = 9.81f;
float minError = float.MaxValue;
// 在合理范围内搜索重力值(5 ~ 50 m/s²),使用更精细的步长
for (float testGravity = 5f; testGravity <= 50f; testGravity += 0.1f)
{
// 临时设置重力
float tempGravity = testGravity;
// 计算该重力下满足76米射程的速度
float angleRad = launchHeight * Mathf.Deg2Rad;
float sin2Theta = Mathf.Sin(2f * angleRad);
if (sin2Theta <= 0.001f) continue;
float testVelocity = Mathf.Sqrt((tempGravity * targetRange) / sin2Theta);
// 计算10米处高度
float x = 10f;
float term1 = x * Mathf.Tan(angleRad);
float term2 = (tempGravity * x * x) / (2f * testVelocity * testVelocity * Mathf.Cos(angleRad) * Mathf.Cos(angleRad));
float heightAt10m = term1 - term2;
// 计算误差
float error = Mathf.Abs(heightAt10m - targetHeightAt10m);
if (error < minError)
{
minError = error;
bestGravity = tempGravity;
}
}
// 应用找到的最佳重力值
customGravity = bestGravity;
// 验证结果
float finalVelocity = CalculateVelocityForRange(launchHeight, targetRange);
float finalHeightAt10m = CalculateHeightAt10m(launchHeight, finalVelocity);
/* Debug.Log($"[PhysicsParabola] 设置目标高度: {targetHeightAt10m:F2}m");
Debug.Log($"[PhysicsParabola] 计算结果: 角度={launchHeight:F2}°, 重力={customGravity:F2} m/s², 初速度={finalVelocity:F2} m/s");
Debug.Log($"[PhysicsParabola] 验证: 10米处高度={finalHeightAt10m:F2}m (误差: {Mathf.Abs(finalHeightAt10m - targetHeightAt10m):F4}m)");*/
}
/// <summary>
/// 设置发射方向
/// </summary>
public override void SetHorizontalAngle(float angle)
{
horizontalAngle = Mathf.Clamp(angle, -45f, 45f);
//Debug.Log($"水平角度设置为: {horizontalAngle}°");
}
private static float MapRangeClamped(float value, float inMin, float inMax, float outMin, float outMax)
{
if (Mathf.Approximately(inMin, inMax))
return outMin;
float t = Mathf.InverseLerp(inMin, inMax, value);
return Mathf.Lerp(outMin, outMax, t);
}
/// <summary>
/// 设置飞行俯仰角(限制在 -45° 到 45° 之间)
/// </summary>
public override void SetPitchAngle(float angle)
{
flightPitchAngle = Mathf.Clamp(angle, -45f, 45f);
// Debug.Log($"[FlyingDiscLauncher] 飞行俯仰角设置为: {flightPitchAngle}°");
}
/// <summary>
/// 设置初速度,由DicsusShooterV2统一控制
/// </summary>
public override void SetSpeed(float speed)
{
configuredSpeed = Mathf.Max(0.01f, speed);
ApplyConfiguredSpeed();
}
/// <summary>
/// 设置初速度(通过速度倍率控制快慢)
/// 保持射程76米和10米处高度不变(使用外部通过SetHeight设置的高度)
/// </summary>
/// <param name="speedMultiplier">速度倍率(1.0为正常速度,>1更快,<1更慢)</param>
public override void SetSpeedMultiplier(float speedMultiplier)
{
// 限制倍率在合理范围内
speedMultiplier = Mathf.Clamp(speedMultiplier, 0.01f, 2.0f);
// 调整重力:速度变为k倍,重力变为k²倍
customGravity = 9.81f * speedMultiplier * speedMultiplier;
// 使用迭代法找到合适的角度,使得在新速度和新重力下:
// 1. 射程仍为76米
// 2. 10米处高度保持为外部设置的targetHeightAt10m
float bestAngle = launchHeight;
float minError = float.MaxValue;
// 在合理范围内搜索角度,使用更精细的步长
for (float testAngle = 8f; testAngle <= 20f; testAngle += 0.05f)
{
// 计算该角度下满足76米射程的速度
// 注意:CalculateVelocityForRange已经使用了调整后的customGravity
// 所以这里不需要再乘speedMultiplier
float testVelocity = CalculateVelocityForRange(testAngle, targetRange);
// 计算10米处高度
float heightAt10m = CalculateHeightAt10m(testAngle, testVelocity);
// 计算误差(使用外部设置的目标高度)
float error = Mathf.Abs(heightAt10m - this.targetHeightAt10m);
if (error < minError)
{
minError = error;
bestAngle = testAngle;
}
}
// 应用找到的最佳角度
launchHeight = bestAngle;
// 计算最终速度(不需要再乘speedMultiplier,因为customGravity已经调整过了)
initialVelocity = CalculateVelocityForRange(launchHeight, targetRange);
// 验证结果
float finalHeightAt10m = CalculateHeightAt10m(launchHeight, initialVelocity);
float angleRad = launchHeight * Mathf.Deg2Rad;
float finalRange = (initialVelocity * initialVelocity * Mathf.Sin(2f * angleRad)) / customGravity;
Debug.Log($"[PhysicsParabola] 速度倍率: {speedMultiplier:F2}x");
Debug.Log($"[PhysicsParabola] 调整后角度: {launchHeight:F2}°, 初速度: {initialVelocity:F2} m/s, 重力: {customGravity:F2} m/s²");
Debug.Log($"[PhysicsParabola] 10米处高度: {finalHeightAt10m:F2}m (目标: {this.targetHeightAt10m:F2}m)");
Debug.Log($"[PhysicsParabola] 射程: {finalRange:F2}m (目标: {targetRange:F2}m)");
}
private void ApplyConfiguredSpeed()
{
if (configuredSpeed <= 0f)
{
initialVelocity = CalculateVelocityForRange(launchHeight, targetRange);
return;
}
initialVelocity = configuredSpeed;
float angleRad = launchHeight * Mathf.Deg2Rad;
float sin2Theta = Mathf.Sin(2f * angleRad);
if (sin2Theta <= 0.001f)
{
Debug.LogWarning("[MultipleDirLauncher] 当前角度太小,无法根据速度计算射程重力");
return;
}
customGravity = (initialVelocity * initialVelocity * sin2Theta) / targetRange;
//Debug.Log($"[MultipleDirLauncher] 使用统一速度: {initialVelocity:F2} m/s, 重力:{customGravity:F2} m/s²");
}
/// <summary>
/// 绘制轨迹预测(编辑模式下)
/// </summary>
void OnDrawGizmos()
{
if (!showTrajectory || Application.isPlaying) return;
// 计算初速度
float velocity = CalculateVelocityForRange(launchHeight, targetRange);
if (velocity <= 0) return;
float verticalAngleRad = launchHeight * Mathf.Deg2Rad;
float horizontalAngleRad = horizontalAngle * Mathf.Deg2Rad;
// 计算速度分量
float verticalSpeed = velocity * Mathf.Sin(verticalAngleRad);
float horizontalSpeed = velocity * Mathf.Cos(verticalAngleRad);
// 计算水平方向上的速度分量
float horizontalVelocityX = horizontalSpeed * Mathf.Sin(horizontalAngleRad);
float horizontalVelocityZ = horizontalSpeed * Mathf.Cos(horizontalAngleRad);
// 计算总飞行时间(只考虑垂直方向)
float totalTime = (2f * verticalSpeed) / customGravity;
Gizmos.color = trajectoryColor;
Vector3 prevPos = transform.position;
// 绘制轨迹
for (int i = 1; i <= trajectoryPoints; i++)
{
float t = i * (totalTime / trajectoryPoints);
// 计算当前点的位置
float x = horizontalVelocityX * t;
float z = horizontalVelocityZ * t;
float y = verticalSpeed * t - 0.5f * customGravity * t * t;
Vector3 point = transform.position + new Vector3(x, y, z);
Gizmos.DrawLine(prevPos, point);
// 标记10米处
float horizontalDistance = Mathf.Sqrt(x * x + z * z);
if (Mathf.Abs(horizontalDistance - 10f) < 0.3f)
{
Gizmos.color = Color.green;
Gizmos.DrawSphere(point, 0.2f);
Gizmos.color = trajectoryColor;
}
prevPos = point;
// 如果低于地面,停止绘制
if (point.y <= 0) break;
}
// 标记76米射程处
float flightTime = (2f * verticalSpeed) / customGravity;
float rangeX = horizontalVelocityX * flightTime;
float rangeZ = horizontalVelocityZ * flightTime;
Vector3 landingPoint = transform.position + new Vector3(rangeX, 0, rangeZ);
Gizmos.color = Color.red;
Gizmos.DrawSphere(landingPoint, 0.5f);
// 绘制发射方向指示器
Gizmos.color = Color.blue;
Vector3 direction = new Vector3(
Mathf.Sin(horizontalAngleRad),
0,
Mathf.Cos(horizontalAngleRad)
).normalized;
Gizmos.DrawRay(transform.position, direction * 5f);
}
/// <summary>
/// 在Inspector中自动计算并显示信息
/// </summary>
void OnValidate()
{
if (Application.isPlaying) return;
// 计算速度
float velocity = CalculateVelocityForRange(launchHeight, targetRange);
if (velocity > 0)
{
// 计算10米处高度
float heightAt10m = CalculateHeightAt10m(launchHeight, velocity);
// 计算射程
float angleRad = launchHeight * Mathf.Deg2Rad;
float range = (velocity * velocity * Mathf.Sin(2f * angleRad)) / customGravity;
// 更新显示
initialVelocity = velocity;
// 验证约束
float minAllowedHeight = minHeight - heightTolerance;
float maxAllowedHeight = maxHeight + heightTolerance;
float minRange = targetRange - rangeTolerance;
float maxRange = targetRange + rangeTolerance;
bool heightValid = (heightAt10m >= minAllowedHeight) && (heightAt10m <= maxAllowedHeight);
bool rangeValid = (range >= minRange) && (range <= maxRange);
if (!heightValid || !rangeValid)
{
Debug.LogWarning($"当前参数不满足约束: 高度={heightAt10m:F2}m, 射程={range:F2}m");
}
}
}
/// <summary>
/// 在运行时显示轨迹(从固定起点绘制)并检测落地
/// </summary>
void Update()
{
if (showTrajectory && Application.isPlaying)
{
DrawRuntimeTrajectory();
}
// 检测飞碟是否落地
// 条件:已发射 && 飞行距离超过75.5米 && 低于地面高度
if (isLaunched)
{
float horizontalDistance = Vector3.Distance(
new Vector3(transform.position.x, 0, transform.position.z),
new Vector3(launchStartPosition.x, 0, launchStartPosition.z)
);
if (horizontalDistance >= 75.5f && transform.position.y <= groundHeight)
{
HandleLanding();
}
}
}
/// <summary>
/// 处理飞碟落地
/// </summary>
private void HandleLanding()
{
isLaunched = false;
FinishTrajectory(TrajectoryRecordResult.Miss);
// 计算飞行距离
float horizontalDistance = Vector3.Distance(
new Vector3(transform.position.x, 0, transform.position.z),
new Vector3(launchStartPosition.x, 0, launchStartPosition.z)
);
// 计算飞行时间
float flightTime = Time.time - launchTime;
// 停止物理运动
if (rb != null)
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
Debug.Log($"[PhysicsParabola] 飞碟落地 - 飞行距离: {horizontalDistance:F2}m, 飞行时间: {flightTime:F2}s");
// 延迟回收
if (recycleDelay > 0)
{
Invoke(nameof(TriggerRecycle), recycleDelay);
}
else
{
TriggerRecycle();
}
}
/// <summary>
/// 触发回收回调
/// </summary>
private void TriggerRecycle()
{
OnComplete?.Invoke();
Debug.Log($"[FlyingDiscLauncher] 飞碟已回收到对象池");
}
public override void ForceRecycle()
{
OnRecycleRequested();
base.ForceRecycle();
}
public override void SetSlowMotionScale(float scale)
{
if (!hasSlowMotionGravity)
{
originalCustomGravity = customGravity;
hasSlowMotionGravity = true;
}
customGravity = originalCustomGravity * scale * scale;
}
public override void RestoreSlowMotionScale()
{
if (!hasSlowMotionGravity)
return;
customGravity = originalCustomGravity;
hasSlowMotionGravity = false;
}
protected override void OnRecycleRequested()
{
CancelInvoke(nameof(TriggerRecycle));
isLaunched = false;
if (rb != null)
{
ObjectSlowMotionController.Instance?.RemoveRigidbodyWithoutRestore(rb);
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
RestoreSlowMotionScale();
}
void DrawRuntimeTrajectory()
{
if (initialVelocity <= 0) return;
float verticalAngleRad = launchHeight * Mathf.Deg2Rad;
float horizontalAngleRad = horizontalAngle * Mathf.Deg2Rad;
// 计算速度分量
float verticalSpeed = initialVelocity * Mathf.Sin(verticalAngleRad);
float horizontalSpeed = initialVelocity * Mathf.Cos(verticalAngleRad);
// 计算水平方向上的速度分量
float horizontalVelocityX = horizontalSpeed * Mathf.Sin(horizontalAngleRad);
float horizontalVelocityZ = horizontalSpeed * Mathf.Cos(horizontalAngleRad);
// 计算总飞行时间
float totalTime = (2f * verticalSpeed) / customGravity;
// 从记录的发起点开始绘制
Vector3 prevPos = launchStartPosition;
// 绘制轨迹
for (int i = 1; i <= 20; i++)
{
float t = i * (totalTime / 20f);
// 计算当前点的位置
float x = horizontalVelocityX * t;
float z = horizontalVelocityZ * t;
float y = verticalSpeed * t - 0.5f * customGravity * t * t;
// 从发射点开始计算位置
Vector3 point = launchStartPosition + new Vector3(x, y, z);
Debug.DrawLine(prevPos, point, Color.cyan);
prevPos = point;
if (point.y <= 0) break;
}
// 在落地点画一个标记
float flightTime = (2f * verticalSpeed) / customGravity;
float rangeX = horizontalVelocityX * flightTime;
float rangeZ = horizontalVelocityZ * flightTime;
Vector3 landingPoint = launchStartPosition + new Vector3(rangeX, 0, rangeZ);
// 绘制目标点标记
Debug.DrawLine(landingPoint + Vector3.up * 0.5f, landingPoint - Vector3.up * 0.5f, Color.red);
Debug.DrawLine(landingPoint + Vector3.right * 0.5f, landingPoint - Vector3.right * 0.5f, Color.red);
Debug.DrawLine(landingPoint + Vector3.forward * 0.5f, landingPoint - Vector3.forward * 0.5f, Color.red);
}
}
}