Creating trajectory prediction using LineRenderer

Hell all. I’ve been working on a game now for a bit and I’m stuck on one part. How do you create a trajectory prediction using a Line Renderer. Like you see in the game brawl you can aim and displays a grey line showing it’s trajectory, is this possible with a line renderer. Thanks in advance!

Y-Hello there,

if you poke around google much you can find plenty of resources that can guide you to build that kind of system.

Here’s one that make uses of the LineRenderer:

Easy Trajectory Line in Unity 2D

hope this helps :slight_smile:

Thanks for your reply, Appreciate your effort, but thats not what I’m looking for unfortunately. First of all I’m making a 3d game. Look at this. I’m tryna do something like this (
https://www.youtube.com/watch?v=A46nHOgdW-E
) you’ll see the player aiming and the line aim. Thanks for your help anyways!

Ok, as an advice for the next time, try to be a bit more specific with the request, like if you are looking for 2d or 3d.

You are not gonna be able to find something out of the box for your case scenario, but again on Google there are plenty of tutorials to do trajectory lines also in 3d.

Look for this keywords on YouTube “unity 3d trajectory line”. Of course you will have to tweak the scripts for your needs, but is a good starting point.

Once you have something set up, if you still have trouble, you can come back here, post your scripts and get more help :slight_smile:

First of all, I should’ve told you earlier that I do have a script which does do the job. But it’s soo buggy, like the line is actually crooked it’s not straight
7122104--850436--upload_2021-5-9_9-43-24.png
As you can see. Also there is a trail distance, so how long the line is, and depending on how long the line. The bullet will travel that far, but what’s happening the bullet travels and extra amount of distance for some reason. Here is the code (I tried making changes but nothing happened, so that’s why I was trying to find another another way to do this)

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

public class PlayerAttacking_Shoot : MonoBehaviour
{
    [SerializeField] LineRenderer LR;
    [SerializeField] Joystick AttackJoystick;
    [SerializeField] Transform Player;
    [SerializeField] Transform AttackLookAtPoint;
    [SerializeField] public float TrailDistance = 1;
    [SerializeField] Transform Bullet;
    [SerializeField] int NoOfBullets;
    [SerializeField] Transform PlayerSpine;
    [SerializeField] Animator PlayerAnim;
    public Slider slider;
    public Slider slider2;
    public Slider slider3;
    const float MovementPerSecond = 2.0f;
    RaycastHit hit;
    bool Shoot;
    void Start()
    {
        slider.value = slider.maxValue;
        slider2.value = slider2.maxValue;
        slider3.value = slider3.maxValue;
    }

    void Update()
    {
        if (Mathf.Abs(AttackJoystick.Horizontal) > 0.3f || Mathf.Abs(AttackJoystick.Vertical) > 0.3f)
        {
            if (LR.gameObject.activeInHierarchy == false)
            {
                LR.gameObject.SetActive(true);
            }

            transform.position = new Vector3(Player.position.x, 0, Player.position.z);

            AttackLookAtPoint.position = new Vector3(AttackJoystick.Horizontal + Player.position.x, 0, AttackJoystick.Vertical + Player.position.z);

            transform.LookAt(new Vector3(AttackLookAtPoint.position.x, 0, AttackLookAtPoint.position.z));

            transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);

            LR.SetPosition(0, transform.position);

            if (Physics.Raycast(transform.position, transform.forward, out hit, TrailDistance))
            {
                LR.SetPosition(1, hit.point);
            }
            else
            {
                LR.SetPosition(1, transform.position + transform.forward * TrailDistance);
                LR.SetPosition(1, new Vector3(LR.GetPosition(1).x, 0, LR.GetPosition(1).z));
            }

            if (Shoot == false)
            {
                Shoot = true;
            }
        }
        else if (Shoot && Input.GetMouseButtonUp(0))
        {
            Debug.Log("Shoot");

            Shoot = false;
        }
        else if (Shoot && Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);

            if (touch.phase == TouchPhase.Ended)
            {
                Debug.Log("Shoot");
                Shoot = false;
            }
        }
        else if (Mathf.Abs(AttackJoystick.Horizontal) < 0.3f || Mathf.Abs(AttackJoystick.Vertical) < 0.3f && LR.gameObject.activeInHierarchy == true)
        {
            LR.gameObject.SetActive(false);
            Shoot = false;
        }
    }

    public void PlayerShooting()
    {
        if (Shoot)
        {
            StartCoroutine(ShootBullet());

            Shoot = false;
        }
    }

    IEnumerator ShootBullet()
    {
        if (PlayerAnim.GetBool("Shooting") == false)
        {
            PlayerAnim.SetBool("Shooting", true);
        }

        for (int i = 0; i < NoOfBullets - 1; i++)
        {
            yield return new WaitForSeconds(0.2f);

            Instantiate(Bullet, new Vector3(transform.position.x, 0.5f, transform.position.z), transform.rotation);
            slider.value = slider.minValue;
        }

        if (PlayerAnim.GetBool("Shooting") == true)
        {
            PlayerAnim.SetBool("Shooting", false);
        }
    }

    private void Reload()
    {
        if (slider.value == slider.minValue)
        {
            Mathf.MoveTowards(slider.value, slider.maxValue, MovementPerSecond * Time.deltaTime);
        }
        if (Shoot == true)
        {
            if (slider.value == slider.minValue && slider2.value == slider.maxValue)
            {
                slider2.value = slider.minValue;
            }
        }
    }
}

This is the aim and shoot script!

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

public class Bullet : MonoBehaviour
{
    [SerializeField] public PlayerAttacking_Shoot PA;
    [SerializeField] float speed;
    Vector3 BulletEndDist;
    float Damage_ = 300;
   
    private void Awake()
    {
        transform.position += transform.forward * 0.5f;
    }

    void Start()
    {
        PA = GameObject.Find("Attack Trail").GetComponent<PlayerAttacking_Shoot>();
        BulletEndDist = transform.position + transform.forward * PA.TrailDistance;
    }

    void Update()
    {
        if(transform.position == BulletEndDist)
        {
            Destroy(this.gameObject);
        }

        transform.Translate(Vector3.forward * speed);
    }

    private void OnCollisionEnter(Collision collision)
    {
        if(collision.transform.tag == "Enemy")
        {
            collision.transform.GetComponent<Health>().Damage(Damage_);
           
            Destroy(this.gameObject);
        }
        else
        {
            Destroy(this.gameObject);
        }
    }
}

Bullet script.

lol, next time start with this right away, it will be easier for ppl to understand what you actually need.

So, I see a couple of problems in your scripts.

For the bullet you are adding on awake 0.5f on the forward, it will make it travel that extra 0.5f in respect of the trailDistance.

private void Awake()
    {
        transform.position += transform.forward * 0.5f; //adding that extra 0.5f is making the bullet travel farther of 0.5f
    }

Secondly, you are comparing the position with (Vector3 == Vector3)

if(transform.position == BulletEndDist)

It’s not the right method since float precision start to loosen up when the numbers are really small, also if fps is not stable it could potentially skip a position and the values will never be equals.

Regarding the line renderer, make sure the width is equally spread if you want a straight line

I simplified your script to debug it better, here’s a version that is working.

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

public class PlayerAttacking_Shoot : MonoBehaviour
{
    [SerializeField] LineRenderer LR;
    [SerializeField] Transform Player;
    [SerializeField] Transform AttackLookAtPoint;
    [SerializeField] public float TrailDistance = 1;
    [SerializeField] Transform Bullet;
    [SerializeField] int NoOfBullets;
    const float MovementPerSecond = 2.0f;
    RaycastHit hit;
    bool Shoot;

    void Update()
    {
        if (Mathf.Abs(Input.GetAxis("Horizontal")) > 0.3f || Mathf.Abs(Input.GetAxis("Vertical")) > 0.3f)
        {

            transform.position = new Vector3(Player.position.x, 0, Player.position.z);

            AttackLookAtPoint.position = new Vector3(Input.GetAxis("Horizontal") + Player.position.x, 0, Input.GetAxis("Vertical") + Player.position.z);

            transform.LookAt(new Vector3(AttackLookAtPoint.position.x, 0, AttackLookAtPoint.position.z));

            transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);

            LR.SetPosition(0, transform.position);

            if (Physics.Raycast(transform.position, transform.forward, out hit, TrailDistance))
            {
                LR.SetPosition(1, hit.point);
            }
            else
            {
                LR.SetPosition(1, transform.position + transform.forward * TrailDistance);
                LR.SetPosition(1, new Vector3(LR.GetPosition(1).x, 0, LR.GetPosition(1).z));
            }

            if (Shoot == false)
            {
                Shoot = true;
            }
        }
        else if (Input.GetMouseButtonDown(0))
        {
            Debug.Log("Shoot");
            PlayerShooting();
        }
    }

    public void PlayerShooting()
    {
        StartCoroutine(ShootBullet());
    }

    IEnumerator ShootBullet()
    {
        for (int i = 0; i < NoOfBullets - 1; i++)
        {
            yield return new WaitForSeconds(0.2f);
            Debug.Log("shooting bullet" + LR.GetPosition(0).z);
            Instantiate(Bullet, new Vector3(transform.position.x, 0.5f, transform.position.z), transform.rotation);
        }
    }
}

and the Bullet

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Bullet : MonoBehaviour
{
    [SerializeField] public PlayerAttacking_Shoot PA;
    [SerializeField] float speed;
    Vector3 BulletEndDist;
    float Damage_ = 300;
  
    private void Awake()
    {
        // transform.position += transform.forward * 0.5f;
        Destroy(this,12f);
    }
    void Start()
    {
        PA = GameObject.Find("Attack Trail").GetComponent<PlayerAttacking_Shoot>();
        BulletEndDist = transform.position + transform.forward * PA.TrailDistance;
        Debug.Log("init pos" + transform.position + " " + "EndDistance: " + BulletEndDist);
    }
    void Update()
    {
        Debug.Log((transform.position-BulletEndDist).magnitude);
        if( (transform.position-BulletEndDist).magnitude <= 0.1f)
        {
           
            Debug.Log("reached" + transform.position);
            Destroy(this.gameObject);
            return;
        }
        transform.Translate(Vector3.forward * speed);
    }
    private void OnCollisionEnter(Collision collision)
    {
        if(collision.transform.tag == "Enemy")
        {
            // collision.transform.GetComponent<Health>().Damage(Damage_);
          
            Destroy(this.gameObject);
        }
        else
        {
            Destroy(this.gameObject);
        }
    }
}

If you want the bullet to travel in an arc motion you will need to work more on your script and research Projectile Motion. Hope this helps a bit

Hey M4dR0b, I can’t tell you how grateful I am, I wouldn’t be able to thank you enough. But unfortunately the bullet script you provided me doesn’t actually shoot a bullet. Here is what currently happens. I changed a bit of your Attack script to suit my needs. One question is my line doesn’t move smoothly as yours does, check out this video

file:///C:/Users/Aryan/Videos/Captures/Brawl%20Stas%20Dynamics%20-%20Trial%20Scene%20-%20PC,%20Mac%20&%20Linux%20Standalone%20-%20Unity%202019.4.19f1%20Personal_%20_DX11_%202021-05-10%2019-48-58.mp4

You can see, when I just move the line renderer around with the joystick, It doesn’t smoothly move around the player, and my bullet doesn’t shoot for some reason, but it does with my old script!

I cant see the video like this, upload it on Vimeo or YouTube and embed it in the post.

The script I posted is a simplified version that works with keyboard and mouse.

Try this:

Yeah, I saw, I just changed it to fit using joysticks!

Sorry to disturb but I also have problems with my projectile motion, I have already worked on arc motion, here is the script

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

public class PlayerAttacking_Throw : MonoBehaviour
{
    [SerializeField] LineRenderer LR;
    [SerializeField] Joystick AttackJoystick;
    [SerializeField] Transform Player;
    [SerializeField] Transform AttackLookAtPoint;
    [SerializeField] public float TrailDistance = 1;
    [SerializeField] Transform Bullet;
    [SerializeField] int NoOfBullets;
    [SerializeField] Transform PlayerSpine;
    [SerializeField] float Power;
    [SerializeField] float LinePower_Y;
    public Vector3[] BulletPoints;
    RaycastHit hit;
    bool Shoot;
    void Start()
    {
        LR.positionCount = 10;

        BulletPoints = new Vector3[9];
    }

    void Update()
    {
        if (Mathf.Abs(AttackJoystick.Horizontal) > 0.01f || Mathf.Abs(AttackJoystick.Vertical) > 0.01f)
        {
            if (LR.gameObject.activeInHierarchy == false)
            {
                LR.gameObject.SetActive(true);
            }

            transform.position = new Vector3(Player.position.x, 0, Player.position.z);

            AttackLookAtPoint.position = new Vector3(AttackJoystick.Horizontal + Player.position.x + 1, 0, AttackJoystick.Vertical + Player.position.z);

            transform.LookAt(new Vector3(AttackLookAtPoint.position.x, 0, AttackLookAtPoint.position.z));

            transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);

            LR.SetPosition(0, new Vector3(transform.position.x, transform.position.y + 1f, transform.position.z));

            for (int i = 1; i < 10; i++)
            {
                LR.SetPosition(i, new Vector3(LR.GetPosition(i - 1).x + AttackJoystick.Horizontal / 2, Mathf.Cos(LinePower_Y * (i * 0.1f)) * i, LR.GetPosition(i - 1).z + AttackJoystick.Vertical / 2));
                BulletPoints[i - 1] = LR.GetPosition(i);
            }

            if (Physics.Raycast(transform.position, transform.forward, out hit, TrailDistance))
            {
                LR.SetPosition(1, hit.point);
            }
            else
            {
                LR.SetPosition(1, transform.position + transform.forward * TrailDistance);
            }

            if (Shoot == false)
            {
                Shoot = true;
            }
        }
        else if (Shoot && Input.GetMouseButtonUp(0))
        {
            Debug.Log("Shoot");

            StartCoroutine(ShootBullet());

            Shoot = false;
        }
        else if (Shoot && Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);

            if (touch.phase == TouchPhase.Ended)
            {
                Debug.Log("Shoot");
                Shoot = false;
            }
        }
        else if (Mathf.Abs(AttackJoystick.Horizontal) < 0.3f || Mathf.Abs(AttackJoystick.Vertical) < 0.3f && LR.gameObject.activeInHierarchy == true)
        {
            LR.gameObject.SetActive(false);
            Shoot = false;
        }
    }

    public void PlayerShooting()
    {
        if (Shoot)
        {
            StartCoroutine(ShootBullet());

            Shoot = false;
        }
    }

    IEnumerator ShootBullet()
    {

        PlayerSpine.LookAt(AttackLookAtPoint);

        PlayerSpine.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);

        Instantiate(Bullet, new Vector3(transform.position.x, 0.5f, transform.position.z), transform.rotation);

        for (int i = 0; i < NoOfBullets - 1; i++)
        {
            yield return new WaitForSeconds(0.2f);

            Instantiate(Bullet, new Vector3(transform.position.x, 0.5f, transform.position.z), transform.rotation);
        }
    }
}
This is for the bullet
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BulletThrow : MonoBehaviour
{
    PlayerAttacking_Throw PA;
    Vector3[] Points;
    Rigidbody rb;
    [SerializeField] float speed;
    bool Throw;
    public int CurrentIndex;

    void Start()
    {
        PA = GameObject.Find("Attack Trail").GetComponent<PlayerAttacking_Throw>();

        Points = new Vector3[9];

        PA.BulletPoints.CopyTo(Points, 0);

        rb = transform.GetComponent<Rigidbody>();
    }

    void Update()
    {
        transform.Translate(Vector3.forward * speed);

        if (Throw)
        {
            transform.LookAt(Points[CurrentIndex]);
        }
        else if ((Points[CurrentIndex] - transform.position).sqrMagnitude < 0.2f)
        {
            if (CurrentIndex == 8)
            {
                rb.useGravity = true;
            }
            else
            {
                CurrentIndex++;

                transform.LookAt(Points[CurrentIndex]);
            }
        }
    }
}

But my bullet doesn’t travel on the set path, plus the line renderer is kinda bugged. See for your self

https://www.youtube.com/watch?v=ZgPnYmMnoQI

hey @Johan_Liebert123 ,

I think you are approaching this from the wrong angle. You need to use kinematic equations to properly shoot a projectile.

Take this example (it was made some years ago, so there are some stuff not well optimized, like all those Find()):

https://vimeo.com/548046825

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

public class CannonBallProjectile : MonoBehaviour
{
    public Slider angleSlider;
    public Transform target;
    public float gravity;
    public float initialAngle;

    private Turret turret;
    private Rigidbody rb;
    private float angle;

    void Awake()
    {
        rb = GetComponent<Rigidbody>();
        target = GameObject.FindGameObjectWithTag("Player").transform;
        turret = GameObject.FindObjectOfType<Turret>();
        angleSlider = GameObject.FindObjectOfType<Slider>();
    }

    void Start()
    {
        ShootAtTarget();
    }

    void ShootAtTarget()
    {

        //position of target
        Vector3 p = target.position;

        //out friend gravity 9.81f
        float gravity = Physics.gravity.magnitude;

        //we transform the angle from degree to radians
        initialAngle = angleSlider.value;
        float angle = initialAngle * Mathf.Deg2Rad;

        //the planar coordinates of the target
        Vector3 planarTarget = new Vector3(p.x, 0, p.z);

        //the planar coordinates of our shooting point
        Vector3 shootingPointPlanar = new Vector3(transform.position.x, 0, transform.position.z);

        //the distance between the target and the shooting point
        float distance = Vector3.Distance(planarTarget, shootingPointPlanar);

        //the differences between the Y cordinates of shootingPoint and target (if for example the shootingPoint or target are on a hill)
        float yOffset = transform.position.y - p.y;

        //we get our velocity with standard Kinematic equations
        float initialVelocity = (1 / Mathf.Cos(angle)) * Mathf.Sqrt((0.5f * gravity * Mathf.Pow(distance, 2)) / (distance * Mathf.Tan(angle) + yOffset));
        Vector3 velocity = new Vector3(0, initialVelocity * Mathf.Sin(angle), initialVelocity * Mathf.Cos(angle));
        float angleBetweenObjects = Vector3.Angle(Vector3.forward, planarTarget - shootingPointPlanar) * (p.x > transform.position.x ? 1 : -1);
        Vector3 finalVelocity = Quaternion.AngleAxis(angleBetweenObjects, Vector3.up) * velocity;

        //we pass the velocity to our rigid body
        rb.velocity = finalVelocity;

    }
}

and the Turret:

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

public class Turret : MonoBehaviour
{
    public Transform target;
    public GameObject cannonBall;
    public float minDelay = 1.0f;
    public float maxDelay = 4.0f;

    private float lastTimeShoot = 0.0f;
    private float delayTime = 0.0f;

    void Update()
    {
        FollowTarget();
        ShootCannonBall();
    }

    void FollowTarget()
    {
        transform.LookAt(target);
    }

    void ShootCannonBall()
    {
        if(Time.time > lastTimeShoot + delayTime)
        {
            lastTimeShoot = Time.time;
            delayTime = GetRandomValue();
            GameObject obj = Instantiate(cannonBall, transform.position, transform.rotation) as GameObject;
            obj.name = "CannonBall";
        }

    }

    float GetRandomValue()
    {
        return Random.Range(minDelay,maxDelay);
    }
}

now from this script you can surely get that arc trajectory with a variation of angle. Keep in mind that the angle cannot be = 0 or =90.

If you want to do it with your method, you should make the projectile travel point to point following the LineRenderer points, and apply either a bezier smoothing or a catmull-rom to avoid the projectile traveling in a straight line, which seems a lot of work to be honest.

Regarding the LineRenderer you are probably seeing those issue because by default the line renderer is drawn in respect of the camera view. Try to uncheck Use World Space and set the Alignment = Transform Z, then you can rotate the object that hold the Line to which angle you prefer

I really appreciate your help, that is basically what I’m doing, except the player aim’s to shoot, but in your game I think it automatically shoot’s to your nearest enemy, but to be honest. I’d like to use the auto aim script. There will be a part in my game that if you just tap the shoot joystick, it auto aim’s to the nearest enemy!

When you said about the World space, I did uncheck it, but it made the line worse, Is it possible to move the line renderer around a cirlce at the bottom, (see below)

7131944--852383--upload_2021-5-12_9-56-24.png

So when I rotate it, it moves around the circle! Appreciate your help, Thanks a lot :slight_smile:

Of course, you don’t have to change your mechanics, this was an example of kinematic equations and projectile motion. If, for instance, you take the method ShootAtTarget() and pass as shootingPoint the first point of your lineRenderer and has target the last point of the line, you are gonna be able to shoot with the arc motion.

About the line, I don’t really know what’s going on. Try to open another thread with just that, maybe someone else could help.

thanks for everything M4dR0b, really do appreciate everything you’ve done for me, I’ll create a new thread regarding the line renderer:)