Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Bug Raycast2D against procedural tilemap (w/o composite) does not detect tilemap collider

Discussion in '2D' started by sidestepper, Jul 12, 2022.

  1. sidestepper

    sidestepper

    Joined:
    Oct 2, 2008
    Posts:
    96
    Posting for posterity's sake. Just wasted a couple hours trying to figure out why doing a Physics2D.Raycast and/or Physics2D.RaycastNonAlloc against a tilemap collider (Without composite) will FAIL if the map was built within the same frame.

    CONTEXT:
    I've got a 2D procedurally generated "cave" game. It does a lot of work on load, including using a noise function to generate a texture, then a series of operations on that texture which ultimately gets turned into a tilemap using rule tiles and such.

    Immediately after terrain generation and rendering, it spawns entities based on spawn tables built using scriptable objects. Objects being spawned so far were just trying to find empty spots, which works just fine by checking the "walls" tilemap for empty places to put new entities. However, we just added a new entity type that spawns against a wall - and this was where our trouble began.

    Capitalizing on the original methods to find an empty position in the tilemap - I then used the approach to find a random direction from the empty spot found, and raycast against the terrain to find a spot on a wall to spawn the new entity - and every attempt to do so failed to collide with anything...

    SOLUTION:
    After much deliberation, it was discovered that the logic was fine, but trying to raycast against a tilemap collider on/within the same frame it was "generated" or modified on must not have updated colliders.

    This was discovered after attaching the spawning of these entities to a button instead of on load of the map - once attached to a button press, it worked without issue.

    CONCLUSION:
    Perhaps I missed something in the documentation, but also searching online yielded nothing useful. This was headache enough I felt the need to post about it in case it helps someone in the future. If it's not in the documentation, it should be added and made bold: Dynamically creating a tilemap does not immediately update/generate colliders, one must wait a frame before tying a 2D raycast - or it will always fail.

    EDIT 1:
    Turns out frame is not even the important part - I had to set an intentional delay of 1-3 seconds before spawning these entities. I would hope that if it's an inner working of Rule Tile (Such as queueing data before actually creating colliders and stuff) then there should be an event I can use to hook into when it's ready - not yet found
     
    Last edited: Jul 12, 2022
  2. DanielTanBK

    DanielTanBK

    Unity Technologies

    Joined:
    Aug 20, 2019
    Posts:
    80
    sidestepper likes this.
  3. sidestepper

    sidestepper

    Joined:
    Oct 2, 2008
    Posts:
    96
    I very much appreciate the link. Not sure how I missed this, but thanks! I removed our intentional delay and added a call to ProcessTilemapChanges and that did in fact solve the issue. For reference, if anyone else finds this useful, based on your link I also spotted this one, which may be used when you're not sure if you need to process changes or not but want to check: https://docs.unity3d.com/ScriptReference/Tilemaps.TilemapCollider2D-hasTilemapChanges.html

    Edit : I could not find a way to remove the Bug tag, which no longer applies
     
  4. atilla_the_hun

    atilla_the_hun

    Joined:
    Apr 14, 2020
    Posts:
    2
    Hi,

    I'm having a similar problem to the one described above, but unfortunately calling ProcessTilemapChanges doesn't work in my case. I've a dynamically created hex grid, with four tilemaps as children, with each tilemap providing a layer over each other - terrain, fog of war, movement, and path finding. What I want to do is create a line-of-sight algorithm, by taking an origin hex, finding all the hexes within a certain range of the origin, then doing a Physics2D.LinecastAll on all the hexes in range. With all the RaycastHit2D results for each hex in range, I can then determine which hexes the origin hex can see, based on the height of each hex in the results. My problem is that I'm only able to ever detect the first (origin) hex, even though (debug) drawing the lines shows that they pass into all the required hexes.

    As I said, the tiles are dynamically set into the tilemaps, with the collider types also being dynamically set (I've tried all the types with no success). The map is created in the Start method after which the LinecastAll method is called. One of the tilemaps I'm using has a TilemapCollider2D (and I've tried adding Ridgidbody2D as well). The code to is below

    Code (CSharp):
    1.  
    2. TilemapCollider2D terrainCollider =     terrain.GetComponent<TilemapCollider2D>();
    3.             if (terrainCollider.hasTilemapChanges)
    4.             {
    5.                 terrainCollider.ProcessTilemapChanges();
    6.             }
    7.  
    8.             BaseHex unitPosition = unitStack.Position;
    9.             Vector3 origin = terrain.CellToWorld(unitPosition.PositionToVector());
    10.  
    11.             BaseHex[] hexesInRange =
    12.                 Hex.NeighborsWithInRange(
    13.                     unitPosition,
    14.                     unitStack.MaxVisibilityRange(),
    15.                     hexData.MapWidth,
    16.                     hexData.MapHeight,
    17.                     false);
    18.  
    19.             for (int i = 0; i < hexesInRange.Length; i++)
    20.             {
    21.                 BaseHex hex = hexesInRange[i];
    22.                 Vector3 destination =
    23.                     terrain.CellToWorld(hex.PositionToVector());
    24.  
    25.                 RaycastHit2D[] results = Physics2D.LinecastAll(destination, origin);
    26.                 Debug.LogError("Number of results = " + results.Length + ", origin=" + origin + ", destination=" + destination);
    27.  
    28.                 foreach(RaycastHit2D hit in results)
    29.                 {
    30.                     Vector3Int hitLoc =
    31.                         terrain.WorldToCell(new Vector3(hit.point.x, hit.point.y, 0));
    32.                     Debug.LogError(hitLoc);
    33.                 }
    34.             }
    I'm just wondering what I'm missing here. It seems from the above post that his should work. Thanks in advance for any advice on this.
     
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,623
    That's because your problem isn't the same. ;) It is far better to create your own threads rather than hijacking existing ones TBH because now this thread is solving two different problems.

    A TilemapCollider2D is a single collider, it's not a collider per tile. The physics query will only detect a single hit on a collider. If there were multiple colliders then it would detect a hit on each but as I said, each tile is not an individual collider which should be clear because it's all contained within a single component (TilemapCollider2D deriving from Collider2D).