Unity Transform GetChild() works weird - c#

I have 4 objects but when i deleting GameObject in transform.GetChild(n) deleted object still alive.
Before deleting
After deleting
Logs
IEnumerator DeletePlanet(int item) {
yield return new WaitForSeconds(5);
planets.RemoveAt(item);
Destroy(transform.GetChild(item).gameObject);
for (int i = 0; i < planets.Count; i++) {
UpdatePositions(transform.GetChild(i).gameObject.GetComponent<RectTransform>(), i);
}
for (int i = 0; i < startPlanetCount; i++) {
print(transform.GetChild(i).gameObject.GetComponent<RectTransform>().anchoredPosition);
}
}
Idk what can i do here.

There could be two things going on here.
The script is removing the item from the parents transform before deleting it, so the object is no longer accessible using transform.GetChild
The script assumes that a destroyed object is instantly destroyed, but it is not. In an update method, the object is destroyed after the update method is finished. I'm not sure when an object is destroyed during a coroutine, but my guess is after the coroutines finishes or after a yield.

Related

How to Instantiate prefab clone and each clone to have random number of children of children?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateObjects : MonoBehaviour
{
public GameObject objectPrefab;
[Range(1, 100)]
public int numberOfObjects = 1;
private GameObject parent;
// Start is called before the first frame update
void Start()
{
parent = GameObject.Find("Generate Objects");
StartCoroutine(SpawnObjects());
}
// Update is called once per frame
void Update()
{
}
IEnumerator SpawnObjects()
{
while (true)
{
yield return new WaitForSeconds(0);
if (objectPrefab != null)
{
Instantiate(objectPrefab, new Vector3(Random.Range(0,10), Random.Range(0,10), Random.Range(0,10)), Quaternion.identity,parent.transform);
}
yield return null;
}
}
}
now it's generating random objects.
i want it also to generate for each clone of the prefab and random number of childs and random childs of childs with unlimited depth of children of children. it can be very nested.
The idea in the end is that each clone of the prefab will have some children some with only children and some with more depth levels. the goal is to make the tree of children randomly.
I need to use Transform.childCount, Transform.GetChild and Transform.SetParent.
Also you need to stop your coroutine, of not it'll run forever.
Not debugged draft for the first children hierarchy of private GameObject parent:
IEnumerator SpawnObjects() {
for (int i=0; i<parent.childCount;i++) {
Transfrom parentGo = parent.transform.GetChild(i);
GameObject instantiatedGO = Instantiate(objectPrefab, new Vector3(Random.Range(0,10), Random.Range(0,10), Random.Range(0,10)), Quaternion.identity,parent.transform);
instantiatedGO.transform.SetParent(parentGo);
}
}
Also check the coroutine documentation. The execution of a coroutine can be paused at any point using the yield statement. When a yield statement is used, the coroutine pauses execution and automatically resumes at the next frame, so if you do not what to handle the stop of it, or need some frame or time related operation, you can execute it without the yield statement. That leads the question of if you need the coroutine at all. If not, why then not just call the method directly without a coroutine. Thats for you to decide.
With what you've got already and the documentation you should be able to achieve it :)

Using a Coroutine in a loop

I have a question about coroutines in a loop. More specific about how I can achieve it that my loop continues to check the condition until the waitForSeconds in the coroutine are over.
I have attached a screenshot of my code. My problem is that the Line "Now I am executed" is shown right after myAudio.Play();
It makes sense since the coroutine only waits for the statements after the yield return but how can I make it work with the for loop?
Any help would be appreciated! Thank you so much.
StartCoroutine starts each IEnumeratoras a new Coroutine and immediately continuous with the rest of the code.
So as said without having further context of your code what you would do is something like
private void playDBStack()
{
fillList();
StartCoroutine(playDBStackRoutine())
}
private IEnumerator playDBStackRoutine()
{
for(var i = 0; i < 2; i++)
{
// executes playAudio and waits for it to finish
yield return playAudio();
}
}
private IEnumerator playAudio()
{
var x = chooseList(score);
// executes PlayAndWait and wait for it to finish
yield return PlayAndWait(x);
}
this way you execute and at the same time wait until the IEnumerators are finished before going to the next one.
Some further notes:
The line
myAudio.GetComponent<AudioSource>();
does absolutely nothing.
The entire method updateScore in its current state could simply be replaced by using
score++;
I would consider a method that can be replaced by a simple operator "code smell" ;)

GameObject.FindGameObjectsWithTag("Enemy").Length off by one for some reason?

so I'm wanting to pause the game once the amount of enemies hits 0. So I'm using GameObject.FindGameObjectsWithTag("Enemy").Length to find the number of enemies. I put this in a function that's called right when the enemies are instantiated so I can see the length go to 4, as there's 4 enemies spawning. When an enemy is killed the function is called again where the length is printed to console again. For some reason, on the first enemy killed the count repeats with a 4 again despite there only being 3 enemies. Once another enemy is killed it reports 3 when there's actually 2 and so on until I get to 1 when there's 0 enemies.
Here's the first snippet of code:
public class EnemyList : MonoBehaviour
{
public List<GameObject> weakMobs = new List<GameObject>();
public List<GameObject> mediumMobs = new List<GameObject>();
public List<GameObject> bossMobs = new List<GameObject>();
public List<Transform> spawningChildren = new List<Transform>();
public static int mobCount;
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0], spawningChildren[Random.Range(0, 4)]) as GameObject;
}
CheckMobCount();
}
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Length;
print(mobCount);
}
The next piece of code is where the enemy is killed and the CheckMobCount() is called again.
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
enemyList.CheckMobCount();
//needs death animations
}
}
Here's the console messages:
Console of printed lengths
I'm self taught so I apologize if this is elementary. I've tried doing this several different ways and this is the closest I've been but I'm open to new ideas as well.
Thank you!!
As noted in this answer, the object is not actually destroyed in the current frame.
From the documentation:
The object obj is destroyed immediately after the current Update loop… Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
I also agree that using DestroyImmediate() is a bad idea.
Ultimately, your question seems to really be about pausing the game when the enemy count reaches 0, which unfortunately hasn't actually been answered yet.
In fact, you don't really need to do anything different except move the check for the enemy count to the beginning of the Update() method, and pause the game there if it's 0. Then you'll find that the component for the enemy has been destroyed at that point.
Presumably enemies are spawned before the update loop starts (i.e. before the first frame), but if not then you can use whatever logic you're already using to decide that new enemies need to be spawned, to detect the fact that you haven't spawned any yet and avoid pausing before the enemies have spawned.
Here you have attached your script to your enemy instances. And they are still alive when you are querying for the number of enemies left.
You should do the following:
public class Enemy: MonoBehaviour
{
public static int EnemyCount = 0;
private void Start()
{
EnemyCount++;
}
private void OnDestroy()
{
EnemyCount--;
}
}
And then you can query the enemy count from anywhere but just excessing the EnemyCount by Enemy.EnemyCount.
If you want to get a more difficult example then you can check out this Game Dev tutorial: https://www.youtube.com/watch?v=LPBRLg4c5F8&t=134s
Destroy is actually executed at the end of the frame. There is DestroyImmediate that is executed immidiatelly but it's not recommended to be used. What I would do is to add a field or a property to identify whether the enemy is still alive and then to check against it. Something like:
class Enemy : MonoBehaviour
{
public bool IsAlive { get; set; } = true;
}
public class EnemyList : MonoBehaviour
{
//...
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Select(x => x.GetComponent<Enemy>()).Count(x => x.IsAlive);
print(mobCount);
}
}
And then:
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
this.GetComponent<Enemy>().IsAlive = false;
enemyList.CheckMobCount();
//needs death animations
}
}
This can be further optimized to store the Enemy somewhere and not use GetComponent every time but you get the idea.
As already mentioned by others the issue is that Destroy is executed delayed.
Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
You could simply count only the GameObjects that are still alive, those for which the bool operator is true.
Does the object exist?
It will be false for objects destroyed in that same frame.
E.g. using Linq Count
using System.Linq;
....
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Count(e => e);
which basically equals doing
mobCount = 0;
foreach(e in GameObject.FindGameObjectsWithTag("Enemy"))
{
if(e) mobCount++;
}
There is no need for an additional property or Component.
I am suggesting you to use “DestroyImmediate” instead of “Destroy”,Then look at the result.
I have a better idea, why not just use static variables when spawning enemies?
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0],
spawningChildren[Random.Range(0, 4)]) as GameObject;
mobCount++;
}
}
Do not use Linq
Do not use DestroyImmediate (it will freeze and bug your game, probably)
Avoid FindGameObjectsWithTag in loops, only in initialization.
Track your enemies in an array or list
When you destroy an enemy, remove it's reference from the list
Use the list count/length to get the real actual number.

XNA - Collision never happens

I'm creating a game in XNA and I need a collision detection logic:
public Rectangle boundingBox = new Rectangle((int)playerShipPos.X, (int)playerShipPos.Y, frameWidth, frameHeight);
this.boundingBox = new Rectangle((int)meteorPosPub.X, (int)meteorPosPub.Y, (int)meteorTexture.Width, (int)meteorTexture.Height);
for (int i = meteorList.Count - 1; i >= 0; i--)
{
meteorGenerator meteor = new meteorGenerator(Vector2.Zero);
if (meteorList[i].meteorPosPub.Y > 664)
{
meteorList.RemoveAt(i);
if (meteor.boundingBox.Intersects(playerShip.boundingBox))
{
meteorList.RemoveAt(i);
}
}
}
So I want to achive this effect: if the player ship touches the meteor the meteor is hides and is removed from the list but nothing happens, actually.
for (int i = meteorList.Count - 1; i >= 0; i--)
{
meteorGenerator meteor = new meteorGenerator(Vector2.Zero);//you are creating new list meteors every frame, this is ridiculous
if (meteorList[i].meteorPosPub.Y > 664)
{
meteorList.RemoveAt(i);//if you are removing a position from a list, not destroying the meteor
if (meteor.boundingBox.Intersects(playerShip.boundingBox))
{
meteorList.RemoveAt(i);//you already did this, this conditional is unnecessary
}
}
}
I have no idea what it is you are doing, but this is what I would do.
1.Let the player and meteor inherit from a class with the properties a solid object would have.
Add those to a list with unique IDs based on the object type.
Every frame, check for IDs (this gives you extra control on what you want to collide with what).
Proceed to check for collisions, in case you want to remove an element, just remove it from the list and destroy it.

how to delete only one clone of a game object

For example I have coin1(clone) and coin1(clone)(clone). If I click coin1(clone)(clone) it will be destroyed. But in my case, when I click coin1(clone)(clone), coin1(Clone) is deleted.
Codes I've been using
void NumberOfSelectedCoins()
{
Debug.Log (BlueCoinScript.b);
if(SelectedCoins.cntCoins%BlueCoinScript.b == 0)
{
for (int n=0;n<selectC.selectedNumCoins.Count; n++)
{
Destroy(selectC.selectedNumCoins[n]);
TotalSum.totalValue = 0;
SelectedCoins.breakPointsCount++;
}
breakpointsText.text = SelectedCoins.breakPointsCount.ToString ();
selectC.selectedNumCoins.Clear();
}
}
And also if you have 1 game object and you cloned it so that you can have 3 same game objects, how can you identify that when you clicked that game object, that game object is clone1 or clone2 or clone3?
You can for example identify GameObject instances by Object.GetInstanceID() or by Object.name. In the latter case though you have to take care that the object you search for is named and that this name is unique.
Further reference is found here.

Categories

Resources