4RZG4
4RZG4

Reputation: 60

Object with many children does not show up in the middle although it has coordinates set to 0, 0, 0

I am doing a Rubik cube generator with unity. Each of the pieces are basically a 1x1 cube which will be repeated in the shape of a bigger cube in my code as children of an empty object. The empty object is in the exact middle of the pieces, and all the pieces have their origins in the exact middle. However, when I put the empty to the center of the scene (0, 0, 0) It shows up in a different place. Here are some pictures from the editor:

As you can see, the empty is in the center with coordinates set to 0, 0, 0

Now ,when it has children and the coordinates are all still 0, it shows in a different place

Edit: @derHugo helped me out, but now my code that creates the cubes and sets the empty object to the middle of them does not work. Here is the full code:

    public GameObject PiecePrefab;
    public int CubeSize;
    Vector3 avg;
    Vector3 ijk;
    int cubeCount = 0;

    // Start is called before the first frame update
    void Start()
    {
        //Vector3 orgpos = gameObject.transform.position;

        if (CubeSize <= 0)
        {
            CubeSize = 1;
            Debug.LogError("The cube can not be smaller than 1!");
        }
        else if (CubeSize > 30)
        {
            CubeSize = 30;
            Debug.LogError("The cube should not be bigger than 30!");
        }

        avg = new Vector3(0, 0, 0);

        for (float k = 0; k < CubeSize; k++)
        {
            for (float j = 0; j < CubeSize; j++)
            {
                for (float i = 0; i < CubeSize; i++)
                {
                    if (i == CubeSize - 1 || i == 0)
                    {
                        CreatePiece(i, j, k);
                    }
                    else if (j == CubeSize - 1 || j == 0)
                    {
                        CreatePiece(i, j, k);
                    }
                    else if (k == CubeSize - 1 || k == 0)
                    {
                        CreatePiece(i, j, k);
                    }
                }
            }
        }

        avg /= cubeCount;
        gameObject.transform.position = avg;

        var _Go = GameObject.FindGameObjectsWithTag("KuutionPala");

        foreach (GameObject KuutionPala in _Go)
        {
            KuutionPala.transform.SetParent(transform);
        }

        //gameObject.transform.localPosition = orgpos;
       

        void CreatePiece(float x, float y, float z)
        {
            ijk = new Vector3(x, y, z);
            avg += ijk;
            cubeCount++;

            Vector3 offset3D;
            offset3D = new Vector3(x / CubeSize, y / CubeSize, z / CubeSize);
            var Piece = Instantiate(PiecePrefab, offset3D, transform.rotation);
            Piece.transform.localScale /= CubeSize;

            //Debug.LogFormat("x:" + x);
            //Debug.LogFormat("y:" + y);
            //Debug.LogFormat("z:" + z);
        }
    }
}

I think the error is on this row:

gameObject.transform.position = avg;

(Sorry if bad code)

Upvotes: 0

Views: 607

Answers (1)

derHugo
derHugo

Reputation: 90872

As said there are two pivot modes in Unity (see Positioning GameObjects → Gizmo handle position toggles)

  • Pivot: positions the Gizmo at the actual pivot point of the GameObject, as defined by the Transform component.
  • Center: positions the Gizmo at a (geometrical) center position based on the selected GameObjects.

Yours is set to Center so in order to change that click on the button that says Center.


Then to your code

You are currently just hoping/assuming that your parent is correctly placed on 0,0,0.

Then you spawn all tiles in a range from 0 to (CubeSize - 1)/2 and then want to shift the center back.

I would rather go the other way round and calculate the correct local offset beforehand and directly spawn the tiles as children of the root with the correct offset. Into positive and negative direction.

Step 1: What is that local position?

For figuring the general maths out just look at two examples.

Let's say you have 3 cubes with indices 0,1,2. They have extends of 1/3 so actually there positions would need to look like

-0.5       0       0.5
  |  .  |  .  |  .  |

Let's say you have 4 cubes with indices 0,1,2,3 and extends 1/4 then the positions would need to look like

-0.5          0          0.5
  |  .  |  .  |  .  |  .  |

So as you can see the simplest way to go would be

  1. start with the minimum position (e.g. -0.5f * Vector3.one)
  2. always add half of the extends for the first offset (e.g. 1/CubeSize * 0.5f * Vector3.one)
  3. add an offsets of the extends multiplied by the indices on top (e.g. 1/CubeSize * new Vector3(x,y,z))

so together something like

// be sure to cast to float here otherwise you get rounded ints
var extends = 1 / (float)CubeSize;
var offset = (-0.5f + extends * 0.5f) * Vector3.one + extends * new Vector3(x,y,z);

Step 2: Directly spawn as children with correct offset

void CreatePiece(float x, float y, float z)
{
    var extends = 1 / (float)CubeSize;
    var offset = (-0.5f + extends * 0.5f) * Vector3.one + extends * new Vector3(x,y,z);
    var Piece = Instantiate(PiecePrefab, transform, false);
    // This basically equals doing something like
    //var Piece = Instantiate(PiecePrefab, transform.position, transform.rotation, transform);

    Piece.transform.localPosition = offset;
    Piece.transform.localScale = extends * Vector3.one;
}

Then you can reduce your code to

// Use a range so you directly clamp the value in the Inspector
[Range(1,30)]
public int CubeSize = 3;

// Start is called before the first frame update
void Start()
{
    UpdateTiles();
}

// Using this you can already test the method without entering playmode
// via the context menu of the component
[ContextMenu(nameof(UpdateTiles)])
public void UpdateTiles()
{
    // Destroy current children before spawning the new ones
    foreach(var child in GetComponentsInChildren<Transform>().Where(child => child != transform)
    {
        if(!child) continue;

        if(Application.isPlaying)
        {
            Destroy(child.gameObject);
        }
        else
        {
            DestroyImmediate(child.gameObject);
        }
    }

    if (CubeSize < 1)
    {
        CubeSize = 1;
        Debug.LogError("The cube can not be smaller than 1!");
    }
    else if (CubeSize > 30)
    {
        CubeSize = 30;
        Debug.LogError("The cube should not be bigger than 30!");
    }

    // For making things easier to read I would use x,y,z here as well ;)
    for (float x = 0; x < CubeSize; x++)
    {
        for (float y = 0; y < CubeSize; y++)
        {
            for (float z = 0; z < CubeSize; z++)
            {
                if (x == CubeSize - 1 || x == 0)
                {
                    CreatePiece(x, y, z);
                }
                else if (y == CubeSize - 1 || y == 0)
                {
                    CreatePiece(x, y, z);
                }
                else if (z == CubeSize - 1 || z == 0)
                {
                    CreatePiece(x, y, z);
                }
            }
        }
    }
}

private void CreatePiece(float x, float y, float z)
{
    var extends = 1 / (float)CubeSize;
    var offset = (-0.5f + extends * 0.5f) * Vector3.one + extends * new Vector3(x,y,z);
    var Piece = Instantiate(PiecePrefab, transform, false);
    Piece.transform.localPosition = offset;
    Piece.transform.localScale = extends * Vector3.one;
}

enter image description here

Upvotes: 1

Related Questions