2d游戏:火灾,在一个移动的目标通过预测交叉路口的抛射体和单元
-
20-09-2019 - |
题
好了,这一切都发生在一个很好的和简单的2D世界...:)
假设我有一个静态的对象一个在位置的',以及一个直线运动的对象B在商务外包与bVelocity和弹药轮速度Avelocity...
我怎么会找出来的角度,一个具有开枪,打B,考虑到B的直线速度和速度的弹药?
现在的目标是在目前的位置的对象,这意味着通过时我抛射到达那里的单元已经转移到更安全的职位:)
解决方案
首先旋转轴,使AB是垂直(通过执行旋转)
现在,拆分B的速度矢量到x和y分量(说Bx和By)。你可以用这个来计算,你需要在拍摄的矢量的x和y分量。
B --> Bx
|
|
V
By
Vy
^
|
|
A ---> Vx
您需要Vx = Bx
和Sqrt(Vx*Vx + Vy*Vy) = Velocity of Ammo
。
这应该给你你在新的系统所需要的载体。变换回旧系统和完成后(通过向另一方向做旋转)。
其他提示
我写了一个瞄准子程序 坦克 一会儿回来。我将尝试阐述我是如何做到的。
免责声明: 我可能在这里的任何地方犯了一个或多个愚蠢的错误;我只是想用我生锈的数学技能来重建推理。不过,我先切入正题,因为这是编程问答而不是数学课:-)
怎么做
它归结为求解以下形式的二次方程:
a * sqr(x) + b * x + c == 0
请注意,通过 sqr
我的意思是平方,而不是平方根。使用以下值:
a := sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)
b := 2 * (target.velocityX * (target.startX - cannon.X)
+ target.velocityY * (target.startY - cannon.Y))
c := sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)
现在我们可以查看判别式来确定是否有可能的解决方案。
disc := sqr(b) - 4 * a * c
如果判别式小于 0,就别想击中目标了——你的射弹永远无法及时到达目标。否则,看看两个候选解决方案:
t1 := (-b + sqrt(disc)) / (2 * a)
t2 := (-b - sqrt(disc)) / (2 * a)
请注意,如果 disc == 0
然后 t1
和 t2
是平等的。
如果没有其他考虑因素,例如介入障碍物,则只需选择较小的正值。(消极的 t 值需要及时向后射击才能使用!)
替换所选的 t
将值带回到目标的位置方程中,以获得您应该瞄准的引导点的坐标:
aim.X := t * target.velocityX + target.startX
aim.Y := t * target.velocityY + target.startY
推导
在时间 T 时,弹丸与大炮的(欧几里德)距离必须等于经过的时间乘以弹丸速度。这给出了一个圆的方程,以经过的时间为参数。
sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
== sqr(t * projectile_speed)
类似地,在时间 T 处,目标沿其矢量移动时间乘以速度:
target.X == t * target.velocityX + target.startX
target.Y == t * target.velocityY + target.startY
当炮弹与炮弹的距离匹配时,炮弹就能击中目标。
sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
== sqr(target.X - cannon.X) + sqr(target.Y - cannon.Y)
精彩的!将表达式替换为 target.X 和 target.Y 给出
sqr(projectile.X - cannon.X) + sqr(projectile.Y - cannon.Y)
== sqr((t * target.velocityX + target.startX) - cannon.X)
+ sqr((t * target.velocityY + target.startY) - cannon.Y)
代入等式另一边可得:
sqr(t * projectile_speed)
== sqr((t * target.velocityX + target.startX) - cannon.X)
+ sqr((t * target.velocityY + target.startY) - cannon.Y)
...减法 sqr(t * projectile_speed)
从两侧并翻转它:
sqr((t * target.velocityX) + (target.startX - cannon.X))
+ sqr((t * target.velocityY) + (target.startY - cannon.Y))
- sqr(t * projectile_speed)
== 0
...现在解析子表达式的平方结果...
sqr(target.velocityX) * sqr(t)
+ 2 * t * target.velocityX * (target.startX - cannon.X)
+ sqr(target.startX - cannon.X)
+ sqr(target.velocityY) * sqr(t)
+ 2 * t * target.velocityY * (target.startY - cannon.Y)
+ sqr(target.startY - cannon.Y)
- sqr(projectile_speed) * sqr(t)
== 0
...并将相似的术语分组...
sqr(target.velocityX) * sqr(t)
+ sqr(target.velocityY) * sqr(t)
- sqr(projectile_speed) * sqr(t)
+ 2 * t * target.velocityX * (target.startX - cannon.X)
+ 2 * t * target.velocityY * (target.startY - cannon.Y)
+ sqr(target.startX - cannon.X)
+ sqr(target.startY - cannon.Y)
== 0
...然后将它们结合起来...
(sqr(target.velocityX) + sqr(target.velocityY) - sqr(projectile_speed)) * sqr(t)
+ 2 * (target.velocityX * (target.startX - cannon.X)
+ target.velocityY * (target.startY - cannon.Y)) * t
+ sqr(target.startX - cannon.X) + sqr(target.startY - cannon.Y)
== 0
...给出标准二次方程 t. 。找到该方程的正实零点给出(零、一或两个)可能的命中位置,这可以通过二次公式完成:
a * sqr(x) + b * x + c == 0
x == (-b ± sqrt(sqr(b) - 4 * a * c)) / (2 * a)
杰弗里·汉丁(Jeffrey Hantin)在这里的出色回答+1。我到处搜索,发现解决方案要么太复杂,要么不是专门针对我感兴趣的情况(2D 空间中的简单等速射弹)。他正是我生成下面的独立 JavaScript 解决方案所需要的。
我要补充的一点是,除了判别式为负之外,您还必须注意一些特殊情况:
- “a==0”:如果目标和射弹以相同的速度行进,就会发生这种情况。(解是线性的,而不是二次的)
- “a == 0 且 b == 0”:如果目标和射弹都静止。(除非 c == 0,否则无解,即src 和 dst 是同一点。)
代码:
/**
* Return the firing solution for a projectile starting at 'src' with
* velocity 'v', to hit a target, 'dst'.
*
* @param Object src position of shooter
* @param Object dst position & velocity of target
* @param Number v speed of projectile
* @return Object Coordinate at which to fire (and where intercept occurs)
*
* E.g.
* >>> intercept({x:2, y:4}, {x:5, y:7, vx: 2, vy:1}, 5)
* = {x: 8, y: 8.5}
*/
function intercept(src, dst, v) {
var tx = dst.x - src.x,
ty = dst.y - src.y,
tvx = dst.vx,
tvy = dst.vy;
// Get quadratic equation components
var a = tvx*tvx + tvy*tvy - v*v;
var b = 2 * (tvx * tx + tvy * ty);
var c = tx*tx + ty*ty;
// Solve quadratic
var ts = quad(a, b, c); // See quad(), below
// Find smallest positive solution
var sol = null;
if (ts) {
var t0 = ts[0], t1 = ts[1];
var t = Math.min(t0, t1);
if (t < 0) t = Math.max(t0, t1);
if (t > 0) {
sol = {
x: dst.x + dst.vx*t,
y: dst.y + dst.vy*t
};
}
}
return sol;
}
/**
* Return solutions for quadratic
*/
function quad(a,b,c) {
var sol = null;
if (Math.abs(a) < 1e-6) {
if (Math.abs(b) < 1e-6) {
sol = Math.abs(c) < 1e-6 ? [0,0] : null;
} else {
sol = [-c/b, -c/b];
}
} else {
var disc = b*b - 4*a*c;
if (disc >= 0) {
disc = Math.sqrt(disc);
a = 2*a;
sol = [(-b-disc)/a, (-b+disc)/a];
}
}
return sol;
}
杰弗里Hantin有针对此问题一个很好的解决方案,虽然他的推导是过于复杂。下面是在底部与一些所得到的代码的导出它的更清洁的方式。
我将使用到x.y格式表示向量点积,并且如果一个矢量量被平方,这意味着我正在与本身打点它。
origpos = initial position of shooter
origvel = initial velocity of shooter
targpos = initial position of target
targvel = initial velocity of target
projvel = velocity of the projectile relative to the origin (cause ur shooting from there)
speed = the magnitude of projvel
t = time
我们知道,弹靶相对于对t
时间的位置可以与一些方程来描述。
curprojpos(t) = origpos + t*origvel + t*projvel
curtargpos(t) = targpos + t*targvel
我们希望这些是在某点(交叉点)彼此相等,因此让我们将它们设置彼此相等并求解自由变量,projvel
。
origpos + t*origvel + t*projvel = targpos + t*targvel
turns into ->
projvel = (targpos - origpos)/t + targvel - origvel
让我们忘掉起源和目标位置/速度的概念。相反,让我们相对来说工作,因为一件事运动相对于另一个。在这种情况下,我们现在已经是relpos = targetpos - originpos
和relvel = targetvel - originvel
projvel = relpos/t + relvel
我们不知道是什么projvel
,但我们知道,我们要projvel.projvel
等于speed^2
,所以我们会广场两侧,我们可以得到
projvel^2 = (relpos/t + relvel)^2
expands into ->
speed^2 = relvel.relvel + 2*relpos.relvel/t + relpos.relpos/t^2
我们现在可以看到,唯一的自由变量是时间,t
,然后我们将使用t
来解决projvel
。我们将解决与二次公式t
。首先,它分离成a
,b
和c
,然后求解根。
解,虽然之前,请记住,我们要在那里t
最小的最佳解决方案,但我们需要确保t
不为负(你不能打过去的东西)
a = relvel.relvel - speed^2
b = 2*relpos.relvel
c = relpos.relpos
h = -b/(2*a)
k2 = h*h - c/a
if k2 < 0, then there are no roots and there is no solution
if k2 = 0, then there is one root at h
if 0 < h then t = h
else, no solution
if k2 > 0, then there are two roots at h - k and h + k, we also know r0 is less than r1.
k = sqrt(k2)
r0 = h - k
r1 = h + k
we have the roots, we must now solve for the smallest positive one
if 0<r0 then t = r0
elseif 0<r1 then t = r1
else, no solution
现在,如果我们有一个t
值,我们可以插t
回到原始方程式并求解projvel
projvel = relpos/t + relvel
现在,为了拍摄弹丸,得到的全球位置和速度的抛射物是
globalpos = origpos
globalvel = origvel + projvel
大功告成!
我执行我的Lua中,溶液的其中VEC * VEC表示向量点积:
local function lineartrajectory(origpos,origvel,speed,targpos,targvel)
local relpos=targpos-origpos
local relvel=targvel-origvel
local a=relvel*relvel-speed*speed
local b=2*relpos*relvel
local c=relpos*relpos
if a*a<1e-32 then--code translation for a==0
if b*b<1e-32 then
return false,"no solution"
else
local h=-c/b
if 0<h then
return origpos,relpos/h+targvel,h
else
return false,"no solution"
end
end
else
local h=-b/(2*a)
local k2=h*h-c/a
if k2<-1e-16 then
return false,"no solution"
elseif k2<1e-16 then--code translation for k2==0
if 0<h then
return origpos,relpos/h+targvel,h
else
return false,"no solution"
end
else
local k=k2^0.5
if k<h then
return origpos,relpos/(h-k)+targvel,h-k
elseif -k<h then
return origpos,relpos/(h+k)+targvel,h+k
else
return false,"no solution"
end
end
end
end
以下是极坐标基于在C ++代码瞄准
要具有矩形坐标使用你需要首先转换目标相对坐标与角度/距离和目标x / y的速度到角度/速度。
在“速度”输入为射弹的速度。的速度和targetSpeed的单位是无关紧要,因为只有速度比在计算中使用。输出是角弹丸应当在烧制和碰撞点的距离。
的算法是从源代码位于 http://www.turtlewar.org/ 。
// C++
static const double pi = 3.14159265358979323846;
inline double Sin(double a) { return sin(a*(pi/180)); }
inline double Asin(double y) { return asin(y)*(180/pi); }
bool/*ok*/ Rendezvous(double speed,double targetAngle,double targetRange,
double targetDirection,double targetSpeed,double* courseAngle,
double* courseRange)
{
// Use trig to calculate coordinate of future collision with target.
// c
//
// B A
//
// a C b
//
// Known:
// C = distance to target
// b = direction of target travel, relative to it's coordinate
// A/B = ratio of speed and target speed
//
// Use rule of sines to find unknowns.
// sin(a)/A = sin(b)/B = sin(c)/C
//
// a = asin((A/B)*sin(b))
// c = 180-a-b
// B = C*(sin(b)/sin(c))
bool ok = 0;
double b = 180-(targetDirection-targetAngle);
double A_div_B = targetSpeed/speed;
double C = targetRange;
double sin_b = Sin(b);
double sin_a = A_div_B*sin_b;
// If sin of a is greater than one it means a triangle cannot be
// constructed with the given angles that have sides with the given
// ratio.
if(fabs(sin_a) <= 1)
{
double a = Asin(sin_a);
double c = 180-a-b;
double sin_c = Sin(c);
double B;
if(fabs(sin_c) > .0001)
{
B = C*(sin_b/sin_c);
}
else
{
// Sin of small angles approach zero causing overflow in
// calculation. For nearly flat triangles just treat as
// flat.
B = C/(A_div_B+1);
}
// double A = C*(sin_a/sin_c);
ok = 1;
*courseAngle = targetAngle+a;
*courseRange = B;
}
return ok;
}
这里有一个例子,在那里我的设计和实施问题的解决方案的预测针对使用递归算法: http://www.newarteest.com/flash/targeting.html
我要尝试一些其他的解决方案提出的,因为它似乎更有效率,以计算出它在一个步骤,但解决的办法我来是为估计的目标位置和饲料这一成果回入算法,以使一个新的更准确的估计,重复了几次。
对于第一个估计,我的"消防"的目标的目前位置和随后的使用三角法来确定其目标时将拍摄到达位置发射。然后在下一次我"火灾",在这一新的职位和确定其目标将是这个时间。经过约4重复我在一像素的准确性。
我砍死这个版本瞄准2D空间,我没有测试它非常彻底还,但它似乎工作。其背后的想法是这样的:
垂直于从枪口到目标的矢量指向创建的载体。 为了发生碰撞时,目标的速度和沿着该矢量(轴)弹丸应该是相同的! 使用相当简单的余弦的东西,我来到这个代码:
private Vector3 CalculateProjectileDirection(Vector3 a_MuzzlePosition, float a_ProjectileSpeed, Vector3 a_TargetPosition, Vector3 a_TargetVelocity)
{
// make sure it's all in the horizontal plane:
a_TargetPosition.y = 0.0f;
a_MuzzlePosition.y = 0.0f;
a_TargetVelocity.y = 0.0f;
// create a normalized vector that is perpendicular to the vector pointing from the muzzle to the target's current position (a localized x-axis):
Vector3 perpendicularVector = Vector3.Cross(a_TargetPosition - a_MuzzlePosition, -Vector3.up).normalized;
// project the target's velocity vector onto that localized x-axis:
Vector3 projectedTargetVelocity = Vector3.Project(a_TargetVelocity, perpendicularVector);
// calculate the angle that the projectile velocity should make with the localized x-axis using the consine:
float angle = Mathf.Acos(projectedTargetVelocity.magnitude / a_ProjectileSpeed) / Mathf.PI * 180;
if (Vector3.Angle(perpendicularVector, a_TargetVelocity) > 90.0f)
{
angle = 180.0f - angle;
}
// rotate the x-axis so that is points in the desired velocity direction of the projectile:
Vector3 returnValue = Quaternion.AngleAxis(angle, -Vector3.up) * perpendicularVector;
// give the projectile the correct speed:
returnValue *= a_ProjectileSpeed;
return returnValue;
}
我见过很多方法可以用数学解决这个问题,但这是被要求我的课在高中做有关项目的组成部分,并在此编程类不是每个人都用微积分的背景下,甚至载体对于这个问题,所以我创建了一个与方式更多的是编程方式,来解决这个问题。交点将是准确的,尽管它可能以后击中1帧比数学计算。
考虑:
S = shooterPos, E = enemyPos, T = targetPos, Sr = shooter range, D = enemyDir
V = distance from E to T, P = projectile speed, Es = enemy speed
在标准实现这个问题[S,E,P,ES,d]的全部的Givens和你正在解决或者找到T或在其处拍摄,以便使你打T取代的适当的定时的角度。
解决问题的该方法的主要方面是考虑射手的范围为圆形包围,可以在任何给定时间被拍摄的所有可能的点。该圆的半径等于:
Sr = P*time
其中时间被计算为一个循环的迭代。
因此,为了找到一个敌人行进给定的时间迭代,我们创建矢量的距离:
V = D*Es*time
现在,为了真正解决我们想找到一个点处从目标(T),以我们的射手(S)的距离小于我们的射手(SR)范围内的问题。这里有点的伪代码实现这个等式的。
iteration = 0;
while(TargetPoint.hasNotPassedShooter)
{
TargetPoint = EnemyPos + (EnemyMovementVector)
if(distanceFrom(TargetPoint,ShooterPos) < (ShooterRange))
return TargetPoint;
iteration++
}
我在这里做了一个公共领域的统一C#功能:点击 http://ringofblades.com/Blades/Code/PredictiveAim.cs
它是3D的,但可以很容易地通过用Vector2s替换Vector3s和使用你选择的向下轴线重力如果有重力修改此为2D。
在你的情况下,我经过这里的数学推导漫步理论的兴趣:点击 http://www.gamasutra.com/blogs/KainShin/20090515/83954 /Predictive_Aim_Mathematics_for_AI_Targeting.php
基本上,是不是真的需要在这里交汇概念,至于你使用抛运动,你只需要打在一个特定的角度和拍摄时的实例,让你从一开始你的目标的精确距离源,然后一旦你有距离,就可以计算出与它应该以击中目标射门适当的速度。
下面的链接,使德概念明确,被认为是有用的,可以帮助: 抛射体运动总是击中的移动目标
我抓住从这里的解决方案之一,但他们没有考虑到射手的帐户运动。如果你的射手是移动的,你可能要考虑到这一点(如当你火射手的速度应该被添加到你的子弹的速度)。真的,所有你需要做的就是减去目标的速度你的射手的速度。所以,如果你正在使用broofa的代码上面(这我会建议),改线
tvx = dst.vx;
tvy = dst.vy;
到
tvx = dst.vx - shooter.vx;
tvy = dst.vy - shooter.vy;
,你应该准备就绪。