I'm still not sure how to avoid some cubes from being spawn near terrain edge half inside half out ?

This is the script i’m using to spawn cubes at random positions in specific area:

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

public class SpawnObjects : MonoBehaviour
{
    public int numberOfObjects;
    private int currentObjects;
    public GameObject objectToPlace;
   
    private int wallsLengthX;
    private int wallsLengthZ;
    private int wallsPosX;
    private int wallsPosZ;

    void Start()
    {
        var wi = GetComponent<WallsTest>();
        wallsLengthX = (int)wi.lengthX;
        wallsLengthZ = (int)wi.lengthZ;
        wallsPosX = (int)wi.wallsStartPosition.x;
        wallsPosZ = (int)wi.wallsStartPosition.z;
    }
    // Update is called once per frame
    void Update()
    {
        if (currentObjects <= numberOfObjects)
        {
            float posx = Random.Range(wallsPosX, wallsPosX + wallsLengthX);
            float posz = Random.Range(wallsPosZ, wallsPosZ + wallsLengthZ);

            posx += wallsLengthX * -0.5f;
            posz += wallsLengthZ * -0.5f;
            float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz));
            GameObject newObject = (GameObject)Instantiate(objectToPlace, new Vector3(posx, posy, posz), Quaternion.identity);
            newObject.transform.localScale = new Vector3(5, 5, 5);
            currentObjects += 1;
        }
        if (currentObjects == numberOfObjects)
        {
            Debug.Log("Generate objects complete!");
        }
    }
}

The problem is when a cube is near one of the area edges/walls so half of the cube is inside the area and half out side the area. How can i fix it ?

In the screenshot on the bottom of the scene view there two cubes almost at same position one of the is half or some of it out of the walls area.

here is a script I come up with. I hope it points you in the right direction.

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

public class SpawnObjects : MonoBehaviour
{
    public int numberOfObjects;
    private int currentObjects;
    public GameObject objectToPlace;

    private int wallsLengthX;
    private int wallsLengthZ;
    private int wallsPosX;
    private int wallsPosZ;

    void Start()
    {
        //var wi = GetComponent<WallsTest>();
        wallsLengthX = 200; // I don't have this script so I put in my own values
        wallsLengthZ = 200; // I don't have this script so I put in my own values
        wallsPosX = 100; // I don't have this script so I put in my own values
        wallsPosZ = 50; // I don't have this script so I put in my own values
    }
    // Update is called once per frame
    void Update()
    {
        if (currentObjects <= numberOfObjects)
        {
            float posx = UnityEngine.Random.Range(wallsPosX, wallsPosX + wallsLengthX);
            float posz = UnityEngine.Random.Range(wallsPosZ, wallsPosZ + wallsLengthZ);

            posx += wallsLengthX * -0.5f;
            posz += wallsLengthZ * -0.5f;
            float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz));
            GameObject newObject = (GameObject)Instantiate(objectToPlace, new Vector3(posx, posy, posz), Quaternion.identity);
            newObject.transform.localScale = new Vector3(5, 5, 5);
            newObject.name = "cube-" + currentObjects; // i change the name so I could verify my script.  you can remove this

            //check if the spawned object is in the correct place
            float x = (wallsPosX + wallsLengthX - newObject.transform.localScale.x)/2;//you might not need the wallsPosX
            float z = (wallsPosZ + wallsLengthZ - newObject.transform.localScale.z) / 2;//you might not need the wallsPosZ
            if (Math.Abs(newObject.transform.position.x) > x || Math.Abs(newObject.transform.position.z) > z)
            {
                Debug.Log("too far out : remove it : " + newObject.name);
                DestroyObject(newObject);
            }
            else
            {
                currentObjects += 1;
            }
        }
        if (currentObjects == numberOfObjects)
        {
            Debug.Log("Generate objects complete!");
        }
    }
}

This make the whole cubes far out the walls area:

if you copied my script line for line. then yah. it’s not going work. you have a script called WallsTest. I don’t have that script, so i made up some values for the X and Z. Look through my script. I added some notes.

1 Like

I’m getting now the message “too far out”

This is the WallsTest script:

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

public class WallsTest : MonoBehaviour
{
    public GameObject prefab;
    public Vector3 wallsStartPosition;
    public float width = 0;
    public float height = 1;
    public float lengthX = 2;
    public float lengthZ = 2;
    public float time = 1;
    public Camera wallsCamera;

    private List<GameObject> walls = new List<GameObject>();
    private IEnumerator coroutine;

    void Start()
    {
        wallsCamera.transform.position = new Vector3(wallsStartPosition.x, wallsStartPosition.y + 100, wallsStartPosition.z - 250);

        for (int i = -2; i < 2; i++)
        {
            GameObject go = Instantiate(prefab);
            go.transform.parent = transform;
            Vector3 scale = Vector3.one;
            Vector3 adjustedPosition = wallsStartPosition;

            float sign = Mathf.Sign(i);
            if ((i * sign) % 2 == 0)
            {
                adjustedPosition.x += (lengthX * sign) / 2;
                scale.x = width;
                scale.y = height;
                scale.z *= lengthZ + width;
            }
            else
            {
                adjustedPosition.z += (lengthZ * sign) / 2;
                scale.x *= lengthX + width;
                scale.y = height;
                scale.z = width;
            }
            adjustedPosition.y -= height / 2;
            go.transform.localScale = scale;
            go.transform.localPosition = adjustedPosition;
            walls.Add(go);
        }
        coroutine = raiseWall(walls[0], height, time, 0, 100);
        StartCoroutine(coroutine);
        coroutine = raiseWall(walls[2], height, time, 0, 100);
        StartCoroutine(coroutine);
        coroutine = raiseWall(walls[1], height, time, 0, 100);
        StartCoroutine(coroutine);
        coroutine = raiseWall(walls[3], height, time, 0, 100);
        StartCoroutine(coroutine);
    }

    IEnumerator raiseWall(GameObject wall, float amount, float time, float delay, float steps)
    {
        Vector3 position = wall.transform.localPosition;
        float finalHeight = position.y + amount;

        yield return new WaitForSeconds(delay);

        while (position.y < finalHeight)
        {
            float remainingHeight = finalHeight - position.y;
            position.y += Mathf.Min(remainingHeight, amount / steps);
            wall.transform.localPosition = position;
            yield return new WaitForSeconds(time / steps);
        }
        yield break;
    }
}

In your code i’m getting the values i checked with break point from the WallsTest:

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

public class SpawnObjects : MonoBehaviour
{
    public int numberOfObjects;
    private int currentObjects;
    public GameObject objectToPlace;

    private int wallsLengthX;
    private int wallsLengthZ;
    private int wallsPosX;
    private int wallsPosZ;

    void Start()
    {
        var wi = GetComponent<WallsTest>();
        wallsLengthX = (int)wi.lengthX; // I don't have this script so I put in my own values
        wallsLengthZ = (int)wi.lengthZ; // I don't have this script so I put in my own values
        wallsPosX = (int)wi.wallsStartPosition.x; // I don't have this script so I put in my own values
        wallsPosZ = (int)wi.wallsStartPosition.z; // I don't have this script so I put in my own values
    }
    // Update is called once per frame
    void Update()
    {
        if (currentObjects <= numberOfObjects)
        {
            float posx = UnityEngine.Random.Range(wallsPosX, wallsPosX + wallsLengthX);
            float posz = UnityEngine.Random.Range(wallsPosZ, wallsPosZ + wallsLengthZ);

            posx += wallsLengthX * -0.5f;
            posz += wallsLengthZ * -0.5f;
            float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz));
            GameObject newObject = (GameObject)Instantiate(objectToPlace, new Vector3(posx, posy, posz), Quaternion.identity);
            newObject.transform.localScale = new Vector3(5, 5, 5);
            //newObject.name = "cube-" + currentObjects; // i change the name so I could verify my script.  you can remove this

            //check if the spawned object is in the correct place
            float x = (wallsPosX + wallsLengthX - newObject.transform.localScale.x) / 2;//you might not need the wallsPosX
            float z = (wallsPosZ + wallsLengthZ - newObject.transform.localScale.z) / 2;//you might not need the wallsPosZ
            if (System.Math.Abs(newObject.transform.position.x) > x || System.Math.Abs(newObject.transform.position.z) > z)
            {
                Debug.Log("too far out : remove it : " + newObject.name);
                DestroyObject(newObject);
            }
            else
            {
                currentObjects += 1;
            }
        }
        if (currentObjects == numberOfObjects)
        {
            Debug.Log("Generate objects complete!");
        }
    }
}

But it’s giving me the message “too far out”
I tried to remove the wallsPosX and wallsPosZ at lines 41 and 42 but still same thing “too far out”

In my case i set the WallsTest: Walls Start Position: X = 250 Y = 0 Z = 250
Width = 0
Height = 10
LengthX = 100
LengthZ = 100
Time 1

And the main camera as camera

Both scripts attached to the same empty GameObject and both scripts using same cube as Prefab and Object To Place.

update the script, thanks for the wallsTest script.

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

public class SpawnObjects : MonoBehaviour
{
    public int numberOfObjects;
    private int currentObjects;
    public GameObject objectToPlace;

    private int wallsLengthX;
    private int wallsLengthZ;
    private int wallsPosX;
    private int wallsPosZ;

    void Start()
    {
        var wi = GetComponent<WallsTest>();
        wallsLengthX = (int)wi.lengthX;
        wallsLengthZ = (int)wi.lengthZ;
        wallsPosX = (int)wi.wallsStartPosition.x;
        wallsPosZ = (int)wi.wallsStartPosition.z;
    }
    // Update is called once per frame
    void Update()
    {
        if (currentObjects <= numberOfObjects)
        {
            float posx = UnityEngine.Random.Range(wallsPosX, wallsPosX + wallsLengthX);
            float posz = UnityEngine.Random.Range(wallsPosZ, wallsPosZ + wallsLengthZ);

            posx += wallsLengthX * -0.5f;
            posz += wallsLengthZ * -0.5f;
            float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz));
            GameObject newObject = (GameObject)Instantiate(objectToPlace, new Vector3(posx, posy, posz), Quaternion.identity);
            newObject.transform.localScale = new Vector3(5, 5, 5);
            newObject.name = "cube-" + currentObjects;

            //check if the spawned object is in the correct place
            float xleft = (wallsPosX - wallsLengthX/2) + (newObject.transform.localScale.x / 2);
            float xright = (wallsPosX + wallsLengthX/2) - (newObject.transform.localScale.x / 2);
            float zbottom = (wallsPosZ - wallsLengthZ/2) + (newObject.transform.localScale.z / 2);
            float ztop = (wallsPosZ + wallsLengthZ/2) - (newObject.transform.localScale.z / 2);
            if (newObject.transform.position.x < xleft ||
                newObject.transform.position.x > xright ||
                newObject.transform.position.z < zbottom ||
                newObject.transform.position.z > ztop)
            {
                Debug.Log("Out of bounds : remove it : " + newObject.name);  //not an error, just info
                DestroyObject(newObject); //remove the game object that is out of the box
            }
            else
            {
                currentObjects += 1;
            }
        }
        if (currentObjects == numberOfObjects)
        {
            Debug.Log("Generate objects complete!");
        }
    }
}
1 Like

This is working but i have two questions please:

First is why not starting the variable private int currentObjects; value as 1 ?
If for example i make in the inspector that numberOfObjects value is 10 it will create 11 cubes not 10.
So i changed currentObjects to 1.

private int currentObjects = 1;

Second thing i don’t understand is what this part mean and suppose to do ?

Debug.Log("Out of bounds : remove it : " + newObject.name);  //not an error, just info
DestroyObject(newObject); //remove the game object that is out of the box

Is that mean that if i create 10 cubes and one or more of the cubes out of the 10 was suppose to be out of the walls area then destroy this cube ? But each time i see a message in the console window for example “Out of bounds : remove it : cube-6” i can still see 10 cubes inside the walls area including cube 6.

This line:

DestroyObject(newObject);

If one of the cube is out of the walls area it will destroy it and will e position it again until it will be inside the walls area ?

No need to make checks, all you have to do is to make sure that you know the size of your cubes and add a padding on each side by half of that size. So you reduce the rect in which the cubes are spawned. Add this to your script from your first post:

//Add padding to your rect by half the size of the cubes.
float paddingX = CubeSize.X / 2f;
float paddingZ = CubeSize.Z / 2f;

float originX = wallsPosX + paddingX- wallsLengthX/2f;
float originZ = wallsPosZ + paddingZ- wallsLengthZ/2f;
float posx = Random.Range(originX, originX + wallsLengthX-paddingX);
float posz = Random.Range(originZ, originZ + wallsLengthZ-paddingZ);

Remove this, it is included in origin:

posx += wallsLengthX * -0.5f;
posz += wallsLengthZ * -0.5f;

The only thing that could be done is to make sure that the size of the object is not bigger than the size of the rect or simply limit the padding to that size:

float paddingX = Mathf.Clamp(CubeSize.X,0,wallsLengthX) / 2f;
1 Like

you have a big If statement based on currentObjects. if (currentObjects is lessthen or equal to numberOfObjects)
so every fame it runs this.
if true the spawn a cube.
after a cube is created, Then we check it’s location with another if statement. and we ether delete the cube or we increase the currentObjects int.

if (currentObjects <= numberOfObjects)
{
    //do stuff to spawn a cube
   
    //check cube location
    if (newObject.transform.position.x < xleft ||
        newObject.transform.position.x > xright ||
        newObject.transform.position.z < zbottom ||
        newObject.transform.position.z > ztop)
    {
        //if true
        //if the cube is outside the box then we need to remove the cube
        Debug.Log("Out of bounds : remove it : " + newObject.name);  //not an error, just info
        DestroyObject(newObject); //removes the cube that is out of the box
        //notice here we do not increase the currentObjects int
    }
    else
    {
        // if false
        //if it's not out side the box, then we keep the cube and increase the currentObjects int
        currentObjects += 1;
    }
}

//this is just a msg that prints so you know it's completed.
if (currentObjects == numberOfObjects)
{
    Debug.Log("Generate objects complete!");
}

if you take out the DestoyObject line, you’ll see that you will have duplicate game object names. one of them will be outside the box.

1 Like

I’m using johne5 solution now but i’m trying to use your solution too.
The problem is when in your solution if i set the cubes scale to 30,30,30 instead 5,5,5 some of them half or more out of the walls area.

This is the code i’m trying to use according to your solution:

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

public class SpawnObjects : MonoBehaviour
{
    public int numberOfObjects;
    private int currentObjects;
    public GameObject objectToPlace;

    private int wallsLengthX;
    private int wallsLengthZ;
    private int wallsPosX;
    private int wallsPosZ;

    void Start()
    {
        var wi = GetComponent<WallsTest>();
        wallsLengthX = (int)wi.lengthX;
        wallsLengthZ = (int)wi.lengthZ;
        wallsPosX = (int)wi.wallsStartPosition.x;
        wallsPosZ = (int)wi.wallsStartPosition.z;
    }
    // Update is called once per frame
    void Update()
    {
        if (currentObjects != numberOfObjects)
        {
            float paddingX = Mathf.Clamp(objectToPlace.transform.localScale.x, 0, wallsLengthX) / 2f;
            float paddingZ = Mathf.Clamp(objectToPlace.transform.localScale.z, 0, wallsLengthZ) / 2f;
            float originX = wallsPosX + paddingX - wallsLengthX / 2f;
            float originZ = wallsPosZ + paddingZ - wallsLengthZ / 2f;
            float posx = Random.Range(originX, originX + wallsLengthX - paddingX);
            float posz = Random.Range(originZ, originZ + wallsLengthZ - paddingZ);
            float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz));
            GameObject newObject = (GameObject)Instantiate(objectToPlace, new Vector3(posx, posy, posz), Quaternion.identity);
            newObject.transform.localScale = new Vector3(30, 30, 30);
            currentObjects += 1;
        }
    }
}

That’s because you calculate a padding independently from the scale that you apply later… Of course it’ll not be contained completely in your area.
The padding needs to be dependent on each cube’s scale…

1 Like

Still not working. What i changed is to this:

 void Update()
    {
        if (currentObjects != numberOfObjects)
        {
            GameObject newObject = (GameObject)Instantiate(objectToPlace);//(GameObject)Instantiate(objectToPlace, new Vector3(posx, posy, posz), Quaternion.identity);
            newObject.transform.localScale = new Vector3(30, 30, 30);
            float paddingX = Mathf.Clamp(newObject.transform.localScale.x, 0, wallsLengthX) / 2f;
            float paddingZ = Mathf.Clamp(newObject.transform.localScale.z, 0, wallsLengthZ) / 2f;
            float originX = wallsPosX + paddingX - wallsLengthX / 2f;
            float originZ = wallsPosZ + paddingZ - wallsLengthZ / 2f;
            float posx = UnityEngine.Random.Range(originX, originX + wallsLengthX - paddingX);
            float posz = UnityEngine.Random.Range(originZ, originZ + wallsLengthZ - paddingZ);
            float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz));
            newObject.transform.localPosition = new Vector3(posx, posy, posz);
            currentObjects += 1;
        }
    }

Padding should be the half of an axis’ scale.

First of all, if the distance of 2 walls ( or in other words, the wall’s length in that direction) is less than the cube’s sclale-value in that direction, it cannot be placed without overlapping.
E.g the cube is 30x30x30, walls are 25 units apart => doesnt work without overlapping.

One possible formula would be (for the x axis, pseudo code):
wallPosX + (cube.scale.x / 2f) + Random(0, wallLengthX - cube.scale.x).

The above formular with a cube 30x30x30 and wallPosX = 0, wallLengthX = 40, wallPosZ = 12, wallLengthZ = 50:

X Axis: 0 + (30 / 2f) + Random(0, 40 - 30) = 15 + [a value between 0 and 10]
=> Valid x values are between 15 and 25.
Z Axis: 12 + (30 / 2f) + Random(0, 50 - 30) = 27 + [value between 0 and 20]
=> Valid z values are between 27 and 47.

So a cube of size 30x30x30 can be placed anywhere within a rect-area that is defined by
minX, minZ: (15,27)
maxX, maxZ: (25, 47)

(The other corners (15, 47) and (25, 27) are implicitly given as this is a rectangular area, considering we’re only talking about x and z).

If you now do the inverse calculation with any position within that rect, you’ll see that a cube of scale 30x30x30 is always contained in that area and only touches the walls if either of the values x or z happens to be one of the calculated min / max.

1 Like

I didn’t understand how to use the forumla in the code:

 void Update()
    {
        if (currentObjects != numberOfObjects)
        {
            GameObject newObject = (GameObject)Instantiate(objectToPlace);//(GameObject)Instantiate(objectToPlace, new Vector3(posx, posy, posz), Quaternion.identity);
            newObject.transform.localScale = new Vector3(30, 30, 30);
            float paddingX = Mathf.Clamp(newObject.transform.localScale.x, 0, wallsLengthX) / 2f;
            float paddingZ = Mathf.Clamp(newObject.transform.localScale.z, 0, wallsLengthZ) / 2f;
            var scaled = newObject.transform.localScale.x / 2f;
            var rr = UnityEngine.Random.Range(0, wallsLengthX - newObject.transform.localScale.x);
            var sum = scaled + rr;
            wallsPosX = wallsPosX + (int)sum;
            float originX = wallsPosX + paddingX - wallsLengthX / 2f;
            float originZ = wallsPosZ + paddingZ - wallsLengthZ / 2f;
            float posx = UnityEngine.Random.Range(originX, originX + wallsLengthX - paddingX);
            float posz = UnityEngine.Random.Range(originZ, originZ + wallsLengthZ - paddingZ);
            float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz));
            newObject.transform.localPosition = new Vector3(posx, posy, posz);
            currentObjects += 1;
        }
    }

Tried

var scaled = newObject.transform.localScale.x / 2f;
var rr = UnityEngine.Random.Range(0, wallsLengthX - newObject.transform.localScale.x);
var sum = scaled + rr;