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
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Creating 2d image map of objects based off 3d space colliders Unity C#

Discussion in 'Scripting' started by mudflaps, May 12, 2020.

  1. mudflaps

    mudflaps

    Joined:
    Mar 16, 2017
    Posts:
    8
    I have been trying the last few days to generate a .jpeg/.png image colour tile map based off of a unity scene where it is mapping objects with the layer "block", anything that it collides with including inside of, gets marked in a colour. Anything it doesnt is marked in black. Imagine a 1025 grid with the below format as an example, an "x" is a area with an object / series of objects with the layer as "block", and a mesh collider.

    Code (CSharp):
    1.     xxx             x                x
    2.  
    3.     xxx                      xxx        x
    4.  
    5.     xxx      xx              xxx

    Assuming everything has the y value 0, I have been using two for loops to iterate through space x and z and I have tried raycasting 1 point forwards which works, except it wont map the inside of objects. I cant get a cube with a collider to generate the map either, OnCollisionEnter doesnt trigger.

    What I am aiming for is ideally using a point of scale 1,1,1 to iterate through each point on the map using the for loops and saying basically *I am inside of a collider with tag* or *not inside collider* and it moves like so

    Code (CSharp):
    1. 1 - inside collider
    2.  
    3.  
    4.  
    5.  
    6.     0xx             x                x
    7.  
    8.     xxx                      xxx        x
    9.  
    10.     xxx      xx              xxx
    11.  
    12. 2 - inside collider
    13.  
    14.  
    15.  
    16.  
    17.     x0x             x                x
    18.  
    19.     xxx                      xxx        x
    20.  
    21.     xxx      xx              xxx
    22.  
    23.  
    24.  
    25.  
    26. 3 - inside collider
    27.  
    28.  
    29.  
    30.  
    31.     xx0             x                x
    32.  
    33.     xxx                      xxx        x
    34.  
    35.     xxx      xx              xxx
    36.  
    37.  
    38.  
    39.  
    40. 4 - not inside collider
    41.  
    42.  
    43.  
    44.  
    45.     xxx0            x                x
    46.  
    47.     xxx                      xxx        x
    48.  
    49.     xxx      xx              xxx


    For the raycasting, it almost works fine except for mapping the inside of objects, and it seems to have weird clipping around objects where its placing ray collisions a few points away from the object mesh colliders.

    I created the `mapMarker` game object to visually display where the rays were colliding to help my debugging, and it is on the edges of objects. Which is how i discovered the weird clipping where its a good few points out.


    Code (CSharp):
    1.      for (float i = 0; i < 1025; i++)
    2.  
    3.         {
    4.  
    5.             for (float j = 0; j < 1025; j++)
    6.  
    7.             {
    8.  
    9.                 height = getHeightOfTerrain(j, i);
    10.  
    11.      
    12.  
    13.                 // -1 as we are casting 1 point forward
    14.  
    15.                 Ray r = new Ray(new Vector3(j-1, height, i), new Vector3(1, 0, 0));
    16.  
    17.                 RaycastHit hit;
    18.  
    19.  
    20.  
    21.  
    22.                 // if ray hits collider with the layer mask
    23.  
    24.                 if (Physics.Raycast(r, out hit, 1, LayerMask.GetMask("block")))
    25.  
    26.                 {
    27.  
    28.                     obj = hit.transform.gameObject;
    29.  
    30.  
    31.  
    32.  
    33.                     // add the colour to the 2d map
    34.  
    35.                     texture.SetPixel((int)hit.point.x, (int)hit.point.z, new Color(255, 0, 0));
    36.  
    37.  
    38.  
    39.  
    40.                     GameObject mapMarker = GameObject.CreatePrimitive(PrimitiveType.Cube);
    41.  
    42.                     mapMarker.name = obj.name;
    43.  
    44.                  
    45.  
    46.                     mapMarker.GetComponent<Renderer>().material.color = new Color(255, 0, 0);
    47.  
    48.                     mapMarker.transform.position = new Vector3(hit.point.x + 1f, height + 2f, hit.point.z);
    49.  
    50.                 }
    51.  
    52.                 else
    53.  
    54.                 {
    55.  
    56.                     texture.SetPixel((int)j, (int)i, new Color(0, 0, 0));
    57.  
    58.                 }
    59.  
    60.             }
    61.  
    62.         }
    How would you approach this? Is my method just wrong? I tried iterating a box collider through the above code and doing collision tests on the box collider but it never triggered anything.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,780
    This certainly seems like a reasonable way to automap a level, but you won't be able to performantly do this every frame. As long as you realize this is a "when the level loads up" type of approach, this should work.

    Some other tips:

    Raycast will only hit the very first thing and nothing else. RaycastAll will return a bunch of things, everything the ray passes through.

    Raycast only hits colliders, modulo setting the extra flag
    queriesHitTriggers
    . If you have a tiny object occupying a 1x1 square not precisely at whatever your math considers to be the "center" where the ray goes through, it won't be hit.

    You won't be able to iterate a box collider because that only triggers during the physics phase of updating, which occurs NOT when your script is running. Here is how Unity operates each frame:

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
  3. mudflaps

    mudflaps

    Joined:
    Mar 16, 2017
    Posts:
    8
    EDIT: edited out the edit

    Thanks for the speedy reply, I am running this script once to "export" the map. I think what you have described with `queriesHitTriggers` is exactly my problem for it missing certain objects that the ray goes past, or perhaps why there are strange boxes appearing where there are no objects nearby. I'll give that a google.

    Good to know I shouldn't focus on using a box collider, I probably spent the best part of 2 days getting frustrated at that!

    In respect of doing something like
    texture.SetPixel((int)hit.point.x, (int)hit.point.z, Color(255, 0, 0));
    inside of a collider, what is the best way to determine this as the ray wont touch it when during the iteration the point has moved inside of an objects collider?
     
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,724
    Instead of raycasting, what if you just gave your "block" objects a child Cube GameObject on a "map" layer with the appropriate color. Hide the "map" layer on your main camera. Then you could use a camera with its culling mask set to only render the "map" layer, and use a technique like this: https://forum.unity.com/threads/how-to-save-manually-save-a-png-of-a-camera-view.506269/ to just render that camera's view out to a PNG. It should be a lot faster than doing raycasts.

    After you're done, you can delete all the unneeded GameObjects and cameras etc..
     
    Kurt-Dekker likes this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,780
    That's why I was suggesting using RaycastAll... coupled with your "block" layer mask, this should return to you a list of items that you are interested in, and you can iterate over them:

    https://docs.unity3d.com/ScriptReference/Physics.RaycastAll.html

    Now if one collider you hit should be drawn RED and one should be GREEN, that's sort of up to you to decide what to do in that case, and very specific to your game's needs.

    ALSO... to avoid missing stuff, there is this approach:

    https://docs.unity3d.com/ScriptReference/Physics.SphereCastAll.html

    It is similar to raycast but actually sweeps out a "tube" of space. I would also lift myself up really high above your map, like perhaps above the highest mountain, and raycast down a long way, like twice the highest mountain height.

    And since it is an export step, you can also make it run super-fine-grained checks if you don't care that it might take a long time to build the map. That's another way to make sure you hit everything you're interested in, by just doing a bazillion sub-checks per pixel area. I would make a tiny map to test it first, just to keep the iteration time short!