Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by Ludovic · Mar 11, 2011 at 04:46 PM · transformparentchildforeach

Unparenting a transform in a foreach loop in the parent transform

Hi everyone, This little piece of code has a bug.

using UnityEngine; using System.Collections;

public class TestRemoveChild : MonoBehaviour { public void Update() { if( Input.GetKeyDown( KeyCode.Y ) ) { RemoveChild(); } }

 public void RemoveChild()
 {
     foreach( Transform child in transform )
     {
         Debug.Log( "child : " + child.name + "\n" );
         if( child.name == "Child_1" ) // Or some given condition
             child.parent = null;

         // Next child won't be enumerated
     }
 }

}

If we set the parent of a transform in a foreach loop of the parent, the next child is not enumerated.

I know removing an item while enumerating is not generally a good idea. But Unity don't even throw a warning about that, either in the doc or at runtime. On the other hand, C# or Mono throws an exception when we do something like that.

And I just spent an afternoon figuring why some object was correctly suppressed when some other doesn't while both of them pass the condition :-(.

Comment
Add comment · Show 3
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Jessy · Mar 11, 2011 at 05:19 PM 0
Share

There is no question here. Put the children into a List, first, and then operate on that.

avatar image Ludovic · Mar 14, 2011 at 09:13 AM 0
Share

The solution for this bug is obvious. But again it's easy to check if we are in a foreach loop when we access to the parent property. Then we can either throw an exception (simple solution which prevents time loss) or it could be managed (more complex but I think it can be done).

avatar image Ludovic · Mar 14, 2011 at 09:33 AM 0
Share

Ok I didn't see there were the GetChild and the GetChildCount methods like Statement says below (well it's not a surprise since they are not documented :-(). With these methods, a for loop can be used and I can manage the index myself.

3 Replies

· Add your reply
  • Sort: 
avatar image
2
Best Answer

Answer by Statement · Mar 11, 2011 at 05:34 PM

A simple example depicting the suggestion put by Jessy.

public void RemoveChild() { List<Transform> unparent = new List<Transform>(transform.childCount);

 foreach (Transform child in transform)
 {
     if (child.name == "Child_1")
         unparent.Add(child);
 }

 foreach (Transform child in unparent)
 {
     child.parent = null;
 }

}


Extra reading that is slowly but steadily going off topic ahead:

You can make use of predicates to allow to unparent a child following an arbitrary test:

public void UnparentChildren(System.Func<Transform, bool> predicate) { List<Transform> unparent = new List<Transform>(transform.childCount);

 foreach (Transform child in transform)
 {
     if (predicate(child))
         unparent.Add(child);
 }

 foreach (Transform child in unparent)
 {
     child.parent = null;
 }

}

And use it as such:

// Unparents children named Child_1 // Unparents children tagged Enemy UnparentChildren(t => t.name == "Child_1"); UnparentChildren(t => t.tag == "Enemy");

// Or do it like this, combined, "if name is Child_1 or if tag is Enemy": UnparentChildren(t => t.name == "Child_1" || t.tag == "Enemy");


And you can even make it a bit more reusable if you also provide an action so you can do other things that just unparenting them:

// Assume using System; public void ForEachChildThat(Func<Transform, bool> predicate, Action<Transform> action) { List<Transform> children = new List<Transform>(transform.childCount);

 foreach (Transform child in transform)
 {
     if (predicate(child))
         children.Add(child);
 }

 foreach (Transform child in children)
 {
     action(child);
 }

}

Then you could do:

// Unparens children named Child_1 
// Calls OnShakeOff on children tagged Enemy 
ForEachChildThat(t => t.name == "Child_1", t => t.parent = null);
ForEachChildThat(t => t.tag == "Enemy", t => t.SendMessage("OnShakeOff"));
Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Statement · Mar 11, 2011 at 05:47 PM 0
Share

I seem to always overcomplicate it. Ugh. Just do with the first example :)

avatar image
1

Answer by MvD · Mar 11, 2011 at 05:08 PM

Like you say, you're not supposed to change the object you are enumerating (in this case 'transform').

This should work:

    Transform childToRemove;
    do
    {
        childToRemove = null;
        foreach (Transform child in transform)
        {
            if (child.name == "RemoveMe")
            {
                childToRemove = child;
                break;
            }
        }
        if (childToRemove != null)
        {
            childToRemove.parent = null;
        }
    } 
    while (childToRemove != null);

You could use some fancy LINQ stuff if you want for the 'find' bit.

Comment
Add comment · Show 8 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Statement · Mar 11, 2011 at 05:26 PM 1
Share

While this probably works, its complexity is quite bad. For example, had you 50 children and the last 25 were the ones to be removed you'd end up iterating 26 * 26 (676) times, or so. $$anonymous$$aybe 675 given the last time it wouldn't find any object.

avatar image Statement · Mar 11, 2011 at 05:49 PM 0
Share

But a new and interesting approach I've never stumbled across. $$anonymous$$emory usage is very low :)

avatar image Statement · Mar 11, 2011 at 06:13 PM 0
Share

But practically speaking, I did some performance tests on this approach vs. using a list, and there is no noticable difference at all until you start to reach into the thousands. And if you're dealing with thousands of children chances are you have bigger problems to handle :)

avatar image MvD · Mar 11, 2011 at 06:24 PM 0
Share

Yeah, it's quadratic, but, like you say, it will only become a problem when the number of children is really high. If you know that the children are 'vector-like', you can always do a single for-loop over the children and only increment the index when the child is not removed.

avatar image MvD · Mar 11, 2011 at 06:25 PM 1
Share

Or simpler: '...and don't increment the index when the child is removed'

Show more comments
avatar image
1

Answer by rageingnonsense · Apr 28, 2015 at 12:02 PM

This is an old question, but, here is how I deal with operating on children in a foreach loop a bit cleanly. Just add an extension method for Transform. Drop this class in your project:

 using UnityEngine;
 using System.Collections.Generic;
 
 public static class TransformExtensions {
 
     public static Transform[] GetChildren(this Transform transform) {
         List<Transform> children = new List<Transform>();
         foreach (Transform child in transform) {
             children.Add(child);
         }
 
         return children.ToArray();
     }
 }

And use it like this:

 foreach(Transform child in myTransform.GetChildren()) {
     child.SetParent(someOtherTransform);
 }

It's pretty much the same thing as the accepted answer, except it eliminates having to write two foreach loops everything you need to operate on children.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

1 Person is following this question.

avatar image

Related Questions

Make a simple tree 1 Answer

using foreach transform child, unity gets stuck 2 Answers

Transform child object over time and then destroy it. 1 Answer

Parent transform not following child transform 0 Answers

parenting problem 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges