Search Unity

[Solved] Elevator problem

Discussion in 'Physics' started by Necronomicron, Feb 18, 2019.

  1. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    I've created elevator that moves with acceleration. The problem is that all physical objects start to bounce during movement. Changing some thresholds helps a bit, but CharacterController is still bouncing (when moving down). Slowing elevator down doesn't help much either.

    Code (CSharp):
    1.     void Awake()
    2.     {
    3.         rb = GetComponent<Rigidbody>();
    4.     }
    5.  
    6.     public void MoveToFloor(Floors floor)
    7.     {
    8.         if (!IsMoving && floor != currentFloor)
    9.         {
    10.             currentFloor = floor;
    11.             StartCoroutine(MoveElevator(floor));
    12.         }
    13.     }
    14.  
    15.     IEnumerator MoveElevator(Floors floor)
    16.     {
    17.         IsMoving = true;
    18.         float
    19.             speed = 0,
    20.             target = floors[floor];
    21.         bool up = target - transform.position.y > 0;
    22.  
    23.         while (true)
    24.         {
    25.             // Moving up.
    26.             if (up)
    27.             {
    28.                 if (MoveElevator(ref speed, target, Vector3.up, up))
    29.                     break;
    30.             }
    31.             // Moving down.
    32.             else
    33.             {
    34.                 if (MoveElevator(ref speed, target, Vector3.down, up))
    35.                     break;
    36.             }
    37.  
    38.             yield return null;
    39.         }
    40.  
    41.         IsMoving = false;
    42.     }
    43.  
    44.     bool MoveElevator(ref float speed, float target, Vector3 dir, bool up)
    45.     {
    46.         float t = speed / acceleration;
    47.         float brakingDistance = acceleration * t * t / 2;
    48.  
    49.         // Decelerating.
    50.         if (brakingDistance >= Mathf.Abs(target - transform.position.y))
    51.             speed -= acceleration * Time.deltaTime;
    52.         // Accelerating.
    53.         else if (speed < maxSpeed)
    54.         {
    55.             speed += acceleration * Time.deltaTime;
    56.  
    57.             // Max speed is reached.
    58.             if (speed >= maxSpeed)
    59.                 speed = maxSpeed;
    60.         }
    61.  
    62.         // New position.
    63.         Vector3 pos = transform.position + dir * speed * Time.deltaTime;
    64.  
    65.         if (up ? target - pos.y > 0 : pos.y - target > 0)
    66.         {
    67.             rb.MovePosition(pos);
    68.             return false;
    69.         }
    70.         // Target is reached.
    71.         else
    72.         {
    73.             rb.MovePosition(transform.position.SetY(target));
    74.             return true;
    75.         }
    76.     }
    (Don't mind visual glitch on moving objects.)

     
  2. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,536
    I think you can get around it with parenting the objects in the lift to the lift.

    something like a trigger occupying the inner space of the lift and OnTriggerEnter -> parent and OnTriggerExit -> make them like batman(de-parented ;))
     
    arsenpoter7777777 likes this.
  3. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    Forgot to mention that parenting doesn't work either. Why would it? Those are all physical objects and they don't care about parenting at all.
     
  4. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,536
    Try adding them to some list/array and move them along with the lifts movement.
     
    Necronomicron likes this.
  5. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    I thought about that but it sounds like some hack, cheap trick. I would use it if there is no better solution...
     
  6. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,536
    #Smoke&Mirrors ;)
     
    bart_the_13th likes this.
  7. lightbug14

    lightbug14

    Joined:
    Feb 3, 2018
    Posts:
    447
    Hello, forget about parenting, it will cause you a lot of troubles.

    that's exactly how every good platform controller works,...or the other way, i mean that every object inside moves along with the ground (that's what i do in my asset), it depends, in this case i would say go with the option A, the elevator should apply "something" to the objects.

    IMO the safest way is going kinematic, you can put every object inside the elevator as kinematics, that way the objects wont bounce, but also wont move/rotate/etc nothing, you gonna have to move them in some way (Rigidbody.position or MovePosition) with the deltaPosition of the elevator.

    Also you can instead modify the mass and drag of every object while the elevator is moving, it could work.

    In any case the elevator will have to know the objects inside, so the array or list is a good start.

    And for the character controller the same (I assume you are using a "Character Controller" component, is far better that having a dynamic rigidbody, unless you want to make one of that funny games when you don't have control over your player ... otherwise go with the character controller.) You will have to move the character controller just like the other rigidbodies.
     
    Necronomicron likes this.
  8. Antony-Blackett

    Antony-Blackett

    Joined:
    Feb 15, 2011
    Posts:
    1,778
    The character controller isn't great to be honest. I fight this problem constantly as well.

    There's an asset somewhere that's meant to be a bit better and handling this sort of stuff, maybe give that a go if you haven't got too far into dev yet.

    https://assetstore.unity.com/packages/tools/physics/kinematic-character-controller-99131

    Disclaimer, I haven't used this myself, but I kinda wish i had... It claims to solve all the issues I have with the default character controller.

    Another issue with the default character controller is that it allocates memory every frame. :(
     
  9. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,536
    Also, just moving the platform up with a line of code like 'transform.position += Vector3.up' is also a "cheap trick", why don't you simulate a rope attached to it and a motor on the other side, and so on and so on..

    the point i'm trying to make is if the end result is the same for the user, don't throw away a good solution just because it's "a cheap trick".
    *not saying my solution is good, just a general piece of advise, altough I think it may do the "trick" as you say ;)
     
    Last edited: Feb 19, 2019
  10. lightbug14

    lightbug14

    Joined:
    Feb 3, 2018
    Posts:
    447
    Well, the character controller by itself won't do a thing about that specific problem, you will have to do a spherecast (or whatever) in order to probe the ground, and if the test is successful force the character to the ground (via Move(Vector3.down ...) ), that is called "ground clamping". But, this technique is not a definitive solution (to this problem), because the elevator could be moving too fast, so, just move the character and everything else along with the platform, and problem solve.

    I didn't know the Character Controller allocates memory, mmm i'm going to do some measures.
     
  11. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    That's not what I meant. I meant it looks like kludge. Maybe it's not though.
     
  12. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    What's wrong with isGrounded?
     
  13. lightbug14

    lightbug14

    Joined:
    Feb 3, 2018
    Posts:
    447
    I think "isGrounded" become true only when the vertical deltaPosition (y component of the input of the Move method) is negative and the character hits the ground. If the component is zero nothing happens, that's why isGrounded keep showing as true. what ground clamping does is simply probe the ground with a fixed vertical magnitud (independent of the vertical movement) even if the character Y component is negative or not. Of course it depends on the implementation, that's what i do in my asset (while the character is grounded).
     
  14. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    I've just built an elevator that's similar in behavior to yours. That is, it moves up and down via Rigidbody.MovePosition. For a while, I was having a similar issue as you: While moving down, any dynamic rigidbodies on the lift would bound a bit. I've gotten that sorted out now, and "passengers" on the lift move smoothly.

    The big issue I was having was multiple kinematic rigidbodies in my elevator. Do you only have a single rigidbody making up your elevator? For a while, I had more than one, due to my perception that I needed two, for some reason. But it only caused problems. So, first thing, make 100% sure you only have a single kinematic rigidbody making up your elevator.

    The other observation I have is that we both use a Coroutine to move the platform, and we both move it via MovePosition. However, you're calling MovePosition every frame (as your coroutine uses `yield return null`) while I use `yield return new WaitForFixedUpdate` so that it only calls MovePosition on physics updates. Maybe you can try that?
     
  15. ryancampfire

    ryancampfire

    Joined:
    Jan 4, 2017
    Posts:
    31
    Yeah, I'm not 100% sure but looking at this:

    https://docs.unity3d.com/Manual/ExecutionOrder.html

    I think the yield return null; will make it run every Update rather than every FixedUpdate, and anything where you move Rigidbodies should be done every FixedUpdate or you will get a jittery movement.

    I might also try making the elevator kinematic and moving it using an Animator rather than a coroutine?
     
  16. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    Yes.
     
  17. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    So yeah,
    yield return new WaitForFixedUpdate();
    helped with boxes and stuff, but player was still bouncing.

    Then I started to move it with elevator. Player's script:
    Code (CSharp):
    1. ...
    2.     public Transform Elevator { get; set; } = null;
    3.     public Vector3 ElevatorDelta { get; set; } = Vector3.zero;
    4.  
    5.     void FixedUpdate()
    6.     {
    7.         // If player is in elevator.
    8.         if (Elevator)
    9.         {
    10.             cc.Move(ElevatorDelta);
    11.             Elevator = null;
    12.         }
    13.     }
    14. ...
    And elevator with a box trigger. I pass traveled distance by elevator to the player:
    Code (CSharp):
    1.     void Awake()
    2.     {
    3.         rb = GetComponent<Rigidbody>();
    4.     }
    5.  
    6.     void OnTriggerStay(Collider other)
    7.     {
    8.         if (other is CharacterController)
    9.         {
    10.             PlayerControls pc = other.GetComponent<PlayerControls>();
    11.  
    12.             if (pc)
    13.                 pc.Elevator = transform;
    14.         }
    15.     }
    16.  
    17.     public void MoveToFloor(Floors floor)
    18.     {
    19.         if (!IsMoving && floor != currentFloor)
    20.         {
    21.             currentFloor = floor;
    22.             StartCoroutine(MoveElevator(floor));
    23.         }
    24.     }
    25.  
    26.     IEnumerator MoveElevator(Floors floor)
    27.     {
    28.         IsMoving = true;
    29.         float
    30.             speed = 0,
    31.             target = floors[floor];
    32.         bool up = target - transform.position.y > 0;
    33.  
    34.         while (true)
    35.         {
    36.             if (MoveElevator(ref speed, target, up ? Vector3.up : Vector3.down, up))
    37.                 break;
    38.  
    39.             yield return new WaitForFixedUpdate();
    40.         }
    41.  
    42.         IsMoving = false;
    43.     }
    44.  
    45.     bool MoveElevator(ref float speed, float target, Vector3 dir, bool up)
    46.     {
    47.         float t = speed / acceleration;
    48.         float brakingDistance = acceleration * t * t / 2;
    49.  
    50.         // Decelerating.
    51.         if (brakingDistance >= Mathf.Abs(target - transform.position.y))
    52.             speed -= acceleration * Time.deltaTime;
    53.         // Accelerating.
    54.         else if (speed < maxSpeed)
    55.         {
    56.             speed += acceleration * Time.deltaTime;
    57.  
    58.             // Max speed is reached.
    59.             if (speed >= maxSpeed)
    60.                 speed = maxSpeed;
    61.         }
    62.  
    63.         // New position.
    64.         Vector3 newPos = transform.position + dir * speed * Time.deltaTime;
    65.  
    66.         if (up ? target - newPos.y > 0 : newPos.y - target > 0)
    67.         {
    68.             rb.MovePosition(newPos);
    69.             GameManager._.playerControls.ElevatorDelta = newPos - transform.position;
    70.             return false;
    71.         }
    72.         // Target is reached.
    73.         else
    74.         {
    75.             rb.MovePosition(transform.position.SetY(target));
    76.             GameManager._.playerControls.ElevatorDelta = transform.position.SetY(target) - transform.position;
    77.             return true;
    78.         }
    79.     }

    The only problem left is when elevator moves down, player is slightly jittering:
     
    Last edited: Sep 18, 2019
  18. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    I fixed jittering by replacing
    cc.Move(ElevatorDelta);
    with
    transform.position += ElevatorDelta;
    . It seems
    Move()
    method is delayed.
     
  19. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    Does cc.Move ultimately call Rigidbody.MovePosition? The problem with setting transform.position is that it can cause issues with the physics system. You might not get correct collisions if you manually change the position of a rigidbody transform, as opposed to moving it either via forces or via MovePosition.

    If you're seeing jittering, and your character has a non-kinematic rigidbody on it, you might just need to set the rigidbody's Interpolation to Interpolate.
     
  20. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    I used CharacterController.Move and I'm still using it for moving around, I just don't use it now to add elevator's delta movement to the player. I don't have Rigidbody on the player and I don't know if Move() calls MovePosition(), but I doubt because AFAIK CharacterController doesn't have Rigidbody. There is no Interpolation options for CharacterController either.
     
  21. Pedro_Barros

    Pedro_Barros

    Joined:
    Sep 17, 2019
    Posts:
    1
    Yes, I Know this is from 2015. But I always find solutions for my projects from people who answered later. Well, I tried each tip and possible solutions in the comments but no one worked for me. So I found out the simplest solution: I just made everything that touches the elevator ground it's child. It reduced the bug a lot, but the jittering kept happening, so i changed elevator movement from void Update to FixedUpdate. Now EVERYTHING goes smoothly.
     
  22. Necronomicron

    Necronomicron

    Joined:
    Mar 4, 2015
    Posts:
    108
    It's not from 2015 though. :D