5

Unity makes me confused when it comes to referencing players to another object's script. I have used several ways to reference a localPlayer object:

  1. Comparing Tags(not so good in some situations)
  2. gameObject.Name(never used it but it is also not reliable)
  3. Assigning public GameObject Player; in the inspector Drag and Drop (face problems when the player is a prefab)
  4. Making public static [PlayerControllerClass] instance; and setting instance = thisin void Start() function.In order to access playerSpeed from another script I do PlayerControllerClass.instance.playerSpeed = 10;

Is there other solutions for referencing players in a proper way.

Note: I know my question might not be relating to a specific problem but I can explain more if somebody will help.

Edit1: for example I want to find localPlayer in a NetworkGame where all players have same name, same tag but only one player is a localPlayer. Can I access the localPlayer from another script?

Edit2 : The answer I accepted is what I felt the best.I will use the answer provided in previewing score text of the localPlayerby accessing it from a set of players [localPlayer]

Community
  • 1
  • 1
Bak Stak
  • 127
  • 3
  • 16
  • You mean referencing Objects in the scene and Hierarchy tab? – Programmer Jul 24 '18 at 09:29
  • yes ,I mean I want to access other objects like prefabs by (prefabs,UI,...) – Bak Stak Jul 24 '18 at 09:34
  • 3
    I use GameObject.Find() a lot, if the object has unique name :) – Daahrien Jul 24 '18 at 09:35
  • There is `FindObjectOfType` which you can then use `.gameObject` on. For the `localPlayer` part, what are you using for your network? `NetworkBehaviour` has a `isLocalPlayer` property (an others like `OnStartLocalPlayer`, see docs https://docs.unity3d.com/ScriptReference/Networking.NetworkBehaviour.html?_ga=2.197603695.1198815842.1532361260-443772525.1497961314 – Gunnar B. Jul 24 '18 at 10:05

3 Answers3

6

Your question wasn't clear until your edit then I realized that this is a networking question.

I need to find localPlayer in a NetworkGame where all players have same name, same tag but only one player is a localPlayer can I access the localPlayer from another script?

You can find players with NetworkConnection.playerControllers which retruens List of PlayerController then loop over it and check if it is valid. After that, get NetworkBehaviour from it check the isLocalPlayer property. That's really it.

Something like this:

GameObject FindLocalNetworkPlayer()
{
    NetworkManager networkManager = NetworkManager.singleton;
    List<PlayerController> pc = networkManager.client.connection.playerControllers;

    for (int i = 0; i < pc.Count; i++)
    {
        GameObject obj = pc[i].gameObject;
        NetworkBehaviour netBev = obj.GetComponent<NetworkBehaviour>();

        if (pc[i].IsValid && netBev != null && netBev.isLocalPlayer)
        {
            return pc[i].gameObject;
        }
    }
    return null;
}

If you have NetworkIdentity attached to the player instead of NetworkBehaviour, just get the NetworkIdentity and check the isLocalPlayer property. You can also find the GameObject then check the NetworkIdentity and the isLocalPlayer property.

if (obj.GetComponent<NetworkIdentity>().isLocalPlayer){}

Other useful player operation you may need:

Find all players:

List<GameObject> ListPlayers()
{
    NetworkManager networkManager = NetworkManager.singleton;
    List<PlayerController> pc = networkManager.client.connection.playerControllers;

    List<GameObject> players = new List<GameObject>();
    for (int i = 0; i < pc.Count; i++)
    {
        if (pc[i].IsValid)
            players.Add(pc[i].gameObject);
    }
    return players;
}

Find player by name(Can also be changed to by tag or layer):

GameObject FindNetworkPlayer(string name)
{
    NetworkManager networkManager = NetworkManager.singleton;
    List<PlayerController> pc = networkManager.client.connection.playerControllers;

    for (int i = 0; i < pc.Count; i++)
    {
        if ((pc[i].IsValid) && (name == pc[i].gameObject.name))
            return pc[i].gameObject;
    }
    return null;
}

OLD answer before your edit:

Is there other solutions for referencing players in a proper way.

Yes, you Find the GameObject with GameObject.Find, after this, you can use the GetComponent function to get the script that is attached to it.

I notice you mentioned you want to reference prefabs in the scene too. There is a difference between prefabs and objects already the scene and they both have different ways to reference them.

Here is an example of Objects in the Scene already. You can see them in the Hierarchy tab:

enter image description here

Let's reference the "Canvas" GameObject by name:

GameObject canvasObj = GameObject.Find("Canvas");

Let's reference the Canvas script attache to the "Canvas" GameObject:

GameObject canvasObj = GameObject.Find("Canvas");
Canvas canvas = canvasObj.GetComponent<Canvas>();

Prefabs:

Here is an example of prefabs in the Project tab:

enter image description here

Put the prefabs in a folder named "Resources". You must so that you can reference them with the Resources API.

Let's reference the "Capsule" prefab GameObject:

GameObject prefab = Resources.Load<GameObject>("Capsule");

To use it, you have to instantiate it:

GameObject instance = Instantiate(prefab);

You can only get components on prefabs after when they are instantiated:

CapsuleCollider cc = instance.GetComponent<CapsuleCollider>();
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • 2
    Thanks That Explains a lot. – Bak Stak Jul 24 '18 at 10:46
  • Cheers for the reply! – Koshux Jul 24 '18 at 10:55
  • On the old answer: I'm not great at coding but the way i get it is that loading prefabs from the resource folder is not always the best way to go, especially for large prefabs (Resource.Load loads the prefab from disk). Btw this slowdown is not noticed in the editor because editor loads resource folder in memory. If the prefab is referenced directly in the inspector unity creates a prototype instance that it "loads" from on instantiate (ofc the price is the memory that the prototype occupies). – Nikaas Jul 25 '18 at 10:22
  • @Nikaas Yes, that true. Use [AssetBundle](https://stackoverflow.com/questions/47030894/build-and-load-assetbundles-in-unity/47033537#47033537). The process for using AssetBundle is a bit complicated for new users but it's better. – Programmer Jul 25 '18 at 10:26
1

I recommend the Singleton Pattern. I implement it in almost all my Unity Projects for objects that only have 1 class, yet is a monobehaviour.

using UnityEngine;


public abstract class SingletonBehaviour<T> : MonoBehaviour where T : MonoBehaviour
{
    public bool dontDestroyOnLoad = true;
    private bool isInitialized = false;

    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<T>();
                if (instance == null)
                {
                    var obj = new GameObject
                    {
                        name = typeof(T).Name
                    };
                    instance = obj.AddComponent<T>();
                }
            }

            return instance;
        }
    }

    protected virtual void Awake()
    {
        if (instance == null)
        {
            instance = this as T;

            if (dontDestroyOnLoad)
            {
                DontDestroyOnLoad(this.transform.root.gameObject);
            }
        }
        else if (instance != this)
        {
            Destroy(gameObject);
        }
    }


    private void OnEnable()
    {
        if (!isInitialized)
        {
            OnInitialize();
            isInitialized = true;
        }
    }

    public abstract void OnInitialize();
}

You can then create a class like so:

public class MyClass : SingletonBehaviour<MyClass>
{
    public void OnInitialize()
    {
        // You can use this instead of awake or start
    }

    public void DoStuff();
}

And call it like so:

MyClass.Instance.DoStuff()
Immorality
  • 2,164
  • 2
  • 12
  • 24
  • I am going to search about `Singleton` the word that I've seen multiple times but didn't dive into it. – Bak Stak Jul 24 '18 at 10:00
  • It will not suit all scenarios, but it definitly makes life easier when dealing with classes like GameManager, InventoryManager etc. which all should have no more than 1 instance. Feel free to use the first code snippet in my post. It is ready to use. – Immorality Jul 24 '18 at 10:10
1

I have always sought usage of GameObject.FindGameObjectsWithTag when I need multiple references or else GameObject.FindWithTag when I need just the one reference to a specific GameObject available in the system.

To reference a script on said GameObject (found using the API reference above), you simply use `` as described in GameObject.GetComponent API reference.

A complete example would be:

  1. Assign a tag to your Player GameObject e.g. PlayerA.
  2. GameObject player = GameObject.FindWithTag("PlayerA");
  3. PlayerScript playerScript = player.GetComponent<PlayerScript>();

I hope this helps and should you require further clarification, I'll be more than happy to help.

EDIT: Another solution:

Concern: 50 or x amount of players would be too mundane to assign tags.

Solution: If you have a set of 50 players:

  1. I would group them under a parent GameObject.
  2. Assign the parent GameObject a tag e.g. ParentPlayer
  3. Use the GetComponentsInChildren reference.
Koshux
  • 346
  • 1
  • 2
  • 10
  • 1
    thanks for your answer you are close to what I need, but there is something though I need to know *managing*. Is it practical to make 50 tags each tag with the player letter next to it with every player having different tag than others.I don't think this is the solution. – Bak Stak Jul 24 '18 at 10:20
  • It truly depends. If you have a set of 50 players, I would group them under a parent GameObject, give that parent GameObject a tag e.g. `ParentPlayer` and then use the [GetComponentsInChildren](https://docs.unity3d.com/ScriptReference/Component.GetComponentsInChildren.html) reference. – Koshux Jul 24 '18 at 10:31
  • OK so now from what I understood I have to instantiate all players in a parent(`EmptyGameObject`) and walk through these GameObjects to know which one is the local player? – Bak Stak Jul 24 '18 at 10:36
  • Is your assumption that there will be only 1 GameObject named `localPlayer`? – Koshux Jul 24 '18 at 10:38