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:
Comparing Tags(not so good in some situations)
gameObject.Name(never used it but it is also not reliable)
Assigning public GameObject Player; in the inspector Drag and Drop (face problems when the player is a prefab)
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]
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:
Assign a tag to your Player GameObject e.g. PlayerA.
GameObject player = GameObject.FindWithTag("PlayerA");
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:
I would group them under a parent GameObject.
Assign the parent GameObject a tag e.g. ParentPlayer
Use the GetComponentsInChildren reference.
Related
Hi,
i have a problem with my 2d unity project.
What was my goal:
I want to have a player with an extra Collider. This Collider should put every gameObject with the tag "Stone" in a list.
What i tried:
I made another Object that is the child of my player object.
The Child Object has a round 2d Collider (set as a trigger) and should put everything that has the tag "Stone" in a list of gameObjects in his parent. That gave me an error(see below for more details) as soon as i collided with a stone.
Then i tried putting it in a list in the child object and it worked. i cant really image why it works in the child and not in the parent.
Here are the scripts:
Relevant part of the stompRadiusCollScript(child):
public class stompRadiusCollScript : bendingScript
{
private GameObject parent;
private List<GameObject> colls;
void Start()
{
colls = new List<GameObject>();
parent = base.gameObject;
}
private void OnTriggerEnter2D(Collider2D givenCollider)
{
if (givenCollider.tag == "Stone")
{
GameObject co = givenCollider.gameObject;
//is able to add in Child List
colls.Add(co);
//cannot add in parent list
//throws error
currCollisions.Add(co);
}
}
What was the error message:
NullReferenceException: Object reference not set to an instance of an object
stompRadiusCollScript.OnTriggerEnter2D (UnityEngine.Collider2D givenCollider)
Relevant Code from bendingScript(parent):
public class bendingScript : MonoBehaviour
{
protected List<GameObject> currCollisions;
public Collider2D coll;
private System.DateTime _lastTimeStompAttack;
private float _stompAttackMinDelayInMs = 500f;
private float _forceForStone = 100f;
void Start()
{
currCollisions = new List<GameObject>();
coll = GetComponent<CircleCollider2D>();
_lastTimeStompAttack = DateTime.UtcNow;
}
}
additional information:
The objects with the tags stone have a rigidbody2d and a circlecollider2d
Question:
How do i get my all stone collisions as gameObjects in a list in the parent (bendingScript)?
Instead of having a list in the parent object you could keep the list in the child object and access the child object later when required.
This question already has answers here:
Find inactive GameObject by name, tag or layer
(4 answers)
Closed 4 years ago.
I want to find and active inactive object: Main Menu.
I try this code, but it isn't working. What am I doing wrong?
if (Input.GetKey(KeyCode.Escape))
{
this.gameObject.SetActive(false);
GameObject.Find("Main Menu").SetActive(true);
}
Either a reference to the object before setting it off or create your own method since you have them all under the same parent object you can use GetComponentsInChildren
public static GameObject FindObject(this GameObject parent, string name)
{
Transform[] trs= parent.GetComponentsInChildren<Transform>(true);
foreach(Transform t in trs){
if(t.name == name){
return t.gameObject;
}
}
return null;
}
this is an extension method that should be stored in a static class. You use it like this:
GameObject obj = parentObject.FindObject("Main Menu");
You're setting the parent gameobject to inactive first. Try this:
if (Input.GetKey(KeyCode.Escape))
{
GameObject.Find("Main Menu").SetActive(true);
this.gameObject.SetActive(false);
}
The easiest way to do this is to create a public reference at the top of your class, like so:
public GameObject MainMenu;
Then, in the editor, drag the MainMenu object over to the newly created reference.
After that, in your logic, you may simply do this:
public GameObject MainMenu;
if (Input.GetKey(KeyCode.Escape))
{
MainMenu.SetActive(true);
this.gameObject.SetActive(false);
}
Doing it this way helps to prevent null references, as it is type safe.
I am a bit confused with how i set up my scene to do this. I want to create an array of my class System. Of which contains a Vector3 position a reference to a sprite renderer and the game object. I also instantiate the game object in that position in my array creation.
I setup my array like this in an empty game object in my game world:
public System[] systemArray;
void Start () {
systemArray = new System[totalSystems];
for (int i = 0; i < totalSystems; i++)
{
systemArray[i].worldPos = new Vector3(Random.Range(min,max),Random.Range(min,max), Random.Range(minZ,maxZ));
Instantiate(systemObject,systemArray[i].worldPos,Quaternion.identity);
}
}
Now my System script is not attached to a game object it is just a script that will store the data of the instantiated game objects i just made. And the code looks like this:
public class System : MonoBehaviour {
public SpriteRenderer spriteRenderer;
public GameObject gameObject;
public Vector3 worldPos;
}
My confusion here is how do i link this all up.
The goal here is i will want to be able to destroy the game objects from the scene but i will still be able to access their Vector3, the GO and sprite renderer from System so i can re-instantiate again.
I got myself really confused so am hoping for some help on how i am suppose to set this up efficiently.
Create a seperate class that is responsible for maintaining the systems.
For best practice: The class would not expose the collection of systems directly but rather through methods to make it possible to decorate manipulation with logic.
It would look something like this:
public class SystemRepository
{
private static List<System> _systems;
public List<System> GetAll() {
if (_systems == null) {
_systems = new List<System>()
}
// Further logic
return _systems
}
public System GetBy(GameObject v) { ... }
public void Add(List<System> s) { ... }
public void DestroyBy(Vector3 v) { ... }
...
}
So when you need to access the Systems for adding/removing/getting them you'd do the following:
using Namespace/Of/SystemRepository
// ...
var systemRepo = new SystemRepository();
systemRepo.Add(totalSystems);
And somewhere else..
var systemRepo = new SystemRepository();
var allSystems = systemRepo.GetAll();
for (int i = 0; i < allSystems.Count(); i++)
{
allSystems[i].worldPos = new Vector3(Random.Range(min, max), Random.Range(min, max), Random.Range(minZ, maxZ));
Instantiate(systemObject, allSystems[i].worldPos, Quaternion.identity);
}
Note: Inside the SystemRepository I declared the list of systems to be static and only instantiate it if it is null. That means it can be only instantiated once in the lifetime of your game so that you always get the same data back no matter what instance of the repo you access. Such a thing usually would be done using a Database.
I'm currently trying to set up multiple Input Field in Unity and having issue do so. I can only get one input to work (intPressure) but don't know how to add a second one (drillpipe). It seem to me that unity only allow one Input Field at a time. I would think that just changing it to InputField2, ect will do the trick, but that doesn't seem to work. Other have mentioned creating an empty gameobject and inserting the inputfield to each gameobject.
To be honest, I still quite confuse about how to setup the second input field.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class UserInput : MonoBehaviour {
InputField input;
int intPressure = 0;
int drillpipe = 0;
public DataManager data;
void Start ()
{
var input = gameObject.GetComponent<InputField>();
var se= new InputField.SubmitEvent();
se.AddListener(SubmitName);
input.onEndEdit = se;
}
private void SubmitName(string pressure)
{
var pressureScript =
GameObject.FindObjectOfType(typeof(DataManager)) as DataManager;
if (int.TryParse(pressure, out intPressure))
{
pressureScript.pressure = intPressure;
}
else
{
// Parse fail, show an error or something
}
}
}
I am not sure about the way you want to implement this (using single GameObject), however you most certainly would be able to do this if you had a couple of GameObjects (One object per control) nested within another GameObject (let's call it UserInputRoot) like this:
UserInputRoot (has UserInputController script)
|
+--- InputPressure (has InputField component)
|
+--- InputDrillpipe (has InputField component)
The controlling script would then have either a couple of public InputField's or a couple of private ones, initialized within the Start() or Awake() method:
class UserInputController : MonoBehaviour {
//These can be set from the inspector and controlled from within the script
public InputField PressureInput;
public InputField DrillpipeInput;
// It would be better to pass this reference through
// the inspector instead of searching for it every time
// an input changes
public DataManager dataManager;
private void Start() {
// There is no actual need in creating those SubmitEvent objects.
// You can attach event handlers to existing events without a problem.
PressureInput.onEndEdit.AddListener(SubmitPressureInput);
DrillpipeInput.onEndEdit.AddListener(SubmitDrillpipeInput);
}
private void SubmitDrillpipeInput(string input) {
int result;
if (int.TryParse(input, out result)) {
dataManager.drillpipe = result;
}
}
private void SubmitPressureInput(string input) {
int result;
if (int.TryParse(input, out result)) {
dataManager.pressure = result;
}
}
}
And by the way, the formatting of your code is absolutely atrocious. You MUST fix it.
It looks like the script you are showing is attached to an input field gameobject is that correct? You have a member variable in your object named input but then you create a new variable input in your Start method. Rather than have a script attached to your first InputField, create an empty gameobject in the editor and attach a script to that. In the script add two public members:
public class UserInput : MonoBehaviour {
public InputField PressureInput;
public InputField DrillPipeInput;
Now pop back to the editor, you should see the two input fields showing up when you select your empty gameobject. Drag and drop your two input fields into the slots for each inputfield. Now when the scene starts your UserInput script will be set with the input fields and you can use them both.
I have one class that is going to contain multiple lists that hold a variety of game objects. These lists will get populated by JSON scripts further in development.
Right now, I'm trying to access 1 element in 1 list. I have filled my class out as so:
public class ShopManager : MonoBehaviour
{
public List<GameObject> primaryWeapons = new List<GameObject>();
public List<GameObject> PrimaryWeapons
{
get { return primaryWeapons; }
}
public GameObject gameObj1, gameObj2;
// Use this for initialization
void Start ()
{
FillList();
}
public void FillList()
{
primaryWeapons.Add(gameObj1);
primaryWeapons.Add(gameObj2);
}
}
In my second class I'm trying to access one of the game objects I have placed in the list. This what I have so far:
ShopManager primary;
public GameObject temp;
public List<GameObject> tmpList;
void Awake()
{
primary = new ShopManager();
primary.FillList();
tmpList= new List<GameObject>();
}
// Use this for initialization
void Start()
{
for (int i = 0; i < primary.PrimaryWeapons.Count; i++)
{
tmpList.Add(primary.PrimaryWeapons[i]);
}
Debug.Log(primary.PrimaryWeapons.Count);
}
public void SelectWeapon1()
{
temp = primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault();
}
}
In my list in the shop manager class I am manually setting the objects myself, so I know the names of them. However, whilst I can get a count returning correctly, I am unable to access this named object.
When I run the code I get a null reference pointing to the following line:
temp = primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault();
Additonally I even created another list with the idea of passing the contents from my List property in the ShopManager class to this temp one. However, this lists populates with 2 empty positions.
I'm still not 100% on using properties like this. Espcially with lists. Could someone please tell me what it is I'm doing wrong?
You didn't initialize your objects in ShopManager.
Replace this:
public GameObject gameObj1, gameObj2;
With this:
public GameObject gameObj1 = new GameObject(), gameObj2 = new GameObject();
Check GameObject initialization. Especially gameObject field.
primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault(); can fail with null reference in just few conditions:
primary is null
primary.PrimaryWeapons is null
obj.gameObject is null
The first two look covered in your code. So, check how GameObject.gameObject field is initialized.