Parabolic Motion Unity3D Physics

Developing algorithms for my videogame I bumped into the need to shoot cannonballs from a moving ship on an exact point into the sand of a Pirate’s island.
Coming back to my Engineering studies of Physics I developed an algorithm in C# for Unity3D for Parabolic Motion that I deem it’s interesting to share. I’m going to discuss about that with a recap on Parabolic Motion in Unity3D (hoping to not horrify Physicians Professors) and an explanation of what I did.

Parabolic Motion

Parabolic Motion, generally know as “Projectile Motion” is the motion of a body shot from one point in space and subject to gravity force.

Considering a 2D coordinates system, we can depict the body in a starting point in space with an initial velocity (vector V0) that can be decomposed into its components V0X and V0Y.

Parabolic Motion Unity3D
Diagram 1 – Parabolic Trajectory in 2D space

 

Starting with V0, the motion of the body, in absence of any friction, is described by a system of two different equations that combine a linear motion (not subject to forces) along the x-axis and an uniformly accelerated motion along y-axis (because of the action of the gravity force).

Parabolic Motion Unity3D

Ref.1 – Parabolic Motion Equations in 2D space

 

In the second equation we consider the gravity acceleration (g) as negative (therefore [-1/2*gt^2])

With this in mind, we are in the position to predict the trajectory the body will follow during its 2D trip in the space. We can now extend the motion description in 3D space:

Parabolic Motion Unity3D

Diagram 2 – Parabolic Trajectory in 3D space

 

where the motion is now represented by three equations:

Parabolic Motion Unity3D

Ref.2 – Parabolic Motion Equations in 3D space

 

We have added the motion along the z-axis that will still be a linear motion.

 

Solution in Unity3D

Described the theory behind, coming back to the initial problem (shooting the body in a precise point in space from a moving parent body), the solution is exploiting the Unity3D engine that will take care of the Physics simulation (deploying the Maths above described) and we can concentrate on writing an algorithm that only needs to calculate and apply the initial force to the body to shoot it with the velocity required to land in the point we want. Exciting.

Just a word on Unity3D: it’s a leading 3D engine for Windows and MacOS that is widely spread for videogames and apps production, it has a lot of features that cover 3D Physics management (thanks to the PhysX nVidia engine), 3D animations, Lightning & Graphics, Scripting, UI Interface, Audio and VR. Its use is not straightforward, but after some practice and tutorial it’s really easy to create a basic 3D app.

I created a basic new project in which I added a plane, a moving 3D body (a cube) and a 3D target (a sphere). You could wonder why I’m using a cube to hit a ball and not the contrary, well I don’t know, basically I was in that mood :). Each object in Unity3D has the chance to get components (in order to determine position or its physic behaviour or again, to manage its animations or audio). I added therefore a collider component for both the cube, the sphere and the plane in order to manage the physic collisions and allowing interacting each other and be subject to the gravity simulated by the engine.

By also adding a Rigidbody component I have allowed the cube to react to the PhyscX engine and be subject to Physics calculations we need for the simulation.

Finally, I added a Trailer component to the cube in order to track its movement while flying.

Next step was adding a C# script to the cube to solve the problem: the algorithm deployed was aimed to calculate the force to be applied to the cube in order to have the body shot with the right velocity to have it fallen into the target point, during a trip of predetermined time (t).

Thinking about the Physics definition of a force (F=ma), being the body mass known, in order to determine the required force, we need an acceleration. An acceleration is generated by a velocity change  and therefore I calculated the initial velocities the body should start with at the shoot instant:

Parabolic Motion Unity3D

Ref.3 – Velocity Equations

 

Now, if we apply a change in velocity in a short instant (i.e. from 0 to V0x for the x-axis and anyway the same for all the axis), we’ll have an instant acceleration that will produce the required force for the shoot moment, so that having the body falling exactly where we expect it.

In order to do that I used the Unity3D AddForce function, asking the physics engine to apply a force that is produced as a result of an instant change in velocity and pushes the shot body with a velocity vector made by the three components above calculated.

And that’s it. It works. Let’s delve into the solution:

 

C# Code

ApplyThrust Function

This is the core function and deploys all the logic.

Step 1) We calculate the vector (forceDirection) that determines the distance from the starting position of the body to the target (Vector r in Diagram 2).

       Vector3 forceDirection = _target.transform.position  transform.position; 

Step 2) We determine the three components of the forceDirection vector, basically the three distances to the target while X0, Y0, Z0 will be always 0 because we’re calculating the distance starting from current position of the shot body.

        X = forceDirection.x;         // Distance to travel along X : Space traveled @ time t
        Y = forceDirection.y;         // Distance to travel along Y : Space traveled @ time t
        Z = forceDirection.z;         // Distance to travel along Z : Space traveled @ time t

        X0 = 0;        
        Y0 = 0;     
        Z0 = 0; 

Step 3) We detach the shot body from the parent object (if any), in order to make its position system indipendent from the father.

        transform.parent = null;        // Detach the shot object from parent in order to get its own velocity

Step 4) Time Setup: the variable t will assume the value _time specified in the project inspector by the user.

        t = _time; 

You can change it into the inspector, on the ParabolicShoot Script component of the shot body. Indicating a longer time, the trajectory will be more high and the hit will take place later, if you indicate a shorter time, you’ll see a more rectified trajectory and more speed on the shot object.

Step 5) Velocity calculation: the following code will implement the equations in Ref.3 above

        V0x = (X  X0) / t;
        V0z = (Z  Z0) / t;
        V0y = (Y  Y0 + (0.5f * Mathf.Abs(Physics.gravity.magnitude) * Mathf.Pow(t,2))) / t;

Step 6) If the shot body has already a current velocity we have to subtract it from the calculated one, because the calculated one is the velocity needed to be applied to the shot object if it were stopped.

        V0x -= _rigidBody.velocity.x;
        V0y -= _rigidBody.velocity.y;
        V0z -= _rigidBody.velocity.z;

Step 7) Applying forces: we ask the engine to apply to the body a force that pushes it only for a moment to get the velocity we previously calculated. The function AddForce requires a Vector and a parameter. The magnitude of the vector is expressed in N -Newton- (1N = 1Kg * 1m / s^2), as 1N represents a force able to push an object to a speed of 1m/s every 1 second. So, if we indicate a vector of magnitude Vector3.right * Vox, we’re asking to apply a force of Vox Newtons in the direction of the x-axis (Vector3.right = x-axis, Vector3.up = y-axis, Vector3.forward = z-axis).
The parameter “ForceMode” tells the AddForce function how to apply the force and if we use ForceMode.VelocityChange, it means we’re asking to produce a force only for a moment (a frame in the program, that means only one Physics loop), having therefore an impulsive force that gives the body shoot a velocity of module Vox that will be maintained over the time because no friction is setup for the body. If we had used instead ForceMode.Acceleration, the body would have been given an acceleration able to add Vox to its velocity every second.

// VelocityChange Add an instant velocity change to the rigidbody, applying an impulsive force, ignoring its mass.
_rigidBody.AddForce (Vector3.right * V0x, ForceMode.VelocityChange);
_rigidBody.AddForce (Vector3.up * V0y, ForceMode.VelocityChange);
_rigidBody.AddForce (Vector3.forward * V0z, ForceMode.VelocityChange);  

ApplyStartVelocity Function

This function adds a starting velocity to the shot body before the shot (if you want it moving). You can enter the start velocity values into the inspector, on the ParabolicShoot Script component of the shot body.

        _rigidBody.AddForce (Vector3.right * _startVelocity.x, ForceMode.VelocityChange);
        _rigidBody.AddForce (Vector3.up * _startVelocity.y, ForceMode.VelocityChange);
        _rigidBody.AddForce (Vector3.forward * _startVelocity.z, ForceMode.VelocityChange);

Reset

This function resets the position of the shot body and calls the ApplyStartVelocity and ApplyThrust functions.

Start & Update Functions

These two functions are called by Unity3D only once at the start of the script (function Start) and every frame (function Update). Every frame means that the engine has a main loop that produces at every cycle an output on the screen (frame) and at every frame the function Update is called.

Therefore Start can be used to manage starting setup, while Update will deploy the logic to be called for every frame and before it’s is rendered on the screen.
I saved in Start () the start position of the body shoot and used InvokeRepeating to recall the Reset function to restart the shot 2.5 seconds after the launch.
Inside the Update function I calculated the elapsed time after the shot and the current speed of the shot body.

 OnCollisionEnter

This is still a function called by the Unity3D engine when the body hits something. In this case, if the collision has taken place after the shot, the program fires the OnHit() event that will recall the delegate function associated to it (in case you’ll want to deploy your own behavior on target hit).

Unity3D Project

I’m including here the Unity3D 5.4.2 project for download.

Parabolic Motion Unity3D Project Download

 

Ciao,
MaX

 


Annex 1 – Full C# Code

using System.Collections;
using UnityEngine;

public class ParabolicShoot : MonoBehaviour {

    [SerializeField]
    GameObject _target;                                        /* The target object to hit */

    [SerializeField]
    public float _time = 3f;                                /* The time of the travel */

    [SerializeField]
    public bool _repeat = true;                                /* If true, the program will keep on shot */

    [SerializeField]
    public Vector3    _startVelocity = new Vector3(0,0,0);    /* The start velocity to be applied to the shot object before the shoot */

    [SerializeField]
    public Vector3 _speed = Vector3.zero;                    /* Shows the speed during the shoot */

    [SerializeField]
    public float _elapsed = 0f;                                /* Time elapsed from the starting of the shoot */

    public delegate void OnHitActionHandler ();                /* Delegate function to be called when target hit */
    public static event OnHitActionHandler OnHit;            

    Rigidbody         _rigidBody;                                /* Rigidbody of the shot object */

    Vector3         _startPosition;                            /* Start position of the shot object */

    Transform         _parent;                                /* Parent object of the shot object (in case you want to to shoot the object from another moving one) */

    float             _timeStartThrust = 0f;                    /* Time the thrust force has been applied */

    bool             _shootCompleted = false;                /* Register if the shoot has taken place */

    void Start () {

        _rigidBody = gameObject.GetComponent<Rigidbody> ();        // Rigidbody caching
        _startPosition = transform.localPosition;                // Save shot start position
        _parent = transform.parent;

        if (_repeat) {
            InvokeRepeating (Reset, 1f, _time + 2.5f);
        } else {
            Invoke (Reset, 1.5f);
        }
        

    }
    

    void Update () {

        if (_shootCompleted) _elapsed = Time.time  _timeStartThrust;
        _speed = _rigidBody.velocity;

    }
        
    public void Reset () {

        transform.parent = _parent;
        _shootCompleted = false;
        _rigidBody.isKinematic = true;                    // Avoid bounces on the ground before the shooting begin
        transform.localPosition = _startPosition;

        ApplyStartVelocity ();
        Invoke (ApplyThrust, 1.5f);

    }

    public void ApplyStartVelocity () {

        _rigidBody.isKinematic = false;

        _rigidBody.AddForce (Vector3.right * _startVelocity.x, ForceMode.VelocityChange);
        _rigidBody.AddForce (Vector3.up * _startVelocity.y, ForceMode.VelocityChange);
        _rigidBody.AddForce (Vector3.forward * _startVelocity.z, ForceMode.VelocityChange);

    }

    public void ApplyThrust () {

        float X;
        float Y;
        float Z;
        float X0;
        float Y0;
        float Z0;
        float V0x;
        float V0y;
        float V0z;
        float t;

        _rigidBody.isKinematic = false;    // Avoid bouncing of the body before the shoot

        Vector3 forceDirection = _target.transform.position  transform.position; 

        X = forceDirection.x;         // Distance to travel along X : Space traveled @ time t
        Y = forceDirection.y;         // Distance to travel along Y : Space traveled @ time t
        Z = forceDirection.z;         // Distance to travel along Z : Space traveled @ time t

        // As we calculate in this very moment the distance between the shot object and the target, the intial space coordinates X0, Y0, Z0 will be always 0.
        X0 = 0;        
        Y0 = 0;     
        Z0 = 0;     

        transform.parent = null;        // Detach the shot object from parent in order to get its own velocity

        t = _time; 

        // Calculation of the required velocity along each axis to hit the target from the current starting position as if the shot object were stopped 
        V0x = (X  X0) / t;
        V0z = (Z  Z0) / t;
        V0y = (Y  Y0 + (0.5f * Mathf.Abs(Physics.gravity.magnitude) * Mathf.Pow(t,2))) / t;

        /* Subtraction of the current velocity of the shot object */
        V0x -= _rigidBody.velocity.x;
        V0y -= _rigidBody.velocity.y;
        V0z -= _rigidBody.velocity.z;

        _rigidBody.AddForce (Vector3.right * V0x, ForceMode.VelocityChange);    // VelocityChange Add an instant velocity change to the rigidbody, applying an impulsive force, ignoring its mass.
        _rigidBody.AddForce (Vector3.up * V0y, ForceMode.VelocityChange);
        _rigidBody.AddForce (Vector3.forward * V0z, ForceMode.VelocityChange);  

        _timeStartThrust = Time.time;
        _shootCompleted = true;

    }

    void OnCollisionEnter (Collision c) {

        // Logic for Target Hit

        if (!_shootCompleted)
            return;

        if (c.gameObject.name == _target.name) {

            if (OnHit != null)
                OnHit ();

        }

    }

}

 [Meta Ref.] Unity3D Parabolic Motion Physics, Physics, Parabolic Motion, Physics, Unity3D

Un pensiero su “Parabolic Motion Unity3D Physics

I commenti sono chiusi.