-1

I've been trying to improve the behavior of one of the bosses in a top-down perspective shooter game that I'm working on, and one thing I haven't been able to quite implement correctly is plotting an intercept trajectory between the boss' "hook" projectile and the player according to the player's movement.

I've tried implementing it using the quadratic equation described here: https://stackoverflow.com/a/2249237/1205340

But I had pretty much the same results as this algorithm I came up with, which often will aim close to the player's expected position, but almost always misses unless the player is backpedaling away from the boss.

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);
}

This method is supposed to return the theta (angle) for the boss to throw his hook in order to intercept the player according to the player's movement vector at the time the hook is thrown.

For reference, the Calculate.Hypotenuse method just uses atan2 to calculate the angle between two points. Calculate.Distance gets the distance in pixels between two positions.

Does anyone have any suggestions on how to improve this algorithm? Or a better way to approach it?

Darin Beaudreau
  • 375
  • 7
  • 30

2 Answers2

0

Your question is confusing (as you also talk about a quadratic equation). If your game is a 2d platform game in which the boss throws a hook with a given velocity with a certain angle with the floor, then I foud your solution:

By playing with the kinematic equations, you find that

<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mrow class="MJX-TeXAtom-ORD">
    <mo>&#x3B8;</mo>
  </mrow>
  <mo>=</mo>
  <mrow class="MJX-TeXAtom-ORD">
    <mrow class="MJX-TeXAtom-ORD">
      <mfrac>
        <mrow>
          <mi>arcsin</mi>
          <mo>&#x2061;<!-- ⁡ --></mo>
          <mo stretchy="false">(</mo>
          <mi>d</mi>
          <mi>g</mi>
          <mo stretchy="false">)</mo>
        </mrow>
        <mrow class="MJX-TeXAtom-ORD">
          <msup>
            <mi>v</mi>
            <mn>2</mn>
          </msup>
        </mrow>
      </mfrac>
    </mrow>
    <mo>&#x2217;<!-- ∗ --></mo>
    <mrow class="MJX-TeXAtom-ORD">
      <mfrac>
        <mn>1</mn>
        <mrow class="MJX-TeXAtom-ORD">
          <mn>2</mn>
        </mrow>
      </mfrac>
    </mrow>
  </mrow>
</math>

With d being the distance between the player and the boss, g being the gravitational constant and v being the initial velocity of the hook.

  • I probably should have mentioned that this a top-down perspective shooter game, and no gravity is involved. The projectile travels the same rate in any direction without external influences. Also, what is the "2 * 12" after "v" in the equation? – Darin Beaudreau Aug 15 '18 at 01:27
  • Ok I understand your game now All I can see for solving this would be two lines in a 3-dimensional plane (the third dimension being time) I will be posting an answer to that soon (I'm still trying to solve this system). – Najmaoui Yassir Aug 15 '18 at 01:36
0

The reason that the hook keeps missing is that you always use a fixed timestep of 1 unit when integrating the player and hook's motion. This means that both objects' trajectories are a series of straight-line "jumps". 1 unit is far too large a timestep for there to be accurate results - if the speeds are high enough, there is no guarantee that the loop condition while(Calculate.Distance(position, hPos) < Calculate.Distance(position, pPos)) will even be hit.

The quadratic equation approach you mentioned was along the correct lines, but since you haven't understood the link, I will try to derive a similar method here.

Let's say the player's and hook's initial positions and velocities are p0, u and q0, v respectively (2D vectors). v's direction is the unknown desired quantity. Below is a diagram of the setup:

enter image description here

Applying the cosine rule:

enter image description here

Which root should be used, and does it always exist?

  • If the term inside the square root is negative, there is no real root for t - no solutions (the hook will never reach the player).
  • If both roots (or the single root) are negative, there is also no valid solution - the hook needs to be fired "backwards in time".
  • If only one root is positive, use it.
  • If both roots are positive, use the smaller one.
  • If the speeds are equal, i.e. v = u, then the solution is simply:

    enter image description here

    Again, reject if negative.

Once a value for t is known, the collision point and thus the velocity direction can be calculated:

enter image description here


Update: sample Java code:

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);
}
meowgoesthedog
  • 14,670
  • 4
  • 27
  • 40