Getting NullReferenceExeption when trying to GetComponentInParent from a instantiated buttom prefab - c#

I'm instanciating buttons via script and need then to call a script in a parent object a few levels up
unity hierarchy
This is in the prefab script and gets called when one of the buttons gets clicked. Performance isn't relevant at all, so I just told it to go from the bottom up until it reaches the controller game object that has the mllhildTestController.cs script attached.
public void ClickMe()
{
string parentNode = ReturnLabel();
string path = this.GetComponentInParent<mllhildTestController>().path;
this.GetComponentInParent<mllhildTestController>().Clear();
this.GetComponentInParent<mllhildTestController>().Load(path, parentNode);
}
but it only results in an error
NullReferenceException: Object reference not set to an instance of an object
this.something is working fine, so it has to be an error in my logic with the GetComponentInParent<mllhildTestController>() part.
Could someone please help me?
EDIT: this function seems to work fine, but since it was asked
public string ReturnLabel()
{
return buttonText.text;
}
Controller script
public class mllhildTestController : MonoBehaviour
{
public mllhildTestLinq linq;
public mllhildTestDisplay display;
public mllhildTestButtons buttons;
public List<GameObject> listToStuff;
public string test = "smName";
public string smName = "mllhild";
public string displayText = "display.textWindow.font";
public string path = #"C:\SlaveMaker4\Slaves\";
// Start is called before the first frame update
void Start()
{
ClearText();
// linq.LinqTest(#"C:\SlaveMaker4\Rhia.xml");
// string filename = #"C:\SlaveMaker4\Rhia.xml";
// linq.QueryXML(filename, parentNode);
// Debug.Log(this.GetType().GetField("test").GetValue(this));
// Debug.Log(this.GetType().GetField(test).GetValue(this));
// Debug.Log(display.textWindow.font);
// Debug.Log(this.GetType().GetField("display").GetType().GetField("textWindow").GetType().GetField("font").GetValue(this));
// Debug.Log(typeof(TextMeshProUGUI).GetProperty(displayText).GetValue(this));
// Debug.Log(this.GetType().GetField(displayText).GetValue(this));
}
// Update is called once per frame
void Update()
{
}
public void SetText(string text)
{
display.textWindow.text = text;
}
public void AddText(string text)
{
display.textWindow.text += text;
}
public void ClearText()
{
display.textWindow.text = null;
}
public GameObject bfield;
public GameObject AddNewButtonList(string label)
{
GameObject b = Instantiate(bfield) as GameObject;
b.SetActive(true);
b.GetComponent<PrefabListButtoms>().title.text = label;
b.transform.SetParent(bfield.transform.parent, false);
return b;
}
public void Clear()
{
foreach(GameObject b in listToStuff)
{
Destroy(b);
}
listToStuff.Clear();
}
public void LoadButton() { Load(path, "Language" ); }
public void Load(string path, string parentNode)
{
string[] fileArray = Directory.GetFiles(path, "*.xml");
foreach (string xmlfile in fileArray)
{
GameObject blist = AddNewButtonList(xmlfile + "\n" + parentNode);
listToStuff.Add(blist);
//AddText("\n\n" + xmlfile + "\n");
//AddText("Parent-Node:" + parentNode + "\n");
//AddText("Child-Nodes:" + "\n");
linq.QueryXML(xmlfile, parentNode, blist);
}
}
}

You cant access directly because you have to find parent and then you find the desired children
so your first challenge is to find the parent of your component:
so first command is : GameObject.Find("NameofObj") or GameObject.FindWithTag("NameOfTag")
so after you just find the desired children component from this GameObject..and finally you could access to the method of script linked to child

Related

Function can't access Label

I have two function that use a UIDocument Label
public class UIController : MonoBehaviour
{
private Label argentTexte;
private int currentInt;
private string currentString;
void Start()
{
var root = GetComponent<UIDocument>().rootVisualElement;
argentTexte = root.Q<Label>("currentMoney");
Debug.Log(argentTexte.text); //<- show some string
}
public void IncreaseMoney(int moneyToAdd)
{
currentString = GetComponent<UIDocument>().rootVisualElement.Q<Label>("currentMoney").text; // Don't show anything, throw error
Debug.Log(argentTexte.text); //throw an NullReferenceException: Object reference not set to an instance of an object
int.TryParse(currentString, out currentInt);
argentTexte.text = $"{currentInt + moneyToAdd}";
}
public void DecreaseMoney(int moneyToDecrease)
{
currentString = GetComponent<UIDocument>().rootVisualElement.Q<Label>("currentMoney").text; // show some string
int.TryParse(currentString, out currentInt);
argentTexte.text = $"{currentInt - moneyToDecrease}";
}
}
DecreaseMoney() is used on an object when i spawn it and it work, IncreaseMoney() is used on a child of an object when its destroyed and doesn't work.
Why DecreaseMoney() have access to argentTexte.text and not IncreaseMoney() ?

Unity - is there a way to show the popup in my game only once per game session?

[EDIT] Everything is working now! Thank's to #MrMoonMan!
As the title says, I'm working on a mobile game and I just implemented a Check Update popup.
The thing is, the popup is displayed every time the menu scene is loaded, and this is very annoying because if the player goes to the weapon menu and then comes back to the main menu 10 times, he will see the popup 10 times. The goal is to have the popup appear only once per game session, Here is what I tried for the moment but without success:
Using a bool variable
Destroy the gameobject to which the checkupdate script is attached when the user presses the "No Thank's" button
Here is the code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class CheckUpdate : MonoBehaviour
{
public string versionUrl = "";
public string currentVersion;
private string latestVersion;
public GameObject newVersionAvailable;
private static bool hasBeenChecked = false;
void OnAwake()
{
DontDestroyOnLoad(this);
StartCoroutine(LoadTxtData(versionUrl));
}
private IEnumerator LoadTxtData(string url)
{
UnityWebRequest loaded = new UnityWebRequest(url);
loaded.downloadHandler = new DownloadHandlerBuffer();
yield return loaded.SendWebRequest();
latestVersion = loaded.downloadHandler.text;
CheckVersion();
}
private void CheckVersion()
{
Debug.Log("currentVersion = " + currentVersion);
Debug.Log("latestVersion = " + latestVersion);
Version versionDevice = new Version(currentVersion);
Version versionServer = new Version(latestVersion);
int result = versionDevice.CompareTo(versionServer);
if ((latestVersion != "") && (result < 0))
{
newVersionAvailable.SetActive(true);
}
if (GetChecked())
return;
}
public void ClosePopUp(GameObject obj)
{
obj.SetActive(false);
Destroy(this);
}
public void OpenURL(string url)
{
Application.OpenURL(url);
}
public static bool GetChecked()
{
if (hasBeenChecked)
{
return hasBeenChecked;
}
hasBeenChecked = true;
return false;
}
}
EDIT
Ok so you mentioned that it still happens every time the scene is reloaded. That is because the object is created when the scene is loaded. It isn't saved between scenes. To handle that properly you need to tell it to not get destroyed when the scene is unloaded and to do that is simple, and that is by Adding DontDestroyOnAwake. Here is an example of the code:
public class CheckUpdate : MonoBehaviour
{
public string versionUrl = "";
public string currentVersion;
private string latestVersion;
public GameObject newVersionAvailable;
void OnAwake()
{
DontDestroyOnLoad(this);
}
void OnStart()
{
StartCoroutine(LoadTxtData(versionUrl));
}
private IEnumerator LoadTxtData(string url)
{
UnityWebRequest loaded = new UnityWebRequest(url);
loaded.downloadHandler = new DownloadHandlerBuffer();
yield return loaded.SendWebRequest();
latestVersion = loaded.downloadHandler.text;
}
private void CheckVersion()
{
Debug.Log("currentVersion = " + currentVersion);
Debug.Log("latestVersion = " + latestVersion);
Version versionDevice = new Version(currentVersion);
Version versionServer = new Version(latestVersion);
int result = versionDevice.CompareTo(versionServer);
if ((latestVersion != "") && (result < 0))
{
newVersionAvailable.SetActive(true);
}
}
public void ClosePopUp(GameObject obj)
{
obj.SetActive(false);
}
public void OpenURL(string url)
{
Application.OpenURL(url);
}
}
,,Menu scene''? It sound some suspicious. If you load and unload whole new scene as menu, state as bool field in MonoBehaviours or modifing the scene structure (destory, create object) will not work beacuse allways will be loaded new scene from scratch. ;)
If you have to have menu on seperated scene use someting other to manage the state. For example:
DontDestoryOnLoad objects.
Or some C# static class or singleton (not a MonoBehaviour).

Syncing Text using PUN in Unity

I'm making a simple Word Builder type of game (where one player enters a word, and the other player enters a word starting with the last letter of the first player's word), and I can't get the Text to show up on both the screens.
I'm able to get the text to show up if I hard code it, like:
[PunRPC]
public void DisplayWord ()
{
MainWord.text = "Word";
}
But I can't get it to display the word a player just entered to both screens, I thought this code would word:
[PunRPC]
public void DisplayWord ()
{
MainWord.text = UsedString;
//UsedString is the word the (one of the) players just entered.
}
Any help would be greatly appreciated, thank you so much!
Here's the full code for entering a word:
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using Photon.Pun;
public class DictionaryScript : MonoBehaviourPunCallbacks
{
List<string> DictA = new List<string> () {
"a",
"aa",
"aaa",
"aah",
"aahed",
"aahing",
"aahs",
"aal",
"aalii",
"aaliis",
"aals",
"aam",
"aani",
"aardvark",
"aardvarks",
"aardwolf",
"aardwolves",
"aargh",
"aaron",
"aaronic",
"aaronical",
"aaronite",
"aaronitic",
"aarrgh",
"aarrghh",
"aaru",
"aas",
"aasvogel",
"aasvogels"};
public List<string> DictAUsed = new List<string>() { };
public string UsedString;
public string NewString;
public InputField inputField;
public Text AddedWord;
public Text MainWord;
public Text TurnText;
public Text ConnectedText;
public Button StartButton;
public Canvas OverlayCanvas;
// Start is called before the first frame update
public void Awake()
{
if (PhotonNetwork.IsMasterClient)
{
StartButton.enabled = true;
OverlayCanvas.enabled = true;
}
else
{
StartButton.enabled = false;
OverlayCanvas.enabled = true;
}
}
void Start()
{
if (PhotonNetwork.InRoom)
{
ConnectedText.text = "Connected!";
}
}
public void StartGame ()
{
photonView.RPC("CanvasDisplay", RpcTarget.All);
}
public void MyWord (string newWord)
{
string newString = newWord.ToLower();
print(newString);
if (DictA.Contains(newString) && newString[0] == MainWord.text[0] && !DictAUsed.Contains(newString))
{
UsedString = newString;
Debug.Log(UsedString);
photonView.RPC("OnInput", RpcTarget.All);
}
else
{
print("This word does not exist!");
AddedWord.text = newString + " is not a valid word!";
}
inputField.text = "";
}
[PunRPC]
public void OnInput ()
{
DictAUsed.Add(UsedString);
AddedWord.text = "The word was: " + UsedString;
photonView.RPC("DisplayWord", RpcTarget.All);
}
[PunRPC]
public void DisplayWord ()
{
MainWord.text = UsedString;
//UsedString is the word the (one of the) players just entered.
}
[PunRPC]
public void CanvasDisplay()
{
//string Alphabet = "abcdefghijklmnopqrstuvwxyz";
string Alphabet = "aaaaaaaaaaaaaaaaaaaaaaaaaa";
MainWord.text = Alphabet[Random.Range(0, 25)].ToString();
StartButton.enabled = false;
OverlayCanvas.enabled = false;
}
}
I only have words starting with 'aa' for now, I'll add all the words once I can get it working.
UsedString is not synchronized in your network => Each player might have a different value for UsedWorld at the moment the RPC get called.
Why not pass on the UsedWord as argument to OnInput and DisplayWord?
Also why is DisplayWord even called via RPC at all? Since OnInput is alreay synchronized to ALL you could simply call the method right away
...
photonView.RPC(nameof(OnInput), RpcTarget.All, UsedWord);
...
[PunRPC]
public void OnInput (string usedString)
{
DictAUsed.Add(usedString);
AddedWord.text = "The word was: " + usedString;
// since this shall happen on any client as well
// why even call it via RPC?
// OnInput is called on ALL anyway so just do it right away
DisplayWord(usedString);
}
public void DisplayWord (string usedString)
{
MainWord.text = usedString;
}

Don't Destroy on Load Object and GameData

I want to save my game data as my 2D game progresses using a DontDestroyOnLoad object.
I have a GameData class/script which stores values like health, magicType, etc, a SaveLoad class/script which saves current values in the GameData class to Json and an empty game object called PlayerData that has both of the above scripts.
Currently, I'm saving values in one scene by using button click events. My object attached to the button is the PlayerData object, and I change values using the GameData script on the click event.
But this only works on the first scene - I can't attach the PlayerData object and store its GameData values because there is no PlayerData object in the editor.
So how can I attach this object, which is in a different scene? Or am I doing this wrong?
Here's a photo of my current project.
Also, when I save a boolean, then open my Json file, the file is empty. Is this normal? Here's my save and load code:
Load:
private string _filePath;
private static DataService _instance;
public DataService Instance
{
get { return _instance; }
}
public GameData _gameData;
private void Awake()
{
DontDestroyOnLoad(gameObject);
}
public void LoadGameData()
{
_filePath = Path.Combine(Application.persistentDataPath, "GameData1.json");
string json;
if (File.Exists(_filePath))
{
json = File.ReadAllText(_filePath);
JsonUtility.FromJsonOverwrite(json, _gameData);
}
else
{
Debug.Log("File missing.");
}
}
Save:
public void SaveGameData1()
{
string json = JsonUtility.ToJson(_gameData);
_filePath = Path.Combine(Application.persistentDataPath, "GameData1.json");
if (File.Exists(_filePath))
{
File.Create(_filePath).Dispose();
}
File.WriteAllText(_filePath,json);
}
EDIT: This is the code of my GameData class:
[Serializable]
public class GameData : MonoBehavior
{
public float Health { get; set; }
}
Data Path
First of all as just recently answered here I wouldn't use the persistentDataPath for development but rather the streamingAssetsPath and on first app run copy the file over.
Singleton Pattern
I see you have the first half of a Singleton pattern but: You never assigned a value to instance. I usually would do it "lazy" in the property or Awake like e.g.
private static DataService instance;
public static DataService Instance
{
if(instance) return instance;
instance = FindObjectOfType<DataService>();
if(instance) return instance;
// If it wasnt found in the scene create it now
instance = new GameObject("DataService", typeof (GameData)). AddComponent<DataService>();
instance._gameData = instance.GetComponent<GameData>();
DontDestroyOnLoad (instance.gameObject);
return instance;
}
private void Awake ()
{
if(instance && instance != this)
{
Debug.LogWarning("Another instance of DataService I already in use");
Destroy(gameObject);
return;
}
if(instance) return;
instance = this;
DontDestroyOnLoad (gameObject);
if(!_gameData) _gameData = GetComponent<GameData>();
}
Or static Class
Actually with the code you provided I doubt that any of your two classes really needs to be a MonoBehaviour!
You could probably simply use something like
public static class DataService
{
private const string FILE_NAME = "GameData1.json";
public static GameData GameData = new GameData();
public static void SaveGameData1()
{
var json = JsonUtility.ToJson(_gameData);
var filePath = Path.Combine(Application.persistentDataPath, FILE_NAME);
if (File.Exists(filePath))
{
File.Create(filePath);
}
File.WriteAllText(filePath,json);
}
public static void LoadGameData()
{
var filePath = Path.Combine(Application.persistentDataPath, FILE_NAME);
if (File.Exists(filePath))
{
var json = File.ReadAllText(_filePath);
JsonUtility.FromJsonOverwrite(json, GameData);
}
else
{
Debug.Log("File missing.");
}
}
}
and
[Serializable]
public class GameData
{
public float SomeValue;
...
}
Now you can from everywhere simply call
DataService.LoadGameData();
And then access value like
DataService.GameData.SomeValue = ...;
For the buttons
I would create a class DataServiceButton which is attached to a UI.Button and registers itself as a listener to onClick like
[RequireComponent(typeof(UI.Button))]
public class DataServiceButton : MonoBehaviour
{
public enum ButtonType
{
Save,
Load
}
// This is set via the Inspector
[SerializeField] private ButtonType type;
// Here you can already reference the according Button component
[SerializeField] private Button button;
private void Awake()
{
if(!button) button = GetComponent<Button>();
// Register as callback
button.onClick.AddListener(HandleClick);
}
private void HandleClick()
{
switch(type)
{
case ButtonType.Load:
DataService.LoadGameData();
// OR IF YOU STICK TO THE SINGLETON
//DataService.Instance.LoadGameData();
break;
case ButtonType.Save:
DataService.SaveGameData();
// OR ACCORDINGLY WITH SINGLETON
//DataService.Instance.SaveGameData();
break;
}
}
}
The question why your file is empty after writing data unfortunately can not be answered without seeing the code of your GameData class...
Make sure your types are serializeable.

Unity array confusion

hello i'm trying to make a dialogue box using an array but i seem to be having a bit of an issue. instead of showing the different strings that's in DialogHolder class, it keeps on showing the DialogueManager instead.
From my understanding, I wanted to make it that the DialogHolder holds that dialogue and i got the idea of making it something like this -->
[DialogHolder = DialogueManager]
but the only thing that is showing is the DailogueManager with the test strings i placed to see if the dialogue changes
DailogHolder
public string dialogue;
private DialogueManager sentence;
public string[] NPC_name;
[TextArea(3, 10)]
public string[] dialogueLines;
void Start()
{
}
public void TriggerDialogue()
{
FindObjectOfType<DialogueManager>().ShowDialogue(dialogueLines);
FindObjectOfType<DialogueManager>().ShowName(NPC_name);
}
DialogueManager
//holds the text GameObject
public Text NPC_nameText;
public Text NPC_DialogueText;
public string[] names;
[TextArea(3, 10)]
public string[] dialogueLines;
public int currentlines;
void Start () {
//lets the box types the char individual
StopAllCoroutines();
}
void Update()
{
StartDialog();
}
public void StartDialog()
{
if (currentlines >= dialogueLines.Length)
{
currentlines = 0;
EndDialogue();
return;
}
NPC_DialogueText.text = "";
StartCoroutine(TypeSentence());
}
public void ShowDialogue(string[] dialogueLines)
{
NPC_DialogueText.text = dialogueLines[currentlines];
}
public void ShowName(string[] NPC_name)
{
//selected a name
NPC_nameText.text = NPC_name[0];
//this part works for some reason since it's taken from the DialogHolder class
}
//pressed by a button
public void NextLine()
{
currentlines++;
StopAllCoroutines();
}
void EndDialogue()
{
Debug.Log("End of Conversation");
}
IEnumerator TypeSentence()
{
//types the characters for each sentence
foreach (char letter in dialogueLines[currentlines].ToCharArray())
{
NPC_DialogueText.text += letter;
yield return null;
}
}
if i remove the [size] of the array in the DailogHolder class, i get an error that says [Array index is out of range] but if i just remove the strings that was saved inside that array, it wouldn't matter anyway since it just keeps on using the array in the DialogueManager class.
Please help, it works if i'm using only a single string and not an array but i need to learn how to print multiple lines

Categories

Resources