简体   繁体   中英

Unity - Help needed with Object Positioning

Need some help here with unity object positioning, I've been trying to play around for almost two days and got no result.

So if you look at the image below there are 2 objects.

在此处输入图片说明

One is Plate (red colour)

One is Cube (blue colour)

I created the entire objects below from the script. When the Plate (Red colour object) was generated the scale is (4 x 0.4 x 4).

And as for the cube, please look at this image, so there are 5 possible types of cube and each has its own vector size在此处输入图片说明

What I am trying to achieve is that every Cube (blue colour) has it own pre-defined vector size, and when the script ran, I want to position the cube accordingly in a tidy manner on top of the Plate.

If you look at the first image, everything is messy right now the cube is outside the plate, etc. Anyone know whats the best way to achieve what I wanted?

The image below is what I want to achieve, everything is tidy and on top of the plates.

在此处输入图片说明

Thanks and much appreciated!

Edit: the number of cubes is not fixed basically we populate the cubes from reading a TXT file and it tells the system the number of cubes per plate and what are the sizes of the cubes, we just need to position it correctly. For example, when reading the TXT file, it can tell the system that the first plate will be having 4 cubes and X amount of size. (so we have 5 different types of size and based on the size then we need to fit it accordingly on a plate)

I think you need to calculate the position of the cubes (blue) according to the boundaries of the plate. Check here : Bounds

Assuming you only position them in xz-plane you can quantify your cubes' sizes (as multiples of 0.8), leaving out the y-dimension. Then you have a plate of 5 by 5 slots and you can place the available cubes iteratively.

This is a test script first filling the rows of your plate (aka horizontally) and if necessary fitting the remaining cubes column first (aka vertically). You may simplify this, but I think adapting it to your needs is straightforward.

using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class PositioningTest : MonoBehaviour {
    private const char FREE = 'x';

    private readonly Vector3Int _quantizedPlateSize = new Vector3Int(5, 1, 5);

    private readonly Vector3Int[] _quantizedCubeSize = new[]{
        new Vector3Int(1, 1, 1),
        new Vector3Int(1, 1, 1),
        new Vector3Int(2, 1, 1),
        new Vector3Int(2, 2, 2),
        new Vector3Int(4, 2, 2),
    };

    private Transform _plate;

    [SerializeField]
    private int[] _availableCubes;

    private void OnGUI() {
        if (GUI.Button(new Rect(10, 10, 100, 100), "Run")) {
            if (_availableCubes == null || _availableCubes.Length == 0)
                return;
            List<Vector3Int> cubes = new List<Vector3Int>();
            for (int i = 0; i < _availableCubes.Length; i++) {
                int sizeIndex = _availableCubes[i];
                if (sizeIndex < 0 || sizeIndex >= _quantizedCubeSize.Length) {
                    Debug.LogError("invalid size index " + sizeIndex);
                    return;
                }
                cubes.Add(_quantizedCubeSize[sizeIndex]);
            }
            PlaceCubesOnPlate(cubes, _quantizedPlateSize.x, _quantizedPlateSize.z);
        }
    }

    private void PlaceCubesOnPlate(List<Vector3Int> allCubes, int plateRows, int plateColumns) {
        if (allCubes == null || allCubes.Count == 0) {
            Debug.LogError("no cubes to position");
            return;
        }

        // reset the plate
        ResetPlate();
        char[,] plate = CreateEmptyPlate(plateRows, plateColumns);

        // sort them so we fit the biggest cubes first
        SortCubesDescending(ref allCubes);

        // index of the cube to be fit in next
        int currentCubeIndex = 0;

        // fit the cubes row first
        for (int z = 0; z < plate.GetLength(0); z++) {
            for (int x = 0; x < plate.GetLength(1); x++) {
                if (plate[z, x] != FREE)
                    continue;
                Vector3Int cube = allCubes[currentCubeIndex];
                if (TryPositionCubeRowFirst(ref plate, cube, z, x, GetChar(currentCubeIndex))) {
                    currentCubeIndex++;
                    if (currentCubeIndex == allCubes.Count) {
                        PrintPlate(plate);
                        return;
                    }
                }
            }
        }

        // fit remaining cubes column first
        for (int x = 0; x < plate.GetLength(1); x++) {
            for (int z = 0; z < plate.GetLength(0); z++) {
                if (plate[z, x] != FREE)
                    continue;
                Vector3Int cube = allCubes[currentCubeIndex];
                if (TryPositionCubeColumnFirst(ref plate, cube, z, x, GetChar(currentCubeIndex))) {
                    currentCubeIndex++;
                    if (currentCubeIndex == allCubes.Count) {
                        PrintPlate(plate);
                        return;
                    }
                }
            }
        }

        Debug.LogError("could not fit all cubes");
    }

    private char[,] CreateEmptyPlate(int rows, int columns) {
        char[,] plate = new char[columns, rows];
        for (int z = 0; z < plate.GetLength(0); z++) {
            for (int x = 0; x < plate.GetLength(1); x++) {
                plate[z, x] = FREE;
            }
        }
        return plate;
    }

    private void SortCubesDescending(ref List<Vector3Int> cubes) {
        cubes.Sort((c1, c2) => {
            int indexC1 = Array.IndexOf(_quantizedCubeSize, c1);
            int indexC2 = Array.IndexOf(_quantizedCubeSize, c2);
            return indexC2.CompareTo(indexC1);
        });
    }

    private void PrintPlate(char[,] plate) {
        StringBuilder sb = new StringBuilder("plate:\n");
        for (int z = 0; z < plate.GetLength(0); z++) {
            for (int x = 0; x < plate.GetLength(1); x++) {
                sb.Append(plate[z, x]);
                sb.Append(" ");
            }
            sb.Append("\n");
        }
        Debug.Log(sb.ToString());
    }

    private char GetChar(int index) {
        if (index >= 0 && index <= 15)
            return index.ToString("X")[0];
        return (char) (Math.Abs(index) - 16 + 97);
    }

    private bool TryPositionCubeRowFirst(ref char[,] plate, Vector3Int cubeSize, int columnIndex, int rowIndex, char representingChar) {
        for (int z = 0; z < cubeSize.z; z++) {
            for (int x = 0; x < cubeSize.x; x++) {
                if (columnIndex + z >= plate.GetLength(0)
                    || rowIndex + x >= plate.GetLength(1)
                    || plate[columnIndex + z, rowIndex + x] != FREE)
                    return false;
            }
        }

        for (int z = 0; z < cubeSize.z; z++) {
            for (int x = 0; x < cubeSize.x; x++) {
                plate[columnIndex + z, rowIndex + x] = representingChar;
            }
        }

        Transform newCube = CreateCube(representingChar.ToString(), _plate, new Vector3(rowIndex, 0, columnIndex));
        newCube.localScale = cubeSize;
        return true;
    }

    private bool TryPositionCubeColumnFirst(ref char[,] plate, Vector3Int cubeSize, int columnIndex, int rowIndex, char representingChar) {
        for (int z = 0; z < cubeSize.z; z++) {
            for (int x = 0; x < cubeSize.x; x++) {
                if (columnIndex + x >= plate.GetLength(0)
                    || rowIndex + z >= plate.GetLength(1)
                    || plate[columnIndex + x, rowIndex + z] != FREE)
                    return false;
            }
        }

        for (int z = 0; z < cubeSize.z; z++) {
            for (int x = 0; x < cubeSize.x; x++) {
                plate[columnIndex + x, rowIndex + z] = representingChar;
            }
        }

    
        Transform newCube = CreateCube(representingChar.ToString(), _plate, new Vector3(rowIndex, 0, columnIndex));
        newCube.localScale = new Vector3(cubeSize.z, cubeSize.y, cubeSize.x);
        return true;
    }

    private void ResetPlate() {
        if (_plate != null) {
            Destroy(_plate.gameObject);
        }
        _plate = new GameObject("plate").transform;
    }

    private Transform CreateCube(string name, Transform parent, Vector3 localPosition) {
        Transform pivot = new GameObject(name).transform;
        Transform cube = GameObject.CreatePrimitive(PrimitiveType.Cube).transform;
        cube.SetParent(pivot);
        cube.localPosition = new Vector3(0.5F, 0.5F, 0.5F);
        cube.localScale = new Vector3(0.9F, 0.9F, 0.9F);
        pivot.SetParent(parent);
        pivot.localPosition = localPosition;
        return pivot;
    }
}

Note: The resulting debug output is upside down (compared to the unity scene), since both are just representations for understanding the algorithm.

If you only have one cube and want that one positioned in the center of the plate you might just add a check in the positioning method allowing an early return for one cube (like it already has for 0 cubes).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM