Search Unity

Dynamic instantiating of prefabs and positioning with RayCast gives weird behaviour

Discussion in 'Physics' started by mtrompier, Jan 12, 2022.

  1. mtrompier

    mtrompier

    Joined:
    Jan 8, 2022
    Posts:
    4
    I am creating a map onto which I add some environment object as prefabs.
    I created a function to draw my object on a line, where I calculate dynamically the spacing between each objects so that it creates a wall that the user cannot go through.

    Here is the code:

    Code (CSharp):
    1.  
    2. private void DrawOnLine(List<GameObject> listOfWallObjects, List<int> listOfWallObjectsOccurences, Vector3 start, Vector3 end)
    3.     {
    4.         List<GameObject> listOfWalls = new List<GameObject>();
    5.  
    6.         //Calculate direction from start to end
    7.         Vector3 direction = (end - start).normalized;
    8.  
    9.         //Create random Tree or Rock at pos -100, -100, -100 to make sure that no object interferes with the raycast
    10.         GameObject wallObject = getRandomElementWithWeight(listOfWallObjects, listOfWallObjectsOccurences);
    11.         GameObject startWall = Instantiate(wallObject, new Vector3(-100, -100, -100), Quaternion.identity);
    12.  
    13.         //Create ray object
    14.         RaycastHit ray;
    15.  
    16.         //Store spacing between the first object and any next object
    17.         float previousRadiusSameDirection = 0;
    18.         //Calculate spacing
    19.         Vector3 rayStart = startWall.transform.position + direction * 20f;
    20.         Vector3 rayDirection = -direction;
    21.         if (Physics.Raycast(rayStart, rayDirection, out ray, 30))
    22.         {
    23.             previousRadiusSameDirection = 20f - ray.distance;
    24.         }
    25.  
    26.         //Set position of first object at start position
    27.         startWall.transform.position = start;
    28.  
    29.         //Add the object to the list of walls
    30.         listOfWalls.Add(startWall);
    31.  
    32.         //Loop until last created object is behind the end point
    33.         while(Vector3.Dot(direction, (end - listOfWalls[listOfWalls.Count - 1].transform.position).normalized) > 0)
    34.         {
    35.             //Create random object (Tree or Rock)
    36.             wallObject = getRandomElementWithWeight(listOfWallObjects, listOfWallObjectsOccurences);
    37.             GameObject nextWall = Instantiate(wallObject,
    38.                 new Vector3(-100, -100, -100),
    39.                 Quaternion.identity);
    40.  
    41.             //Calculate spacing between current and next object in the start to end direction
    42.             float currentRadiusSameDirection = 0;
    43.             rayStart = nextWall.transform.position + direction * 20f;
    44.             rayDirection = -direction;
    45.             if (Physics.Raycast(rayStart, rayDirection, out ray, 30))
    46.             {
    47.                 currentRadiusSameDirection = 20f - ray.distance;
    48.             }
    49.  
    50.             //Caculate spacing between previous object and current in the end to start direction
    51.             float currentRadiusOppositeDirection = 0;
    52.             rayStart = nextWall.transform.position - direction * 20f;
    53.             rayDirection = direction;
    54.             if (Physics.Raycast(rayStart, rayDirection, out ray, 30))
    55.             {
    56.                 currentRadiusOppositeDirection = 20f - ray.distance;
    57.             }
    58.  
    59.             //Set position of current ojbect adding the previous spacing of object in the direction start to end and the current spacing of object in the direction end to start
    60.             nextWall.transform.position = new Vector3(listOfWalls[listOfWalls.Count - 1].transform.position.x, 0, listOfWalls[listOfWalls.Count - 1].transform.position.z)
    61.                 + (previousRadiusSameDirection + currentRadiusOppositeDirection) * direction;
    62.  
    63.             //Save the spacing between current object and next object
    64.             previousRadiusSameDirection = currentRadiusSameDirection;
    65.  
    66.             //Add object to wall list
    67.             listOfWalls.Add(nextWall);
    68.         }
    69.  
    70.         //Destroy last item because it goes over the end point
    71.         Destroy(listOfWalls[listOfWalls.Count - 1]);
    72.  
    73.         //Apply terrain height to all objects
    74.         foreach (GameObject wall in listOfWalls)
    75.         {
    76.             wall.transform.position = new Vector3(wall.transform.position.x, terrain.SampleHeight(wall.transform.position), wall.transform.position.z);
    77.         }
    78.     }
    79.  
    I use the RayCast functionality to calculate the exact spacing between two objects.
    It works perfectly when my listOfWallObjects contains only one type of GameObject:
    test2.png

    When I start adding two different prefabs in the list, the spacing that is calculated becomes the largest it has encountered since iterating through my list.

    This is the render I get:
    test.png

    Everything is fine at first, trees are added next to one another with perfect spacing. Then comes a rock. It is also spaced correctly with the previous tree. However, the next tree that comes in is spaced as if it is a rock, and I simply don't understand why. (spacing values are exactly the same as the rock object)
    The function that calculates the spacing uses RayCast on the created object, which is clearly of instance Tree.

    Can someone help?
     
  2. mtrompier

    mtrompier

    Joined:
    Jan 8, 2022
    Posts:
    4
    I have added the following code at line 58 of the above code to stop code when a tree after a rock is placed and show he ray:
    Code (CSharp):
    1. //If spacing information is wrong and current object is a tree
    2.             if ((currentRadiusSameDirection > 2 || currentRadiusOppositeDirection > 2) && nextWall.name.StartsWith("t"))
    3.             {
    4.                 //Show ray for 100 sec
    5.                 Debug.DrawRay(rayStart, rayDirection * ray.distance, Color.red, 100);
    6.                 //Exist function, leaving last tree at pos -100, -100, -100
    7.                 return;
    8.             }
    And this is the render I get:

    test3.png
    Clearly, the ray is not touching the collider
     
  3. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,492
    Be aware that changing a Transform doesn't instantly change anything else in Unity no matter how it might be visualised. The renderers, gizmos and physics system do not see changes to Transforms until they "run". Renderers/gizmos obviously run per-frame, physics runs (by default) per fixed-update so for physics, there's a delay until it can read your changes.

    This is how the Transform system works; it doesn't notify systems that something has changed proactively i.e. no callbacks or anything internally.

    For physics, it "runs" when the simulation steps so it'll see that you've changed the Transform and be forced to read it and update the respective physics objects.

    When a Rigidbody is added and when a Collider is added/enabled, it reads the Transform there and then as part of its initialisation of the pose.

    This is why it's important that when instantiating a prefab, you specify the position/rotation there and then because the Transform is added/set prior to any other component so the physics will be at the correct pose too.

    I only mention this, not to say this is absolutely your problem, but to highlight it so you don't have code that instantiates, modifies the Transform and then performs a query prior to the simulation running because any Transform changes will not have been read yet.
     
  4. mtrompier

    mtrompier

    Joined:
    Jan 8, 2022
    Posts:
    4
    Thank you MelvMay, I just figured that out.
    I managed to actually get my code working by checking the "Auto Sync Tranforms" in my project settings.
    I guess now the code applied to Tranform updates also the respective physics objects.
    My question now is: does it have any performance impact? I am guessing yes, because physics are recalculated each time I change a transform, but how much?
     
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,492
    That's a legacy feature and it'll potentially have a huge impact on performance. What it does is perform that sync-operation I was describing above (that should only happen once just prior to the simulation running) prior to every single read operation you can do in the physics system so reading a body position/rotation, performing any query etc.

    And no, what I said previously still stands; the Transform doesn't notify anything inside Unity of the change. This option causes that check prior to any read-op because of that.

    If you've got yourself into this situation, it's a sign of you doing something bad i.e. changing the Transform then querying. If you need to alter the position/rotation of a body instantly (teleport) then do so via the Rigidbody API.

    Typically, you perform your changes during the FixedUpdate callback then immediately after, the simulation runs. If you're doing stuff per-frame then you have to account for the fact that physics isn't running per-frame so the question becomes, why change it per frame.

    In the end, you're obviously free to use it but look at the profiler; you'll see if it's becoming a problem; there's a "Physics.SyncTransforms" (and "Physics2D.SyncTransforms") profiler entry. You'll likely see these prior to every call like queries etc.