


思路概述
假设起点为source,目标点为target,source以theta角度朝上抛出,求初速度M使得能够正好落在target。
可以将问题分为2个部分。
第1部分,求得“仰角”为theta的抛出方向ThrowDir。
第2部分,解算求得初速度M。
可以将运动划分为2个部分。
第1部分,从出发点开始到最高点结束。
第2部分,从最高点开始到target结束。


抛射方向
我用chatGPT问的,然后改了一下bug。dir为target-source方向,angle为“仰角”
public static Vector3 EnsureUpAngle(Vector3 dir, float angle)
{
dir = dir.normalized;
if(dir.magnitude == 0)
{
Debug.LogError("zero dir");
return Vector3.forward;
}
// 将 dir 向量投影到水平平面上(即 y 轴为零)
Vector3 dirFlat = Vector3.ProjectOnPlane(dir, Vector3.up).normalized;
// 计算从 dirFlat 指向 dir 的旋转值
Quaternion rotation = Quaternion.FromToRotation(Vector3.forward, dirFlat);
// 将旋转值绕着世界空间的 x 轴旋转目标角度
rotation = rotation * Quaternion.AngleAxis(-angle, Vector3.right) ;
// 应用旋转值到 dir 向量上
dir = rotation * Vector3.forward;
return dir.normalized;
}
列关系式
设throwDir的xz平面分量大小为k1,垂直大小为k2。
设source和target的水平距离d1,垂直距离d2。
设h为第一阶段垂直距离。
设第1阶段用时t1,第2阶段用时t2。
关系1:t1*g=M*k2。因为只受重力,垂直速度均匀降为0
关系2:h=M*k2*t-1/2*g*t^2。初中“公式”,其实是速度Mk2-gt积分来的。
关系3:d2+h=1/2*g*t^2。同理初中“公式”,因为第二阶段是从最高点0速度开始的。
关系4:M*k1*(t1+t2) = d1。 水平方向是匀速运动。
公式见下图,

由于貌似不能化简出简单方程,所以用二分法(我图省事)解算方程。
以上图波浪线的关系,也就是水平位移关系式做解方程f(x)=0。
即为f(M) = M*k1*(t1+t2) - d1 = 0,求零点。
f比较复杂:
Func<float, float> fx = x => k1*(x*k2/gravity+sqrt((2*d2+x*x*k2*k2/gravity)/gravity))*x-d1; 整个变量初始化和解算:
float CalculateThrowSpeedMagnitude(Vector3 throwDir)
{
if(throwDir.magnitude==0)
{
Debug.LogError("throwDir is 0");
return 0.0f;
}
throwDir = throwDir.normalized;
Vector3 dis = endPos - startPos;
float d1 = new Vector2(dis.x, dis.z).magnitude;
float d2 = abs(dis.y);
float k1 = new Vector2(throwDir.x, throwDir.z).magnitude;
float k2 = abs(throwDir.y);
Func<float, float> fx = x => k1*(x*k2/gravity+sqrt((2*d2+x*x*k2*k2/gravity)/gravity))*x-d1;
return SolveFunc_Range2(fx, 0.0f, 100.0f, 0.0001f);
} 二分法解方程代码(设定了最多100000步,其实10来步error就<0.0001了):
float SolveFunc_Range2(Func&lt;float,float&gt; f,float x1,float x2, float accept)
{
float y1 = f(x1);
float y2 = f(x2);
float newx = 0.5f * (x1 + x2);
float error = abs(y1);
int count = 0;
if(y1*y2&gt;0)
{
Debug.LogError(&quot;invalid range to solve&quot;);
return 0;
}
while(error &gt; accept)
{
count++;
if(count&gt;=100000)
{
Debug.LogError(count+&quot; cannot solve!!!&quot;);
return 0;
}
float newy = f(newx);
if(newy*y1&gt;0)
{
x1 = newx;
}
else
{
x2 = newx;
}
newx = (x1 + x2) * 0.5f;
error = abs(newy);
//Debug.Log(error);
}
Debug.Log(&quot;solved use rounds:&quot; + count);
return newx;
} 迭代和终点问题
1.必须使用FixedUpdate
2.必须先speed+=a*dt,其次pos+=speed*dt
3.由于到终点速度过快,无法完全对准:

由于我的需求是完全对准,所以就硬set过去了,这点误差对我的项目来说可以接受:
if (source.transform.position.y &lt;= endY)
{
if((source.transform.position-endPos).magnitude&gt;0.5f)
{
Debug.Log(&quot;Warning: XCThrow error too large!!! &gt;0.5f&quot;);
}
source.transform.position = endPos;
doing = false;
done = true;
} 结束。