0
votes

This is going to be a long post, so please bear with me. I first encountered this issue a long time ago. I made a post about it then but wasn't able to resolve it. I have since gathered some more details about it and I hope that this time I can make some progress.

My game doesn't use the built in physics system, instead characters perform a SweepTest in a given direction before updating their position with transform.position += moveVector;. This allows me to keep them nicely aligned to grid spaces, which is necessary for my puzzle game. Sometimes when playing my game, a character will fall through a piece of floor. Other characters or objects are able to move over the same piece of floor without any issues, it seems to be a special relationship between just the two objects, seemingly selected at random. It does not always effect the same character and it does not always effect the same piece of floor. It does not matter which direction the character approaches the floor from, or how it is positioned relative to the floor. Because of the given number of pieces of floor (or walls or other objects) as well as characters, I only stumble across it once in a blue moon. It can be months between when I happen upon it (I spend a lot of time developing but not a lot of time playing). Starting a new game in the application through the menus, restarting the application (if it is a built version) or exiting play mode (if it is in the editor) all fail to reproduce the issue in the same way. Again, it seems to happen seemingly at random.

This means that if I want to do some debugging I need to have it all coded before the current execution, because editing any scripts causes them to recompile and causes errors in the current session.

My game build the puzzle map by additively scene loading multiple puzzle rooms. I do not know if this could be a factor, but I will mention it anyway.

My character is has a scale of (100, 200, 100) and has a move speed of 20. His box Collider is set at a scale of (0.99, 0.99, 0.99). He also has a kinematic Rigidbody. The top of the floor is at a height of 0, and its box Collider has the equivalent scale of (1, 1, 1). The character normally sits at positional height of 100 and his origin is in his center. This means that his base should always be about 1 above the floor, meaning that his base should never align perfectly with the floor preventing floating point error overlap. This also means that a SweepTest with a magnitude of 20 downwards should pick up the floor without issue.

Character Inspector: https://i.imgur.com/c4detlv.png Floor Inspector: https://i.imgur.com/fQFotn7.png

Using some debugging I logged the results of every downwards SweepTest, here is the code and the results.

RaycastHit[] hitThings = GetComponent<Rigidbody>().SweepTestAll(moveVector, moveVector.magnitude, QueryTriggerInteraction.Ignore);
Debug.Log(gameObject.transform.position);
Debug.Log(moveVector);
Debug.Log(moveVector.magnitude);
Debug.Log(hitThings.Length);
foreach (RaycastHit hitThing in hitThings) {
    Debug.Log(hitThing.collider.name);
    Debug.Log(hitThing.collider.tag);
    Debug.Log(hitThing.collider.transform.parent.name);
    Debug.Log(hitThing.collider.transform.root.name);
    Debug.Log(hitThing.distance);
    Debug.Log(hitThing.collider.transform.position);
}
Debug.Log("--------");

.

(-2370.0, 100.0, -4050.0)
(0.0, -20.0, 0.0)
20
1
Piston
Piston
WalkwayExtendedEast
Tier1Room-
1.252515
(-2450.0, -50.0, -4000.0)
--------
(-2350.0, 100.0, -4050.0)
(0.0, -20.0, 0.0)
20
0
--------
(-2350.0, 80.0, -4050.0)
(0.0, -20.0, 0.0)
20
0
--------
(-2350.0, 60.0, -4050.0)
(0.0, -20.0, 0.0)
20
0
--------
(-2350.0, 40.0, -4050.0)
(0.0, -20.0, 0.0)
20
0
--------
(-2350.0, 20.0, -4050.0)
(0.0, -20.0, 0.0)
20
0
--------

The character starts on top of something solid. As soon as he moves 20 to the right he is now solely over the floor in question. As you can see the SweepTest does have any hits detected.

In a separate bit of debugging I did a Physics.OverlapBox() test on the character, to determine if the character begins inside the piece of floor in question. Here are those results.

Debug.Log(trasform.position);
Debug.Log(transform.scale * 0.99f);
Collider[] overlapping = Physics.OverlapBox(gameObject.transform.position, transform.scale * 0.99f / 2f);
foreach (Collider currentCollider in overlapping) {
    if (currentCollider.tag != "Character") {
        Debug.Log(currentCollider.tag);
        Debug.Log(currentCollider.transform.position);
        Debug.Log(currentCollider.transform.root.position);

    }
}
Debug.Log("--------")

.

(-2370.0, 100.0, -4050.0)
(99.0, 198.0, 99.0)
--------
(-2350.0, 100.0, -4050.0)
(99.0, 198.0, 99.0)
--------
(-2350.0, 80.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, 60.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, 40.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, 20.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, 0.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -20.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -40.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -60.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -80.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -100.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -120.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -140.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -160.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -180.0, -4050.0)
(99.0, 198.0, 99.0)
Fixture
(-2250.0, -75.0, -4000.0)
(-4000.0, 0.0, -4000.0)
--------
(-2350.0, -200.0, -4050.0)
(99.0, 198.0, 99.0)
--------
(-2350.0, -220.0, -4050.0)
(99.0, 198.0, 99.0)
--------

These results indicate that the character is not overlapping with the floor in any way, until it has started falling through it.

My character is kinematic. Making it non kinematic and enabling gravity will cause the character to come to rest on the offending piece of floor. This, combined with the Physics.OverlapBox() results show that the physics system is detecting the floor, just not the SweepTest.

I tried moving the piece of floor variously up and down slightly, the character still passed through. I tried moving the piece of floor to chest height so the character could approach it from another angle. Still, the the character was able to move through the object, but no other objects or characters could.

Character through object: https://imgur.com/VwbUfUx

I copied and pasted the piece of floor. My character was not able to phase through this new object and the debugging showed this as well. Even though the copied object had the exact same properties and values, collisions were now being detected with the SweepTest.

Adding another box Collider to the floor also prevented my character from falling through it. So did adding another box Collider to my character. I think that something is wrong with these two particular instances of these Colliders, adding a new Collider or copying the object was creating a new Collider component instance and that new instance presumably has the same "once in a blue moon" chance of having this glitch.

Lastly, I tried resetting each of the components on the offending piece of floor and my character. This didn't yield any results, the character was still able to pass through it.

Although it happens rarely, this can cause a big problem in a game. The player with the effected character could be forced to restart his game. Being unable to cross certain sections of floor can make many games unfinishable. The other players can not finish the game without every player present, so they would be interrupted as well.

This seems to me to be a problem with Unity, not with my code. If anybody knows of a Unity staff member in these forums, could they please tag the staff member? I think that would go a long way to narrowing in on this issue.

My theory: Although I have only experienced this issue between one of my characters and another object, I would hazard a guess that it is possible for this issue to occur between any two objects. Seeing as I only happen upon it once every couple of months and seeing as my characters are the objects which most often come into contact with other objects, I doubt I'm likely to ever experience this. This problem seems to link two Collider instances together and I imagine that there is a very slight chance that any two Colliders will have this relationship.

Unity 2018.4.9f1 Personal LTS. Physics settings: https://imgur.com/pKI7Q07

1
SweepTest is expensive, and kind of overkill. Does the bug still occur using simple raycasts or boxcasts?Louis Garczynski

1 Answers

0
votes

Being completely honest, I just read like the first two paragraphs; but I may know the answer, since am having similar issues and am also not using Unity physics at all. You said that collisions aren´t always detected with a SweepTestAll. This may be due to float´s lack of precision, which sometimes detect the collision slightly wrong. The object is moved slightly more than is supposed to and, because the object is now on top of the collider, the next frame the sweepTest doesn´t detect the collision in that same direction. You said that it happens with the floor, which makes me think you are constantly moving down. The solution is as simple as moving the player backwards and then increasing the SweepTest length (am doing a simple sweeptest).

    if (movement != Vector3.zero)
    {
        float maxDistance = movement.magnitude;
        //this because magnitude * (offset / magnitud) = magnitude´s offset for behindVector (in this case 0.003 which I thought was a safe number)
        Vector3 behindVector = movement * (0.003f / maxDistance);//keeps movement´s direction. Since magnitude is always positive, behindVector keeps movement´s sign.    
        transform.position -= behindVector;

        maxDistance += 0.003f;//movement.magnitude + behindVector.magnitude

        RaycastHit hit;
        if (rb.SweepTest(movement, out hit, maxDistance))
        {
            if (hit.collider)//movement will become the vector of hit.distance
            {
                movement = movement / movement.magnitude * hit.distance;
            }
        }
    }

Am using 0.003f as offset, but I´ve discovered that is still not perfect when you jump while moving on very inclined slopes (since the movement´s direction is less inclined than the slope). The solution is increasing the offset. I haven´t tested that yet, but am going to go for a bigger number like 0.1; however, this causes another issue: you may now collide with something that should be behind you. To solve this, you need to check that the hit distance is > offset, so I would recommend doing a SweepTestAll here. Or maybe a bunch of sweeptests, whatever suits you better.