I have created a procedurally generated 'tiled floor' in unity3d, using a block prefab asset and script as follows:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Wall : MonoBehaviour
{
public GameObject block;
public int width = 10;
public int height = 10;
public int timeDestroy = 1;
List<GameObject> blockList = new List<GameObject>();
void Start(){
for (int y=0; y<height; ++y)
{
for (int x=0; x<width; ++x)
{
Vector3 offset = new Vector3(x, 0, y);
GameObject hello= (GameObject)Instantiate(block, transform.position + offset, Quaternion.identity);
blockList.Add(hello);
}
}
StartCoroutine(SelfDestruct());
}
void Update()
{
SelfDestruct();
}
IEnumerator SelfDestruct()
{
yield return new WaitForSeconds(timeDestroy);
Destroy(blockList[UnityEngine.Random.Range(0,(width*height))]);
}
}
When I run the game, one of the 100 blocks has been destroyed, but then nothing happens. From my script, I was expecting one block to destroy every second, as defined by:
yield return new WaitForSeconds(timeDestroy);
where int timeDestroy = 1;
and repeat until all the blocks are gone - game over. How can I change my script so the 100 gameObjects are destroyed one after another, until none are left?
you need to put your IEnumerator SelfDestruct(); in a while loop:
IEnumerator SelfDestruct()
{
while (1<2)
{
Destroy(blockList[UnityEngine.Random.Range(0, (width * height))]);
yield return new WaitForSeconds( timeDestroy );
}
}
that way it will resume destroying the blocks, and you need to remove the SelfDestruct(); in Update.
Ps. Thanks derHugo I have forgotten that ^^.
A couple of issues here
you start a coroutine once but it doesn't really wait for anything (only at the end). There is no repetition anywhere. You probably wanted to use a loop there
you call the SelfDestruct every frame within Update which will execute everything until the first yield!
you potentially try to destroy the same object multiple times since you never remove them from your list.
Actually the entire thing can be one single coroutine!
I would also use Linq OrderBy to randomize the order of blocks once and then simply iterate and destroy them one by one in already randomized order.
Something like e.g.
using System.Linq;
...
public class Wall : MonoBehaviour
{
public GameObject block;
public int width = 10;
public int height = 10;
public int timeDestroy = 1;
// Yes this is valid and Unity will automatically
// run this as a Coroutine!
private IEnumerator Start()
{
// Don't even need this as a field only locally
// already pre-allocate the needed amount
var blockList = new List<GameObject>(height * width);
for (var y = 0; y < height; ++y)
{
for (var x = 0; x < width; ++x)
{
var offset = new Vector3(x, 0, y);
var newBlock = Instantiate(block, transform.position + offset, Quaternion.identity);
blockList.Add(new Block);
}
}
// shuffle the blocks once
var randomizedBlocks = blockList.OrderBy(blockInstance => Random.value);
// then simply iterate them in already randomized order
foreach (var blockInstance in randomizedBlocks)
{
yield return new WaitForSeconds (timeDestroy);
Destroy(blockInstance);
}
// Now you additionally also have direct control when they are all destroyed
Debug.Log("All blocks have been destroyed!");
}
}
Related
I've been experimenting with Unity for the past few weeks; I'm still new. I have dove slightly into ECS, compute shaders, and so forth, but I really don't want to implement these solutions if it isn't necessary due to the complexity.
Can default Unity physics engine really not handle moving 500 cube instances with RigidBodies at once? Or is the way I am doing things particularly bad for performance?
Here is the code I'm using; it's just one script on an empty GameObject. It slows to 16FPS when instantiating the 500 cubes, and then slows to 0.3 FPS when moving them all at once via Rigidbody MoveTowards.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnAndMove : MonoBehaviour
{
public TargetCube TargetCube;
public GameObject CubePrefab;
public Vector3 brickPosition = new Vector3(10, 0, 0);
GameObject[] objects;
int moveCubeInstances;
// Start is called before the first frame update
void Start()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
yield return new WaitForSeconds(3f);
for (int i = 0; i < 500; i++)
{
var cubeClone = Instantiate(CubePrefab, transform.position + brickPosition, transform.rotation);
cubeClone.tag = "CubeInstance";
}
objects = GameObject.FindGameObjectsWithTag("CubeInstance");
yield return new WaitForSeconds(3f);
moveCubeInstances = 1;
while (moveCubeInstances == 1)
{
for (int i = 0; i < 500; i++)
{
objects[i].GetComponent<Rigidbody>().transform.position =
Vector3.MoveTowards(objects[i].GetComponent<Rigidbody>().transform.position, TargetCube.transform.position, 12f);
}
yield return new WaitForFixedUpdate();
}
print("exited while loop");
}
}
Thank you for any help here.
Your call to FindGameObjectsWithTag does an extra search, not only for the new 500 objects but also for all other objects on this scene
transform is already cached field and to call it you do not need to do GetComponent<>
Instantiate is a very difficult operation and it is better to do it in advance, and you can even break it into several calls per frame, for example, 50 each, but definitely not 500 in one frame, but rather use Pooling (https://docs.unity3d.com/Manual/MobileOptimizationPracticalScriptingOptimizations.html)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnAndMove : MonoBehaviour
{
public TargetCube TargetCube;
public Rigidbody CubePrefab;
public Vector3 brickPosition = new Vector3(10, 0, 0);
Rigidbody[] objects;
int moveCubeInstances;
void Start()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
yield return new WaitForSeconds(3f);
objects = new Rigidbody[500];
for (int i = 0 ; i < 500 ; i++)
{
Rigidbody cubeClone = Instantiate(CubePrefab, transform.position + brickPosition, transform.rotation);
cubeClone.tag = "CubeInstance";
objects[i] = cubeClone;
if (i % 50 == 0)
{
yield return new WaitForFixedUpdate();
}
}
yield return new WaitForSeconds(3f);
moveCubeInstances = 1;
while (moveCubeInstances == 1)
{
for (int i = 0 ; i < 500 ; i++)
{
objects[i].transform.position =
Vector3.MoveTowards(objects[i].transform.position, TargetCube.transform.position, 12f);
}
yield return new WaitForFixedUpdate();
}
print("exited while loop");
}
}
I am generating gameobjects (spheres) based on coordinates, which are stored in a .csv file. I have a Gameobject with a Single Sphere as primitive childobject. Based on the data the Object will clone this sphere 17 times and move them around. I can move the whole thing around like i want it to by accessing the parent object, but in editing mode the position of the root sphere makes it uneasy to use.
The following Code makes this possible.
public GameObject parentObj;
public TextAsset csvFile;
[SerializeField]
private float scaleDownFactor = 10;
private int index = 0;
//class Deck : MonoBehaviour
//{
[SerializeField]
private GameObject[] deck;
private GameObject[] instanciatedObjects;
private void Start()
{
Fill();
}
public void Fill()
{
instanciatedObjects = new GameObject[deck.Length];
for (int i = 0; i < deck.Length; i++)
{
instanciatedObjects[i] = Instantiate(deck[i]) as GameObject;
}
}
//}
// Update is called once per frame
void Update()
{
readCSV();
}
void readCSV()
{
string[] frames = csvFile.text.Split('\n');
int[] relevant = {
0
};
string[] coordinates = frames[index].Split(',');
for (int i = 0; i < 17; i++)
{
float x = float.Parse(coordinates[relevant[i] * 3]) / scaleDownFactor;
float y = float.Parse(coordinates[relevant[i] * 3+1]) / scaleDownFactor;
float z = float.Parse(coordinates[relevant[i] * 3+2]) / scaleDownFactor;
//objectTest.transform.Rotate(float.Parse(fields[1]), float.Parse(fields[2]), float.Parse(fields[3]));
//objectTest.transform.Translate(x, y, z);
//parentObj.transform.position = new Vector3(x, y, z);
instanciatedObjects[i].transform.position = new Vector3(parentObj.transform.position.x, parentObj.transform.position.y, parentObj.transform.position.z);
instanciatedObjects[i].transform.eulerAngles = new Vector3(parentObj.transform.eulerAngles.x, parentObj.transform.eulerAngles.y, parentObj.transform.eulerAngles.z);
//instanciatedObjects[i].transform.position = new Vector3(x, y, z);
instanciatedObjects[i].transform.Translate(x, y, z);
}
if (index < frames.Length - 1)
{
index++;
}
if (index >= frames.Length -1)
{
index = 0;
}
}
Here is a Screenshot:
So my question is:
How can I set the Position of this Sphere to one of the moving points, without changing the position of the cloned objects? Since all behave based on the BaseSphere?
Is it possible to make the BaseSphere not visible While the Objects are getting cloned or generated?
I am looking for a solution, that makes it easier to move the datagenerated Object around in Editor.
I would appreciate any kind of input.
Make all Spheres children of Empty Gameobject (for example. Sphere_root) and use this for moving Spheres.
Also check Scriptable Objects. It is simple and very quick method to manage data in Unity.
#Edit
public void Fill()
{
instanciatedObjects = new GameObject[deck.Length];
for (int i = 0; i < deck.Length; i++)
{
instanciatedObjects[i] = Instantiate(deck[i]) as GameObject;
instanciatedObjects[i].transform.parent = Baumer; // or Sphere Root or somehing else.
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateStairs : MonoBehaviour
{
public GameObject stairsPrefab;
public int delay = 3;
public int stairsNumber = 5;
public int stairsHeight = 0;
public Vector3 stairsPosition;
public Vector2 stairsSize;
// Use this for initialization
void Start ()
{
StartCoroutine(BuildStairs());
}
// Update is called once per frame
void Update ()
{
}
private IEnumerator BuildStairs()
{
for (float i = 0; i <= stairsSize.x; i++)
{
for (float k = 0; k <= stairsSize.y; k++)
{
stairsPosition = new Vector3(i, stairsHeight, k);
GameObject stairs = Instantiate(stairsPrefab, stairsPosition, Quaternion.identity);
stairs.transform.localScale = new Vector3(stairsSize.x, 1 , stairsSize.y);
stairsHeight += 1;
yield return new WaitForSeconds(delay);
}
}
}
private void CalculateNextStair()
{
}
}
I messed it up. For example I want to build 5 stairs but the loops are over the stairs size and not number of stairs.
Second it's creating 10 sets of stairs not 5 stairs:
Another problem is how can I make that each stair will be build slowly ? Now it's just Instantiate slowly with delay but how can I generate each stair with delay?
Screenshot of the script inspector:
My current code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateStairs : MonoBehaviour
{
public GameObject stairsPrefab;
public float delay = 0.3f;
public int stairsNumber = 5;
public int stairsPositions = 0;
public int stairsStartPositionHeight = 0;
public float stairsScalingHaight = 1;
public Vector2 stairsPosition;
public Vector2 stairsSize;
// Use this for initialization
void Start()
{
StartCoroutine(BuildStairs());
}
// Update is called once per frame
void Update()
{
}
private IEnumerator BuildStairs()
{
for (float i = 0; i <= stairsNumber; i++)
{
// x=0f, y=z=stairsHeight
stairsPosition = new Vector3(0f, stairsPositions, stairsPositions);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairs.transform.localScale = new Vector3(
stairsSize.x,
stairsScalingHaight,
stairsSize.y);
stairsStartPositionHeight += 1;
yield return new WaitForSeconds(delay);
}
}
private void CalculateNextStair()
{
}
}
There's no reason to loop over the size of the stairs at all; you want to loop over stairsNumber, which is yet unused in your code.
Also, you don't need to change the x component of your stairs' positions. Keep it at 0f (or whatever you need).
The y and z components of your stairs positions (relative to the starting point) should both be factors of stairHeight. In this particular case, you want them to be equal to stairHeight, so that you get "square" step shapes.
private IEnumerator BuildStairs()
{
for (int i = 0; i <= stairsNumber ; i++) {
// x=0f, y=z=stairsHeight
stairsPosition = new Vector3(0f, stairsHeight, stairsHeight);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairs.transform.localScale = new Vector3(
stairsSize.x,
1f ,
stairsSize.y);
stairsHeight += 1f;
yield return new WaitForSeconds(delay);
}
}
If you change stairSize to be a Vector3, then you can just use stairSize directly as the localScale, and increment stairsHeight by stairsSize.y instead of just 1f.
If you want to offset the starting position of your stairs, you need to include an offset. I recommend keeping it separate from the height counter until you need to add them.
Also, if you want to have rectangular sized steps, keep a widthFactor to multiply by the height to find how far each step moves horizontally.
Combining these changes might look like this:
Vector3 stairSize;
float stepWidthFactor=1f;
Vector3 stairsStartPosition;
private IEnumerator BuildStairs() {
for (int i = 0; i <= stairsNumber ; i++) {
stairsPosition = new Vector3(
stairsStartPosition.x,
stairsStartPosition.y + stairsHeight,
stairsStartPosition.z + stairsHeight*stepWidthFactor);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairsHeight += stairsSize.y;
stairs.transform.localScale = stairSize;
yield return new WaitForSeconds(delay);
}
}
a and objeler [] are gameobjects.I use yoketme 5 times.I get some random objects with a and give them gravity.But only 1 of the objects lose gravity after they get in bekleme.The last a is the one overwriten to all a objects that uses yoketme before timer(9 seconds).How can i make bekleme to take all gameobjects and not the last one only?
void YokEtme(){
int x = Random.Range (35, 0);
a = objeler [x];
z = a.GetComponent<Transform> ().position;
a.GetComponent<Rigidbody> ().useGravity = true;
Debug.Log (a);
StartCoroutine (bekleme ());
}
IEnumerator bekleme (){
yield return new WaitForSeconds (9);
a.GetComponent<Rigidbody> ().useGravity = false;
a.GetComponent<Rigidbody> ().constraints = RigidbodyConstraints.FreezePosition;
a.transform.position= z;
a.GetComponent<Rigidbody> ().constraints = RigidbodyConstraints.None;
Debug.Log (a);
}
Since some contexts are missing from the question, please allow me to boldly assume that:
The provided code are part of a MonoBehaviour.
a and z are fields/properties of the MonoBehaviour.
What you want to do is to randomly pick five gameobjects from objeler, apply gravity to them, and unapply gravity after a few seconds.
If my assumptions are correct, the problem is pretty simple. The reason that only one chosen object loses gravity is because that Unity allows only one coroutine being executed at a time.
That is, even though you called YokEtme five times and each will lead to StartCoroutine(bekleme()) and expected that there will be five coroutine running in parallel, actually each call to StartCoroutine(bekleme()) will discard the previously running coroutine and start a new one.
So a possible solution is to handle all the five gameobjects in one coroutine instance. Such as:
using System.Collections.Generic;
using UnityEngine;
public class SomeMono : MonoBehaviour
{
private List<GameObject> _controlledGOs = new List<GameObject>();
private List<float> _loggedZs = new List<float>();
void YokEtme()
{
int x = Random.Range(35, 0);
a = objeler [x];
a.GetComponent<Rigidbody>().useGravity = true;
_controlledGOs.Add(a);
_loggedZs.Add(a.GetComponent<Transform>().position);
}
void SetGravityForGameObjects()
{
for (var i = 0; i < 5; i++)
YokEtme();
StartCoroutine()
}
IEnumerator bekleme ()
{
yield return new WaitForSeconds (9);
for (var i = 0; i < _controlledGOs.Count; i++)
{
var a = _controlledGOs[a];
a.GetComponent<Rigidbody>().useGravity = false;
a.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezePosition;
a.transform.position= _loggedZs[i];
a.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.None;
Debug.Log (a);
}
}
}
Here is my code, brick_col is updating itself as it should be, print(brick_col), tells me once the loop is complete brick_col is +1 itself, but, print (positions[i]), tells me my y value is always 0) the Vector3 isn't being updated with the value. Any ideas? Many thanks
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Brick_Spawn_Test : MonoBehaviour {
List<Vector3> positions = new List<Vector3>();
private int bricks_in_row=9;
public GameObject Brick;
private int no_in_row=9;
private int brick_col=0;
private int number_of_brick_col=2;
void Start(){
Check_Bricks ();
}
void Check_Bricks(){
if (brick_col != number_of_brick_col) {
print ("not enough bricks");
Create_Bricks ();
}
}
void Create_Bricks(){
for (int i = 0; i <= bricks_in_row-1; i++)
{
for (int a = -4; a <= no_in_row/2; a++)
{
positions.Add(new Vector3(a,brick_col,0f));
}
print (brick_col);
print (positions [i]);
transform.position = positions[i];
Instantiate(Brick,transform.position, transform.rotation);
}
brick_col = brick_col + 1;
Check_Bricks ();
}
}
In your code you use the following variable as your y value
private int brick_col=0;
In your inner loop you add elements to your positions list with
positions.Add(new Vector3(a,brick_col,0f));
Without updating the brick_col until you are outside both loops.
Move this brick_col = brick_col + 1;
to where you want the update to really happen and if you put it into the inner loop you will probably also want to reset it just before entering again.
Alright honestly, you are doing some unnecessary things I will explain why as I go over it, I do things like this at times as well when I am trying to figure out what is going on, or when I am in a rush to build something I am excited to try, so starting out I will use your code and explain, then the fix then I will show another way to do this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Brick_Spawn_Test : MonoBehaviour {
List<Vector3> positions = new List<Vector3>();
private int bricks_in_row=9;
public GameObject Brick;
private int no_in_row=9; // No need for a number in row because you have bricks in row which is the same thing.
private int brick_col=0; // No need for this variable, as you are going to be counting anyways (in this case your i variable)
private int number_of_brick_col=2;
void Start(){
Check_Bricks ();
}
void Check_Bricks(){ // This function is unnessary, it appears it may have been added when you were trying to fix your y issue.
if (brick_col != number_of_brick_col) {
print ("not enough bricks");
Create_Bricks ();
}
}
void Create_Bricks(){
for (int i = 0; i <= bricks_in_row-1; i++) // This will run 9 times.
{
for (int a = -4; a <= no_in_row/2; a++) // This will also run 9 times
{
positions.Add(new Vector3(a,brick_col,0f));
}
// Move all this into the inner loop.
print (brick_col);
print (positions [i]); // By this point you will have 9 then 18 then 27... as your inner loop this position would be positons[i * bricks_in_row + (a +4)] with how you are looping
transform.position = positions[i]; /// This positions should be based off of the individual brick, next time around you are setting this position to the second index but by this time you have 18.
Instantiate(Brick,transform.position, transform.rotation);
//
// brick_col = brick_col + 1; This will be in the outter loop
}
brick_col = brick_col + 1; // This should be before the closing bracket. not outside the loop
Check_Bricks ();
}
}
This is how it would look, if I kept your variables and just fixed your y and positioning problems:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Brick_Spawn_Test : MonoBehaviour {
List<Vector3> positions = new List<Vector3>();
private int bricks_in_row=9;
public GameObject Brick;
private int no_in_row=9;
private int brick_col=0;
private int number_of_brick_col=2;
void Start(){
Check_Bricks ();
}
void Check_Bricks(){
if (brick_col != number_of_brick_col) {
print ("not enough bricks");
Create_Bricks ();
}
}
void Create_Bricks(){
for (int i = 0; i <= bricks_in_row-1; i++)
{
for (int a = -4; a <= no_in_row/2; a++)
{
positions.Add(new Vector3(a,brick_col,0f));
print (brick_col);
print (positions [i * bricks_in_row + a +4]);
transform.position = positions[i * bricks_in_row + a +4];
Instantiate(Brick,transform.position, transform.rotation);
}
brick_col = brick_col + 1;
}
Check_Bricks ();
}
}
This is a way to handle this, you can ignore the way I name variables as it is a matter of preference:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Brick_Spawn_Test : MonoBehaviour {
[SerializeField]
private int totalBrickColumns = 9; // Serializing it so I can use this for multiple test cases and edit the variable in the inpsector.
[SerializeField]
private int totalBrickRows = 2;
[SerializeField]
private Vector2 startingPoint = new Vector2(-4, 0);
[SerializeField]
private GameObject Brick;
void Start()
{
CreateBricks();
}
void CreateBricks()
{
Vector2 spawnPosition = startingPoint;
for(int x = 0; x < totalBrickColumns; ++x) // x is my column
{
spawnPosition.x = startingPoint.x + x; // the x is my offset from the startingPoint.x so if I wanted to start at -4 i just set the startingPoint.x to -4
for(int y = 0; y < totalBrickColums; ++y) // y is my row
{
spawnPosition.y = startingPoint.y + y; // the y is my offset from the startingPoint.y
print("Brick Location: " + spawnPosition.toString());
Instantiate(Brick,spawnPosition, transform.rotation);
}
}
}
}
In regards to why your y isn't updating, is because you are not updating the variable inside of your first loop. See the comment in your code on brick_col in the Create_Brick() function.
EDIT: I noticed something I wasn't considering when I said you needed to update your outter loop, I also added a fix using only your code, with your variables.