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. Dismiss Notice

Is there a way to manually update a collider position?

Discussion in 'Scripting' started by DaemonicDreams, Jun 23, 2019.

  1. DaemonicDreams

    DaemonicDreams

    Joined:
    Aug 23, 2018
    Posts:
    41
    Hello,

    I am currently working on a level generator for my game and here is a simplified version of how it (should) work:

    I have "Room"-Prefabs that have a number of "Connectors" (basically empty transforms with a script attached), where other Connectors can be connected to. However, Rooms are obviously not allowed to overlap, so I tried the following:
    1. Each Room has a bounding box (BoxCollider as Trigger).
    2. If the bounding box of a new room intersects any other rooms bounding box it's not a valid room and is destroyed

    Each new room is instantiated at origin and then moved to the correct location, depending on the two connectors that are to be connected. This works fine, however I can not check the validity of a room immediately after positioning it. The collider just doesn't update until the next frame

    So the question is: is there any way to tell a collider to refresh its position?
     
    DragonCoder likes this.
  2. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,917
    How are you checking? I use things like boxCast and checkBox, and those pick up colliders immediately - things which I may have positioned and resized in the previous line of code.
     
  3. DaemonicDreams

    DaemonicDreams

    Joined:
    Aug 23, 2018
    Posts:
    41
    I was using the bounds.Interesects function and also tried Physics.OverlapBox, but in both cases the collides wouldn't properly update.

    I solved the problem by running the generator on a coroutine and waiting for fixed update between placements. If someone knows a better way let me know.
     
  4. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,917
    How are you moving them? For example, I've seen how AddForce delays changing the speed, whereas velocity= is instant. I wonder if rigidbody.move does something similar? I've also just set position=. I've heard of a 1-frame delay in colliders, but have never been able to create one.
     
  5. DaemonicDreams

    DaemonicDreams

    Joined:
    Aug 23, 2018
    Posts:
    41
    I'm moving them by directly modifying the transform with this piece of code:

    Code (CSharp):
    1. private void PositionRoomAtConnector(ref Room room, Connector roomConnector, Connector targetConnector)
    2.     {
    3.         Vector3 targetRotationEuler = targetConnector.transform.eulerAngles;
    4.         Vector3 roomRotationEuler = roomConnector.transform.eulerAngles;
    5.         float deltaAngle = Mathf.DeltaAngle(targetRotationEuler.y, roomRotationEuler.y);
    6.         Quaternion currentTargetRotation = Quaternion.AngleAxis(deltaAngle, Vector3.up);
    7.         room.transform.rotation = currentTargetRotation * Quaternion.Euler(0.0f, 180.0f, 0.0f);
    8.  
    9.         room.transform.position = targetConnector.transform.position + roomConnector.GetOffset();
    10.     }
    If I output the transform using Debug.Log the position is updated correctly, however even if I intentionally place the rooms in a way that the colliders can not possibly overlap (or just scale the colliders down so that they can only overlap if they are basically at the same location) the overlap check still returns true, unless I wait for the next FixedUpdate after placing a room.
    This is my overlap code:

    Code (CSharp):
    1. private bool CheckOverlap(Room room)
    2.     {
    3.         Collider[] colliders = Physics.OverlapBox(room.transform.position, room.Bounds.size / 2);
    4.         foreach(Collider c in colliders)
    5.         {
    6.             Room other = c.transform.GetComponent<Room>();
    7.             if (other != null && other != room)
    8.             {
    9.                 return true;
    10.             }
    11.         }
    12.  
    13.         return false;
    14.     }
    I guess both might not be ideal but they work now if I use this coroutine:

    Code (CSharp):
    1. private IEnumerator GenerateLevel()
    2.     {
    3.         WaitForSeconds startup = new WaitForSeconds(1);
    4.         WaitForFixedUpdate interval = new WaitForFixedUpdate();
    5.  
    6.         yield return startup;
    7.  
    8.         PlaceStartRoom();
    9.  
    10.         yield return interval;
    11.  
    12.         for(int i = 0; i < (Random.Range(m_IterationRange.x, m_IterationRange.y)); i++)
    13.         {
    14.             PlaceRoom();
    15.             yield return interval;
    16.         }
    17.  
    18.         yield return interval;
    19.  
    20.         InitalizeGame();
    21.         StopCoroutine("GenerateLevel");
    22.     }
     
  6. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,917
    I can't see, since it's in the Room class, but the problem may be collider.Bounds. I believe that only updates itself later. What I do is don't move my new object yet. I use my code position/size for the overlap check, as in: Vector3 testPos, testSz; ... Physics.OverlapBox(testPos, ... testS/z/2 ...). But if what you have works, I wouldn't change it.

    For fun: I think "yield return null;" would be simpler - it waits for 1 frame, which is the shortest amount of time you can (the fancier yields only check the condition each frame). Using eulerAngles is only safe if x never goes above or below +/-90, but I'm assuming x and z are always 0.
     
  7. developerideal

    developerideal

    Joined:
    Apr 8, 2019
    Posts:
    1
    Another workaround is to deactivate and activate Gameobject (Room) immediately. In this case collider position will be updated in single frame.
     
    Aseemy and Ell223 like this.
  8. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,008
  9. matthklo

    matthklo

    Joined:
    Jun 1, 2017
    Posts:
    2
    This is it! You just save my day.
    My situation is much simpler: I have a GameObject (X) which attaches a Collider component (C) in scene. And in a single frame I want to rotate the parent GameObject of X and immediately perform C.Raycast() to see if it hits anything. But just like what DaemonicDreams has observed, it keeps using old transform data until next frame. Calling Physics.SyncTransforms() after the rotation fixes it.
     
    DavidSof likes this.
  10. Gamba04

    Gamba04

    Joined:
    Feb 2, 2018
    Posts:
    2
    DavidSof and Nith666 like this.
  11. kontakt_unity924

    kontakt_unity924

    Joined:
    Aug 8, 2020
    Posts:
    5
    Performance-wise, the best method seems to be updating the RigidBody.position (and also rotation if it changed). Even if you don't have a Rigidbody on the object, add it for this purpose, it's still much faster than any other method.

    Code (CSharp):
    1.     private Rigidbody Rigidbody;
    2.  
    3.     void Start()
    4.     {
    5.         Rigidbody = gameObject.GetComponent<Rigidbody>();
    6.     }
    7.  
    8.     void Upate()
    9.     {
    10.         //.... your code
    11.  
    12.         Rigidbody.position = newPosition;
    13.  
    14.         //don't forget to also update rotation if it changed
    15.         Rigidbody.rotation = newRotation;
    16.     }
    Much faster then deactivate/activate or Physics.SyncTransform().
     
    Last edited: Jun 29, 2022
    Digimaster likes this.