Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Physics2D platform: Limit translations and rotations

Discussion in 'Physics' started by FeastSC2, Jan 29, 2019.

  1. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    I'm making platforms using the physics of Unity because it's visually interesting. But I don't want it to break the intended purpose of my level design.
    It basically just has to be something that makes the platforms move and rotate around a bit reacting to various elements of my game but it's not supposed to be something more than visual.

    So I need a way to be able to restrict the movement (in X and Y) and rotation (in Z) of my platforms with rigidbodies.

    Using a damping ratios and other non-sure fire ways of limiting the movement is good but it is not safe and can go beyond what I have intended in certain extreme situations.
    Additionally, I don't want to be restricted later on in the process of altering the mass of anything because I would be afraid to break previous physics system. So I would like to add something on top of those systems to not allow to go beyond certain limits.

    Something like "Limit Y translation to max 2 units of distance" or "rotate max 20 degrees".

    How can I achieve this?

    I tried with a script like this but it doesn't anything, probably because it's being overwritten by the physics:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class PhysicsLimiter : MonoBehaviour
    4. {
    5.     public bool LimitXTranslation;
    6.     public bool LimitYTranslation;
    7.     public bool LimitZRotation;
    8.  
    9.     public float XLimit;
    10.     public float YLimit;
    11.     [Range(0,360f)]public float ZRotationLimit;
    12.  
    13.     public Vector3 StartPos;
    14.     public Vector3 StartRot;
    15.     void Start()
    16.     {
    17.         StartPos = transform.localPosition;
    18.         StartRot = transform.localEulerAngles;
    19.     }
    20.  
    21.     void FixedUpdate()
    22.     {
    23.         var translateDifference = transform.localPosition - StartPos;
    24.         var rotateDifference = transform.localEulerAngles - StartRot;
    25.         if (LimitXTranslation)
    26.         {
    27.             if (Mathf.Abs(translateDifference.x) > XLimit)
    28.             {
    29.                 transform.localPosition = transform.localPosition.With(x:Mathf.Sign(XLimit) * XLimit);
    30.             }
    31.         }
    32.         if (LimitYTranslation)
    33.         {
    34.             if (Mathf.Abs(translateDifference.y) > YLimit)
    35.             {
    36.                 transform.localPosition = transform.localPosition.With(y: Mathf.Sign(YLimit) * YLimit);
    37.             }
    38.         }
    39.         if (LimitZRotation)
    40.         {
    41.             if (Mathf.Abs(rotateDifference.z) > ZRotationLimit)
    42.             {
    43.                 Debug.Log("limiting z rotation: " + rotateDifference.z);
    44.                 transform.localEulerAngles = transform.localEulerAngles.With(z: Mathf.Sign(ZRotationLimit) * ZRotationLimit);
    45.                 Debug.Log("set z rotation to: " + transform.localEulerAngles);
    46.             }
    47.         }
    48.     }
    49. }
    50.  
    51. public static class TransformExt
    52. {
    53.     public static Vector3 With(this Vector3 _vec3, float? x = null, float? y = null, float? z = null)
    54.     {
    55.         float newX = x ?? _vec3.x;
    56.         float newY = y ?? _vec3.y;
    57.         float newZ = z ?? _vec3.z;
    58.         return new Vector3(newX, newY, newZ);
    59.     }
    60. }
    The logs on the console when using the script and limiting the rotation in Z to 20dgr:
    Unity_2019-01-29_19-34-29.png

    Here's a video to show what currently happens in the game:
    The platform rotates too much when the character falls from on high. (the angle is > 20degrees so my controller considers it a slope and the character falls off).
     
  2. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    No one has any idea how to achieve this?
     
  3. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    @MelvMay Do you know about a way to limit the movement of platforms affected by physics?
     
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,321
    Can you not use a HingeJoint2D with limits?

    Take a look at my example GitHub project here, specifically this scene. All joints are "soft" so if you hit them with too much force or your movement model is just repositioning bodies (causing overlaps) then it won't be as effective. You can also increase the solver iteration count (in 2D physics settings) from its default to produce more accurate joints.

    https://gyazo.com/b816e40ab13c254b9b1545eb8855eda6
     
  5. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    I checked the scene out and I'm aware that the joints have an angle limit but I want to use this limitation on as many types of physics component as possible.

    Example: a simple dynamic rigidbody on a surface effector. I would like to allow it some z rotation but not higher than 20degrees. How can I do that?
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,321
    You asked specifically about platforms but now you're saying you want this on any Rigidbody2D instead. There are no built-in rotational limits on a Rigidbody2D. The only constraints are joints or manually via script. When doing so via script, you have to be aware that no matter what you do to a dynamic body, it'll be affected by external forces.

    When manipulating a RB by script you should never touch the transform as you demonstrated above, always work with the Rigidbody2D directly. Use "Rigidbody2D.rotation" and limit its rotation with "Rigidbody2D.MoveRotation" which'll give you correct collisions. If you're not bother by that then you can simply instantly change the rotation directly.

    Note that if you do this prior to the simulation running then it's possible that the simulation will run and external forces still move it beyond your limit which is then rendered and then next loop you limit it again but you're always seeing it out-of-limit. To get around this, set your limit after the simulation has run. One quick way of doing this is to use manual simulationl; run the simulation then apply your constraints.
     
    FeastSC2 likes this.
  7. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    My bad indeed, I want this to work on as much physics components as possible.

    I tried this but I must be doing something incorrectly because I get similar as when I was limiting through the transform if using the MoveRotation function.

    I'm now running the Physics2d simulations myself:
    Code (CSharp):
    1. using UnityEngine;
    2. public class PhysicsSimulation : MonoBehaviour
    3. {
    4.     void Awake()
    5.     {
    6.         Physics2D.autoSimulation = false;
    7.     }
    8.  
    9.     // Important: this allows us to know WHEN the simulation is being called.
    10.     // We call it here and set in this script in the project execution order to be the first script called for physics.
    11.     // This allows us to use PhysicsLimiter where we can know for sure that a rotation or movement of a physics rb2d doesn't go above the limitation.
    12.     void FixedUpdate()
    13.     {
    14.         if (Physics2D.autoSimulation == false)
    15.             Physics2D.Simulate(Time.fixedDeltaTime);
    16.     }
    17. }
    With the modified script of PhysicsLimiter.cs:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [RequireComponent(typeof(Rigidbody2D))]
    4. public class PhysicsLimiter : MonoBehaviour
    5. {
    6.     public bool LimitZRotation;
    7.     [Range(0,180f)]public float ZRotationLimit;
    8.  
    9.     private Rigidbody2D Rb2D;
    10.  
    11.  
    12.     private void Awake()
    13.     {
    14.         Rb2D = GetComponent<Rigidbody2D>();
    15.     }
    16.  
    17.     public float CurrentRotation
    18.     {
    19.         get
    20.         {
    21.             return Rb2D.rotation;
    22.         }
    23.         set
    24.         {
    25.             Rb2D.MoveRotation(value);
    26.             // Rb2D.rotation = value; // This acually doesn't call the errorline unlike the MoveRotation function.
    27.         }
    28.     }
    29.  
    30.     void FixedUpdate()
    31.     {
    32.         if (LimitZRotation)
    33.         {
    34.             if (Mathf.Abs(CurrentRotation) > ZRotationLimit)
    35.             {
    36.                 Debug.Log("limiting z rotation: " + CurrentRotation+ " to: "+ ZRotationLimit);
    37.                 CurrentRotation = Mathf.Sign(CurrentRotation) * ZRotationLimit;
    38.                 if (Mathf.Abs(CurrentRotation) > ZRotationLimit)
    39.                     Debug.LogError("rotation is not below limitation. Angle is: " + CurrentRotation);
    40.             }
    41.         }
    42.     }
    43. }
    The line "Debug.LogError("rotation is not below limitation. Angle is: " + CurrentRotation);" gets called if I use Rb2D.MoveRotation but not if I use Rb2D.rotation.
     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,321
    Just putting stuff in fixed-update doesn't control which one gets called first. If you want the manual simulation first then you need to set the script execution order but I think you're also encountering something else.

    MovePosition/MoveRotation on a dynamic body will move correctly but because it's a dynamic body it'll have a collision response so if it's in contact with something it won't necessarily get to that position/rotation. Obviously the same on a kinematic body won't have this issue.

    If it's a dynamic body and you absolutely want it to move to that position/rotation then you have no choice but to set the position/rotation directly on the body. It is this set-up I was referring to when I said run the simulation then set the constraint i.e. let the simulation run then check all constraints. The obvious downside here is that it's possible to cause bad collision-detection if you're repositioning bodies directly.

    The problem here is that there isn't a single great solution, it just isn't a feature of rigidbody2d. If you set the pose after the simulation then you are guaranteed it is correct however you'll have to wait until the next simulation to see the results. If you do it before the simulation then the simulation might take it out of bounds before you correct it again next update. Another way is to set the position on the transform (not a great idea) and then perform a SyncTransform after the simulation is run.

    For platforms, increasing iterations and using the hingejoint2d should work well. For other dynamic characters you end up hacking with tricks like adding a dynamic body with Z rotation constraint fixed and then add another dynamic body where you main rendering is on. You can then connect the two with a hingejoint2d with rotational limits. This unfortunatley means you have two rigidbodies so need to be careful which you use to move. It really depends on what you want and I'm not sure there's a works-for-all set-up.
     
    Last edited: Feb 11, 2019
    FeastSC2 likes this.