Select Page

Unity Ball Throw Like Pokemon Go with Curve Ball

Unity Ball Throw Like Pokemon Go with Curve Ball

Hello people of the Internet,

This is a perfect Unity tutorial for building a Pokemon Go style Game, Ball Throwing Games, Basket Ball Game, Paper Toss Game, or even a Bowling Game.

With the scripts I have provider

  • You will leave how to create a mouse and touch compatible game utilizing OnMouseDown events
  • Flick a throw an object versus Drag – only flick will actually toss the object NOT fast drag
  • Curving a Object when you throw it
  • Spinning object when Curve
  • Adding Particle Effects when Curving (like Pokemon Go)

Check out the video for more details.

Okey!, Let cut the small talk, we know exactly why you are here SOURCE CODE. Calm down you copy and paste programmer, let me at least give you some of the settings I use for better success. Also check out the video which has a full detailed guide.

Best Settings for this tutorial

 

Lazy?

After reviewing the settings I use, lets begin my create 3 object for this guide.

  1. Sphere  = Lets call this _ball
  2. Another Sphere = Lets call this
  3. Cube = Lets call this _throwController

Now adjust the _ball and _ballContainer to the size that you desire your throw object to me. (note, if you already have an object you want to throw then replace _ball with that object)

Now that _ball and _ballContainer and the same size and the size you want them to be, place them perfectly in front of your camera at the lower middle of your screen where you want it. Now make _ball a child of _ballContainer. Remove the rigidbody and collider from _ball. Remove the mesh render from _ballContainer and make sure it has a collider and rigidbody enabled on it. Turn off the use gravity on _ballContainer

Almost done with the setup.

Now lets, add the ball.cs script below on the _ballContainer and make it a prefab be dragging it and dropping it into a folder. Before you delete it from the scene, move the _throwController to match the exact location of your _ballContainer, then scale the _throwController to be about the same size as the ball. (slightly bigger is better)

Final Step – Now that you have a prefab of _ballContainer, you can delete the original from your scene. Lets finish up by adding the ThrowBallController.cs script to the _throwController.  Drop the prefab in the script variable called Ball, the rest is optional. Finally, Disable the mesh renderer from _throwController so we can see right through it.

Note – the collider should remain enable only of the _throwController.

Note – _ballContainer should be the only object with a rigidbody and use gravity unchecked.

Note –  _ballContainer should have a collider, but it should remain disabled. the script will enable it.

 

Here is the ThrowBallController.cs script:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ThrowBallController : MonoBehaviour
{
    //instance of singleton
    private static ThrowBallController throwPaperInstance; 
    //ball properties
    [Header("Ball Properties")]
    List<Vector3> ballPos = new List<Vector3> ();
    List<float> ballTime = new List<float> ();
    private GameObject currentBall;
    public bool isCurveReady = false;
    private float factor = 230.0f;
    private float startTime;
    public Vector3 startPos;
    public GameObject ball;
    private Transform ballChildObj;
    public Vector3 minThrow;
    public Vector3 maxThrow;
    //Bool Logics
    private bool isCalculatingDir = false;
    private bool isGameStart = true;
    private bool isStartRoate = false;
    private bool ObjectMouseDown = false;
    //Vectors and direction variables
    private Vector3 lastPos;
    private Vector3 lastBallPosition = Vector3.zero;
    private Vector3 v3Offset;
    float lastAngel = 0f;
    float rotationSpeed = 2.5f;
    float totalX = 0f, totalY = 0f;
    public int angleDirection = 0;
    int DirectionL= 0, DirectionR = 0;
    //Game Objects
    private Plane plane;
    public GameObject linkedObject; // use for a 2d aim object
    public Transform target;
    public ParticleSystem curveParticle;

    //set instance to this object only happends once per game startup 
    void Awake () 
    {
        throwPaperInstance = this;
    }


    public static ThrowBallController Instance {
        get {
            return throwPaperInstance;
        }
    }


    //Time to spawn the ball calling GetBallNow 
    void Start ()
    {
        StartCoroutine (GetBallNow());
    }

    //spawn new ball
    IEnumerator GetBallNow()
    {
        Debug.Log ("Spawming new Ball..");
        //wait for few seconds then spawn ball when game starts
        yield return new WaitForSeconds (3f);
        if (currentBall != null)
        {
            //destroy old ball
            Destroy (currentBall);
        }
        //spawn new ball
        currentBall = Instantiate (ball, ball.transform.position, Quaternion.identity) as GameObject;
        //disable collider just in case
        currentBall.GetComponent<Collider> ().enabled = false;
    }


    void Update ()
    {
        //if current ball exist, lets follow it with this collider
        if (currentBall) {
            transform.position = currentBall.transform.position;
        }
        //if current ball exist and we are calulating direction
        if (currentBall && isCalculatingDir) {
            transform.position = currentBall.transform.position;
            float angle = 0f;
            if (ballChildObj != null) {
                Vector3 mousePos = Input.mousePosition;
                Vector3 playerPos= Camera.main.WorldToScreenPoint (ballChildObj.localPosition);
                mousePos.x = mousePos.x - playerPos.x;
                mousePos.y = mousePos.y - playerPos.y;
                angle = Mathf.Atan2 (mousePos.y, mousePos.x) * Mathf.Rad2Deg;
            }
            if (Vector3.Distance (currentBall.transform.position, lastBallPosition) > 0f) {

                if (!isStartRoate) {
                    Invoke ("ResetBall", 0.01f);
                } else {
                    Vector3 dir = (currentBall.transform.position - lastBallPosition);
                    if (dir.x > dir.y) {
                        totalX += 0.12f;
                    } else {
                        if (dir.x != dir.y) {
                            totalY += 0.12f;
                        }
                    }
                    if (totalX >= 2 && totalY >= 2) {
                        isCurveReady = true;

                        //if you want to add a paricle effects when the ball is spining and ready for a curve throw
                        if (curveParticle != null) 
                        {
                            curveParticle.gameObject.SetActive (true);
                            if (curveParticle.isStopped) {
                                curveParticle.Play ();
                            }
                            curveParticle.transform.position = currentBall.transform.position;
                        }

                    }
                }

                isStartRoate = true;
                if (angle > 0) {
                    if (lastAngel >= angle) {
                        DirectionR++;
                    } else {
                        DirectionL++;
                    }
                } else {
                    if (lastAngel >= angle) {
                        DirectionR++;
                    } else {
                        DirectionL++;
                    }
                }

                if (DirectionL< DirectionR) {
                    angleDirection = 1;
                    if (ballChildObj != null)
                        ballChildObj.transform.Rotate (new Vector3 (0, 0, 40f));
                } else {
                    angleDirection = -1;
                    if (ballChildObj != null)
                        ballChildObj.transform.Rotate (new Vector3 (0, 0, -40f));
                }

            } else {

                if (isStartRoate == true) {
                    StartCoroutine (StopRotation (angleDirection, 0.5f));
                }
            }

            lastAngel = angle;
            lastBallPosition = currentBall.transform.position;
        }
    }

    private void ResetBall ()
    {
        if (!isStartRoate) {
            totalX = 0f;
            totalY = 0f;
            DirectionL= 0;
            DirectionR = 0;
            isCurveReady = false;
            startPos = currentBall.transform.position;
            ballPos.Clear ();
            
            ballTime.Clear ();
            ballTime.Add (Time.time);
            
            ballPos.Add (currentBall.transform.position);

            //if you have a particle attached then lets reset everything and stop it / disble it
            if (curveParticle != null) 
            {
                curveParticle.Stop ();
                curveParticle.gameObject.SetActive (false);
            }

            ThrowBallController.Instance.isCurveReady = false;
        }
    }

    public bool IsGameStart {
        get {
            return isGameStart;
        }set {
            isGameStart = value;
        }
    }


    IEnumerator StopRotation (int direction, float t)
    {
        isStartRoate = false;
        float rate = 1.0f / t;
        float i = 0f;
        while (i<1.0f) {
            i += rate * Time.deltaTime;
            if (!isStartRoate) {
                if (direction == 1) {
                    if (ballChildObj != null)
                        ballChildObj.transform.Rotate (new Vector3 (0, 0, 40f) * (Mathf.Lerp (rotationSpeed * totalX, 0, i)) * Time.deltaTime);
                } else {
                    if (ballChildObj != null)
                        ballChildObj.transform.Rotate (new Vector3 (0, 0, -40f) * (Mathf.Lerp (rotationSpeed * totalX, 0, i)) * Time.deltaTime);
                }
            }

            yield return 0;
        }

        if (!isStartRoate) {
            
            totalX = 0f;
            totalY = 0f;
            DirectionL= 0;
            DirectionR = 0;
            isCurveReady = false;
            rotationSpeed = 100f;

            //if you have a particle attached then lets reset everything and stop it / disble it
            if (curveParticle != null) 
            {
                curveParticle.Stop ();
                curveParticle.gameObject.SetActive (false);
            }
        }
    }

    //when player click on ball / touch if mobile
    void OnMouseDown ()
    {
        if (currentBall == null)
            return;
        plane.SetNormalAndPosition (Camera.main.transform.forward, currentBall.transform.position);
        Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
        float dist;
        plane.Raycast (ray, out dist);
        v3Offset = currentBall.transform.position - ray.GetPoint (dist);     
        ObjectMouseDown = true;

        if (!currentBall || !isGameStart)
            return;
        ballPos.Clear ();

        ballTime.Clear ();
        ballTime.Add (Time.time);

        ballPos.Add (currentBall.transform.position);
        
        totalX = 0f;
        totalY = 0f;
        
        DirectionL= 0;
        DirectionR = 0;
        
        isCurveReady = false;
        
        isCalculatingDir = true;
        currentBall.SendMessage ("isThrow", true, SendMessageOptions.RequireReceiver);
        
        ballChildObj = currentBall.transform.Find ("Ball");
        StartCoroutine (GettingDirection ());
    
    }

    IEnumerator GettingDirection ()
    {
        while (isCalculatingDir) {
            startTime = Time.time;
            lastPos = startPos;
            startPos = Camera.main.WorldToScreenPoint (currentBall.transform.position);
            startPos.z = currentBall.transform.position.z - Camera.main.transform.position.z;
            startPos = Camera.main.ScreenToWorldPoint (startPos);

            yield return new WaitForSeconds (0.01f);
        }
    }

    //when player is dragging the ball with mouse / touch if mobile
    void OnMouseDrag ()
    {
        if (ObjectMouseDown == true) {

            if (!currentBall)
                return;

            Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
            float dist;
            plane.Raycast (ray, out dist);
            Vector3 v3Pos = ray.GetPoint (dist);
            v3Pos.z = currentBall.transform.position.z;
            v3Offset.z = 0;
            currentBall.transform.position = v3Pos + v3Offset;


            if (ballPos.Count > 0) {
            
                
                if (ballPos.Count <= 4) {
                    if (Vector3.Distance (currentBall.transform.position, ballPos [ballPos.Count - 1]) >= 0.01f) {
                        ballTime.Add (Time.time);
                        ballPos.Add (currentBall.transform.position);
                        
                    }
                } else {
                    if (Vector3.Distance (currentBall.transform.position, ballPos [ballPos.Count - 1]) >= 0.01f) {
                        ballTime.RemoveAt (0);
                        ballPos.RemoveAt (0);
                        ballTime.Add (Time.time);
                        ballPos.Add (currentBall.transform.position);
                    }
                }
                
            } else {
                ballPos.Add (currentBall.transform.position);
            }

            if (linkedObject != null) { 
                linkedObject.transform.position = v3Pos + v3Offset;
            }
        }
    }
    
    //when player release the mouse to flick or / touch if mobile
    void OnMouseUp ()
    {
        //if you have a particle attached then lets reset everything and stop it / disble it
        if (curveParticle != null) 
        {
            curveParticle.Stop ();
            curveParticle.gameObject.SetActive (false);
        }

        isCalculatingDir = false;

        if (!currentBall || !isGameStart)
            return;

        var endPos = Input.mousePosition;
        endPos.z = currentBall.transform.position.z - Camera.main.transform.position.z;
        endPos = Camera.main.ScreenToWorldPoint (endPos);

        int ballPositionIndex = ballPos.Count - 2;

        if (ballPositionIndex < 0)
            ballPositionIndex = 0;

        Vector3 force = currentBall.transform.position - ballPos [ballPositionIndex];
        
        if (Vector3.Distance (lastPos, startPos) <= 0.0f) {
            currentBall.SendMessage ("ResetBall", SendMessageOptions.RequireReceiver);
            return;
        }
        
        //if downside
        if (currentBall.transform.position.y <= ballPos [ballPositionIndex].y) {
            currentBall.SendMessage ("ResetBall", SendMessageOptions.RequireReceiver);

            return;
        }

        //if not swipe
        if (force.magnitude < 0.02f) {
            currentBall.SendMessage ("ResetBall", SendMessageOptions.RequireReceiver);
            return;
        }

        force.z = force.magnitude;
        force /= (Time.time - ballTime [ballPositionIndex]);
        force.y /= 2f;
        force.x /= 2f;
    
        force.x = Mathf.Clamp (force.x, minThrow.x, maxThrow.x);
        force.y = Mathf.Clamp (force.y, minThrow.y, maxThrow.y);
        force.z = Mathf.Clamp (force.z, minThrow.z, maxThrow.z);

        //send message ball was thrown
        currentBall.SendMessage ("isThrow", true, SendMessageOptions.RequireReceiver);
        

        if (isCurveReady) {
            force.z -= 0.1f;
            if (angleDirection == 1) {
                if (force.z < 2.3f)
                    force.z = 2.3f;
                currentBall.SendMessage ("SetCurve", -0.2);
            } else {
                if (force.z < 2.3f)
                    force.z = 2.3f;

                currentBall.SendMessage ("SetCurve", 0.2);
            }
        } 

        //get rigidbody
        Rigidbody ballRigidbody = currentBall.GetComponent<Rigidbody> ();
        //enable collider
        currentBall.GetComponent<Collider> ().enabled = true;
        ballRigidbody.useGravity = true;

        ballRigidbody.AddForce (force * factor);

        StartCoroutine (GetBallNow());
        
        //currentBall = null;
    }

    public bool IsGettingDirection {
        get {
            return isCalculatingDir;
        }set {
            isCalculatingDir = value;
        }
    }
        
        
}

Here is the Ball.cs script:

using UnityEngine;
using System.Collections;

public class Ball : MonoBehaviour
{
    //set position and rotation
    private Vector3 initialPosition;
    private Quaternion initialRotation;
    private bool isWind = false;
    private bool isNormalThrow = false;
    private bool isRotateLeft = true;

    //setup floats
    private float curveWind = 1f;
    private float curveForce = 0f;
    public float objectScale = 1.0f;
    private float throwPos = 0f;

    private Transform childBall;
    private bool isCurve = false;
    private Rigidbody rigid;
    private bool ballThrowed= false;
    public Vector3 initialScale;
    private IEnumerator jumpCoroutine;
    private bool isRotate = false;
    public Camera cam;



    void OnEnable ()
    {
        //set default position when object enable
        initialPosition = transform.position;
    }

    void Start ()
    {
        rigid = GetComponent<Rigidbody> ();
        //initalize gradually jump coroutine
        jumpCoroutine = JumpAround (0.3f);
        childBall = transform.Find ("Ball");
        StartCoroutine (jumpCoroutine);

        //set initial scale, 
        initialScale = transform.localScale; 
        
        // if no cam attached, use the default camera
        if (cam == null)
            cam = Camera.main; 
    }
    
    void FixedUpdate ()
    {
        //apply wind after certain distance
        float dist = 0f;
        if (ThrowBallController.Instance.target) {
            dist = (ThrowBallController.Instance.target.position.z - transform.position.z);
        }

        //if throw is cureve then apply wind
        if (isCurve && dist <= (throwPos - (throwPos / 9.5f))) {
            if (!isWind)
                StartCoroutine (wind (0.5f));
            transform.Translate (Vector3.right * -curveForce * curveWind * Time.deltaTime);
        }
    }



    private void isThrow (bool flag)
    {
        if (jumpCoroutine != null) {
            StopCoroutine (jumpCoroutine);
            jumpCoroutine = null;
        }
        ballThrowed= flag;
        if (ThrowBallController.Instance.IsGettingDirection)
            isRotate = false;
        else
            isRotate = true;
        throwPos = (ThrowBallController.Instance.target.position.z - transform.position.z);
    }


    //Resets the ball.
    public void ResetBall ()
    {
        ballThrowed= false;
        StopAllCoroutines ();
        //ball move to initial position
        StartCoroutine (GobackToDefaultPosition (0.3f));
    }
        
    //Sets the curve.
    private void SetCurve (float cFactore)
    {
        curveForce = cFactore;
        isCurve = true;
        Debug.Log("This is a Curve Throw");
    }
        

    //the wind logic
    IEnumerator wind (float t)
    {
        isWind = true;
        float rate = 1.0f / t;
        float i = 0f;
        while (i<1.0f) {
            i += rate * Time.deltaTime;
            curveWind = Mathf.Lerp (1, 26, i);
            yield return 0;
        }
    }


    /// Winds the reverse.
    IEnumerator windReverse (float t)
    {
        isWind = true;
        float rate = 1.0f / t;
        float i = 0f;
        while (i<1.0f) {
            i += rate * Time.deltaTime;
            curveWind = Mathf.Lerp (26, 0, i);
            yield return 0;
        }
    }

    //ball jumping animation
    IEnumerator JumpAround (float tm)
    {

        while (!ballThrowed) {

            yield return new WaitForSeconds (0.4f);
        
            transform.position = initialPosition;

            if (ThrowBallController.Instance.IsGameStart) {


                isRotateLeft = !isRotateLeft;
                isRotate = true;
                float i = 0f;
                float rate = 1.0f / tm;
                Vector3 from = initialPosition;
                Vector3 to = new Vector3 (from.x, from.y + 0.05f, from.z);
    
                while (i<1.0f) {
                    i += rate * Time.deltaTime;
                    transform.position = Vector3.Lerp (from, to, i);
                    yield return 0f;
                }
                i = 0f;
                rate = 1.0f / (tm / 0.7f);

                Vector3 bump = from;
                bump.y -= 0.05f;

                while (i<1.0f) {
                    i += rate * Time.deltaTime;
                    transform.position = Vector3.Lerp (to, bump, i);
                    yield return 0f;
                }

                isRotate = false;

                i = 0f;
                rate = 1.0f / (tm / 1.1f);

                while (i<1.0f) {
                    i += rate * Time.deltaTime;
                    transform.position = Vector3.Lerp (bump, from, i);
                    yield return 0f;
                }

            }
        }
    }


    //The back to initial position
    IEnumerator GobackToDefaultPosition (float tm)
    {
        float i = 0f;
        float rate = 1.0f / tm;
        Vector3 from = transform.position;
        Vector3 to = initialPosition;
        while (i<1.0f) {
            i += rate * Time.deltaTime;
            transform.position = Vector3.Lerp (from, to, i);
            yield return 0f;
        }
        transform.position = initialPosition;
        childBall.localRotation = Quaternion.identity;
        isRotate = false;
        jumpCoroutine = JumpAround (0.3f);
        
        StartCoroutine (jumpCoroutine);
    }

}

You can use what you have learned here to build a Basket Ball Throwing Game with unity, a Paper Tossing Game with unity, A Bowling Game with unity, a Pokemon Go style ball throwing Game, or Any Ball Throwing Game with Curve using Unity 3D. Unity 3D is such a powerful tool. I recommend that you check out these books on Amazon and invest in yourself.

About The Author

Leaton Mitchell

Being smart doesn't count if you do not share your knowledge.

6 Comments

  1. Nice tutorial dude

  2. Question, Which script is which no identifer and tried to do as this said but not seeing the options like i should for the script.

    • I have updated the post.

      • Alright, i’ve been giving this a go, So far i get one error and the ball will only launch straight out. Error is as follows… Any tips on this?

        The referenced script on this Behaviour (Game Object ‘_ballContainer’) is missing!
        UnityEngine.Object:Instantiate(GameObject, Vector3, Quaternion)
        c__Iterator0:MoveNext() (at Assets/Scripts/ThrowBallController.cs:76)
        UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)

        • Hey Ryan, make sure the ballcontainer has the stript on it and also has a collider attached to it.
          Follow along with this video
          https://youtu.be/VNL7rlhte8I

  3. Hey Ryan, make sure the ballcontainer has the stript on it and also has a collider attached to it.
    Follow along with this video
    https://youtu.be/VNL7rlhte8I