二维目标拦截算法

时间:2018-08-14 23:45:32

标签: java 2d game-physics

我一直在尝试改善自上而下的透视射击游戏中老板之一的行为,而我未能完全正确实现的一件事是绘制一条拦截轨迹在老板的“钩子”弹丸和玩家之间根据玩家的移动情况。

我尝试使用此处描述的二次方程式来实现它:https://stackoverflow.com/a/2249237/1205340

但是我得到的结果几乎与我想出的算法相同,该算法通常将目标对准玩家的预期位置,但是除非玩家退缩离开老板,否则几乎总是会错过。

private float findPlayerIntercept(Pair<Float> playerPos, Pair<Float> playerVel, int delta) {
    float hookSpeed = HOOK_THROW_SPEED * delta;
    Pair<Float> hPos = new Pair<Float>(position);
    Pair<Float> pPos = new Pair<Float>(playerPos);

    // While the hook hasn't intercepted the player yet.
    while(Calculate.Distance(position, hPos) < Calculate.Distance(position, pPos)) {
        float toPlayer = Calculate.Hypotenuse(position, pPos);

        // Move the player according to player velocity.
        pPos.x += playerVel.x;
        pPos.y += playerVel.y;

        // Aim the hook at the new player position and move it in that direction.
        hPos.x += ((float)Math.cos(toPlayer) * hookSpeed);
        hPos.y += ((float)Math.sin(toPlayer) * hookSpeed);
    }

    // Calculate the theta value between Stitches and the hook's calculated intercept point.
    return Calculate.Hypotenuse(position, hPos);
}

此方法应该返回上司掷出钩子的theta(角度),以便根据钩子投掷时玩家的运动矢量来拦截玩家。

作为参考,Calculate.Hypotenuse方法仅使用atan2来计算两点之间的角度。 Calculate.Distance获取两个位置之间的距离(以像素为单位)。

有人对如何改进此算法有任何建议吗?还是更好的方法呢?

2 个答案:

答案 0 :(得分:0)

您的问题令人困惑(因为您也谈论二次方程式)。如果您的游戏是2D平台游戏,其中老板以给定的速度与地板成一定角度扔了一个钩子,那么我可以提出您的解决方案:

通过运动学方程,您会发现

unique_id   id          first_name  
----------- ----------- ------------
100         12114       John        
100         123123      John        
90          2591        Mary        
10          2           Davy        
10          12          Davy        
10          43          Davy        

其中d是玩家与老板之间的距离,g是重力常数,v是弯钩的初始速度。

答案 1 :(得分:0)

钩子一直丢失的原因是,在集成播放器和钩子的动作时,始终使用固定的1个单位的时间步长。这意味着两个对象的轨迹都是一系列直线“跳跃”。 1个单元的步长太大,无法获得准确的结果-如果速度足够高,则无法保证甚至会遇到循环条件while(Calculate.Distance(position, hPos) < Calculate.Distance(position, pPos))

您提到的二次方程方法是正确的,但是由于您不了解链接,因此我将尝试在此处推导类似的方法。

比方说,玩家和钩子的初始位置和速度分别为p0, uq0, v(2D向量)。 v的方向是未知的所需数量。下面是设置图:

enter image description here

应用余弦规则

enter image description here

应该使用哪个根,并且它始终存在?

  • 如果平方根内的项为负,则t没有真实的根-没有解(钩子永远不会到达玩家)。
  • 如果两个根(或单个根)均为负,则也没有有效的解决方案-挂钩需要“及时向后”触发。
  • 如果只有一个根为正,则使用它。
  • 如果两个根均为正,则使用较小的根。
  • 如果速度相等,即v = u,则解决方案很简单:

    enter image description here

    再次,如果否定,则拒绝。

一旦知道t的值,就可以计算出碰撞点和速度方向:

enter image description here


更新:示例Java代码:

private float findPlayerIntercept(Pair<Float> playerPos, Pair<Float> playerVel, int delta) 
{
    // calculate the speeds
    float v = HOOK_THROW_SPEED * delta;
    float u = Math.sqrt(playerVel.x * playerVel.x + 
                        playerVel.y * playerVel.y);

    // calculate square distance
    float c = (position.x - playerPos.x) * (position.x - playerPos.x) +
              (position.y - playerPos.y) * (position.y - playerPos.y);

    // calculate first two quadratic coefficients
    float a = v * v - u * u;
    float b = playerVel.x * (position.x - playerPos.x) + 
              playerVel.y * (position.y - playerPos.y);

    // collision time
    float t = -1.0f; // invalid value

    // if speeds are equal
    if (Math.abs(a)) < EPSILON) // some small number, e.g. 1e-5f
        t = c / (2.0f * b);
    else {
        // discriminant
        b /= a;
        float d = b * b + c / a;

        // real roots exist
        if (d > 0.0f) {
            // if single root
            if (Math.abs(d) < EPSILON)
                t = b / a;
            else {
                // how many positive roots?
                float e = Math.sqrt(d);
                if (Math.abs(b) < e)
                    t = b + e;
                else if (b > 0.0f)
                    t = b - e;
            }
        }
    }

    // check if a valid root has been found
    if (t < 0.0f) {
        // nope.
        // throw an exception here?
        // or otherwise change return value format
    }

    // compute components and return direction angle
    float x = playerVel.x + (playerPos.x - position.x) / t;
    float y = playerVel.y + (playerPos.y - position.y) / t;
    return Math.atan2(y, x);
}