1
\$\begingroup\$

I have this method, which just raycasts at a position downwards, and checks what tile is there, if there is one:

    public static Tile RaycastTile(Vector3 position)
    {
        Debug.Log("raycastTile at pos: " + position);
        var layerMask = 1 << LayerMask.NameToLayer("Floor");
        RaycastHit hit;
        Debug.DrawRay(position, new Vector3(0, -10, 0), Color.red, 50);
        if (Physics.Raycast(position, new Vector3(0, -1, 0), out hit, 10, layerMask))
        {
            var tile = hit.collider.gameObject.transform;
            Debug.LogWarning(tile.name + " - " + tile.parent.name + " - at pos: " + tile.position);
            var coordinates = new Vector2Int(Mathf.RoundToInt(tile.position.x) / 2, Mathf.RoundToInt(tile.position.z) / 2);
            return map.GetTile(coordinates);
        }
        else
        {
            Debug.LogError("Tile not found under position: " + position);
            Debug.Break();
            return null;
        }
    }

Currently it is called for the player

  • at Start() ("starting at: < player's absolute position >")

  • and at Update() ("update at pos: < player's absolute position >")

  • And the map is being generated (tiles created, destroyed, etc) in Awake().

and for some reason this is the output:

enter image description here

And the map generation works like this:

  • Creates a room at (0, 0) with tiles and all, then it searches for a connection point on the existing map, whom the new room could be connected to. (with some rotation, if it's needed)

  • (Every tile is 2x2 meters, and they are on a strict grid)

So for some reason calling this method at Start() (with the same position) hits a totally different tile. While in Update() it works correctly.

At first I thought that it's because of Destroy(), because the objects are only destroyed at the end of the frame. But I tried DestroyImmediate() and it didn't help. (And after thinking, I realised that destroying objects at the end of the frame wouldn't cause issues. Or is it?)

Then I had the idea, that maybe Start doesn't wait for Awake because it compute heavy. And the raycast in the Start hits a (yet) non translated newly generated room. So I edited the code so every room is initially created at very far away, which is relatively "infinite far away" from the player: at (9999, 9999). But the issue remained.

EDIT: With (9999, 9999) Sometimes (maybe half of the time?) now it is correctly raycasted even at start, while before it was never correct in start.

EDIT2: With (0, 0) it hits correctly at start as well, but very very rarely.

Do you have any idea what could cause this issue?

Sorry if there isn't enough info, feel free to ask. :)

\$\endgroup\$
2
\$\begingroup\$

The game world of Awake/Start/Update, and the physics world of PhysX, are actually two separate sets of data. It takes some extra work from the engine to apply changes from one world to the other.

Normally this is done once in the physics step for each FixedUpdate, but depending on frame timings, we might not get a physics step / FixedUpdate in the frame that Start runs.

My best guess here is that Start is sometimes asking the physics engine to raycast before the physics engine has been told about all of the tiles, so it gets stale data.

According to the docs, you can force a sync manually with Physics.SyncTransforms, which might help flush any pending transform updates through the pipe before you start your raycast.

Or, if your game can tolerate taking a moment to settle when the objects are first spawned, you can let Start wait for a FixedUpdate or two for the physics to be up to date before it tries to raycast against it, something like this:

IEnumerator Start() {
    // Wait until the first physics step runs...
    yield return new WaitForFixedUpdate();
    // Wait until later in that frame, 
    // after physics is stepped
    yield return null;

    // Now do physics tests.

}
\$\endgroup\$
  • \$\begingroup\$ Physics.SyncTransforms doesn't seem to fix it, I think it's async. But waiting for the first FixedUpdate seems fine. How does that "yield return null" works? And do you know a way to delay every start? Because starting a coroutine in every MonoBehaviour seems pretty dull, it would be better to just systematically wait one fixed update between Awake and Start. \$\endgroup\$ – Tudvari Mar 31 at 16:29
  • \$\begingroup\$ And is it sure that my coroutine will finish by the time of my first update? Update: I just have 2 players, and the order was this: Coroutine1, Update1, Coroutine2 This could cause some undeterministic issues :\ \$\endgroup\$ – Tudvari Mar 31 at 16:34
  • 1
    \$\begingroup\$ To delay all of your setup scripts without coordinating tons of coroutines, I'd recommend giving them an interface like IInitializable, and implement their setup logic in public Initialize methods. Then your level generator can do its generation, start a coroutine, then call all the Initialize() methods in a batch once the physics have ticked once to ready the level. That way you can be sure all of the initialization happens at the same time. For deeper information on Coroutine timing, I recommend reading the docs on execution order \$\endgroup\$ – DMGregory Mar 31 at 16:39
  • \$\begingroup\$ Having nearly everything descended from IInitializable is a bit cumbersome, isn't it? Isn't there a way to separate initialization of the scene and starting of the scene? For example the scene won't call it's Awake, Start, ... methods until I signal OK. \$\endgroup\$ – Tudvari Apr 2 at 22:15
  • 1
    \$\begingroup\$ There is no such requirement to descend from a particular class. If you have questions about implementing this, feel free to post a new Question. \$\endgroup\$ – DMGregory Apr 2 at 23:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.