0

I'm trying to throw an arrow in my XNA game, but I'm having a hard time trying to realize a good parabola.

What I need:

  • The more you hold Enter stronger the arrow goes.
  • The arrow angle will be always the same, 45 degrees.

This is what I have already have:

private float velocityHeld = 1f;
protected override void Update(GameTime gameTime)
{

    if (Keyboard.GetState().IsKeyDown(Keys.Enter) && !released)
    {
        timeHeld += velocityHeld;
        holding = true;
    }
    else
    {
        if (holding)
        {
            released = true;
            holding = false;
            lastTimeHeld = timeHeld;
        }
    }


    if (released && timeHeld > 0)
    {
        float alpha = MathHelper.ToRadians(45f);
        double vy = timeHeld * Math.Sin(alpha);
        double vx = timeHeld * Math.Cos(alpha);

        ShadowPosition.Y -= (int)vy;
        ShadowPosition.X += (int)vx;
        timeHeld -= velocityHeld;
    }
    else
    {
        released = false;
    }
}

My question is, what do I need to do to make the arrow to go bottom as it loses velocity (timeHeld) to make a perfect parabola?

Cyral
  • 13,999
  • 6
  • 50
  • 90
Moondustt
  • 864
  • 1
  • 11
  • 30
  • Hint: on Earth, thrown objects tend to trace out a parabola, because *acceleration* due to gravity is constantly changing the object's *y velocity*. – Kevin Jun 26 '13 at 19:10
  • I see, so you're telling me that I need to implement a gravity method right? – Moondustt Jun 26 '13 at 19:18
  • Usually a good idea to have gravity when attempting to create realistic motion. – mcmonkey4eva Jun 26 '13 at 19:25
  • If they are always launched at 45 degrees, you would be better off calculating math.sin(45 degrees) and math.cos(45 degrees) once and as a final, eg final double angular_component = 1/math.sqrt(2d) – Peter Webb Jun 29 '13 at 05:14

2 Answers2

1

The solutions discussed above rely on you calculating a new velocity on every iteration, and then calculating the delta (change) from the previous position to determine the current position.

This fits in with the normal logic of a game loop. It is however computationally more complex than it needs to be, and is possibly unstable due to rounding errors.

The simpler and more stable solution is to determine the equation for the parabola and use this to directly work out the position at time t after launch.

Let the arrow start at x=0, y=0. Let the launch velocity be v. At time t after launch the a coordinate of the arrow is x = kt, where k = v*cos(45) = v/sqrt(2).

The y position is a quadratic, y = at^2 + bt + c where we don't know a, b, c.

But when t=0, y=0 so c=0

When t=0, the initial downward velocity is v*sin(45) = v/sqrt(2)

Using a tiny bit of calculus (differentiating position to get velocity), at t=0

v/sqrt(2) = 2at + b = b

Differentiating velocity to get acceleration, we get the initial acceleration at t=0 is 2a. But the only acceleration is due to gravity, so 2a=-g where g is your gravitational constant.

Putting these two equations together, at time t

x(t) = v/sqrt(2); y(t) = -(g/2)t^2 + vt/sqrt(2)

You know v and t, you define g, so you can work out the x and y co-ordinates at time t directly from this equation.

This is a more straightforward approach and more robust (rounding errors will not accumulate). It is how I personally do it. My hand grenades always follow perfect parabolic arcs, and do so in a computationally efficient manner.

Peter Webb
  • 671
  • 4
  • 14
0

Note: I never heard of XNA until now, but I do use C#. Let me know if this doesn't quite work, though the gist of it should be there.

In your last if-statement, after Enter key is released, you want to increase the downward velocity by a certain (small constant) amount every time you call Update (I assume increasing the y-coordinate makes things move "down" on screen). To do this, as soon as Enter is released, instead of calling double vy = timeHeld * Math.Sin(alpha) every time, save the result into a variable you can reference later, then use a bool value to keep track of when to calculate that value, which is ONLY right after Enter is released.

In other words, it goes something like this:

// extra variables
bool justReleased = false;
double vy, vx;
...
protected override void Update(GameTime gameTime)
{
    // ...
        if (holding)
        {
            released = true;
            holding = false;
            lastTimeHeld = timeHeld;
            justReleased = true; // add this here
        }
    // ...

    if (released && timeHeld > 0)
    {
        float alpha = MathHelper.ToRadians(45f);

        // not local variables anymore. Also I flipped the sign - my intention is that initial vertical velocity is "upwards"
        if(justReleased)
        {
            vy = -1 * timeHeld * Math.Sin(alpha); 
            vx = timeHeld * Math.Cos(alpha);
            justReleased = false;
        }

        ShadowPosition.Y += (int)vy; // Note: I flipped this operator
        ShadowPosition.X += (int)vx;
        timeHeld -= velocityHeld;

        // increase the downward velocity
        vy += 2; // or some constant. I can't test this. :\
    }
    else
    {
        released = false;
    }
}

Hopefully this works, though there might be more efficient ways to do this. Though this isn't a physics site, see this for reference ;-)