Search Unity

Getting a child collider to not affect the weight distribution of a rigidbody

Discussion in 'Physics' started by UnresolvedExternal, Feb 9, 2017.

  1. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    15
    Hi! See attached image. The object "Player1" has a rigidbody. Its child object "Colliders" has some colliders, in the image these are illustrated with pink color. These I want to be used as weight to the character.

    There is also another child object "ExplosiveCollider" which has a single collider, it is illustrated with blue color in the image. It is used for encapsulating picked up explosives which can be thrown. The purpose of this collider is so that the character and its picked up explosive will behave as one object, i.e. the character cannot walk into a wall with an explosive.

    This works fine, the character walks up to a wall and the picked up explosive's collider makes it stop before going into the wall.

    There is a little problem though: because of this collider the character gets unstable and starts to tilt, sometimes even falling.

    How can I solve this? I still want the collider to be a part of the character and to be used for collision detection, but I don't want it to make the character becoming unstable.

    Have a nice day!

    2017-02-09_20h28_15_v2.png
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    1,972
    Set the 'ExplosiveCollider' to be a trigger. When set to a trigger, it doesn't affect the center-of-mass. You then use the standard OnTriggerXXX callbacks.
     
    ClementCyborn likes this.
  3. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    15
    Thanks for the reply! But if I set it to be a trigger it will just go through the wall. What I want to do is to still get a physics reaction (i.e. stop at the wall), but I don't want it to affect the center-of-mass. Is it possible to force a collision reaction inside OnTriggerEnter?
     
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    1,972
    Your only option then is to set the center-of-mass yourself via Rigidbody2D.centerOfMass. You cannot turn-off the center-of-mass and rotational inertia individually for colliders. It's possible for us to implement but it's not something that has been asked for before.
     
    BigToe likes this.
  5. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    333
    @UnresolvedExternal You can easily fix the tipping over issue by simply toggling on the "Freeze Rotation" option for the X and Z axis under "Constraints" on the rigidbody of your character, unless you actually need those to remain free for some reason?
     
    MelvMay likes this.
  6. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    15
    Hi! I already have "Freeze Rotation" option for X and Z checked. The tilt rotation that occurs is around the local forward axis. Wouldn't this be equal to Z and thus shouldn't rotate since it is freezed?

    When I checked Y as well (i.e. freeze rotation all) the character stopped tilting, which I find strange.

    Worth to mention is that the rotation of the character is handled with "transform.rotation" in Update, could that have any impact?
    Code (CSharp):
    1. _newRotation = Quaternion.AngleAxis((-Mathf.Atan2(Axis.z, Axis.x) * Mathf.Rad2Deg) + Camera.main.transform.rotation.eulerAngles.y + 90, Vector3.up);
    2.  
    3. transform.rotation = Quaternion.Slerp(transform.rotation, _newRotation, Time.deltaTime * RotationSpeed);
    The forward/backward movement is handled with AddForce in FixedUpdate.
     
  7. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    1,972
    For starters, you never stated you were rotating it but not that you are rotating the Transform, not the Rigidbody2D.

    Step back and think about it for a second if you will. :)

    The Transform is a component and so is Rigidbody2D. Each fixed update, the Rigidbody2D transfers its current XY position and Z rotation to the Transform component i.e. it drives the Transform; this is its job.

    By telling the Rigidbody2D to freeze rotation you're telling it to not rotate during the physics simulation step. It therefore won't.

    You then change the Transform rotation yourself with 'transform.rotation = xxx'! This not only changes the Transform rotation but immediately updates the Rigidbody2D.rotation to keep them in sync therefore freezing rotation on the Rigidbody2D makes no difference as you're rotating it. You should not drive it via the Transform and certainly not each frame during Update; this is the job of the Rigidbody2D.

    So you're using AddForce on the Rigidbody2D to change its velocity which changes it's position. This position updates the Transform position each fixed-update. If you want to change the rotation of the Rigidbody2D then you can add a force to rotate it i.e. a Torque using AddTorque. Like AddForce, you should only be doing this during the fixed-update. You can also move the position and rotation of the Rigidbody2D using MovePosition and MoveRotation.

    However, no matter how you rotate it (which you didn't state you wanted to do), it'll rotate around the center-of-mass which has changed.

    You need to set the center-of-mass to a local-point which you want to rotate around. I have no idea how you have set-up your player but you can easily have the Rigidbody2D on a GameObject and have the other colliders offset away from the center-of-mass. You can then set the center-of-mass to zero (the point you want to rotate around).
     
  8. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    333
    @MelvMay You keep referencing the 2D physics system, but I think @UnresolvedExternal is using the 3D physics system? However, If game play is restricted to 2D then switching to 2D physics might make working with physics a bit simpler here.

    Another possible solution to this whole "center of mass" issue could be to simply create a set of predefined child objects on the player. Each representing a different pickup. Then instead of attaching pickups to the player when they collect them, you would simply destroy the pickup and display the corresponding child pickup model on the player that represents it. Then when the player drops the pickup you simply hide the child pickup on the player and Instantiate a new pickup object to drop.

    Now the key here would be that all the predefined child pickups on the player would be visual only, with no colliders attached to them. Meaning they wouldn't effect the rigidbody's center of mass at all. Now if you don't want these child objects to clip through walls I would just create a second wider collider on the player that is only activated when a child pickup is displayed. Or maybe just make the player's collider bigger to start with.
     
  9. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    1,972
    Sorry, it had been a long day and I was tired, sitting on the sofa in the evening with one eye open. ;) You are of course correct but what I said translates directly to 3D so it should be useful info still.
     
    IsaiahKelly likes this.
  10. UnresolvedExternal

    UnresolvedExternal

    Joined:
    Apr 1, 2015
    Posts:
    15
    Hi! Thanks for a thorough explanation @MelvMay! :)

    The reason I have freezed the X and Z constrains is because I don't want the rotation of the character to be affected by physics. So the only way to make it rotate is by changing the rotation of the transform, correct me if I'm wrong.

    I tried to move the rotation logic from Update to FixedUpdate, but the results were the same. Is it better to keep it in FixedUpdate even though I'm using transform.rotation?

    I also tried to store the initial center-of-mass value in Awake and then when a collider is added or enabled I set rigidBody.centerOfMass back to the initial value. By using a custom editor that draws the center-of-mass I have verified that the initial value is used at all times. But, the problem still persists.

    I have probably built up my hierarchy wrongly, there must be a simple solution for this, is there any other way I can build it?

    To sum up, what I want is this:
    • Character is moved with AddForce
    • Character is rotated without physics reaction
    • An extra collider is enabled if the character picks up an item:
      • This collider should belong to the character
      • It should not affect the character's stability, i.e. not wobble
      • It should detect collision and stop at walls

    Hope we can figure this out!
     
  11. IsaiahKelly

    IsaiahKelly

    Joined:
    Nov 11, 2012
    Posts:
    333
    I created a simple script below that should do what you want. Just create a new rigidbody object for the player to test it with, lock all rotation on it, add an empty child named "Hand" and position it somewhere in front of the player (this will be used to hold and position any items picked up), and add a trigger to detect pickups. Then create another rigidbody object and name it "Pickup" and place it in the scene near this player object and run the scene to test it.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class PlayerPickupDemo : MonoBehaviour
    4. {
    5.     public float speed = 10f;
    6.  
    7.     Rigidbody playerBody, pickup;
    8.     Transform hand;
    9.  
    10.     void Awake()
    11.     {
    12.         // Optimization - Cache reference to components.
    13.         playerBody = GetComponent<Rigidbody>();
    14.         hand = transform.Find("Hand");
    15.     }
    16.  
    17.     // Always handle physics updates here.
    18.     void FixedUpdate()
    19.     {
    20.         // Get input.
    21.         float h = Input.GetAxisRaw("Horizontal");
    22.         float v = Input.GetAxisRaw("Vertical");
    23.         Vector3 movement = new Vector3(h, 0f, v).normalized;
    24.  
    25.         // Create a quaternion (rotation) based on the movement vector.
    26.         Quaternion moveDir = Quaternion.LookRotation(movement);
    27.  
    28.         if (movement != Vector3.zero)
    29.         {
    30.             // Add movement force to the player's rigidbody.
    31.             playerBody.AddForce(movement * speed, ForceMode.Acceleration);
    32.  
    33.             // Set player's rigidbody rotation to the new rotation.
    34.             playerBody.MoveRotation(moveDir);
    35.         }
    36.  
    37.         // Drop any pickup when you press the Fire1 button.
    38.         if (Input.GetButton("Fire1"))
    39.             DropPickup();
    40.     }
    41.  
    42.     // We use a trigger attached to the player to detect pickups.
    43.     void OnTriggerEnter(Collider other)
    44.     {
    45.         // It's a much better idea to check tags instead of names.
    46.         // Example: if (other.gameObject.CompareTag("Pickup")),
    47.         // however this is easier to setup for the demo.
    48.         if (other.gameObject.name.StartsWith("Pickup") && !pickup)
    49.         {
    50.             // Disable physics on the pickup.
    51.             other.attachedRigidbody.isKinematic = true;
    52.             // Place pickup in player's hand.
    53.             other.transform.position = hand.position;
    54.             // Attach pickup to player's hand.
    55.             other.transform.parent = hand;
    56.             // Save reference to pickup.
    57.             pickup = other.attachedRigidbody;
    58.         }
    59.     }
    60.  
    61.     void DropPickup()
    62.     {
    63.         if (!pickup)
    64.             return;
    65.  
    66.         // Disconnect pickup from player's hand.
    67.         pickup.transform.parent = null;
    68.         // Re-enable physics on the pickup.
    69.         pickup.isKinematic = false;
    70.         // Add a little throwing force for fun.
    71.         pickup.AddForce(playerBody.velocity + hand.forward * speed, ForceMode.Impulse);
    72.         // Remove reference to pickup.
    73.         pickup = null;
    74.     }
    75. }
    Only issue with this is that the pickup wont collided with walls when it's attached to the player?! @MelvMay is this a bug or am I doing something wrong? Is a rigidbody supposed to stop colliding with other objects when it's parented and kinematic?
     
    Last edited: Mar 1, 2017
  12. JLJac

    JLJac

    Joined:
    Feb 18, 2014
    Posts:
    30
    I'm requesting this! Working with procedural animation you often want to figure out the physics of some kind of simple main body first, and then attach limbs and protrusions to it. Another example is an enemy that has some kind of energy shield hovering in front of it - the energy shield obviously needs a collider, but you don't want the enemy to topple forward because of it.

    I suppose a workaround would be to cache or serialize the Rigidbody.centerOfMass, but a checkbox on the colliders would be super convenient.