My initial intention was to create transform objects in an array, because all I need are certain points in space, that hold information about the position and rotation. I discovered that this wasn't possible, so I then went to create an array of empty GameObjects:
public GameObject[] centers;
From these I hoped to get what I need. Which is this:
centers = new GameObject[length];
GameObject prevCenter = myHeadLink; // myHeadLink is an unrelevant GamObject
for(int i = 0; i < length; i++){
Debug.Log(centers[i]);
centers[i].transform.position = prevCenter.transform.position -
prevCenter.transform.forward * prevCenter.transform.localScale.z * 1.5f;
prevCenter = centers[i];
}
Unfortunately, that's not possible either, because all center[i] are null. Does new GameObject not create an empty GameObject with transform information?
Try to write:
for (int i = 0; i < length; ++i)
centers[i] = new GameObject();
after:
centers = new GameObject[length];
Related
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);
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]
I am currently generating a set amount of GameObjects in code. What is the best way to call the plane positions in a loop where I can get the positions of each plane to guarantee the walls spawning within them?
public class LevelGenerator: MonoBehaviour
{
public NavMeshSurface surface;
private float[,] positionX = { {-9.5f, 9f}, {12f, 30f}, {12f, 30f}};
private float[,] positionZ = {{9f, -9.5f}, {9f, -9.5f}, { 11.5f,30f}};
public GameObject wall;
void OnEnable(){
GenerateMaze();
surface.BuildNavMesh();
}
void GenerateMaze(){
//For every plane
for (int i = 0; i<=2 ; i++){
//Place some walls on that plane
for (int j = 0; j <= 10; j++)
{
Vector3 pos = new Vector3(Random.Range(positionX[i,0], positionX[i,1]), 0.5f, Random.Range(positionZ[i,0], positionZ[i,1]));
Instantiate(wall, pos, Quaternion.identity, transform);
}
}
}
}
You can use GetComponent when Instantiating your objects. After that, you can either store them or mutate them however you like. There are also other functions such as GameObject.FindGameObjectsWithTag and FindObjectsOfType. I would only use these if you absolutely need and do not use them in functions called often like Update. Any of the Find methods come with a decent amount of overhead.
I am unsure of what your script is called that you want to access on your objects, so I will fill it in with YourScriptHere.
// if you want to store the objects, creat a list to reference them
private List<YourScriptHere> yourScripts = new List<YourScriptHere>();
void GenerateMaze(){
//For every plane
for (int i = 0; i<=2 ; i++){
//Place some walls on that plane
for (int j = 0; j <= 10; j++)
{
Vector3 pos = new Vector3(Random.Range(positionX[i,0], positionX[i,1]), 0.5f, Random.Range(positionZ[i,0], positionZ[i,1]));
YourScriptHere tmpScript = Instantiate(wall, pos, Quaternion.identity, transform).GetComponent<YourSciptHere>();
// mutate the data in tmpScript however you like
tmpScript.FunctionCall(data);
// if you want to track and store the data, then add it to a list for further processing later
yourScripts.Add(tmpScript);
}
}
}
If you just want the position, you can just access the instantiated object and grab its Transform, then access the position. If you want to access objects by position later and you are storing the positions, you can make a <Vector3, GameObject> Dictionary.
I am creating an infinite terrain generator and I need to constantly update the terrain as the player moves around.
Everything is fine, but I am having trouble with finding information about the fastest method of creating and rendering sprites on the fly.
Information about sprites:
I am using 1 sprite sheet which has all the frames I need for my terrain. Grass, sand, water etc. all in 1 single .png file. All frames are stored in an array from which I can easily grab them.
Steps I need to do to display my sprite object correctly currently:
Create new object.
Set their position in 2d space.
Add component.
Scale them as needed.
Generated sprites get stored in a GameObject array called chunk. This is the way I am currently generating sprites.
chunk[i] = new GameObject();
chunk[i].gameObject.transform.position = new Vector2(spriteCoordX, spriteCoordY);
chunk[i].AddComponent<SpriteRenderer>();
SpriteRenderer renderer = chunk[i].GetComponent<SpriteRenderer>();
renderer.sprite = tiles[calculatedFrameId]; //Set correct sprite frame.
chunk[i].gameObject.transform.localScale = new Vector2(6.75f , 6.75f);
I don't know, adding component and scaling every single time I want to create a new sprite in code seems redundant and unnecessary and I am sure there is a better way to do that.
To sum up:
I need the best (fastest) possible way to generate large number of sprites, set their frame, position and proper scale.
Cannot help posting this image here, as this is really of thousands of words, thanks #AidenH for the "Object pooling" comment!
I apologize this took me some time to get to... I ended up creating a project where I generate an open world, and I use Perlin Noise to generate portions of the map depending on where the player is(Since I dont have an actual terrain)
I used Object Pooling for this, so I have a World Manager that knows what the world looks like (or in my case it uses Perlin Noise), this WorldManager pools tiles.
NOTE: This is one way to do it, using Object Pooling.
So basically it would look something like this:
public class WorldManager : MonoBehaviour {
// Being cheap here for the time, if you use this route make sure to use a proper singleton pattern
static public WorldManager instance;
[SerializeField] // Created a prefab for all my tile objects
private TileObject tilePrefab;
[SerializeField]
private int StartingTilePool = 300;
[SerializeField] // In my case this list stored 1 sprite, and I just changed that sprite color depending on the value of perlin noise
List<Sprite> terrainSprites;
private Queue<TileObject> objectPool = new Queue<TileObject>();
void Start() {
instance = this; // Again use a proper singleton pattern in your project.
GenerateTilePool();
LoadFirstSetOfTiles();
}
private void LoadFirstSetOfTiles()
{
// my player always starts at 0,0..
for(int x = -SpawnTileBoundry.HorizontalExtents; x <= SpawnTileBoundry.HorizontalExtents; ++x)
{
for(int y = -SpawnTileBoundry.VerticalExtents; y <= SpawnTileBoundry.VerticalExtents; ++y)
{
SetActiveTile(x,y);
}
}
}
private void GenerateTilePool()
{
for(int i =0; i < tilesToGenerate; ++i)
{
TileObject tempTile = Instantiate(tilePrefab);
EnqueTile(tempTile);
}
}
public void EnqueTile(TileObject tile)
{
objectPool.Enqueue(tile);
tile.gameObject.SetActive(false);
}
public void SetActiveTile(int x, int y)
{
TileObject newTile = null;
if(objectPool.count > 0)
{
newTile = objectPool.Dequeue();
}
else
{
// We didn't have enough tiles store in our pool so lets make a new 1.
newTile = Instantiate(tilePrefab);
}
newTile.transform.position = new Vector3(x,y,1); // Used 1 to put it behind my player...
newTile.gameObject.SetActive(true);
// The sprite here would be based off of your world data, where mine is only a white box, I use the second parameters to give it a gray-scaled color.
newTile.UpdateSprite(terrainSprites[0], Mathf.PerlinNoise(x/10.0f, y / 10.0f));
}
}
That is just my WorldManager that handles ObjectPooling for me...
Here is my TileObject
[RequireComponent(typeof(SpriteRenderer))]
public class TileObject : MonoBehaviour {
private SpriteRenderer myRenderer;
private void Awake() {
myRenderer = getComponent<SpriteRenderer>();
}
void Update()
{
if(Mathf.Abs(transform.position.x - Camera.main.transform.position.x) > SpawnTileBoundry.HorizontalExtents || Mathf.Abs(transform.position.y - Camera.main.transform.position.y) > SpawnTileBoundry.VerticalExtents)
{
// With this check the tile knows it is no longer visible,
// I could have used OnBecameInvisible to handle this
// but I added a threshold to the extents, that prevent it so
// players would see tiles "appearing" this caused a nice bug, where
// if you moved just right a row/col of tiles wouldn't spawn.
WorldManager.instance.EnqueTile(this);
}
}
public void UpdateSprite(Sprite sprite)
{
myRenderer.sprite = sprite;
}
public void UpdateSprite(Sprite sprite, float grayColor)
{
UpdateSprite(sprite);
myRenderer.color = new Color(grayColor,grayColor,grayColor,1f);
}
}
Here is my SpawnTileBoundry script:
public class WorldManager : MonoBehaviour {
private int lastX = 0;
private int lastY = 0;
static public int HorizontalExtents;
static public int VerticalExtents;
void Start() {
VerticalExtents = (int)Camera.main.orthograpicSize + 2; // +2 is just my threshold
HorizontalExtents = (int)(VerticalExtents * Screen.width/Screen.height) +3; // +3 is just my threshold you can change these to what you want.
lastX = (int)transform.position.x;
lastY = (int)transform.position.y;
}
void Update() {
int newX = (int)transform.position.x;
int newY = (int)transform.position.y;
HandleNewTileSpawn(lastX - newX, lastY - newY);
}
// This will tell the WorldManager which tiles need to appear
// We are no longer creating new tiles unless we absolutely have to.
// We are also only making new tiles appear in the circumstance that
// we are about to see them.
void HandleNewTileSpawn(int x, int y)
{
if(x != 0)
{
// This code could be refactor to a method so it was less error prone for changes or tweaks...
if(x < 0)
{
for(int i = lastY - VerticalExtents; i < lastY + VerticalExtents; ++i)
{
WorldManager.instance.SetActiveTile(lastX + HorizontalExtents, i);
}
}
else
{
for(int i = lastY - VerticalExtents; i < lastY + VerticalExtents; ++i)
{
WorldManager.instance.SetActiveTile(lastX - HorizontalExtents, i);
}
}
lastX = (int)transform.position.x;
}
if(y != 0)
{
if(lastY < 0)
{
for(int i = lastX - HorizontalExtents; i < lastX + HorizontalExtents; ++i)
{
WorldManager.instance.SetActiveTile(i, lastY + VeritcalExtents);
}
}
else
{
for(int i = lastX - HorizontalExtents; i < lastX + HorizontalExtents; ++i)
{
WorldManager.instance.SetActiveTile(i, lastY - VeritcalExtents);
}
}
lastY = (int)transform.position.y;
}
}
}
I am using the WorldManager to handle my object pooling, yes I am still instantiating quite a few sprites at the beginning but then I eventually stop spawning as there is no need to keep spawning new objects.
Unity doesn't have alot in their documentation in regards to object pooling however they do have a video tutorial that goes over some of the basics of Object Pooling: https://unity3d.com/learn/tutorials/topics/scripting/object-pooling
I'm attempting to write a script that instantiates a row of prefabs. This worked the first time, but every time after that it instantiates one at 0x,0y,0z and the rest ~30x-40x away. I tried setting the initial position before the for loop executes and then resetting the position using the initalPos variable, but that doesn't seem to work. in my code
public class generator : MonoBehaviour {
public int height = 0;
public int width = 0;
private Vector3 temp;
public GameObject sprite;
private Vector3 initialPos;
void Start ()
{
initialPos = new Vector3(0,0,0);
for(int i = 0; i < width; i++)
{
Instantiate (sprite, temp, Quaternion.identity);
temp = sprite.transform.position;
temp.x += 0.089f;
sprite.transform.position = temp;
}
temp = initialPos;
}
}
The temp variable is what I'm setting the current position to so I can add 0.089 to it so that my sprites will line up. I'm trying to reset that value so that they line up starting at 0x every time.
You can simplify your code by specifying the number of prefabs you wish to spawn in a row, rather than the exact width.
Additionally, instead of incrementing your spawn position x value by a hard coded number, use gameObject.transform.localScale.x instead.
For example:
public GameObject Cube;
void Start()
{
SpawnRow(Vector3.zero, 10);
}
void SpawnRow(Vector3 startPosition, int RowLength)
{
Vector3 currentPos = startPosition;
for (int i = 0; i < RowLength; i++)
{
Instantiate(Cube, currentPos, Quaternion.identity);
currentPos.x += Cube.transform.localScale.x;
}
}
Additionally, if you wanted to do something like spawn additional rows next to your each other, you could call SpawnRow() like this:
void Start()
{
Vector3 currentPos = Vector3.zero;
for (int i = 0; i < 3; i++)
{
SpawnRow(currentPos, 10);
currentPos.z += Cube.transform.localScale.z;
}
}
This would give you three rows of 10 gameObjects directly next to each other.