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.
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 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
In the vertical game, I am creating random blocks spawn within a 100f. I am randomly creating 3 different types of objects which are 'platforms' 'boostplatforms' and 'breakableplatforms'. My code for generating all of these give no errors and when I run the game the blocks generated when I check the activity however visually only the objects named 'platform' appear.
The coding I used for this was:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LevelGenerator : MonoBehaviour {
public GameObject platformPrefab;
public GameObject platformPrefab2;
public GameObject platformPrefab3;
public int numberOfPlatforms = 999;
public int numberOfBoostPlatforms = 999;
public int numberOfBreakablePlatforms = 999;
public float levelWidth = 100f;
public float minY = 55f;
public float maxY = 120f;
// Use this for initialization
void Start () {
Vector3 startPosition = new Vector3 ();
for (int i = 0; i < numberOfPlatforms; i++)
{
startPosition.y += Random.Range (minY, maxY);
startPosition.x = Random.Range (-levelWidth, levelWidth);
Instantiate (platformPrefab, startPosition, Quaternion.identity);
}
for (int i = 0; i < numberOfBoostPlatforms; i++)
{
startPosition.y += Random.Range (minY, maxY);
startPosition.x = Random.Range (-levelWidth, levelWidth);
Instantiate (platformPrefab2, startPosition, Quaternion.identity);
}
for (int i = 0; i < numberOfBreakablePlatforms; i++)
{
startPosition.y += Random.Range (minY, maxY);
startPosition.x = Random.Range (-levelWidth, levelWidth);
Instantiate (platformPrefab3, startPosition, Quaternion.identity);
}
}
// Update is called once per frame
void Update () {
}
}
Based on the information provided, I would recommend the following:
Verify that your prefab variables are set to the correct prefabs.
Notice that each platform's Y position is cumulative. Verify that the boost and break platforms aren't simply spawning after all of the standard platforms (which they will be based on your code).
Switch to object pooling. You are spawning 3,000 objects in a single frame even though the majority won't even be visible. The idea would be to spawn a small pool, 10 of each let's say, and when a new platform is needed (coming into view) you recycle one of the existing objects that is no longer visible or needed. Unity's Object Pooling tutorial
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.
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];