Search code examples
c#unity-game-enginerotationpositiongameobject

Rotating and positioning a row of gameobjects without gaps


I have a prefab of a house with a bit of road attached, like so:

3D model of a house, with a strip of road in front of it

I want to arrange these side-by-side to create a street, but I also want to have them at a slight angle so that the road seems to head off into the distance. Something more like this:

Same 3D model, now with the road heading off to the upper-right of the screen

I've been able to place them and rotate them, but I'm having a lot of trouble figuring out the positioning. I'm guessing there's some maths trick I'm not applying or something.

This is my code so far:

        int houseCount = 0;
        float offsetX = 0;
        float offsetZ = 0;
        while (houseCount < 30)
        {
            GameObject house = GameObject.Instantiate(housePrefab);
            house.transform.Rotate(new Vector3(0,-20f,0), Space.Self);
            house.transform.position = new Vector3(offsetX, 0, offsetZ);

            // Calculate offset for next house
            HouseUnit unit = house.GetComponent<HouseUnit>();
            MeshRenderer renderer = unit.GetHouseBase().GetComponent<MeshRenderer>();
            Vector3 size = renderer.bounds.size;
            offsetX += size.x;
            offsetZ += size.z / 2f;

            Debug.Log(size);
            houseCount++;
        }

unit.GetHouseBase() returns the road segment, which is a simple plane. I figured that was the most logical bit to base the positioning on since it's a) the widest part, and b) the bit I need to connect up.

Running this gives the following result:

A row of the above house model, with a gap between each road segment

They're not lining up too badly, but I can't figure out how to close that gap. Has anyone got any suggestions, or resources they can point me at? Thanks in advance.


Solution

  • See MeshRenderer has wrong bounds when rotated

    As a short summary:

    • Unity takes Mesh.bounds which are in local space (unscaled, unrotated, untranslated)
    • Unity then wraps this bounds within the Renderer.bounds
    • Since Bounce are always world axis aligned the Renderer.bounds grows when rotating the object in order to fit the now rotated mesh bounds within

    Just re-posting the image for better illustration

    enter image description here


    So what I would do is

    1. Have a common parent object
    2. Do all house placements while parent object is not rotated
      => Can rely on Renderer.bounds to be accurate
    3. Then in the end rotate the parent according to your needs
      => and all children (houses) automatically along with it
    4. [optional] If you then really really do not want that parent object there for whatever reason, simply once you are done un-parent all the houses maintaining their current positions and rotations and destroy the parent

    something like e.g.

    // either already have that common parent or create one now
    var parent = new GameObject().transform;
    
    // if using previously existing parent store original position and rotation
    var position = parent.position;
    var rotation = parent.rotation;
    
    // ensure not rotated
    parent.position = Vector3.zero;
    parent.rotation = Quaternion.identity;
    
    // span houses into parent
    var offsetX = 0f;
    for(var houseCount = 0; houseCount < 30; houseCount++)
    {
        var house = Instantiate(housePrefab, parent, Vector3.right * offsetX, Quaternion.identity);
    
        // Calculate offset for next house
        var size = house.GetComponentInChildren<Renderer>().bounds.size;
        offsetX += size.x;
    }
    
    parent.position = position;
    // either reapply previous rotation (if already was rotated)
    parent.rotation = rotation;
    // or rotate now to your desired 20°
    parent.rotation = Quaternion.Euler(0, -20f, 0);
    
    
    // [optional] as said if you then d not want the parent anymore you could also add
    while(parent.childCount > 0)
    {
        var child = parent.GetChild(0);
        child.SetParent(parent.parent, true);
    }
    Destroy(parent.gameObject);