2
\$\begingroup\$

I wanted to create my spawn points for NPC's and also the player inside my 3D modelling software. So when I import each level, it has its own set of spawn points already built in.

This seems to work fine for me so far. But I had intended to use the FindObjectsOfType function. The problem is its important that I set the points in order (ie. spawn1 at bottom ... spawn 4 at top).

My problem is that the first element in the array is not my 'spawn_1', in my case here it is actually spawn2 first.

This is my hierarchy: enter image description here

beer_tap_stand 1-4 are the spawn points in question at the moment, as you can see they are in order.

Then i have this code trying to spawn the player at 'beer_tap 1' :

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    Customer_SpawnPoint[] customer_spawns;
    Beer_Tap_Standpoint[] beertap_standpoints;

    Player playerPrefab;
    Player player;

    private void Start()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(this);

        customer_spawns = FindObjectsOfType<Customer_SpawnPoint>();
        beertap_standpoints = FindObjectsOfType<Beer_Tap_Standpoint>();
        playerPrefab = Resources.Load<Player>("Player");
        player = Instantiate(playerPrefab, beertap_standpoints[0].transform.position, Quaternion.identity);

    }
}

I need a way to guarantee the order, but I can't just drag them to the script as I have multiple levels.

\$\endgroup\$
5
  • \$\begingroup\$ Did you consider simply using System.Array.Sort to sort the results by name? \$\endgroup\$ – DMGregory Jun 5 at 11:24
  • \$\begingroup\$ Sorry I have not been online to see these comments. I didn't use any sorting function to sort them. Solely the FindObjectsOfType() function so far \$\endgroup\$ – I_Keep_Trying Jun 5 at 13:19
  • \$\begingroup\$ I'll try the System.Array.Sort solution first. Seems like it will be simple. Thank you for all responses. \$\endgroup\$ – I_Keep_Trying Jun 5 at 13:22
  • \$\begingroup\$ I'm still trying to learn about Lambda functions, they confuse the heck outta me! But is this one of those times I need it to use in the Array.Sort() function? I have seen the detailed answer from Aybe, but just checking there isnt some magic one-line solution. Sorry to be a pain \$\endgroup\$ – I_Keep_Trying Jun 5 at 13:58
  • \$\begingroup\$ In this case, could you use the findobjectswithtag function, or does this rely on hiarchy order too? \$\endgroup\$ – Millard Jun 5 at 17:10
2
\$\begingroup\$

You want a hierarchical sort using Comparer<Transform> with GetSiblingIndex:

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;

namespace xyz
{
    /// <summary>
    ///     Hierarchical comparisons utility.
    /// </summary>
    public static class HierarchyComparer
    {
        /// <summary>
        ///     Gets an instance that compares a hierarchy of <see cref="UnityEngine.Component" />.
        /// </summary>
        [PublicAPI]
        public static System.Collections.Generic.Comparer<Component> Component { get; } = new Comparer<Component>(Compare);

        /// <summary>
        ///     Gets an instance that compares a hierarchy of <see cref="UnityEngine.GameObject" />.
        /// </summary>
        [PublicAPI]
        public static System.Collections.Generic.Comparer<GameObject> GameObject { get; } = new Comparer<GameObject>(Compare);

        /// <summary>
        ///     Gets an instance that compares a hierarchy of <see cref="UnityEngine.Transform" />.
        /// </summary>
        [PublicAPI]
        public static System.Collections.Generic.Comparer<Transform> Transform { get; } = new Comparer<Transform>(Compare);

        private static int Compare([CanBeNull] Component x, [CanBeNull] Component y)
        {
#pragma warning disable IDE0031 // Null check can be simplified
            return Compare(x != null ? x.transform : null, y != null ? y.transform : null);
#pragma warning restore IDE0031
        }

        private static int Compare([CanBeNull] GameObject x, [CanBeNull] GameObject y)
        {
#pragma warning disable IDE0031 // Null check can be simplified
            return Compare(x != null ? x.transform : null, y != null ? y.transform : null);
#pragma warning restore IDE0031
        }

        private static int Compare([CanBeNull] Transform x, [CanBeNull] Transform y)
        {
            if (x == null && y == null)
                return 0;

            if (x == null)
                return -1;

            if (y == null)
                return +1;

            var hierarchy1 = GetHierarchy(x);
            var hierarchy2 = GetHierarchy(y);

            while (true)
            {
                if (!hierarchy1.Any())
                    return -1;

                var pop1 = hierarchy1.Pop();

                if (!hierarchy2.Any())
                    return +1;

                var pop2 = hierarchy2.Pop();

                var index1 = pop1.Index;
                var index2 = pop2.Index;

                if (index1 == index2)
                    continue;

                var compare = index1.CompareTo(index2);

                return compare;
            }
        }

        private static Stack<Hierarchy> GetHierarchy([NotNull] Transform transform)
        {
            if (transform == null)
                throw new ArgumentNullException(nameof(transform));

            var stack = new Stack<Hierarchy>();

            var current = transform;

            while (current != null)
            {
                var index = current.GetSiblingIndex();

                stack.Push(new Hierarchy(index, current));

                current = current.parent;
            }

            return stack;
        }

        /// <summary>
        ///     Hierarchically sort an array of components.
        /// </summary>
        /// <param name="components"></param>
        /// <returns></returns>
        [PublicAPI]
        public static Component[] HierarchicalSort([NotNull] this Component[] components)
        {
            if (components == null)
                throw new ArgumentNullException(nameof(components));

            Array.Sort(components, Component);

            return components;
        }

        /// <summary>
        ///     Hierarchically sort an array of game objects.
        /// </summary>
        /// <param name="gameObjects"></param>
        /// <returns></returns>
        [PublicAPI]
        public static GameObject[] HierarchicalSort([NotNull] this GameObject[] gameObjects)
        {
            if (gameObjects == null)
                throw new ArgumentNullException(nameof(gameObjects));

            Array.Sort(gameObjects, GameObject);

            return gameObjects;
        }

        /// <summary>
        ///     Hierarchically sort an array of transforms.
        /// </summary>
        /// <param name="transforms"></param>
        /// <returns></returns>
        [PublicAPI]
        public static Transform[] HierarchicalSort([NotNull] this Transform[] transforms)
        {
            if (transforms == null)
                throw new ArgumentNullException(nameof(transforms));

            Array.Sort(transforms, Transform);

            return transforms;
        }

        #region Nested type: Comparer

        private sealed class Comparer<T> : System.Collections.Generic.Comparer<T>
        {
            public Comparer([NotNull] Func<T, T, int> func)
            {
                Func = func ?? throw new ArgumentNullException(nameof(func));
            }

            private Func<T, T, int> Func { get; }

            public override int Compare(T x, T y)
            {
                return Func(x, y);
            }
        }

        #endregion

        #region Nested type: Hierarchy

        private struct Hierarchy
        {
            public int Index { get; }

            [SuppressMessage("ReSharper", "MemberCanBePrivate.Local", Justification = "debugging")]
            public Transform Target { get; }

            public Hierarchy(int index, [NotNull] Transform transform)
            {
                if (index < 0)
                    throw new ArgumentOutOfRangeException(nameof(index));

#pragma warning disable IDE0016 // Null check can be simplified
                if (transform == null)
                    throw new ArgumentNullException(nameof(transform));
#pragma warning restore IDE0016

                Index = index;
                Target = transform;
            }

            public override string ToString()
            {
                return $"{nameof(Index)}: {Index}, {nameof(Target)}: {Target}";
            }
        }

        #endregion
    }
}

Throw that to an array of transforms, they will always be sorted in hierarchy order.

Edit:

Refined version, minor adjustments, supports any Component, less code but functionally identical:

using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;

namespace xyz
{
    public static class HierarchicalSorting
    {
        private static int Compare([CanBeNull] Component x, [CanBeNull] Component y)
        {
            return Compare(x != null ? x.transform : null, y != null ? y.transform : null);
        }

        private static int Compare([CanBeNull] GameObject x, [CanBeNull] GameObject y)
        {
            return Compare(x != null ? x.transform : null, y != null ? y.transform : null);
        }

        private static int Compare([CanBeNull] Transform x, [CanBeNull] Transform y)
        {
            if (x == null && y == null)
                return 0;

            if (x == null)
                return -1;

            if (y == null)
                return +1;

            var hierarchy1 = GetHierarchy(x);
            var hierarchy2 = GetHierarchy(y);

            while (true)
            {
                if (!hierarchy1.Any())
                    return -1;

                var pop1 = hierarchy1.Pop();

                if (!hierarchy2.Any())
                    return +1;

                var pop2 = hierarchy2.Pop();

                var compare = pop1.CompareTo(pop2);

                if (compare == 0)
                    continue;

                return compare;
            }
        }

        [NotNull]
        private static Stack<int> GetHierarchy([NotNull] Transform transform)
        {
            if (transform == null)
                throw new ArgumentNullException(nameof(transform));

            var stack = new Stack<int>();

            var current = transform;

            while (current != null)
            {
                var index = current.GetSiblingIndex();

                stack.Push(index);

                current = current.parent;
            }

            return stack;
        }

        [PublicAPI]
        [NotNull]
        [ItemNotNull]
        public static T[] Sort<T>([NotNull] [ItemNotNull] T[] components) where T : Component
        {
            if (components == null)
                throw new ArgumentNullException(nameof(components));

            Array.Sort(components, new RelayComparer<T>(Compare));

            return components;
        }

        [PublicAPI]
        [NotNull]
        [ItemNotNull]
        public static GameObject[] Sort([NotNull] [ItemNotNull] GameObject[] gameObjects)
        {
            if (gameObjects == null)
                throw new ArgumentNullException(nameof(gameObjects));

            Array.Sort(gameObjects, new RelayComparer<GameObject>(Compare));

            return gameObjects;
        }

        [PublicAPI]
        [NotNull]
        [ItemNotNull]
        public static Transform[] Sort([NotNull] [ItemNotNull] Transform[] transforms)
        {
            if (transforms == null)
                throw new ArgumentNullException(nameof(transforms));

            Array.Sort(transforms, new RelayComparer<Transform>(Compare));

            return transforms;
        }

        private sealed class RelayComparer<T> : Comparer<T>
        {
            public RelayComparer([NotNull] Func<T, T, int> func)
            {
                Func = func ?? throw new ArgumentNullException(nameof(func));
            }

            [NotNull]
            private Func<T, T, int> Func { get; }

            public override int Compare(T x, T y)
            {
                return Func(x, y);
            }
        }
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ This seems perfect. Will try to implement this immediately. Thank you \$\endgroup\$ – I_Keep_Trying Jun 5 at 13:20
  • \$\begingroup\$ Thanks. It works perfectly! I can't claim to fully understand the magic yet but I understood parts of it. Very helpful mate! I just needed to add this class to my project and then before I use the array in my code i added line: HierarchyComparer.HierarchicalSort(beertap_standpoints); I will study your class further and hope to gain more knowledge from it. \$\endgroup\$ – I_Keep_Trying Jun 5 at 14:16
  • \$\begingroup\$ Glad it helped you out🙂 \$\endgroup\$ – aybe Jun 6 at 14:59
  • \$\begingroup\$ @I_Keep_Trying I've reworked a much simpler to understand but functionally identical version, added below original. \$\endgroup\$ – aybe Jun 8 at 22:23
2
\$\begingroup\$

You can create a LevelManager script. That script should have an array in the inspector where you can drag the spawnpoints specific to that level. Add the LevelManager to all of your levels.

public class LevelManager : MonoBehaviour
{
    public Customer_SpawnPoint[] customer_spawns;
    public Beer_Tap_Standpoint[] beertap_standpoints;
}

Then in your GameManager script, you can find that level's LevelManager with var myLevelManager = FindObjectOfType<LevelManager>();. Now you can use the spawnpoints with myLevelManager.customer_spawns, and they will be in the same order as you dragged them in the inspector.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for this solution. However I am trying to find a way of avoiding drag-drop properties in the Inspector. \$\endgroup\$ – I_Keep_Trying Jun 5 at 13:20
1
\$\begingroup\$

I have seen the detailed answer from Aybe, but just checking there isnt some magic one-line solution.

var objects = FindObjectsOfType<SomeType>();
System.Array.Sort(objects, (a, b) => a.name.CompareTo(b.name));

I'm still trying to learn about Lambda functions, they confuse the heck outta me! But is this one of those times I need it to use in the Array.Sort() function?

No, you can do it without a lambda. Just define an ordinary named function that takes two components as parameters and returns an integer representing their relative sort order. (You can exchange a & b or negate the return value if you want to sort in the opposite order)

static int CompareComponentNames(Component a, Component b) {
    return a.name.CompareTo(b.name);
}

// ...later, when you have an array you want to sort:

System.Array.Sort(objects, CompareComponentNames);
\$\endgroup\$
1
  • \$\begingroup\$ wonderful. I figured there would be some way to do it with the lambda (not sure how I managed to know that though lol). Now I think I understand them a little better. Thanks \$\endgroup\$ – I_Keep_Trying Jun 6 at 1:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.