简体   繁体   中英

2D Projectile Trajectory Prediction(unity 2D)

using (unity 2019.3.7f1) 2d. I have a player that moves around using a pullback mechanic and has a max power(like in Angry Birds). I'm trying to draw a line(using a line renderer) that shows the exact path the player will go. I'm trying to make the line curve just like the player's path will. so far I've only managed to make a straight line in a pretty scuffed way. The known variables are the Jump Power and the player's position, there is no friction. and I believe gravity is a constant(-9.81). Also, I would like to have a variable that allows me to control the line's length. And, if possible, the line will not go through objects and would act as if it has a collider.

// Edit

This is my current code. I changed The function so it would return the list's points because I wanted to be able to access it in Update() so it would only draw while I hold my mouse button.

My problem is that the trajectory line doesn't seem to curve, it goes in the right angle but it's straight. the line draws in the right direction and angle, but my initial issue of the line not curving remains unchanged. If you could please come back to me with an answer I would appreciate it.

enter code here

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

public class TrajectoryShower : MonoBehaviour
{
LineRenderer lr;
public int Points;


public GameObject Player;

private float collisionCheckRadius = 0.1f;

public float TimeOfSimulation;

private void Awake()
{
    lr = GetComponent<LineRenderer>();
    lr.startColor = Color.white;
}

// Update is called once per frame
void Update()
{
    if (Input.GetButton("Fire1"))
    {
        lr.positionCount = SimulateArc().Count;

        for (int a = 0; a < lr.positionCount;a++)
        {
            lr.SetPosition(a, SimulateArc()[a]);
        }
    }
    if (Input.GetButtonUp("Fire1"))
    {
        lr.positionCount = 0;
    }
}

private List<Vector2> SimulateArc()
{
    float simulateForDuration = TimeOfSimulation;
    float simulationStep = 0.1f;//Will add a point every 0.1 secs.

    int steps = (int)(simulateForDuration / simulationStep);
    List<Vector2> lineRendererPoints = new List<Vector2>();
    Vector2 calculatedPosition;
    Vector2 directionVector = Player.GetComponent<DragAndShoot>().Direction;// The direction it should go
    Vector2 launchPosition = transform.position;//Position where you launch from
    float launchSpeed = 5f;//The initial power applied on the player

    for (int i = 0; i < steps; ++i)
    {
        calculatedPosition = launchPosition + (directionVector * ( launchSpeed * i * simulationStep));
        //Calculate gravity
        calculatedPosition.y += Physics2D.gravity.y * (i * simulationStep);
        lineRendererPoints.Add(calculatedPosition);
        if (CheckForCollision(calculatedPosition))//if you hit something
        {
            break;//stop adding positions
        }

    }

    return lineRendererPoints;



}
private bool CheckForCollision(Vector2 position)
{
    Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
    if (hits.Length > 0)
    {
        for (int x = 0;x < hits.Length;x++)
        {
            if (hits[x].tag != "Player" && hits[x].tag != "Floor")
            {
                return true;
            }
        }

    }
    return false;
}

}

This is basically a sum of 2 vectors along the time.

You have your initial position (x0, y0), initial speed vector (x, y) and gravity vector (0, -9.81) being added along the time. You can build a function that gives you the position over time:

f(t) = (x0 + x*t, y0 + y*t - 9.81t²/2) 

translating to Unity:

Vector2 positionInTime(float time, Vector2 initialPosition, Vector2 initialSpeed){
    return initialPosition + 
           new Vector2(initialSpeed.x * t, initialSpeed.y * time - 4.905 * (time * time);
}

Now, choose a little delta time, say dt = 0.25 .

   Time |            Position
0) 0.00 |  f(0.00) = (x0, y0)
1) 0.25 |  f(0.25) = (x1, y1)
2) 0.50 |  f(0.50) = (x2, y2)
3) 0.75 |  f(0.75) = (x3, y3)
4) 1.00 |  f(1.00) = (x4, y4)
   ...  |         ...

Over time, you have a lot of points where the line will cross. Choose a time interval (say 3 seconds), evaluate all the points between 0 and 3 seconds (using f ) and put your line renderer to cover one by one.

The line renderer have properties like width, width over time, color, etc. This is up to you.

Here's a simple way to visualize this.

To create your line you want a bunch of points.

  • The points represents the player's positions after being fired after X amount of time.
  • The position of each point is going to be: DirectionVector * (launch speed * time elapse) + (GravityDirection * time elapse^2)
  • You can decide in advance how far you pre calculate the points by simulating X duration and choosing the simulation step(calculate a point every X amount of time)
  • To detect collision each time you calculate a point you can do a small circle cast at that location. If it hits something you can stop add new points.
private float collisionCheckRadius = 0.1f;
        private void SimulateArc()
        {
            float simulateForDuration = 5f;//simulate for 5 secs in the furture
            float simulationStep = 0.1f;//Will add a point every 0.1 secs.

            int steps = (int)(simulateForDuration/simulationStep);//50 in this example
            List<Vector2> lineRendererPoints = new List<Vector2>();
            Vector2 calculatedPosition;
            Vector2 directionVector = new Vector2(0.5f,0.5f);//You plug you own direction here this is just an example
            Vector2 launchPosition = Vector2.zero;//Position where you launch from
            float launchSpeed = 10f;//Example speed per secs.

            for(int i = 0; i < steps; ++i)
            {
                calculatedPosition = launchPosition + ( directionVector * (launchSpeed * i * simulationStep));
                //Calculate gravity
                calculatedPosition.y += Physics2D.gravity.y * ( i * simulationStep) *  ( i * simulationStep);
                lineRendererPoints.Add(calculatedPosition);
                if(CheckForCollision(calculatedPosition))//if you hit something
                {
                    break;//stop adding positions
                }
            }

            //Assign all the positions to the line renderer.
        }
        private bool CheckForCollision(Vector2 position)
        {
            Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
            if(hits.Length > 0)
            {
                //We hit something 
                //check if its a wall or seomthing
                //if its a valid hit then return true
                return  true;
            }
            return false;
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM