Unity Dialogue boxes - c#

I'm trying to create a dialogue where the dialogue stops at a given count and then continues after a certain event happens. I've set the number of sentences to 6 at first and I did manage to make the conversation stop after the 3rd sentence
by setting the if statement in DisplayNextSentence() to ==> [sentence.Count == 3]
and make the player do something else but I don't know how to make the NPC continue its dialogue after finishing that certain event.
can you please enlighten me on how to do this? I did watch some youtube video but those videos either confused me or they didn't explain how that certain part work in detail but I found this method somehow easier to understand in my case.
container
[System.Serializable]
public class Dialogue {
//name of npc
public string name;
//sentences of the npc
[TextArea(3,10)]
public string[] sentences;
}
another class
public class DialogueTrigger : MonoBehaviour {
//calls the Dialogue class
public Dialogue dialogue;
//triggers the dialouge
public void TriggerDialogue()
{
FindObjectOfType<DialogueManager>().StartDialogue(dialogue);
}
}
manager
public class DialogueManager : MonoBehaviour {
private Queue<string> sentences;
public Text NPC_nameText;
public Text NPC_DialogueText;
void Start () {
sentences = new Queue<string>();
}
public void StartDialogue(Dialogue dialogue)
{
//Changes the name of the Name Text to given name
NPC_nameText.text = dialogue.name;
sentences.Clear();
//goes through the whole array of sentences
foreach(string sentence in dialogue.sentences)
{
sentences.Enqueue(sentence);
}
//displays the sentences
DisplayNextSentence();
}
public void DisplayNextSentence()
{
//condition if array sentence is finished looping
if (sentences.Count == 0)
{
//ends dialogue and goes to the next UI
EndDialogue();
return;
}
//Sentence appears in queue waiting for each sentence to finish
string sentence = sentences.Dequeue();
StopAllCoroutines();
StartCoroutine(TypeSentence(sentence));
}
void EndDialogue()
{
Debug.Log("End of Conversation");
}
IEnumerator TypeSentence(string sentence)
{
//types the characters for each sentence
NPC_DialogueText.text = "";
foreach (char letter in sentence.ToCharArray())
{
NPC_DialogueText.text += letter;
yield return null;
}
}
}

Related

How to assign different UI panels for each joining player? Mirror

I have three buttons, three players, and I want to know which player clicked on which button by assigning them a different popup UI color. Let's say player 1- red, player 2 - green, player 3 - white. So when player 1 clicks on button(1), the red panel will be visible for all other players telling them who did that.
Possible solution: I have been advised to call to the Host with a Cmd method and pass the connectionId of the player who click on it. Then check the list of players on the NetworkManager, and find the matching player object. Once it has that player object, it would get the name and Id, and calls a RPC to the clients to show the UI element, passing them the correct text and color.
What I Have: I have list of my players:
public List<MyNetworkPlayer> players { get; } = new List<MyNetworkPlayer>();
and i can use my player by
MyNetworkPlayer player = conn.identity.GetComponent<MyNetworkPlayer>();
I also can attatch my NetworkManager if that can help:
public class MyNetworkManager : NetworkManager
{
public static event Action ClientOnConnected;
public static event Action ClientOnDisconnected;
// players should't be able to join the lobby during the game.
private bool isGameInProgress = false;
// here, we create a list of players, in order to display them in the lobby later.
public List<MyNetworkPlayer> players { get; } = new List<MyNetworkPlayer>();
#region Serwer
public override void OnServerConnect(NetworkConnection conn )
{ //kick a player if the game is in progress
if (!isGameInProgress) { return; }
base.OnServerConnect (conn);
conn.Disconnect();
}
// here i write some own logic about what heppens when a client connects to a server. If the connection is successful
// the message about connection will be display in a console!
public override void OnServerDisconnect(NetworkConnection conn)
{ // when the server disconnects someone
// lets grab that player
MyNetworkPlayer player = conn.identity.GetComponent<MyNetworkPlayer>();
players.Remove(player);
base.OnServerDisconnect(conn);
}
public override void OnStopServer()
//what happens when we stop running the server
{
players.Clear();
isGameInProgress = false;
}
public void StartGame()
{ // the game won't start with the less than 2 players
if(players.Count <2){return;}
//if we have more than 2 we can start the game
isGameInProgress = true;
//also we change our scene here
ServerChangeScene("GameScene");
}
public override void OnServerAddPlayer(NetworkConnection conn)
{
base.OnServerAddPlayer(conn);
//reference to network player
MyNetworkPlayer player = conn.identity.GetComponent<MyNetworkPlayer>();
players.Add(player);
// here I assign player's name base on joining order
player.SetDisplayName($"Player {players.Count}");
Debug.Log("Player conn ID: " + conn.connectionId);
//here we dclare who will be a party owner
// if there is only one player he will be a party owner
player.SetPartyOwner(players.Count == 1);
Debug.Log($"There are now {numPlayers} players");
}
//when a client stops, we shold also clear the player's list and currently we only
// add to it on the server
public override void OnStopClient()
{
players.Clear();
}
#endregion
#region Client
[System.Obsolete]
public override void OnClientConnect(NetworkConnection conn)
{
base.OnClientConnect(conn);
Debug.Log("Client connected: " + conn.connectionId);
ClientOnConnected?.Invoke();
}
[System.Obsolete]
public override void OnClientDisconnect(NetworkConnection conn)
{
base.OnClientDisconnect(conn);
ClientOnDisconnected?.Invoke();
}
#endregion
}
And MyNetworkPlayer:
public class MyNetworkPlayer : NetworkBehaviour
{
//Here i refer to a player text and change it based on current player number.
[SerializeField] private TMP_Text displayNameText= null;
[SyncVar(hook = nameof(AuthorityHandlePartyOwnerStateUpdated))]
private bool isPartyOwner = false;
// syncvar that will allow other players to store our name
[SyncVar(hook = nameof(ClientHandleDisplayNameUpdated))]
public string displayName;
// this below is the event created in order to handle the changes of client info
// like it's colour and so on
public static event Action ClientOnInfoUpdated;
//public string which returns the display name
public string GetDisplayName()
{
return displayName;
}
public bool GetIsPartyOwner()
{
return isPartyOwner;
}
public static event Action<bool> AuthorityOnPartyOwnerStateUpdated;
#region Serwer
public override void OnStartServer()
{
DontDestroyOnLoad(gameObject);
}
[Server]
// checking who the party owner is
public void SetPartyOwner(bool state)
{
isPartyOwner = state;
}
[Server]
public void SetDisplayName(string newDisplayName)
{
displayName = newDisplayName;
}
#endregion
#region Commands
[Command]
private void CmdSetDisplayName(string newDisplayName)
{
//here I add the codition saying that the player length can not be shorter than 2 characters
//if(newDisplayName.Length <2 || newDisplayName.Length > 20){ return; }
RpcLogNewName(newDisplayName);
SetDisplayName(newDisplayName);
}
[Command]
// A command so client can tell the server that he wants to start the game
public void CmdStartGame()
{
if(!isPartyOwner) { return; }
//otherwise tell the NetworkManager to stop the game
((MyNetworkManager)NetworkManager.singleton).StartGame();
}
#endregion
#region Authorities
private void AuthorityHandlePartyOwnerStateUpdated(bool oldState, bool newState)
{
// it allows you to change who the party owner is during the lobby
// its goinna be a button to change the authority of the ownershit
if(!hasAuthority) {return; }
// this down below is for the UI button change its statement
AuthorityOnPartyOwnerStateUpdated?.Invoke(newState);
}
#endregion
#region Client
// Here we change the player's name
private void HandleDisplayNameUpdated(string oldName, string newName)
{
displayNameText.text = newName;
}
// Here we connect the reference from MyNetworkManager
public override void OnStartClient()
{
if (NetworkServer.active) {return;}
// whenever a client starts and we're not the host, if we just end up a client, we can
// be added to our list of the players
((MyNetworkManager)NetworkManager.singleton).players.Add(this);
// this below prevcents our player objects from being destroyed when
// changing the scanes
DontDestroyOnLoad(gameObject);
}
private void ClientHandleDisplayNameUpdated(string oldDisplayName, string newDisplayName)
{ // we need to display people's name when they got updated (in the UI)
ClientOnInfoUpdated?.Invoke();
}
public override void OnStopClient()
{
ClientOnInfoUpdated?.Invoke();
//if we are not the server we do this for everyone:
if (!isClientOnly) {return; }
// Here we remove a player from the list of players
((MyNetworkManager)NetworkManager.singleton).players.Remove(this);
//if we are the server we do this for everyone:
if (!hasAuthority) { return; }
}
[ClientRpc]
//rpc log new name is one of our new names being set
private void RpcLogNewName(string newDisplayName)
{
Debug.Log(newDisplayName);
}
#endregion
}
My problem: As I said in possible solution, I don't know how to send connectionId to host and then, based on that, assign corresponding UI element for each different player. I would be really thankful for any advise on how to code that.

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;
}

Getting NullReferenceExeption when trying to GetComponentInParent from a instantiated buttom prefab

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

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