0
\$\begingroup\$

I have the following code used by multiple classes (e.g. Enemy, Scenary, EnemyProjectile etc.) in my game, which seems intrinsically wrong. What are some ways I could write this code once and use it from multiple places?

        GameObject go = GameObject.Find("Player");
        if (go == null)
        {
            Debug.Log("Failed to find Player go");
        }
        else
        {
            _player = go.GetComponent<Player>();

            if (_player == null)
            {
                Debug.Log("Failed to find player component");
            }
        }

I usually have this code in an Awake function but sometimes I do it in a collision event, e.g. for EnemyProjectile grabbing a player handle would often not be required and thus a wasted lookup. Player is a private variable in the class definition of each class.

\$\endgroup\$
  • \$\begingroup\$ Perhaps this question is better suited for StackOverflow as it is more about a generic programming pattern rather than game-specific. \$\endgroup\$ – htmlcoderexe Jul 10 at 12:49
  • 2
    \$\begingroup\$ I think you're right in general, @htmlcoderexe. For this particular use case though, Unity does have a few characteristic ways of solving this, so game developers can give a more contextually-specific answer. \$\endgroup\$ – DMGregory Jul 10 at 15:16
  • \$\begingroup\$ Very good to know, thanks. \$\endgroup\$ – htmlcoderexe Jul 10 at 16:58
1
\$\begingroup\$

This type of thing is often implemented using some form of the Singleton pattern. Here's one version, fairly similar to what you're doing now, just without the redundant double-search for the object, then the component, when all you really wanted was the component:

public class Player: MonoBehaviour {

     static Player _instance;
     public static Player instance {
         get {
             if(_instance == null) {
                 _instance = FindObjectOfType<Player>();    
                 UnityEngine.Assertions.Assert.IsNotNull(_instance, "Failed to find player component.");
             }

             return _instance;
         }
     }
}

Now other scripts can just ask for the current instance like this:

_player = Player.instance;

...and the singleton will look it up for them. It returns a cached copy quickly if it's been asked before, and only does the slow search the first time it's asked, or after the player's been destroyed and re-created.

You can also skip the search entirely and have your player "report" itself on load:

void Awake() { _instance = this; }

...but this can cause timing issues if you have other scripts that need to reference the player in their own Awake methods that happen to run before the player got a chance to do so. If you can safely defer all references to the player until Start or Update though, this is an option.

(As an aside, some folks find they use this pattern so much that they implement it in a Singleton<T> : MonoBehaviour where T: Singleton<T>, using the curiously recurring template pattern, so they can quickly add it to any class by declaring it as Player : Singleton<Player>. This can help if you're worried about the amount of boilerplate this adds to every type you want to look up this way.)

You'll find folks are often critical of the singleton pattern for good reasons, though the risk of making the data global doesn't really apply here - as your code shows, access to the player object was already available globally.

So in this case, the main risk this method introduces is that it limits you to a single player at a time. If you ever want to have multiplayer with multiple instances of the Player class active in the scene at a time, this method only lets you find the first one, and so you have some heavy refactoring to do if you've got dozens of scripts that all find "the" player this way.

That's no worse than your current code, and it's acceptable for many Unity games. And it's still solvable. You can replace your instance property with a public static GetPlayer(int playerID) function, or a GetClosestPlayer(), etc. Then systematically replace each occurrence of Player.instance with the appropriate alternative. It's work, but not an insurmountable obstacle.

| improve this answer | |
\$\endgroup\$

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.