Search Unity

  1. We are migrating the Unity Forums to Unity Discussions by the end of July. Read our announcement for more information and let us know if you have any questions.
    Dismiss Notice
  2. Dismiss Notice

Question What’s the proper way to accelerate an object without using physics?

Discussion in 'Scripting' started by Not_Sure, May 11, 2024.

  1. Not_Sure

    Not_Sure

    Joined:
    Dec 13, 2011
    Posts:
    3,548
    Note the word proper.

    So making a speed speed and acceleration variable then doing “speed += acceleration * deltaTime” won’t cut it.

    that only increases the speed per cycle and it means that people with faster computers will move faster.

    so what’s the “proper” way to do it?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,436
    "Akshually..."

    Actually that's the correct way. By multiplying with Time.deltaTime you're taking into account the frame length.

    You're saying "during this fraction of time, how much does the acceleration affect the speed?"

    Here's my generic "move ballistic stuff in 2D" script... note how it uses Time.deltaTime several times: damping, accelerating and the actual movement.

    You can delete the collision delegates (or leave them null) if you don't want to use them.

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. // @kurtdekker - cheesy 2D ballistics with optional collisions
    5.  
    6. public class Ballistic2D : MonoBehaviour
    7. {
    8.     public Vector2 velocity;
    9.  
    10.     public float damping = 0.2f;
    11.  
    12.     public System.Func<Vector2, bool> checkCollisionDownward;
    13.     public System.Func<Vector2, bool> checkCollisionLateral;
    14.  
    15.     void Update ()
    16.     {
    17.         // damp the velocity
    18.         velocity -= velocity * damping * Time.deltaTime;
    19.  
    20.         // accelerate due to gravity
    21.         velocity += Physics2D.gravity * Time.deltaTime;
    22.  
    23.         // compute our new position
    24.         Vector2 position0 = new Vector2( transform.position.x, transform.position.y);
    25.         Vector2 position = position0;
    26.         position += velocity * Time.deltaTime;
    27.  
    28.         // possible collision check(s)
    29.         if (checkCollisionDownward != null)
    30.         {
    31.             if (checkCollisionDownward( position))
    32.             {
    33.                 // <WIP> put code to back ourselves up to precisely where we hit
    34.            
    35.                 // <WIP> put any code here to cause bouncing/material-ish behaviors
    36.  
    37.                 position.y = position0.y;
    38.                 velocity.y /= 2;
    39.             }
    40.         }
    41.    
    42.         // possible collision check(s)        
    43.         if (checkCollisionLateral != null)
    44.         {
    45.             if (checkCollisionLateral( position))
    46.             {
    47.                 position.x = position0.x;
    48.                 velocity.x /= 2;
    49.             }
    50.         }
    51.  
    52.         // write new position
    53.         transform.position = position;
    54.     }
    55.  
    56.     public void AddVelocity( Vector2 impulse)
    57.     {
    58.         velocity += impulse;
    59.     }
    60. }
     
  3. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    1,129
    Like Kurt shows it should be:
    velocity+=accel*deltaTime
    position+=velocity*deltaTime

    But deltaTime won't make your game truly frame rate independent.

    In a casual game the player probably won't notice any difference in movement speed when playing the game with vsync on or off. But in a competitive game there will be some subtle differences that could lead to players with considerably different frame rates having an advantage or disadvantage, like being able to jump higher or further.

    This is ultimately why I gave up on the character controller.

    In a competitive game I'd recommend using a rigidbody and FixedUpdate for anything player related.
     
    Not_Sure and marcoantap like this.
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,436
    Yes ... this ^ ^ ^ is a very important point.

    Floating point imprecision will cause slight differences always.

    If you MUST simulate 100% accurately to keep everybody in sync, then you need to simulate only in one place and send the results to all users.
     
    Not_Sure likes this.
  5. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    7,019
    Proper means appropriate. Noted.

    There is none. One cannot accelerate an object without using physics.


    Sorry I just felt like answering this with AI. :)
     
  6. Not_Sure

    Not_Sure

    Joined:
    Dec 13, 2011
    Posts:
    3,548
    Yeah, this is what I was getting at.

    I was thinking of doing 100 position movements per second, and adding them in each frame, splitting the last one between frames.

    annoying, but it should make for consistency.
     
  7. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    1,129
    If you're not using physics because you don't want to move objects around with AddForce etc then perhaps you could use a kinematic rigidbody with MovePosition and MoveRotation. You'll then get the benefit of a fixed frame rate but with smooth/interpolated movement.

    Other than that the only alternative is to reinvent the wheel and create your own FixedUpdate and interpolation. But if you do this then don't tell anybody or you could end up being admitted to a mental hospital.
     
    Not_Sure likes this.
  8. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    2,001
    You're correct that an _accelerating_ object will move faster with a faster time step. When moving at a constant speed,
    x+=speedPerSec*Time.deltaTime
    gives perfect results for any time step, but with acceleration it's like the Fundamental Theorem of Calculus. Adding an amount to the speed every 0.1 seconds is like estimating the value of a curve using cubes of width 0.1 under that curve. There's an undercount which accumulates. If you're curious how large it is, try the calc trick where you compute with undercubes and again with overcubes and subtract (it's easy -- just add the speed either before or after). Maybe that's a small enough number or maybe you can add a fudge factor to get it small enough.

    If you want it to be 100% accurate, just use the closed formula: d=v*t+a*t^2/2+c. Each frame plug in the time in seconds to get the position. If/when the object changes speeds you'd need to compute a new base position, base speed, and reset the base time. It doesn't seem too bad (I'd make a class that knows the time, with getPos() and resetAccell(newAc)) but I'd do it side-by-side with maybe a rigidbody just to make sure it's in the ballpark.
     
    Sluggy likes this.
  9. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    2,598
    This may be accurate, but it will lose precision. With an IEEE754 32-bit
    float
    for time, you will lose a bit of precision whenever the Time.time value doubles. So in the first 16 seconds you've lost four bits of time precision. In half an hour, you've lost 11 bits of precision.

    As Isaac Newton found, the fundamental nature of motion can be calculated either with gross measurements like "the sun is overhead again," or the sum of infinitely fine deltas along a continual curve. We can't calculate with an infinitesimal ∂t so we are not going to precisely calculate infinitesimal ∂x changes in position, either. But by continually using very small values for ∂t over the course of each frame, we get the best we can do. It keeps your time values small so they don't lose precision.

    And unless you're only talking about a very limited system with TWO BODIES interacting with each other, like one ball and one planet's gravity, this is the only way to predict motions; this is why they call it the THREE BODY PROBLEM. This technique is called 'forward integration' and is used by NASA on every rocket mission, and anyone else trying to predict where things will end up in the future (albeit with slightly deeper floats or other number encoding). It's imperfect, and varying your deltaTime every frame does make it slightly worse, but every machine is imperfect.
     
  10. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,224
    Here's a very useful video on tackling delta time properly (also debunking of some misconceptions and good visual examples of various motions)

     
    PraetorBlue and MitchStan like this.
  11. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,219
    For simple uniform accelerated behaviour, there's an easy trick to get around the framerate problem. The solution is to apply the movement in two steps. Apply half the velocity before the acceleration and the other half after the acceleration addition. This will work with any framerate. However, it should be clear that any kind of events or changes like a ramp up in acceleration would break this assumption. For most things this should be good enough.

    Code (CSharp):
    1. pos += vel * dt * 0.5f;
    2. vel += acc * dt;
    3. pos += vel * dt * 0.5f;
    There is not any more "proper" way as we're dealing with time discrete simulations here. So this would never represent reality which does an actual integration (in a sense infinite frame rate).

    The only alternative is to not doing "relative" animations using deltaTime but using absolute time as reference. However this doesn't work well with any change in acceleration at all, so pretty useless in most usecases.
     
    orionsyndrome likes this.
  12. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,949
    “speed += acceleration * deltaTime”

    This but in FixedUpdate or an equivalent fixed-framerate simulation.

    Of course you will need to also do the movement in FixedUpdate which means you will need to create a system of interpolation if you care about having buttery smooth-looking movement.

    By the time you do that you may as well just use the physics engine anyway.
     
    Last edited: May 15, 2024
    orionsyndrome and lordofduct like this.
  13. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,219
    You actually got this the other way round. People with faster computers would be accelerating slightly slower. When your framerate drops you would be moving a bit faster. Over here I posted a table with 3 different position values at a frame rate of 10 fps. pos1 is the usual approach, pos2 is the correct position based on absolute time how it would work in the real world and pos3 is the position based on the half-half-trick. As you can see, at a lower framerate you would move further than the one at a better PC. The closer you get to an infinite framerate, the closer you get to the "correct" values.

    Just think about an extreme case of just one frame per second. At a constant acceleration the position would be
    0.5*acceleration*t*t
    .
    Since t is 1 we simply get half the value of the acceleration which would be correct. Though the normal approach means, velocity after 1 second should have the "full value" of acceleration (since acceleration is speed per second). This is actually correct. However when we now just add the velocity with dt to the position we would move twice as far as we should have in that timespan. So the smaller the timespan, the more accurate it gets.

    The half-half approach does move not enough in the first step but too much in the second and they perfectly fix each other.

    FixedUpdate doesn't really make the calculations "correct", it just makes it "consistent". Most physics systems for games simply use a fix timestep because the half-half-trick only world with constant accelerations. So acceleration itself must not be ramped up / down itself. You also run into other issues when it comes to drag and collision forces which is extremely hard to get "right". That's why we simply stick to a fixed update and call it a day. Unity's / PhysX's drag also has nothing to do with real world physics as it's just a linear percentage. Though since we don't really have actual wind, air density and so one to calculate the area of attack and CV value for your object moving, we simply get some mechanism which slows the object down over time.
     
    orionsyndrome likes this.
  14. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,224
    This particular solution is covered exactly in the chapter 8:25-10:30 of the video above (time stamp). So if anyone needs a visual tutorial to better grasp the delta times and what they do exactly, this is why I think this Jonas's video is magnificent.
     
    Bunny83 and PraetorBlue like this.
  15. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,219
    Yes, I've seen a couple of videos in recent years which cover this quite well. Note that you "could" do the speed and position update each in one line, but it would require an additional quadratic term. You could do

    Code (CSharp):
    1.     pos += vel * dt + 0.5f * acc * dt * dt;
    2.     vel += acc * dt;
    I guess it would also work when you switch the two lines but you would have to subtract the quadratic term. Effectively we're working with absolute time in small time intervals. So "vel*dt" is just the linear starting speed and the quadratic term is just the usual uniform accelerated position after "dt" time. Though it's obvious that the half-half approach is actually simpler. Of course you could factor out one "dt" and get
    Code (CSharp):
    1.     pos += (vel + 0.5f * acc * dt) * dt;
    2.     vel += acc * dt;
    But that doesn't really make it faster or more readable. Also it's much easier to remember the half-half trick :)