0
  • I'm using Unity's latest Splines package(v2.5) to move a projectile across a spline with speed. It works perfectly and the projectile moves across the spline to it's target with linear speed.

  • But I'd like to make it look even better by making it behave a bit more realistically. Maybe someone smarter than me can articulate how projectiles move better, but essentially, I think when a projectile is thrown it should have a burst of speed at the start and begin to rise slower as it reaches its peak height. Then as it starts to fall it should gradually begin to fall faster.

  • I'd like to use math to accomplish this, rather than something like an animation curve. The reason is because sometimes enemies will be in different positions. So, the spline will look different depending on the enemy's position.


Here's the code I'm using:

public SplineContainer spline;
public float speed;
public float distancePercentage;
public float splineLength;

private IEnumerator MoveTheProjectile() { // Move the projectile across the spline while (distancePercentage < 1f) { distancePercentage += speed * Time.deltaTime / splineLength; currentPosition = spline.EvaluatePosition(distancePercentage); projectile.transform.position = currentPosition;

    yield return null;
}

}


Since I'm already using a speed variable to move the projectile, I think implementing this should be possible. Is anyone able to please assist me with a method of changing the speed of the projectile to be more realistic? Thanks so much!

DMGregory
  • 134,153
  • 22
  • 242
  • 357
PayasoPrince
  • 188
  • 1
  • 13
  • If you just want to follow the laws of physics, that's relatively simple (at least as long as you're willing to either use a physics engine or make some compromises like ignoring air resistance). But following a spline is more problematic. Moving between control points at a specified rate works, but if those control points aren't evenly spaced, you're going to get speedups and slowdowns. You could potentially solve this by calculating "distance along the curve" instead of the fraction of total curve length, but calculating the lengths of each curve is non-trivial and requires approximation. – Basic Nov 27 '23 at 23:00
  • 1
    Ironically, the non-linear speed you used your last Q&A to get rid of is exactly the non-linearity of a real physical projectile (okay, minus drag). A quadratic Bézier curve (one with two anchor points and one control point in between) traces a parabola: the trajectory followed by a frictionless projectile under constant acceleration (like gravity). So if the non-linearity you had before was not what you wanted, it would help to unpack a bit more what specifically you dislike about it and how a "good" non-linearity should differ from it. – DMGregory Nov 28 '23 at 00:10
  • 1
    @Basic You seem to have misunderstood the problem. He already has a solution for moving along a spline at a constant rate of speed when the control points aren't evenly spaced; it sounds like he now wants to simulate acceleration changes due to gravity. – Kevin Nov 28 '23 at 01:25
  • @DMGregory Hmm, that's so strange... the non-linear speed method I used in my previous Q&A really did not look correct. The projectiles speed would change far too drastically and look unnatural. – PayasoPrince Nov 28 '23 at 18:53

3 Answers3

0

I think when a projectile is thrown it should have a burst of speed at the start and begin to rise slower as it reaches its peak height. Then as it starts to fall it should gradually begin to fall faster.

It's not possible to achieve a realistic-looking acceleration with the solution you're using.

The motion of an object traveling in an environment with gravity is often represented using a ballistic trajectory that ignores air resistance. Under such conditions, we can break the motion of the projectile into two components, the horizontal (x) velocity and the vertical (y) velocity. Physical forces will then change these velocity components over time.

If the projectile is unpowered and we ignore air resistance, the only force acting on the projectile mid-flight is the force of gravity, which will change only the y-component of the velocity. The x-component will remain constant.

You're calculating movement along a parabolic curve using a function which doesn't give you separate control over the X and Y axes. You can speed up or slow down movement along the curve, but this would affect movement along both the X-axis and Y-axis. This won't look right because it's not physically accurate.

You'd probably be better off giving up on bezier curves. You could use a ballistic equation (there are several implementations here), but it sounds like you want something that just looks nice and doesn't need to be super realistic.


Here's a script that moves a projectile from A to B along a parabolic curve. This script doesn't use actual physics; instead, we use a sine curve to add vertical motion to what would otherwise be a straight-line course from A to B. You can adjust the height of the arc and the duration of the animation. I've included a custom editor for previewing the parabola.

public class ParabolaAnimator : MonoBehaviour {
    public IEnumerator FakeBallisticArc(Transform transform, Vector3 start, Vector3 end, float parabolaHeight, float duration) {
        float elapsed = 0;
        while (elapsed < duration) {
            yield return null;
            elapsed += Time.deltaTime;
        float t = elapsed / duration;
        transform.position = Evaluate(start, end, parabolaHeight, t);
    }
}

public Vector3 Evaluate(Vector3 start, Vector3 end, float parabolaHeight, float t) {
    t = Mathf.Clamp01(t);
    Vector3 straightLinePoint = Vector3.Lerp(start, end, t);
    float t2 = Mathf.Sin(t * Mathf.PI); // Curves from 0 to 1 and then back to 0
    float extraHeight = parabolaHeight * t2;
    return straightLinePoint + new Vector3(0, extraHeight, 0);
}

#if UNITY_EDITOR [UnityEditor.CustomEditor(typeof(ParabolaAnimator))] private class ParabolaAnimatorEditor : UnityEditor.Editor {

    private Transform projectile;
    private Vector3 start;
    private Vector3 end;
    private float parabolaHeight = 2;
    private float duration = .75f;

    private void Awake() {
        var animator = target as ParabolaAnimator;
        projectile = animator.transform;
    }

    private void OnEnable() {
        var animator = target as ParabolaAnimator;
        start = animator.transform.position + new Vector3(0, 1, 0);
        end = animator.transform.position + new Vector3(0, 3, 5);
    }

    private void OnSceneGUI() {
        Handles.color = Color.red;
        UnityEditor.Handles.lighting = false;
        start = UnityEditor.Handles.PositionHandle(start, Quaternion.identity);
        UnityEditor.Handles.Label(start + Vector3.up, &quot;Start&quot;);
        end = UnityEditor.Handles.PositionHandle(end, Quaternion.identity);
        UnityEditor.Handles.Label(end + Vector3.up, &quot;End&quot;);

        var animator = target as ParabolaAnimator;

        Vector3 a, b;
        float step = .05f;
        for (float t = 0; t &lt; 1; t += step) {
            a = animator.Evaluate(start, end, parabolaHeight, t);
            b = animator.Evaluate(start, end, parabolaHeight, t + step);
            UnityEditor.Handles.DrawLine(a, b);
        }
    }

    public override void OnInspectorGUI() {
        base.OnInspectorGUI();

        UnityEditor.EditorGUILayout.LabelField(&quot;Debug&quot;, UnityEditor.EditorStyles.boldLabel);

        var animator = target as ParabolaAnimator;

        start = UnityEditor.EditorGUILayout.Vector3Field(&quot;Start&quot;, start);
        end = UnityEditor.EditorGUILayout.Vector3Field(&quot;End&quot;, end);
        parabolaHeight = UnityEditor.EditorGUILayout.FloatField(&quot;Height&quot;, parabolaHeight);
        parabolaHeight = Mathf.Max(parabolaHeight, 0);

        if (Application.isPlaying) {
            duration = UnityEditor.EditorGUILayout.FloatField(&quot;Duration&quot;, duration);
            duration = Mathf.Max(duration, .1f);
            projectile = UnityEditor.EditorGUILayout.ObjectField(projectile, typeof(Transform), true) as Transform;
            GUI.enabled = (projectile != null);
            if (GUILayout.Button(&quot;Test&quot;)) {
                animator.StartCoroutine(animator.FakeBallisticArc(projectile, start, end, parabolaHeight, duration));
            }
        }
    }
}

#endif }

Screenshot

If you're not happy with the vertical motion, you can replace the sine curve in Evaluate() with an Animation Curve or another formula.

This script does not check for obstacles along the trajectory, so if there is a risk of obstacles, you may need to write some additional code to check for obstacles (e.g. with raycasts) and update the trajectory settings accordingly.

Kevin
  • 5,689
  • 1
  • 8
  • 26
  • You tell me I didn't understand the problem then post a solution which is basically my first sentence expanded? [If you just want to follow the laws of physics, that's relatively simple (at least as long as you're willing to either use a physics engine or make some compromises like ignoring air resistance)]. Interesting approach. – Basic Nov 28 '23 at 12:07
  • Thank you for the reply @Kevin! That's exactly what I was doing originally. @DMGregory was kind enough to help me implement the first link you provided a while ago. The ballistic equation seems to work better case-by-case. For my turn-based RPG, I was only able to get the projectiles movement to look okay with very specific speeds for Tmin & it almost always turned into a near straight line. Otherwise the target wasn't reachable or it moved very slowly. So is it not possible to create a custom curve to a target, while also make it look like the projectile is affected by gravity? – PayasoPrince Nov 28 '23 at 18:51
  • @Basic Two-thirds of your comment was describing why it's difficult to move along a bezier curve at a constant speed, although the first sentence of the question indicates that the OP already has a solution for moving along a bezier curve at a constant speed. I disagree that my five-paragraph answer is "basically just" the first sentence of your comment; like you and agone, my first thought was that the OP is not approaching this problem correctly and a physics-driven solution seems better here. – Kevin Nov 29 '23 at 01:12
  • @PayasoPrince It sounds to me like you gave up on the ballistic equation too early. You might try adjusting the speed of the projectile or the rate of gravity if you need to fine-tune the parabolas. – Kevin Nov 29 '23 at 01:28
  • Your answer is definitely more comprehensive, but you started with "It's not possible to achieve a realistic-looking acceleration with the solution you're using.". Gee, if only I'd dedicated 2/3 of my comment to that, you might have realised I wasn't confused... – Basic Nov 29 '23 at 01:44
  • @Basic You wrote an explanation of how he could "potentially solve" a problem that he had already solved, which is why I said it appeared that you had misunderstood the question. – Kevin Nov 29 '23 at 02:20
  • @PayasoPrince I added an alternative solution with a custom script which you may find easier to use. Please check out the updated answer. – Kevin Nov 29 '23 at 02:44
0

This is basic physics.

There are two general solutions. The first is call "analytical" or closed form, as it is a single equation that can give you the position of the particle at any time t. See here for more: Link: Calculating trajectory

The second is approach is numerical, which is much more common for physics because it allows for a lot more variety in what happens. In this approach one marches the object forward in time. Based on the current position, velocity, forces, you could compute the position and velocity at the next time step:

a(t) = SUM forces(t) / mass
v(t+1) = v(t) + a(t) * dt
x(t+1) = x(t) + v(t) * dt

Each equation gives the value at the next time step based on the current time step. Position is updated by velocity. Velocity is updated by acceleration. Acceleration is the sum of all forces acting on the object - in the simplest case just gravity. The acceleration a(t) is just a rewrite of Newton's 2nd law, F=ma. Note that x, v and a are each 2D or 3D vectors. Doing this on every timestep moves the object along the trajectory. Link: https://www.toptal.com/game/video-game-physics-part-i-an-introduction-to-rigid-body-dynamics

The reason this second, numerical, approach is much more common is greater flexibility. If your object hits a building, or is knocked off course, or bounces on the ground, then you just introduce some new forces at any time. This would be much harder to do with a closed form approach.

The use of splines, or sine function (by @Kevin), won't easily achieve this since the speed and acceleration along the path must also change to look realistic. Splines are very similar to the closed form approach -- they specify a known curve for the motion. As mentioned already, any additional obstacles, collisions, or other forces won't be easily captured with this strategy.

ramakarl
  • 61
  • 5
-2

Remove the spline and split speed into 2 components X,Y ie Vector2.

Fix a horizontal velocity.X from the spline. Apply a negative rather large value to velocity.Y, and each step adjusted for delta time subtracts gravity.

agone
  • 357
  • 7