Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Question What is the best way to reference an object/script without using singeltons or hierarchy drag drop?

Discussion in 'Scripting' started by Kokomeant, Aug 17, 2023.

  1. Kokomeant

    Kokomeant

    Joined:
    Dec 23, 2022
    Posts:
    13
    What is the most efficient way to reference an object when I don't know how many instances of a class will be in my game or when I can't use GetComponent because the script managing the player is not directly attached to my player?

    I have a level manager that takes the player's height, and based on this height, the script replaces platforms that the player needs to climb. However, the best solution I've found for my situation is to use a Singleton pattern for my player:

    Code (CSharp):
    1. public static PlayerController Instance { get; private set; }
    The level manager accesses the player's height using:

    Code (CSharp):
    1. PlayerController.Instance.GetPlayerPosition().y
    However, if I want to add more players, I'll need to rewrite all my game logic.

    Is there any way to reference my player without using Singletons and without being dependent on the hierarchy, in case I decide to change my game to support 2 players?

    Also a way that could be used in all kinds of situations.

    Thanks in advance! :)
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    Make a player manager singleton which has something like

    Code (CSharp):
    1. public PlayerController GetPlayer(int playerIndex) {
    2.   return players[playerIndex];
    3. }
     
    Kokomeant, lordofduct and Ryiah like this.
  3. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    I personally make a List of Singletons, and if having sub-classes I just use Inheritance:
    Code (CSharp):
    1. public static List<Projectile> allBullets = new List<Projectile>();
    2. public static List<Projectile> allPlasma = new List<Projectile>();
    3. public static List<Projectile> allLasers = new List<Projectile>();
    With bullets/lasers/plasma classes all being a child class of Projectile.cs
     
    Kokomeant likes this.
  4. TomTheMan59

    TomTheMan59

    Joined:
    Mar 8, 2021
    Posts:
    315
    OP mentioned NO Singletons…..please read before responding :)

    You can make it global/static and reference it that way.
     
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,003
    That's... no different to a singleton.
     
    SisusCo, Bunny83, lordofduct and 3 others like this.
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,384
    This is pretty much the boiler plate for every project I have:
    Code (csharp):
    1.     public class PlayerEntity : IEntity
    2.     {
    3.  
    4.         public static readonly UniqueToEntityMultitonPool<PlayerEntity> PlayerPool = new UniqueToEntityMultitonPool<PlayerEntity>();
    5.  
    6.         public static readonly System.Func<PlayerEntity, bool> FindPlayer = (e) => e.EntityType == EntityType.Player;
    7.  
    8.         #region CONSTRUCTOR
    9.  
    10.         public PlayerEntity() : base(EntityType.Player) { }
    11.  
    12.  
    13.         protected override void OnEnable()
    14.         {
    15.             PlayerPool.AddReference(this);
    16.             base.OnEnable();
    17.         }
    18.  
    19.         protected override void OnDisable()
    20.         {
    21.             PlayerPool.RemoveReference(this);
    22.             base.OnDisable();
    23.         }
    24.  
    25.         #endregion
    26.  
    27.     }
    Where MultitonPool is just a class that wraps around a HashSet and has some optimized 'Find' methods. So I can access it like:
    Code (csharp):
    1. var player = PlayerEntity.PlayerPool.Find(PlayerEntity.FindPlayer);
    And if I want different ways to find the player I create new find helpers. Here's a multiplayer game I've been working on:
    Code (csharp):
    1.     [RequireComponent(typeof(NetworkObject))]
    2.     [DefaultExecutionOrder(SPEntity.DEFAULT_EXECUTION_ORDER)]
    3.     public sealed class PlayerEntity : IEntity, IMStartOrEnableReceiver, ActiveZoneController.IEntityEnteredZoneGlobalHandler, Health.IStruckHandler
    4.     {
    5.  
    6.         public static readonly UniqueToEntityMultitonPool<PlayerEntity> PlayerPool = new UniqueToEntityMultitonPool<PlayerEntity>();
    7.  
    8.         public static readonly System.Func<PlayerEntity, bool> FindLocalPlayer = (e) => e.EntityType == EntityType.Player && e.IsLocalPlayerEntity;
    9.         public static readonly System.Func<PlayerEntity, ulong, bool> FindPlayerByNetworkObjectId = (e, id) => e.EntityType == EntityType.Player && e.NetworkObjectId == id;
    10.         public static readonly System.Func<PlayerEntity, ulong, bool> FindPlayerByClientId = (e, id) => e.EntityType == EntityType.Player && e.OwnerClientId == id;
    11.  
    12.         #region Fields
    13.  
    14.         [SerializeField]
    15.         private bool _fixToGroundOnStart = true;
    16.  
    17.         [SerializeField]
    18.         [Tooltip("Active only if this player is the local player.")]
    19.         private GameObject _localState;
    20.         [SerializeField]
    21.         [Tooltip("Active only if this player is not the local player.")]
    22.         private GameObject _remoteState;
    23.  
    24.         [SerializeField]
    25.         private GameObject _visuals;
    26.  
    27.         [Header("Controller References")]
    28.         [SerializeField]
    29.         [DefaultFromSelf(EntityRelativity.Entity)]
    30.         private PlayerAnimatorBridge _animatorBridge;
    31.         [SerializeField]
    32.         private GameObject _localMotorContainer;
    33.         [SerializeField]
    34.         [DefaultFromSelf(EntityRelativity.Entity)]
    35.         private PlayerInteractionController _interactionController;
    36.         [SerializeField]
    37.         [DefaultFromSelf(EntityRelativity.Entity)]
    38.         private PlayerWeaponController _weaponController;
    39.  
    40.         [System.NonSerialized]
    41.         private PlayerNetworkState _playerNetworkState;
    42.  
    43.         #endregion
    44.  
    45.         #region CONSTRUCTOR
    46.  
    47.         public PlayerEntity() : base(EntityType.Player, EntityActiveZoneInfluence.OnEnterZone) { }
    48.  
    49.         protected override void Awake()
    50.         {
    51.             base.Awake();
    52.  
    53.             this.AddOrGetComponent<PlayerEntity.NetworkObjectHooks>();
    54.         }
    55.  
    56.         protected override void Start()
    57.         {
    58.             base.Start();
    59.  
    60.             Messaging.Broadcast<IPlayerSpawnedGlobalHandler, PlayerEntity>(this, (o, a) => o.OnPlayerSpawned(a));
    61.         }
    62.  
    63.         void IMStartOrEnableReceiver.OnStartOrEnable()
    64.         {
    65.             if (_fixToGroundOnStart) this.transform.position = this.transform.position.SetY(Constants.GROUND_Y);
    66.  
    67.             Messaging.RegisterGlobal<ActiveZoneController.IEntityEnteredZoneGlobalHandler>(this);
    68.             bool islocal = this.IsLocalPlayerEntity;
    69.             if (_localState) _localState.SetActive(islocal);
    70.             if (_remoteState) _remoteState.SetActive(!islocal);
    71.  
    72.             _playerNetworkState = Services.Get<NetworkSessionController>()?.GetPlayerState(this.NetworkObject.OwnerClientId);
    73.             if (_playerNetworkState)
    74.             {
    75.                 _playerNetworkState.ApplyModifiers();
    76.                 foreach (var m in _playerNetworkState.EnumerateModifiers()) m.Activate(_playerNetworkState, this);
    77.                 this.gameObject.Broadcast<IPlayerStateUpdated, PlayerNetworkState>(_playerNetworkState, (o, s) => o.OnPlayerStateUpdated(s));
    78.             }
    79.  
    80.             if (!this.IsLocalPlayerEntity && _visuals)
    81.             {
    82.                 var zone = ActiveZoneController.ActiveZone;
    83.                 _visuals.SetActive(!zone || zone.ContainsTrackedEntity(this) || zone.TestContains(this.transform.position.IncrementY(1f)));
    84.             }
    85.  
    86.             if (this.IsLocalPlayerEntity)
    87.             {
    88.                 this.SyncStall(StallLocalPlayerWhenActive.PlayerShouldBeStalled);
    89.             }
    90.         }
    91.  
    92.         protected override void OnEnable()
    93.         {
    94.             PlayerPool.AddReference(this);
    95.             base.OnEnable();
    96.         }
    97.  
    98.         protected override void OnDisable()
    99.         {
    100.             PlayerPool.RemoveReference(this);
    101.             Messaging.UnregisterGlobal<ActiveZoneController.IEntityEnteredZoneGlobalHandler>(this);
    102.             base.OnDisable();
    103.         }
    104.  
    105.         #endregion
    MultitonPool can be found here:
    https://github.com/lordofduct/space....core/Runtime/src/Collections/MultitonPool.cs

    But honestly you could just use HashSet and query it via linq or some other approach (List also works, but you'll have to be careful to avoid doubling up entries in the list by accident). I've done this in slimmer projects of mine.

    Code (csharp):
    1.     [RequireComponent(typeof(Renderer))]
    2.     public class Outline : MonoBehaviour
    3.     {
    4.  
    5.         #region Multiton Pool
    6.  
    7.         public static readonly HashSet<Outline> Pool = new HashSet<Outline>();
    8.  
    9.         #endregion
    10.  
    11.         #region Fields
    12.  
    13.         [UnityEngine.Serialization.FormerlySerializedAs("color")]
    14.         public OutlineEffect.OutlinePreset presetColor;
    15.         public bool eraseRenderer;
    16.  
    17.         [System.NonSerialized]
    18.         private Material[] _materials;
    19.  
    20.         #endregion
    21.  
    22.         #region CONSTRUCTOR
    23.  
    24.         private void Awake()
    25.         {
    26.             this.Renderer = this.GetComponent<Renderer>();
    27.             this.SkinnedMeshRenderer = this.GetComponent<SkinnedMeshRenderer>();
    28.             this.MeshFilter = this.GetComponent<MeshFilter>();
    29.         }
    30.  
    31.         void OnEnable()
    32.         {
    33.             Pool.Add(this);
    34.         }
    35.  
    36.         void OnDisable()
    37.         {
    38.             Pool.Remove(this);
    39.         }
    40.  
    41.         #endregion
    It's generally in the same space as a singleton... hence why I call it a 'multiton'. Technically not my name for it:
    https://en.wikipedia.org/wiki/Multiton_pattern

    If we want to get into the discussion of singleton/multiton/statics == bad. Well... there's other ways about injecting the info into the object. You could have a PlayerManager that is dragged onto your LevelManager in the editor, or use a DI framework, or you have some factory approach to building the scene that gathers all the necessary information and hands out the necessary info, or whatever.

    There's a reason we go singleton/multiton/static in games. It's just easy. Especially since the way scene loading works... there's no easy hook to inject such data through without creating some process. Singleton/multiton/static is like a line of code! It has its issues, but its easy.
     
    Last edited: Aug 18, 2023
  7. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    I read the post correctly.. and even understood it deeper than you did obviously. He is under the impression that Singletons are a "single" thing:
    To which my post easily shows that Singletons can be used within a list. The fact that they are static in my example is irrelevant, making them static works for no list copies when using generic functions that take a list as a parameter. as this function shows:
    Code (CSharp):
    1. public Projectile GetProjectileFromList(List<Projectile> list, Vector3 position, GameObject prefab)
    2.     {
    3.         if (list.Count > 0)
    4.         {
    5.             for (int i = 0; i < list.Count; i++)
    6.             {
    7.                 if (!list[i].gameObject.activeSelf)
    8.                 {
    9.                     list[i].trans.position = position;
    10.                     list[i].gameObject.SetActive(true);
    11.                     return list[i];
    12.                 }
    13.             }
    14.         }
    15.         Instantiate(prefab, position, Quaternion.identity);
    16.         return list[list.Count - 1];
    17.     }
    Now I wasn't going to go into how those work as Singletons, unless the OP was interested. But, I'm already here now:
    Code (CSharp):
    1. public class Bullet : Projectile
    2. {
    3.    void Awake()
    4.    {
    5.       allBullets.Add(this); // if inherited from ContainerClass
    6.       ContainerClass.allBullets.Add(this); // not inherited
    7.    }
    8. }
    Everyone who talks on Singletons, always say it's a single reference without explaining the "but, if" part. And leaves a lot of people to not think outside the box. Like for example just make them a list for multiple instances, or inherit them and make tons of groups of tons of instances. They think they can only do it for one class, like AudioManager.cs
     
    Kokomeant likes this.
  8. Kokomeant

    Kokomeant

    Joined:
    Dec 23, 2022
    Posts:
    13
    Also fo
    Thank you very much for your respond, I will study about this more deeper! I really appreciate it :)
     
  9. Kokomeant

    Kokomeant

    Joined:
    Dec 23, 2022
    Posts:
    13
    I will try it! Thank you!! :)
     
  10. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,135
    Dependency injection and service locator patterns can both be used to solve situations like this.

    If you want to potentially support multiple players in the future, you can register your player instance as
    typeof(IEnumerable<Player>)
    instead of
    typeof(Player)
    in the DI container / service locator.

    Simple service locator example:
    Code (CSharp):
    1. class Player : MonoBehaviour
    2. {
    3.     void Awake() => Services.Set<IEnumerable<Player>>(new Player[] { this });
    4. }
    5.  
    6. class Client : MonoBehaviour
    7. {
    8.     void Start()
    9.     {
    10.         foreach(var player in Services.Get<IEnumerable<Player>>())
    11.         {
    12.             Debug.Log(player.name);
    13.         }
    14.     }
    15. }
     
    Last edited: Aug 18, 2023
    Ryiah and Kokomeant like this.
  11. Kokomeant

    Kokomeant

    Joined:
    Dec 23, 2022
    Posts:
    13
    Thank you very much! also thank you for the sources :))
     
    SisusCo likes this.
  12. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    I read it. Instead of blindly following that statement I used my brain and reasoned that they were saying without singletons because they were under the impression they couldn't deal with multiple players through a singleton pattern. I was showing that indeed you can do such a thing. Thanks for the pedantic talking down-to though.
     
  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,946
    Just because OP read a Medium article about how singletons are sooooo bad doesn't mean we have to solve his problem by some other crazy whacky thing.

    If OP doesn't like singletons, then OP can go figure out another way. It's not hard. There's PLENTY of locator patterns all over the web. If OP insists on ignoring them all and asking us to do it for them, well, how about "No?"

    "Hey can you guys tell me how to paint my entire house WITHOUT using brushes, paint, sprayers, or any kind of roller?"
     
  14. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    That's a very interesting question Kurt, thanks for asking! I would use a singleton to grab the paint bucket and throw the paint on the house, and smear it around with my hands. :cool:

    See? well out of the box... lol, but giving a poster's questions multiple answers doesn't hurt. not sure why this is still a thing