Rigidbody: All "Collision Detection" options come with issues/bugs in my case. How to proceed?

TL;DR? : My second paragraph with red & green text + my illustration at the bottom of the post pretty much sum up my problem. Everything else is just additional information to help narrow down my issue.

Hi all,

I’m having issues properly detecting collisions with a rigidbody, no matter which collision detection method I choose. The player/vehicle in my game can go extremely fast - it doesn’t actually have a limit but realistically it goes up to ~2000km/h. I’m working with “default units” (meters, seconds, kg). It is not meant to be an accurate physics simulation - I initially tried to use CharacterController, but since you can’t rotate its capsule collider, that made ramps a pain. Here is what happens now depending on my setting:

  • Discrete: Character can go through “corners” and walls
  • Continuous: Character can go through “corners” but not walls
  • Continuous Dynamic: Character can go through “corners” but not walls
  • Continuous Speculative: Character can correctly NOT go through corners nor walls, but has “ghost collision” issue, causing the player to randomly lose speed (an intentional collision penalty) or gain height on planes (classic ghost collision issue described in these docs).

This is a 3D game.

I am moving the player by directly setting the rigidbody’s velocity.

I believe the ghost collision is an issue because the level/world is generated by a tile-like runtime level editor (there’s no actual unity “tile” stuff involved, it’s just a tile-like/grid-restricted 3d level editor). So, when riding along a large plane, it is usually made up of multiple smaller planes which are on the same Y-position, rather than one big continuous plane.

I assume the other three aren’t working because of the extreme speed of my player.

Here’s an illustration of my issues: (The blue model is the player - a bit like a bullet)
RED lines: where the character can improperly go through when not using “Speculative”
BLUE lines: where collisions always work properly, even at 2000km/h (edit: discrete can fail on the walls here sometimes)
PINK lines: where I get ghost collision errors when using “Speculative”

in this screenshot, the cubes/tiles are 10x10x10, and the player is roughly 1x0.8x2
Green floors are “planes”(extremely thin cubes)
Cubes are regular ol cubes
Orange walls are rotated “planes”(extremely thin cubes)

  • Would a viable solution be to scale everything down (as in, use the units as decameters, hectometers or kilometers rather than meters), so that from unity’s perspective the player isn’t going so fast? I’m not sure if that would actually change anything, since the “corners” would also get smaller.

  • Is this expected to happen where 2 cubes meet? Do “corners” NEED to be thicker in order to have proper collision?

Any help would be greatly appreciated, I’ve searched a ton and haven’t really found any good solutions for my case in 3D… Although I’m sure this has been discussed a lot, maybe I just suck at using the forum search.

I was breaking my head over this one… finally got around to a solution that seems to work well… Raycasts of course! Well, I was playing around with raycasts for a while before I could finally get a good implementation working, here’s what I ended up with:

(code within FixedUpdate()):

// check if collision is coming up soon with current speed
Physics.Raycast(frontCollisionCaster.position, playerModel.forward, out frontCollisionHit);
float distMovedPerTick = speed * Time.deltaTime;
float movementScale = 1f;
if (frontCollisionHit.distance > 0.0f &&
    !frontCollisionHit.collider.isTrigger &&
    frontCollisionHit.distance <= distMovedPerTick)
{
    // scale the movementScale to 95% of itself to leave a margin of error
    movementScale = (frontCollisionHit.distance / distMovedPerTick) * 0.95f;
}

// (...)

// move in the direction that the model is facing, but severely reduce speed if unity
// detects any collisions, to prevent clipping/going through obstacles
velocity = playerModel.forward * speed * movementScale *
    (isColliding ? Mathf.Lerp(0.05f, 0.5f, 1f - speed / MAX_SPEED) : 1f);

velocity += gravityForce;
velocity += floatingForce;

rb.velocity = velocity;

Essentially, I check how many units per fixedTimeStep I’m moving at my current speed, and if that distance exceeds the obstacle distance retrieved from a frontal raycast, then I scale down my movement speed vector just enough so that your scaled speed will bring you to just before the obstacle rather than inside it or past it. The additional gravityForce & floatingForce are small/weak enough that they don’t need to be scaled down to prevent collisions. I’ve tested for a decent while with 2000 km/h (where speed = 555.55555) and it seems to work very reliably!

The isColliding ternary in the initial setting of velocity is somewhat unrelated to this fix, it’s simply how speed is affected when colliding in my game, since I am controlling the velocity myself rather than letting unity handle it. It needs to be scaled down while colliding so that unity can continue detecting its collisions rather than moving it through an obstacle. Note the isColliding bool is simply set within on the OnCollisionEnter() & OnCollisionExit() functions.

I can imagine some imperfections with this design (for example, if you’re constantly turning this could improperly miss an obstacle or think you’re going to crash for 1 fixedTimeStep), but it should be accurate enough in my case.

With this, I settled with the collision detection on “Continuous Dynamic”. Even Discrete mostly worked now, but it seems to still have issues very rarely so I’m sticking with Continuous Dynamic to be safe.

I did end up specifying a max speed of 2000km/h, just to know this will be the limit I need to detect - that speed will very rarely be reached or properly controlled anyways.

Hope this can help anyone who needs it.

Feels resolved to me, but I’m all ears if anyone comes across this and has a better idea.