If you have an object with a Rigidbody, you should avoid moving it with its Transform.
That includes setting transform.position
, transform.rotation
, transform.localEulerAngles
, or calling transform.Translate
, transform.Rotate
, etc. All of these rip the object out of the physics engine's control, and are treated like an instantaneous teleportation - allowing it to skip over collisions that should have happened in-between.
Basically, treat your Transform as read-only. Any and all changes should be applied via the Rigidbody.
You can replace your localEulerAngles code with something like...
Quaternion targetOrientation = transform.parent.rotation * Quaternion.Euler(inputEulerAngles);
(You can skip the multiplication by the parent rotation if you're already at the root level of the hierarchy)
Then you can either apply that rotation directly:
rigidbody.MoveRotation(targetOrientation);
Or, in FixedUpdate, you can apply a torque to reach that target for the next timestep:
(If I recall correctly, you'll need to make your body dynamic rather than kinematic for this approach, but it can play nicer with the rest of your physics world)
// Compute the change in orientation we need to impart.
Quaternion rotationChange = targetOrientation * Quaternion.Inverse(rigidbody.rotation);
// Convert to an angle-axis representation.
rotationChange.ToAngleAxis(out float angle, out Vector3 axis);
// I can't remember the range of the returned angle, so you might not need this.
if (angle > 180f)
angle -= 360f;
// If you need to, you can enforce a cap here on the maximum rotation you'll
// allow in a single step, to prevent overly jerky movement from upsetting your sim.
// angle = Mathf.Clamp(angle, -90f, 90f);
angle *= Mathf.Deg2Rad;
// Compute an angular velocity that will bring us to the target orientation
// in a single time step.
var angularVelocity = axis * angle / Time.deltaTime;
// You can reduce this parameter to smooth the movement over multiple time steps,
// to help reduce the effect of sudden jerks.
float catchUp = 1.0f;
// Apply a torque to finish the job.
rigidbody.AddTorque(catchUp * angularVelocity - rigidbody.angularVelocity, ForceMode.VelocityChange);
Note that even when we do our best to give the physics engine full control over the situation, rotational collisions are kind of hard to detect efficiently. If you have a long skinny object, a small rotation can make its distant end jump all the way from one side of an object to another. So other things we can do to reduce tunnelling:
Expand the collider of your floor and walls to give them some extra padding out to the sides and underneath. You can use layers or Physics.IgnoreCollision()
to make sure your expanded floor collider and wall colliders don't interfere with one another if they're not part of the same body (not sure if you move them as one unit, or if you have a moving floor inside stationary walls)
As long as your ball stays on top, this extra padding won't be noticeable to the player, but it can give the collision resolution system more margin for error so it can still determine the side to push the object out through. The thinner your object is, the more chance that a collision ends up slightly past the middle plane and gets pushed out the wrong side.
This change is very cheap, and surprisingly robust, so I'd make it your first option.
Set the ball's collision detection mode to Continuous Dynamic, and the moving part of the maze to use Continuous Speculative collision detection.
These modes are much more expensive than the default discrete mode, but they catch more potential tunnelling cases for fast-moving objects. If you have only a small number of colliders interacting, it might not be too heavyweight.
Shorten your fixed timestep, so you simulate physics more frequently at smaller steps, making it less likely for one object to teleport through another.
This too is expensive, so be sure to profile here.
A combination of these strategies should let you tame your rotational collisions and keep your ball behaving plausibly.