0
\$\begingroup\$

I am trying to create a basic collider for a rigidbody player in Unity 3D. I have been experimenting with numerous shapes but found it hard to get what I want.

The primary issue seems to be if the object does not have a square and large base with which to contact the floor, it will develop a tendency to accumulate angular velocity and spin like a top, even if you just try to make it walk forwards/back, and especially if a turn is initiated.

However, I need smaller objects and ones that can go over small bumps, which a big large cube can't.

I have tried:

1) Capsule

If I increase the mass these seem to behave okay and don't spin when walking straight. They can climb small bumps. But if you put one on a rotating platform, it is not rotated by the platform, since it presumably only has one contact point with the ground and thus can't be rotated by it.

2) Beveled Cylinder

I thought the logical solution would then be a beveled cylinder which looks like this:

enter image description here

However this spins like crazy! It develops angular velocity in the Y axis even when just moving it forward or back. And I can't seem to damp the angular velocity with my code either. I don't know where it's coming from or why but it's unusable.

3) Beveled Cube

The next idea I then had was for a beveled cube. This works okay if the base and overall cube is very large, but if you shrink the base to make it better able to climb bumps or make it more "human sized" it also starts spinning like crazy:

enter image description here

I am at a loss for how you are supposed to move around 3D characters as rigidbodies.

My motion code I've been testing is:

 private void CheckKeyboardMovementCommand() {
        if (Input.GetKey(KeyCode.UpArrow)) {
            //Move the Rigidbody forwards constantly at speed you define (the blue arrow axis in Scene view)
            playerRB.velocity = new Vector3(playerGO.transform.forward.x * playerMotionSpeed, playerRB.velocity.y, playerGO.transform.forward.z * playerMotionSpeed);
        }

        if (Input.GetKey(KeyCode.DownArrow)) {
            //Move the Rigidbody backwards constantly at the speed you define (the blue arrow axis in Scene view)
            playerRB.velocity = new Vector3(-playerGO.transform.forward.x * playerMotionSpeed, playerRB.velocity.y, - playerGO.transform.forward.z * playerMotionSpeed);
        }

        if (Input.GetKey(KeyCode.RightArrow)) {
            //Rotate the sprite about the Y axis in the positive direction
            playerRB.angularVelocity = new Vector3 (0, 3, 0);
       
        }

        if (Input.GetKey(KeyCode.LeftArrow)) {
            //Rotate the sprite about the Y axis in the negative direction
            playerRB.angularVelocity = new Vector3(0, -3, 0);
            //playerGO.transform.Rotate(new Vector3(0, -1, 0) * Time.deltaTime * playerTurnSpeed, Space.World);
        }

        if (Input.GetKeyDown(KeyCode.Space)) {
            playerRB.velocity += new Vector3(0, 9, 0); // add a jump velocity
        }

        if (Input.GetKeyUp(KeyCode.UpArrow) || Input.GetKeyUp(KeyCode.DownArrow)) {
            playerRB.velocity = new Vector3 (playerGO.transform.forward.x * 0, playerRB.velocity.y, playerGO.transform.forward.z * 0);
            playerRB.angularVelocity = new Vector3(0, 0, 0);

        }
        if (Input.GetKeyUp(KeyCode.RightArrow) || Input.GetKeyUp(KeyCode.LeftArrow)) {
            killAngularVelocity = true;
            playerRB.angularVelocity = new Vector3(0, 0, 0);
        }
    }
        // Update is called once per frame
    void Update()
    {
        CheckKeyboardMovementCommand();
    }

However, even putting in commands to kill the angular velocity on releasing a button don't work. These "spinning" rigid bodies will maintain high angular velocity in the editor display and keeping spinning inappropriately.

I have my player object frozen in the z and x axis so it doesn't fall over, but otherwise I need the y-axis unfrozen for movements and to be rotated, eg. by platforms.

Can anyone explain what might be going on or how I can have a collider that:

  • doesn't spin like a top
  • can also climb small bumps
  • works whether big or small
  • can be rotated by an underlying rotating platform?

Why do these objects develop such crazy angular velocities that don't go away even when you stop their motion or try to directly zero it out? Even when they're not moving and appear to be perfectly still the angular velocity can persist in the Editor panel.

Thanks.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ I am not sure if this is connected to the issue at hand, but when you have a rigidbody on an object, it's usually a bad idea to still manipulate its transform directly. So you should perhaps replace those transform.Rotate calls with playerRB.MoveRotation. \$\endgroup\$
    – Philipp
    Commented Mar 25, 2022 at 11:13
  • 1
    \$\begingroup\$ And have you considered using a CapsuleCollider? It's usually the preferred collider for characters which don't need more accurate hitboxes. \$\endgroup\$
    – Philipp
    Commented Mar 25, 2022 at 11:15
  • \$\begingroup\$ Yeah I tried a capsule and it spun like crazy. I thought maybe it was spinning because it didn't have a solid base for good "grip"/friction. Hence I made the beveled cylinder but it was no good. I will try changing the rotation to playerRB later today and update if it works. I copied that code from a Unity page example but I was wondering about that. Thanks. \$\endgroup\$
    – mike
    Commented Mar 25, 2022 at 11:17
  • \$\begingroup\$ Well I tried using playerRB.angularVelocity for rotation and that didn't fix it. Even if I don't myself rotate the object (just press up or down) it drifts if it's not a cube. Any other ideas welcome. \$\endgroup\$
    – mike
    Commented Mar 25, 2022 at 12:02

1 Answer 1

2
\$\begingroup\$

Set playerRB.angularVelocity = Vector3.zero each frame. Or tick the "Freeze Rotation: y" constraint in the Rigidbody component inspector. (Don't worry, this only prevents physics forces from twisting the object - your own code can still force the object to yaw with MoveRotation())

What's happening is that one edge of this collider brushes against a wall, obstacle, or even a bump/slope in the floor, imparting a torque to the body. If nothing opposes that torque, the body will develop a small angular velocity causing it to turn. Angular drag is fairly mild by default, so this small bump can cause a spin that lasts for quite a while, unless you expressly block it.

As Philipp suggests in the comments, a CapsuleCollider is a much better solution than the mesh you've shown. It's significantly more efficient for computing collisions, smoother, and will show fewer collision artifacts than a mesh collision.

MoveRotation() should also be preferred over Transform.Rotate() for physics bodies. You should treat the Transform component as read-only and direct every change in position and orientation through the physics system.


Your updated code working with angular velocity doesn't work because you are zeroing angular velocity only on the one frame when the player releases the arrow key, not on all the other frames when the body could pick up unwanted angular momentum. Your code should read something more like this:

float rotation = 0f;

if (Input.GetKey(KeyCode.RightArrow))
        rotation += playerTurnSpeed;

if (Input.GetKey(KeyCode.LeftArrow))
        rotation -= playerTurnSpeed;

playerRB.angularVelocity = new Vector3(0, rotation * Time.deltaTime, 0);

Note that this controls the angular velocity on every frame, not only the frames when the key is currently held or just recently released.


You'll also want to consider not hard-coding a specific keyboard key for each direction, and instead link these two directions to an axis. Using the old Input Manager, that would look like this:

float rotation = Input.GetAxis("Horizontal") * playerTurnSpeed;

(Note we no longer need two if statements - this one liner handles turning left, right, and stopping. It also supports gamepads and multiple synonymous key options out of the box)

Or using the new Input System, something like this:

 float rotation = steeringAction.ReadValue<float>() * playerTurnSpeed;

...where steeringAction is an Action that can be remapped flexibly to different inputs of your choosing.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .