Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Question Difference in player controllers between directly setting velocities and adding delta velocities?

Discussion in 'Scripting' started by ylhs-262912, May 22, 2024.

  1. ylhs-262912

    ylhs-262912

    Joined:
    Oct 20, 2023
    Posts:
    8
    Hello. I'm making a game where a player is a navigator on a spaceship. the player creates a spline and the spaceship attempts to follow that spline to avoid obstacles. There are many ways to fail a level. The first way is to crash the spaceship by hitting something, and the second way is failing to reach the goal or running out of fuel due to a failed course. To make setting a course harder, I added obstacles that could distort the path by adding an external force. The problem became how to implement this though. I finally created a controller where the 'player' is split into 2 gameobjects. a ghost object that doesn't interact with external forces or have collision and is solely focused on following the line, and a player object with with both external forces and collision but only references the ghost object's delta velocity to decide how to move. The problem is that even with no external forces, the 2 objects become desynced. Here's an example of the code, the editor's log, and a video of the issue:

    Code (CSharp):
    1.     void FixedUpdate()
    2.     {
    3.         Log("Fixed Update");
    4.         if (!run)
    5.             return;
    6.         if (DoPIDcontrol() == 1)
    7.         {
    8.             pos++;
    9.             if (pos == Spline.Curve.Count)
    10.             {
    11.                 run = false;
    12.                 rbGhost.velocity = new Vector2(0, 0);
    13.                 return;
    14.             }
    15.             Target(Spline.Curve[pos]);
    16.         }
    17.     }
    18.  
    19.     public void Run()
    20.     {
    21.         run = true;
    22.         Target(Spline.Curve[1]);
    23.         pos = 1;
    24.     }
    25.  
    26.     public void Target(Vector2 pos)
    27.     {
    28.         Log("target changed to" + pos, "P_SET");
    29.         Ptarget = pos;
    30.         DEBUG_TARGET.position = Ptarget;
    31.     }
    32.  
    33.     private int DoPIDcontrol()
    34.     {
    35.         Vector2 CurrentError = Ptarget - rbGhost.position; //position error
    36.         D = CurrentError - P; // derivative error
    37.         P = CurrentError; // proportional error
    38.         I += P; // integral error
    39.         Vector2 newVelocity = Pgain * P + Igain * I + D * Dgain; // PID CONTROL / new velcoity
    40.         Vector2 changeVelocity = newVelocity - rbGhost.velocity; // difference between this frames Ghost velocity and PID result
    41.         LogV2("new velocity = ", newVelocity, "PID_NEW_VELOCITY");
    42.         LogV2("previous ghost velocity = ", rbGhost.velocity, "PID_PREVIOUS_GHOST");
    43.         LogV2("previous player velocity = ", rb.velocity, "PID_PREVIOUS_PLAYER");
    44.         LogV2("change velocity = ", changeVelocity, "PID_CHANGE_VELOCITY");
    45.  
    46.         rbGhost.velocity = newVelocity;
    47.         rb.velocity += changeVelocity;
    48.         rbGhost.rotation = math.atan2(rbGhost.velocity.y, rbGhost.velocity.x) * 180f / Mathf.PI - 90f;
    49.         rb.rotation = math.atan2(rb.velocity.y, rb.velocity.x) * 180f / Mathf.PI - 90f;
    50.         LogV2("Ptarget = ", Ptarget, "PID_TARGET");
    51.         LogV2("velocity = ", rb.velocity, "PID_PLAYER");
    52.         LogV2("ghost velocity = ", rbGhost.velocity, "PID_GHOST");
    53.         Log("Distance = " + Vector2.Distance(rb.position, Ptarget), "PID_DISTANCE");
    54.         if (Vector2.Distance(rbGhost.position, Ptarget) < 0.01f)
    55.         {
    56.             rbGhost.position = Ptarget;
    57.             I = new Vector2(0, 0);
    58.             LogV2("Reached point", Ptarget, "PID_SUCCESS");
    59.             return 1;
    60.         }
    61.         return 0;
    62.     }
    63.  
    64.  
    Note: In the attached video, the ghost object is translucent and the player is not.
     

    Attached Files:

  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,922
    You may want to upload the video and embed the url so we can play it back without the need to download, unzip, find the file, open it. ;)

    My hunch is that by using Rigidbody for both any slight deviation in forces applied to them will cause them to stray apart.

    You could simply track the position and rotation of the ghost object over time, and every frame position the player object so it trails behind as much as you need it. Be sure to enable interpolation otherwise you effectively move these objects at 50 Hz intervals whereas the game runs at 60 Hz or more.
     
  3. ylhs-262912

    ylhs-262912

    Joined:
    Oct 20, 2023
    Posts:
    8
    Actually, that's what I thought at first too, so I removed all external forces from the system. There are no external forces on either object, and neither rigidbody has any form of drag. This is why I'm so confused they behave different.