How do you make a game object move in a parabolic trajectory using a quadratic equation?

Folks I get quadratic equations as it is taught to you in Form 4 (or what ever) with traditional graph paper with positive y up, negative y down, positive x right and negative x left.

But I am buggered if I can figure out how to make it work simply with Unity’s ar$e about coordinate system (compared to graph paper) and a starting X and an ending X that could be any where in world space (any combination of negative and positive).

I don’t want to use the apply force thing to get physics unity’s engine to do it for me.

My current code is thus.

And this is what I want to do with my game object.


There simply must be an easy way to calculate Y given an X increment by 0.01f from m_vectStartPos.x.

But Y is always coming out below the m_vectStartPos.y on screen.

Has anyone at all had success with this sort of calculation in Unity scripts?

I just can’t seem to get my head around why my code is not working

I did manage to get my parabolic graph plotted the right way up, as in the above picture, but I can’t get the f’ing thing at the right height.

    private float QuadraticY(float fX)
    {
        float fY = 0.0f;

        if (gameObject.name == "RecentBall2")
            fY = (-10.0f * fX * fX);
        else
            fY = (-10.0f * fX * fX);
      
        return fY;
    }

    private float GetY(ref float fX)
    {
        float fY = 0.0f;

        if ((fX + m_vectStartPos.x) >= m_vectEndPos.x)
        {
            fY = -1000.0f;
        }
        else
        {
            fY = QuadraticY(fX) + m_vectStartPos.y;
            fX += 0.01f;
        }
        return fY;
    }

    private void EndMove()
    {
        m_bMove = false;
        DoShow(false);
        transform.localPosition = m_vectOrigPos;

        Texture2D textureCurrent = (Texture2D)m_materialRecentBall.GetTexture("_MainTex");
        m_parameters1.New(BingoMessageIDEnum.MSGID_SET_BALL, textureCurrent, gameObject.name);
    }

    protected override void Update()
    {
        if (m_bMove)
        {              
            m_fY = GetY(ref m_fX);
            if (m_fY <= -1000.0f)
                EndMove();
            else
            {
                m_vectMoveTo.x = m_fX;
                m_vectMoveTo.y = m_fY;
                m_vectMoveTo.z = m_vectStartPos.z;
                transform.localPosition = m_vectMoveTo;
            }
        }
    }

One question, why is there so many private methods? It’s not like it will change that much if you just simply leave it out

unity’s coordinate system is like any other coordinate system:
y: up, -y: down, x: east, -x: west, z: north, -z south
What do you mean it’s not the same as a normal coordinate system?
Instead of 0.0f, 10.0 f, etc, you can simply use an int, like 10, and it will be counted as a float

that seems pointless

Simply for the purpose of separating out the different bits of the task rather than having one continuous hard to read block of code.

If I can get it working correctly then I will simplify it.

I don’t care that you have private methods… what I find chilling is that you have a function ‘GetY’ that passes in a ref pointer to ‘m_fX’ so that really ‘GetY’ is actually a ‘GetXY’ where the return is Y, and the ref is X…

talk about a confusing interface…

THere’s even more “wtf’s” through out this:

    private float QuadraticY(float fX)
    {
        float fY = 0.0f;

//why? 1) name comparison like this creates a lot of garbage
//also the SAME thing happens either way, what's the point?
        if (gameObject.name == "RecentBall2")
            fY = (-10.0f * fX * fX);
        else
            fY = (-10.0f * fX * fX);
    
        return fY;
    }
    private float GetY(ref float fX)
    {
        float fY = 0.0f;

        if ((fX + m_vectStartPos.x) >= m_vectEndPos.x)
        {
//what, why? from what I'm getting you're saying when the position is past the end x pos, set y for -1000?
//why -1000?
            fY = -1000.0f;
        }
        else
        {
//ok, I kind of get this, you're saying base the height off of how far along the x is...
//but then wouldn't the height be then be dependent on how far we move in the x?
            fY = QuadraticY(fX) + m_vectStartPos.y;
//so we move at a fixed 0.01 units per frame? No control over speed?
//you do know that unity does not have a fixed framerate, this means that your motion is going to be different depending on framerate
            fX += 0.01f;
        }
        return fY;
    }

I honestly don’t even know where to start with all this…

I’d say start over.

Think about your quadratic arc as a vector function of time.

f(t) = <tA, tB, tC>

I’m busy right now, I’ll probably back around later to give further assistance.

1 Like

Yeah well the coordinate system is Unity is bloody confusing with this sort of thing.

And then bring in blender .fbx’s, with their swapped y and z axes and I am even more friggin confused.

Don’t have this sort of problem with windows MFC applications because 0,0 is at the top left of the view port and all x,y values are positive.

The code below seems to work better - just need to get the bx term (as in ax2 + bx + c) right

  • m_vectStartPos.y (as the c in ax2 + bx + c) seems to put the parabola in roughly the correct y axis position.

But I need to widen the parabola with the bx term to get the object to land in the right position.

I removed the ref parameter, tidied up the Quadratic function and deleted the EndMove(…) function from the code snippet below (it is not relevant to the problem)

    private float QuadraticY(float fX)
    {
        float fY = 0.0f;

        if (gameObject.name == "RecentBall2")
            fY = (-0.7f * fX * fX);
        else
            fY = (-2.0f * fX * fX) + (1.5f * fX) + m_vectStartPos.y;

        return fY;
    }

    private float GetY(float fX)
    {
        float fY = 0.0f;

        if (fX >= m_vectEndPos.x)
        {
            fY = -1000.0f;
        }
        else
        {
            fY = QuadraticY(fX);
            fX += 0.01f;
        }
        return fY + m_vectStartPos.y;
    }

    protected override void Update()
    {
        if (m_bMove)
        {             
            m_fY = GetY(m_fX);
            if (m_fY <= -1000.0f)
                EndMove();
            else
            {
                m_vectMoveTo.x = m_fX;
                m_vectMoveTo.y = m_fY;
                m_vectMoveTo.z = m_vectStartPos.z;
                transform.localPosition = m_vectMoveTo;
                m_fX += 0.01f;
            }
        }

if you removed the ‘ref’ from ‘GetY’, how is m_fX getting updated?

[edit]
scratch that, I see how

How about you show me the entire class this is from… not knowing the rest of your fields/vars (like m_vectStartPos) and how this all gets a running is rather confusing.

Could you explain me, why unity coordinate system is bloody confusing?
Wait until you use a 2D programmers coordinate system (+y is downwards)
the you have to use the normal 3d system
then you have to switch to blenders rotated ones (which are rotated, because if you want to create a 2d model, or view something in 2d, you could look at it from the top, and you wouldn’t need to use x and z instead of x and y, and BTW, it imports correctly into unity)

I’m not really understanding the code.
You should be trying to find two separate things: x = fx(t) and y = fy(t), where t is the amount of time which has elapsed since starting moving the object.
But in you’re code you have y = f(x), which is not making sense to me.
The y-value should be a function of time, not x distance.

In your formula you’re considering m_fX to be both a distance and time, which is what I think is causing you problems.

1 Like

… [edit] sorry, misinterpreted post of someone else confusing as OP…

I’m not sure exactly what you’re trying to do with the parabola (as TTTTTa said, it seems like x is being used as both distance and time) but if you’re just trying to get something to move with an arc-like motion, you can move it by a vector pointing up and right which you then modify by some fake gravity constant, like this:

public Vector3 initialVelocity;  //in the inspector put whatever you want, like (100,100,0)
private Vector3 currentVelocity;
private const Vector3 gravity = Vector3.down * 10f;

public void Start()
{
    currentVelocity = initialVelocity;
}

public void Update()
{
    transform.Translate(currentVelocity * Time.deltaTime);
    currentVelocity += (gravity * Time.deltaTime);
}

I echo the “I have no idea what’s going on in the OP code” sentiment.

So let’s examine the problem as I understand it. This is pretty much high school maths and physics.

Motion in 3D space can be studied by breaking the motion into its various component dimensions. X and Y are independent of each other. X position is solely dependent on the initial velocity and time. Y movement is dependent on initial velocity, time, and the acceleration of gravity. Fortunately we can use vector maths to do this all in one equation.

There are two basic approaches to movement. The first is the iterative approach. This is commonly used in game engines as it is easy to deal with user input. The big drawback of this approach is that it makes predictions a little difficult. Something like this would work.

Vector3 gravity = new Vector3 (0, 9.8f, 0);
Vector3 velocity = new Vector3 (5, 15, 7);

void FixedUpdate () {
    // Update velocity via Newtons law
    velocity += gravity * Time.deltaTime;
    
     // Update position
     transform.position += velocity * Time.deltaTime;
}

If you need predictions of the objects position at arbitrary future points in time then a deterministic solution might be better. The big drawback here is that collisions and dynamic input gets challanging.

Vector3 gravity = new Vector3 (0, 9.8f, 0);
Vector3 initialVelocity = new Vector3 (5, 15, 7);

Vector3 GetPosition (float time) {
    // Kinematic equations
    return initialVelocity * time + 0.5f * gravity * time * time;
}

Note that these solutions will converge as the fixed time step gets smaller. But if the fixed time step is too large you will get noticeable differences.

1 Like

Mainly, because I am used to the windows coordinate system where I don’t have to worry about negative coordinate values.

The possibilities for two coordinates x1,y1 and x2,y2 are -4,0 and -2,0 or -2,0 and 2,0 or 2,0 and 4,0 etc. And I find this complicates such things like finding mid points between them calculating those coordinates relative to the midpoint.

With the Windows coordinate system, where all values are positive, this complication does not exist.

With my script above, I think my problem was that my sphere was originally a standard Unity sphere and I just could not get the upside down quadratic curve to start at my game object - it always started below it. But then I swapped it for a sphere created with blender that allows a texture to wrap around it without distortion. And the fact that blender x and z are swapped seems to have some how alleviated the problem I was having - I don’t get it but I will take it.

Here is the entire code and the editor windows in unity

The idea is that a bingo ball rolls down the slope, hits the ball on the first hexagon and causes it to bounce up to the middle hexagon…and so on. And all I am doing is passing the numbered ball textures along the chain.

I am open to suggestions for simplifying it.

m_vectStartPos contains the localPosition of the sphere on top of the shortest hexagon and m_vectEndPos contains the position of the sphere on top of the middle hexagon.

OR

m_vectStartPos contains the localPosition of the sphere on top of the middle hexagon and m_vectEndPos contains the position of the sphere on top of the tallest hexagon.

m_vectOrigPos contains the localPosition of one of those spheres hanging in mid air.

The script is re-useable in both cases - I saw no sense in writing two scripts with all but identical code.

All that is important in the parabola coding is that you have a start point and an end point.

The script below is attached to those two spheres hanging in mid air so to speak - I put them over there because I was trying OnTriggerEnter etc and I was having problems unless I moved them away from the spheres on top of the hexagons. I am no longer using OnTriggerEnter etc but I have left it as is.

So when the roller ball hits the sphere sitting on the first hexagon its script (not included in this post) does the following:

  1. Hides the itself.
  2. Changes its numbered texture to the new one from the roller ball.
  3. Sends its previous numbered texture to the first ‘jump’ ball hanging in mid air.

Now the script attached to the jump ball is triggered and it does the following:

  1. Moves itself to the same position as the ball on the first hexagon (m_vectStartPos).
  2. Moves itself in a parabola up to the top of the second hexagon until its X values is >= m_vectEndPos.x.
  3. Hides itself and moves itself back into the mid air position (m_vectOrigPos)
  4. Sends it numbered texture to the ball sitting on top of the second hexagon.

Now the script attached to the ball sitting on top of the second hexagon is triggered and it:

  1. Changes its numbered texture to the one sent by the jump ball.
  2. Shows itself.
public class RecentBallJumpManager : McCormickMonoBehaviourBase
{
    private bool m_bMove = false;
    private float m_fX = 0.0f, m_fY = 0.0f;
    private Vector3 m_vectOrigPos, m_vectStartPos, m_vectEndPos, m_vectMoveTo;
    private Material m_materialRecentBall = null;
    private GameObject m_gameobjRecentBall = null;

    private void GetDetails(string strGameObjectName)
    {
        GameObject gameobj1 = null, gameobj2 = null;

        if (gameObject.name == "RecentBall1Jump")
        {
            gameobj1 = FindGO("RecentBall1");
            gameobj2 = FindGO("RecentBall2");
        }
        else if (gameObject.name == "RecentBall2Jump")
        {
            gameobj1 = FindGO("RecentBall2");
            gameobj2 = FindGO("RecentBall3");
        }
        m_gameobjRecentBall = gameobj1;

        if ((gameobj1 != null) && (gameobj2 != null))
        {
            m_vectStartPos = gameobj1.transform.localPosition;
            m_vectEndPos = gameobj2.transform.localPosition;
            m_vectOrigPos = transform.localPosition;
        }
    }

    // Use this for initialization
    protected override void Start()
    {
        base.Start();

        m_materialRecentBall = GetComponent<Renderer>().material;
        GetDetails(gameObject.name);
        DoShow(false);
    }

    private GameObject GetRecentBall(string strRecentBallName)
    {
        GameObject gameobj = null;

        if (strRecentBallName == "RecentBall1Jump")
            gameobj = FindGO("RecentBall2");
        else if (strRecentBallName == "RecentBall2Jump")
            gameobj = FindGO("RecentBall3");

        return gameobj;
    }

    protected override void DoReceiveMessage(Parameters p)
    {
        if (p.m_nMsgID == BingoMessageIDEnum.MSGID_SET_BALL)
        {
            Texture2D textureCurrent = (Texture2D)m_materialRecentBall.GetTexture("_MainTex"),
                        textureNew = (Texture2D)p.m_objParam1;

            if (textureNew != null)
            {
                m_materialRecentBall.SetTexture("_MainTex", textureNew);
                DoShow(true);
                m_bMove = true;
                transform.localPosition = m_vectStartPos;
                m_fX = m_vectStartPos.x;
            }
            if (textureCurrent != null)
            {
                m_parameters1.New(BingoMessageIDEnum.MSGID_SET_BALL, textureCurrent, gameObject.name);

                GameObject gameobj = GetRecentBall(gameObject.name);
                if (gameobj != null)
                    SendMessage(gameobj, m_parameters1);
            }
        }
        base.DoReceiveMessage(p);
    }

    private float QuadraticY(float fX)
    {
        float fY = 0.0f;

        if (gameObject.name == "RecentBall2")
            fY = (-0.7f * fX * fX);
        else
            fY = (-2.0f * fX * fX) + (1.5f * fX) + m_vectStartPos.y;

        return fY;
    }

    private float GetY(float fX)
    {
        float fY = 0.0f;

        if (fX >= m_vectEndPos.x)
        {
            fY = -1000.0f;
        }
        else
        {
            fY = QuadraticY(fX);
            fX += 0.01f;
        }
        return fY + m_vectStartPos.y;
    }

    private void EndMove()
    {
        m_bMove = false;
        DoShow(false);
        transform.localPosition = m_vectOrigPos;

        Texture2D textureCurrent = (Texture2D)m_materialRecentBall.GetTexture("_MainTex");
        m_parameters1.New(BingoMessageIDEnum.MSGID_SET_BALL, textureCurrent, gameObject.name);
        SendMessage(GetRecentBall(gameObject.name), m_parameters1);
    }

    protected override void Update()
    {
        if (m_bMove)
        {             
            m_fY = GetY(m_fX);
            if (m_fY <= -1000.0f)
                EndMove();
            else
            {
                m_vectMoveTo.x = m_fX;
                m_vectMoveTo.y = m_fY;
                m_vectMoveTo.z = m_vectStartPos.z;
                transform.localPosition = m_vectMoveTo;
                m_fX += 0.01f;
            }
        }
    }
}

Pure animation? If you don’t need your balls to do physics in any way, why not use an animation curve?

If an animation curve involves X images times 75 balls then we don’t want to use it.

Originally the whole app involved sprites only and we were going to implement the roller ball bit as 20 or so individual sprite images, each in a different stage of the roll.

But that meant 25 (or so) x 75 = 1875 individual sprite images for each rolling ball…which is a little excessive.

I am still some what unclear about the difference between localPosition and position and which one should use to move objects.

How about you go look up what an animation curve is. I’m not here to hold your hand and disabuse you of all of your preconceived notions.

They are pretty much what they say. position is the objects position in world space. localPosition is its position relative to the parent GameObject.

The one to use depends on what you are trying to do. Do you want motion relative to world space (eg a projectile moving through space) or relative to the parent GameObject (eg my arm moving relative to my torso)?

1 Like

What do you think, unity users use, when they need to move a gun for example? They wont animate it to every single x and z value, they will use an animation curve, which works in (let’s just say) local position, so the animation has a starting point, and an end point, and if you play this animation at -100.467 x and +10000.45 y, then it will still work, and the same animation will play, but it will be offset

And what do you mean by windows coordinate system? If you mean that not having to deal with negative values, because if you run off the screen it won’t show, then you’re wrong, because in reality, a coordinate system has -x and -y values, and real life has negative values, and for example -x^2 is staright up a thing that goes into negative infinity

Well unless you are dealing with an element that is bigger than the window then you ignore negative values.
And dealing with elements bigger than the window is fairly uncommon for Windows programs outside of document editing.
Most of the time you (or at least I) are dealing with a window containing various controls (tabs, edit field and such like) or a window containing simple formatted or unformatted text.
And for control based applications you have the resource editor to arrange all your controls and where negative window coordinate values are just not relevant.

Thanks for the tip anyway and I will check these out and see if I can use it in my particular situation. Anything that takes the headache out of doing what, at first sight, appears to be a very trivial task is fine by me.

These animation curves look as though they are part of the animation (.anim) that you have in conjunction with an animator controller. I have tried these with sprite images of course.

Is it possible to instead use 3D objects in them rather than sprites