简体   繁体   中英

Find localPlayer when all players have same name

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 = this in 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 localPlayer by accessing it from a set of players [ localPlayer ]

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:

在此输入图像描述

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:

在此输入图像描述

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>();

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()

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 eg 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 eg ParentPlayer
  3. Use the GetComponentsInChildren reference.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM