As the title says, I am wondering how I can stop prefabs from overlapping?
Here is my current code
{
List<GameObject> prefabList = new List<GameObject>();
public GameObject Room1;
public GameObject Room2;
public GameObject Room3;
void Start()
{
prefabList.Add(Room1);
prefabList.Add(Room2);
prefabList.Add(Room3);
for (int i = 0; i < 5; i++)
{
int prefabIndex = UnityEngine.Random.Range(0, prefabList.Count - 0);
var position = new Vector3(Random.Range(-20.0f, 20.0f), Random.Range(-10.0f, 10.0f));
Instantiate(prefabList[prefabIndex], position, Quaternion.identity);
}
}
}
I want to retry placing the prefab in a different location but do not know how to go about this?
Any help is apreciated.
There are a few ways to handle this, but it's an interesting problem to have.
With this line of code:
var position = new Vector3(Random.Range(-20.0f, 20.0f), Random.Range(-10.0f, 10.0f));
You are randomly distributing objects throughout the room, but it is possible that you run out of viable space in the room with a higher number of objects spawned.
There are a few ways games solve the problem of "overlapping objects", but I prefer to use the grid system in cases like this rather than trying to involve physics in level generation.
Grid system
You should identify your largest prefab object that you want to spawn, and then break up your map into a grid with cells of that size or larger. Then you can create a List of Tuples that each represent one cell on that grid. Randomly select 1 tuple from the list and remove it when spawning an object to guarantee that no other objects will appear in this area.
[SerializeField]
List<GameObject> prefabList = new List<GameObject>();
[SerializeField]
int numberToSpawn = 5;
[SerializeField]
Vector3 mapAnchor = Vector3.zero;
[SerializeField]
float mapWidth = 40f;
[SerializeField]
float mapHeight = 20f;
[SerializeField]
float cellSize = 1f;
// Start is called before the first frame update
void Start()
{
//calculate the number of cells along the width and height of the map
int cellWidth = Mathf.RoundToInt(mapWidth / cellSize);
int cellHeight = Mathf.RoundToInt(mapHeight / cellSize);
//Generate a list of tuples that represent each individual cell on the map
List<(int,int)> cells = gridInstantiate(cellWidth, cellHeight);
for (int i = 0; i<5; i++)
{
//Randomly select the cell on the grid
int randCellIndex = Random.Range(0, cells.Count);
(int x, int y) = cells[randCellIndex];
cells.RemoveAt(randCellIndex);
//Instantiate the object at x and y on the grid, converting it back into world space
Vector3 position = mapAnchor;
position.x = position.x + x * cellSize;
position.y = position.y + y * cellSize;
Instantiate(prefabList[Random.Range(0, prefabList.Count)], position, Quaternion.identity);
}
}
List<(int,int)> gridInstantiate(int cellWidth, int cellHeight)
{
List<(int,int)> cells = new List<(int, int)>();
for (int i = 0; i < mapWidth; i++)
{
for (int j = 0; j < mapHeight; j++)
{
cells.Add((i, j));
}
}
return cells;
}
This is what it looks like generating random circles with the above code:
To make the objects spawn more naturally, you can add some extra code to smooth that out by giving each cell a buffer, and allowing the instantiation to randomize within that buffer as an offset, preventing them from aligning so perfectly:
Add another property:
[SerializeField]
float cellBuffer = 1f;
Modify the cellheight/cellwidth calculation:
int cellWidth = Mathf.RoundToInt(mapWidth / (cellSize + cellBuffer));
int cellHeight = Mathf.RoundToInt(mapHeight / (cellSize + cellBuffer));
Give the position a random offset within the limits of the buffer:
Vector3 offset = new Vector3(Random.Range(-cellBuffer / 2, cellBuffer / 2), Random.Range(-cellBuffer / 2, cellBuffer / 2), 0);
Instantiate(prefabList[Random.Range(0, prefabList.Count)], position + offset, Quaternion.identity);
This is what the above code changes add, with another isometric diamond sprite set up as another potential prefab and the numberToSpawn set to 30:
Why do this, rather than randomly generate and check for overlap?
It's possible to run into a race condition if you randomly generate and check for overlap and there is zero space for a new object. You either don't spawn an object, or keep trying to spawn an object and fail. With this strategy, the list of cells will contain all remaining "open" positions on the map to spawn an object onto, so you don't need to perform any overlap checks. Additionally, when that list is empty you can know that there is no more room to spawn an object.
First you need to find out how big your room is. Once you have that have that data in a variable, lets call it offSet. Once the first room is made you need an if statement to see if there is a room in the position of your next prefab. If there is then offset the 2nd prefab by the distance of the room length or width.
Instantiate(prefabList[prefabIndex], offSet, Quaternion.identity);
Related
I am attempting to make a flappybird style game that is underwater with the PC dodging mines and collecting fish to score points. The issue that I am having is that the mines spawn off screen as intended and fly across the screen, however, they spawn in a straight line. I am following a tutorial as I dont know C# so I am practicing to get knowledge up. But in doing so I am not sure where I am going wrong and google searched yielded no solution.
This is the code for spawning in the mines
private void HandleMineSpawning() {
Timer -= Time.deltaTime;
if (Timer < 0) {
Timer += TimerMax;
float heightEdge = 10f;
float minHeight = offset + heightEdge;
float totalHeight = camSize * 2;
float maxHeight = totalHeight - offset *.5f - heightEdge;
float height = Random.Range(minHeight, maxHeight);
CreateMineOffset(height, offset, mineSpawn);
}
And the code that should create an offset on the Y-axis
private void Awake() {
minelist = new List<Mine>();
TimerMax = 1.5f;
offset = 20;
}
private void CreateMineOffset(float offsetY, float offsetSize, float xPosition) {
CreateMine(offsetY - offsetSize * .5f, xPosition);
CreateMine(camSize * 2f - offsetY - offsetSize * .5f, xPosition);
}
This was written for a 3d game but I am sure you can modify it for a platformer game.
Set the y spread to a high value and set the x to a low value. The Z spread can stay at zero for your game.
Hope this helps.
public GameObject itemsToSpread;
GameObject spawn;
public int numberOfItemsToSpawn;
public float Space = 10;//Distance between game objects
//Offset values go here
public float itemXSpread = 5;
public float itemYSpread = 100;
public float itemZSpread = 0;//Add value here for a 3d distribution
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < numberOfItemsToSpawn; i++)
{
SpreadItem();
}
}
void SpreadItem()
{
Vector3 ranPos = new Vector3(Random.Range(-itemXSpread, itemXSpread) + Space, Random.Range(-itemYSpread, itemYSpread) + Space, Random.Range(-itemZSpread, itemZSpread) + Space) + transform.position;
spawn = Instantiate(itemsToSpread, ranPos, Quaternion.identity);
}
Output:
For your game, try these values.
So i have a game where there are 10 obstacles placed in a random about 15 x 300 area and i want them to always be 2 meters apart.
here is my code that generates the random obstacles:
{
public GameObject obstacle;
public int xPos;
public int zPos;
public int obstacleCount;
void Start()
{
while (obstacleCount < 10)
{
xPos = Random.Range(-6, 7);
zPos = Random.Range(85, 245);
Instantiate(obstacle, new Vector3(xPos, 1, zPos), Quaternion.identity);
obstacleCount += 1;
}
}
}
How can i check that the obstacles are atleast 2 meters apart from eachother?
In order to check distance between two objects, you'll have to be able to access and compare their positions. One way to do this is to store them all in an array as you instantiate them, like so:
GameObject[] obstacles = new GameObject[10];
while (obstacleCount < 10)
{
xPos = Random.Range(-6, 7);
zPos = Random.Range(85, 245);
obstacles[obstacleCount] = Instantiate(obstacle, new Vector3(xPos, 1, zPos), Quaternion.identity);
obstacleCount += 1;
}
In order to check if any are too close, you'll have to find the distance between them (you can do this by simply subtracting their position vectors):
Vector3 distance = obstacles[1].transform.position - obstacles[0].transform.position; //order doesn't matter
Then simply check if the magnitude of the distance vector is less than your desired minimum length:
float minDistance = 2f;
if(distance.magnitude<minDistance)
{
//two objects are too close
}
Just make sure you check each pair of obstacles; you can do probably do this with a couple for loops.
I am trying to implement K-means in Unity to cluster randomly spawned assets around a terrain. Once the K-means operation completes I instantiate the last centroid positions and instantiate a Capsule for all locations that belong to a cluster and parent the instantiated Capsules to the centroid position to see and understand the final clustering result. The problem I am getting is that once it's spawned all relevant locations, it begins to spawn more and more capsules which do not parent to any cluster, not sure why.
Also the centroids do not seem to select unique random positions within my list of data points; when inspecting the positions of the final centroids they all seem to be at the same place. If I set k = 4 the 4th centroid never seems to spawn. I am struggling to find out where I am going wrong trying to implement this and would appreciate any insight.
Script for K-means clustering + Spawning assets (done in same script)
K-means.cs
Calculating initial centroid position
Vector3 CentroidPos()
{
var random = new System.Random();
var pos = assetSpawnLocations[random.Next(assetSpawnLocations.Count)];
if(centroidsInUse.Contains(pos))
{
return CentroidPos();
} else
{
return pos;
}
}
// Using funtion in Clustering method
void Clustering()
{
Vector3 centroid1 = CentroidPos();
Vector3 centroid2 = CentroidPos();
Vector3 centroid3 = CentroidPos();
Vector3 centroid4 = CentroidPos();
//...
}
Recalculating centroid position after data points have been assigned to cluster
Vector3 RecalculateCentroid(List<Vector3> Data)
{
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
for (int i = 0; i < Data.Count; i++)
{
x += Data[i].x;
y += Data[i].y;
z += Data[i].z;
}
return new Vector3(((x) / Data.Count), ((y) / Data.Count), ((z) / Data.Count));
}
Spawning the last centroid after K iterations and relevant data point positions using Capsule
// Spawn the last cluster centre and all it's points.
void SpawnResult(List<Vector3> cluster, Vector3 lastCentroid)
{
if(cluster != null && lastCentroid != null && cluster.Count > 0)
{
var c = Instantiate(GameObject.CreatePrimitive(PrimitiveType.Cylinder), lastCentroid, Quaternion.identity);
c.name = "Centroid";
for (int i = 0; i < cluster.Count; i++)
{
var item = Instantiate(GameObject.CreatePrimitive(PrimitiveType.Capsule), cluster[i], Quaternion.identity);
item.transform.parent = c.transform;
}
}
}
I am using DrawGizmos to place cubes with a certain colour depending on the cluster they belong to however it only seems to display Yellow cubes which is for cluster3 (see full script for DrawGizmos function implementation) [Small section of the map shown to clearly see yellow cube but this is the same across the map without colour changing for other clusters]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WallsTest : MonoBehaviour
{
// using a GameObject rather than a transform
public GameObject prefab;
public Vector3 wallsStartPosition;
public float width = 0;
public float height = 1;
public float length = 2;
public Camera wallsCamera;
public float wallsArea;
void Start()
{
wallsCamera.transform.position = new Vector3(wallsStartPosition.x, wallsStartPosition.y + 100, wallsStartPosition.z - 235);
BuildWalls();
}
private void Update()
{
}
void BuildWalls()
{
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 += (length * sign) / 2;
scale.x = width;
scale.y = height;
scale.z *= length + width;
}
else
{
adjustedPosition.z += (length * sign) / 2;
scale.x *= length + width;
scale.y = height;
scale.z = width;
}
adjustedPosition.y += height / 2;
go.transform.localScale = scale;
go.transform.localPosition = adjustedPosition;
}
}
}
For example the length is 100 so the area will be 100x100 i think.
And i have the wallsStartPosition for example 250,0,250
Now i want inside the walls area to instantiate at random position number of objects. For example 50 cubes. But they should not be overlap each other and for example the minimum gap between each other should be 5. and maximum gap as fas it can be.
But i don't understand yet how to calculate the area and position of the walls and just instantiate random objects inside.
And this is the script for spawning gameobjects at random positions in given area. In this case the area is the terrain. But i want the area to be inside the walls i create in the first script. This script is attached to the same empty gameobject like the WallsTest script.
Another problem with the SpawnObjects script is that there is no any set for the gap between the objects and if the objects too good like scale 20,20,20 some of the objects that spawn on the edge half out of the terrain.
spawn
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnObjects : MonoBehaviour
{
public Terrain terrain;
public int numberOfObjects; // number of objects to place
private int currentObjects; // number of placed objects
public GameObject objectToPlace; // GameObject to place
private int terrainWidth; // terrain size (x)
private int terrainLength; // terrain size (z)
private int terrainPosX; // terrain position x
private int terrainPosZ; // terrain position z
void Start()
{
// terrain size x
terrainWidth = (int)terrain.terrainData.size.x;
// terrain size z
terrainLength = (int)terrain.terrainData.size.z;
// terrain x position
terrainPosX = (int)terrain.transform.position.x;
// terrain z position
terrainPosZ = (int)terrain.transform.position.z;
}
// Update is called once per frame
void Update()
{
// generate objects
if (currentObjects <= numberOfObjects)
{
// generate random x position
int posx = Random.Range(terrainPosX, terrainPosX + terrainWidth);
// generate random z position
int posz = Random.Range(terrainPosZ, terrainPosZ + terrainLength);
// get the terrain height at the random position
float posy = Terrain.activeTerrain.SampleHeight(new Vector3(posx, 0, posz));
// create new gameObject on random position
GameObject newObject = (GameObject)Instantiate(objectToPlace, new Vector3(posx, posy, posz), Quaternion.identity);
newObject.transform.localScale = new Vector3(20, 20, 20);
currentObjects += 1;
}
if (currentObjects == numberOfObjects)
{
Debug.Log("Generate objects complete!");
}
}
}
My technical knowledge is limited, but you'll have to either get the script to spawn these objects every [X] amount of position ("posX += 1.0f;", or something to that effect) so that they appear in a uniform manner, or, have the script record each new object's position and use that information to calculate space away from said objects to spawn another one.
In any case, depending on the end result you're aiming for, you'll have to write how these objects operate in a given space. For example, if you have a messy room as a scene, you can have one desk that would find space in the corner of two walls, one bed alongside one wall and rubbish spawning randomly.
You can use Physics.OverlapSphere to test if two objects are overlapping. This assumes that the objects both have colliders.
Once you test this condition and it evaluates to true, I would suggest simply moving the object that overlaps and testing it again against the other objects in the scene. You can get clever with this by only testing objects you think might be close to overlapping, or overlapping.
Alternatively you can just compare every object to every other object in the scene. This would not be ideal if you have a lot of objects, because doing so would be O(n^2) complexity.
In any case you can use this function to test overlapping. Good luck.
hint: if you make the collider bigger than the object, you can place items a minimum space apart. For example if you have a cube of localSize 1,1,1 - the colider can be 5,5,5 in size and Physics.OverlapSphere will return true if the colliders overlap, demanding that the cubes be a certain space apart from each other
Alright, in my game I have a Runner object that moves at a velocity towards the left towards platforms. I have succeeded in spawning a random platform prefab at a pt a semi-random distance away from the last platform.
I do this by spawning the platform a random distance spacing, which the distance the object has moved since last platform was spawned.
public GameObject[] spawnableObjects;
private GameObject prefab;
private float spacing; // spawns every 2 units/metres
public float minY, maxY, minGap, maxGap;
private float oldX = 0; // previous camera position
private float newX = 0; // current camera position
private float distanceTravelled = 0;
private Vector3 spawnPos;
public Transform player; // getting camera reference
public Transform spawnPt;
private float currentY, lastY, currentX, lastX;
void Start()
{
//set beginning y pos
GameEventManager.GameStart += GameStart;
GameEventManager.GameOver += GameOver;
spacing = 20; //random between length of last prefab and max gap?
lastY = 0;
lastX = spawnPt.localPosition.x; //first spawn x pos
}
void Update () {
//Debug.Log (distanceTravelled);
newX = player.transform.localPosition.x;
distanceTravelled += newX - oldX; // if you are goint to the right
// if you're going to the left, do this instead:
// distanceTravelled += oldX - newX;
oldX = newX; // this is very important
if (distanceTravelled >= spacing) {
distanceTravelled = 0;
spawnNewObject ();
}
}
void spawnNewObject () {
// code to spawn. Use the newX + some X offset while declaring your new object's position.
int rand = Random.Range (0, spawnableObjects.Length);
prefab = spawnableObjects[rand];
currentY = lastY + Random.Range(minY, maxY);
currentX = lastX + (prefab.transform.localScale.x + 5f); //randomize 5, 5 is gap
spawnPos = new Vector3((spawnPt.localPosition.x), currentY,0f);
//Debug.Log ("new x:"+currentX);
Instantiate (prefab, spawnPos, Quaternion.identity);
lastY = currentY;
lastX = currentX;
spacing = Random.Range (prefab.transform.localScale.x+3, prefab.transform.localScale.x+5);
Debug.Log("spawn now!! spacing="+spacing);
}
The spawnpt game object moves at the same acceleration/speed as Runner, so it appears as if platforms are moving towards the Runner.
For some reason, the Random.Range (prefab.transform.localScale.x+3, prefab.transform.localScale.x+5); which is the length of the prefab currently being spawned+3 or +4 makes it so the platforms virtually never touch.
My thinking in doing length was if the new platform is being spawned from the empty spawnPt.localPosition.x, if the spacing was atleast the length of the current prefab, they wouldn't touch.
Obviously something is flawed in my logic as if I just do prefab.transform.localScale.x platforms touch. I need a new approach for this.
How can I have my randomly selected prefab spawn with a randomized GAP between itself and the last prefab that was spawned? I have tried everything including Physics spheres (don't want to go that route) but can't create a gap that I can alter.
Is there a way to do this?
Try overlap sphere (untested):
if (!Physics.CheckSphere(spawnPosition, objectRadius)) {
//instantiate object
}
objectRadius is the spherical radius that the object cannot be instantiated within another one
also you can have a Random.insideUnitSphere:
spawnPosition = previous.transform.position + Random.insideUnitSphere * spawnRadius;
I think that will work.