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
Related
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;
}
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
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;
}
}
}
I'm creating a Media player application with Unity3D and C#.
(My question is not related to Unity, it's a pure design problem)
Here is what I currently have:
an IApp interface, with implementers:
TextViewer
ImageViewer
MediaPlayer
an IFile interface, with implementers:
TextFile
ImageFile
MediaFile - with children:
VideoFile
AudioFile
Here's the interface:
public interface IApp
{
void Open(IFile file);
Type SupportedType { get; }
}
Each app has a specific supported file type it could open.
A word about my MediaPlayer, is that it opens/plays both Audio and Video files. But the way I open videos, is different from the way I open audios, so there's a unique logic for each.
Now here's the code - very simple (but not fully implemented yet):
public class MediaPlayer : IApp
{
public Type SupportedType { get { return typeof(MediaFile); } }
public void Open(IFile file)
{
if (file is MediaFile)
Console.WriteLine("MediaPlayer opening media file...");
}
List<MediaFile> Medias = new List<MediaFile>();
public MediaFile Current { private set; get; }
public PlaybackControls Controls { private set; get; }
public PlaybackSettings Settings { private set; get; }
public MediaPlayer()
{
Controls = new PlaybackControls(this);
Settings = new PlaybackSettings(this);
}
public class PlaybackControls
{
private MediaPlayer player;
private int index;
public PlaybackControls(MediaPlayer player)
{
this.player = player;
}
public void Seek(float pos) { }
public void Next()
{
index = (index + 1) % player.Medias.Count;
player.Current = player.Medias[index];
}
public void Previous()
{
index--;
if (index < 0)
index = player.Medias.Count - 1;
player.Current = player.Medias[index];
}
private void PlayVideo(VideoFile video)
{
// video logic
}
private void PlayAudio(AudioFile audio)
{
// audio logic
}
public void Play(MediaFile media)
{
IsPlaying = true;
if (media is AudioFile)
PlayAudio(media as AudioFile);
else if (media is VideoFile)
PlayVideo(media as VideoFile);
}
public void Play()
{
Play(player.Current);
}
public void Pause()
{
IsPlaying = false;
}
public void Stop()
{
IsPlaying = false;
Seek(0);
}
public bool IsPlaying { get; private set; }
}
public class PlaybackSettings
{
// Volume, shuffling, looping, etc
}
}
The thing that I don't quite like, is the Play(Media) method. Inside, I'm doing a check upon the media type, and depending on whether the media is a video or audio, I'm calling the right method. I don't like that, I don't feel it's quite right. What if I had other types of media, like picture? what if I wanted to move ImageFile under MediaFile?
I would then have to add another else-if statement, which isn't polymorphic at all.
What I could do instead, is let the media files choose what method to call, like:
public abstract class MediaFile : IFile
{
//...
public abstract void Open(MediaPlayer from);
//...
}
public class AudioFile : MediaFile
{
public override void Open(MediaPlayer from)
{
from.PlayAudio(this);
}
}
public class VideoFile : MediaFile
{
public override void Open(MediaPlayer from)
{
from.PlayVideo(this);
}
}
Now in the MediaPlayer:
public void Open(MediaFile media)
{
media.Open(this); // polymorphically open it
}
No more else-if, nice! But this introduces other inconveniences I don't like:
VideoFile & MediaPlayer and AudioFile & MediaPlayer are now more tightly coupled.
There's now a circular dependency (MediaPlayer has to know about Audio/VideoFile and vise versa)
I don't think it makes sense for the Audio/VideoFiles to be able to open themselves, by themselves (although they're not really doing so, they're just telling the MediaPlayer how to open them. The MediaPlayer should know how to, he doesn't need anyone telling him how to do his work.)
It feels very redundant, it's like asking somebody to point at his ear, so he wraps his right hand around his head, and points to his left ear instead of right! - What's happening is that we're going:
either
MediaPlayer.Open(Media) -> AudioFile.Open(AudioFile) -> MediaPlayer.OpenAudio(AudioFile)
or
MediaPlayer.Open(Media) -> VideoFile.Open(VideoFile) -> MediaPlayer.OpenVideo(VideoFile)
We're circling around ourselves, in the name of polymorphism where we could have just gone directly to the right methods.
I think both the two above approaches are not best, but if I were to choose one, I would go for the first.
What do you think? Is there a better way? - A nice, elegant, robust polymorphic way that shoots all birds with one stone? How should I have gone about this? Maybe there's a design pattern I could use here?
And please correct me if I was wrong in my judgement.
Thanks a lot for any help in advance.
You have couple options.
1) Use dictionary of delegates and select based on file type, which delegate to run:
public class PlaybackControls
{
private MediaPlayer player;
private int index;
Dictionary<string, Action<MediaFile>> _fileActionMethods;
public PlaybackControls(MediaPlayer player)
{
this.player = player;
_fileActionMethods = new Dictionary<string, Action<MediaFile>>();
_fileActionMethods.Add(typeof(VideoFile).Name, x => PlayVideoFile(x));
_fileActionMethods.Add(typeof(AudioFile).Name, x => PlayAudioFile(x));
}
public void Seek(float pos) { }
public void Next()
{
index = (index + 1) % player.Medias.Count;
player.Current = player.Medias[index];
}
public void Previous()
{
index--;
if (index < 0)
index = player.Medias.Count - 1;
player.Current = player.Medias[index];
}
public void Play(MediaFile media)
{
IsPlaying = true;
_fileActionMethods[media.GetType().Name](media);
}
public void Play()
{
Play(player.Current);
}
public void Pause()
{
IsPlaying = false;
}
public void Stop()
{
IsPlaying = false;
Seek(0);
}
public bool IsPlaying { get; private set; }
private void PlayVideoFile(MediaFile file) { }
private void PlayAudioFile(MediaFile file) { }
}
2) Second option is based on similar concept of dynamic selection, but uses another layer of abstraction that enables you to handle each file using separate class. For lack of imagination I am naming it IFileActionHandler. It has only one method now but you can add more, if you need to. The sample below shows how to dynamically select the correct implementation based on the file type. I created a dictionary of these implementations in the constructor. Depending on how large the memory footprint of the implementations is, you may want to think about another approach - define the key value pairs in a static file (XML, config, txt, whatever) and create the correct instance using one of System.Acticator.CreateInstance overloads.
interface IFileActionHandler
{
void PlayFile(IFile file);
}
class FileActionHandlerBase : IFileActionHandler
{
IApp _app;
public FileActionHandlerBase(IApp app) // It may not be needed depending on what you want to do.
{
_app = app;
}
public abstract void PlayFile(IFile file);
}
class AudioFileActionHandler : FileActionHandlerBase
{
public AudioFileActionHandler(IApp app)
: base(app) { }
public override void PlayFile(IFile file)
{
// Your implementation...
}
}
class VideoFileActionHandler : FileActionHandlerBase
{
public VideoFileActionHandler(IApp app)
: base(app) { }
public override void PlayFile(IFile file)
{
// Your implementation...
}
}
public class PlaybackControls
{
private MediaPlayer player;
private int index;
Dictionary<string, IFileActionHandler> _fileActionHandlers;
public PlaybackControls(MediaPlayer player)
{
this.player = player;
_fileActionHandlers = new Dictionary<string, IFileActionHandler>();
_fileActionHandlers.Add(typeof(VideoFile).Name, new VideoFileActionHandler(player));
_fileActionHandlers.Add(typeof(AudioFile).Name, new AudioFileActionHandler(player));
}
public void Seek(float pos) { }
public void Next()
{
index = (index + 1) % player.Medias.Count;
player.Current = player.Medias[index];
}
public void Previous()
{
index--;
if (index < 0)
index = player.Medias.Count - 1;
player.Current = player.Medias[index];
}
public void Play(MediaFile media)
{
IsPlaying = true;
_fileActionHandlers[media.GetType().Name].PlayFile(media);
}
public void Play()
{
Play(player.Current);
}
public void Pause()
{
IsPlaying = false;
}
public void Stop()
{
IsPlaying = false;
Seek(0);
}
public bool IsPlaying { get; private set; }
}
For some reason the code that I modified doesn't seem to be functioning correctly. There were no exception error when debugging however it does not function the same as in the original code (list) - that is it doesn't seem to update. I've targeted it down to an area of code where I believe is causing the problem:
My original code (using List) that works:
private List<Gem> gems = new List<Gem>();
private List<Enemy> enemies = new List<Enemy>();
private void UpdateGems(GameTime gameTime)
{
for (int i = 0; i < gems.Count; ++i)
{
Gem gem = gems[i];
gem.Update(gameTime);
if (gem.BoundingCircle.Intersects(Player.BoundingRectangle))
{
gems.RemoveAt(i--);
OnGemCollected(gem, Player);
}
}
}
Here's my modified code replacing List to Stack which doesn't work correctly:
private Stack<Gem> gems = new Stack<Gem>();
private Stack<Enemy> enemies = new Stack<Enemy>();
private void UpdateGems(GameTime gameTime)
{
for (int i = 0; i < gems.Count; ++i)
{
Gem gem = gems.Peek();
gem.Update(gameTime);
if (gem.BoundingCircle.Equals(Player.BoundingRectangle))
{
gems.Pop();
OnGemCollected(gem, Player);
}
}
}
Any ideas?
I don't know exactly what you need, but the loop looks a little strange: if you don't pop() anything from the stack at the first turn, peek() will return the same element always.
Stack data structure is not indexed, it allow just to pop/peek the last pushed element. The code with the list behave of course different since you are actually checking all the elements in the list.
By definition, when you pop something off a stack you remove the last item added. Because of this you have reversed the order in which you are checking and removing Gem objects from the collection - when you do the Peek and Pop and BoundingCircle.Equals() check in the Stack<Gem> version you are not checking the first item in the collection like you are in the List<Gem> version.
A List can be iterated in either direction, just adjust your indexer in the appropriate way. A Stack is LIFO (Last In, First Out), so you can only access the last item added.
I think when called to peak method, it maybe keep a references to that item on the top of stack collection. So when you called to pop method. It gonna working not correctly.
I have copy your example and put to a console application as below
class Program
{
private static Stack<Gem> gems = new Stack<Gem>();
private Stack<Enemy> enemies = new Stack<Enemy>();
static void Main(string[] args)
{
gems.Push(new Gem
{
BoundingCircle = new BoundingCircle
{
Name = "abc"
}
});
gems.Push(new Gem
{
BoundingCircle = new BoundingCircle
{
Name = "def"
}
});
UpdateGems(new GameTime());
}
private static void UpdateGems(GameTime gameTime)
{
for (int i = 0; i < gems.Count; ++i)
{
Gem gem = gems.Peek();
gem.Update(gameTime);
if (gem.BoundingCircle.Equals(Player.BoundingRectangle))
{
gems.Pop();
OnGemCollected(gem, null);
}
}
}
private static void OnGemCollected(Gem gem, Player player)
{
}
}
public class Gem
{
public void Update(GameTime gameTime)
{
}
public BoundingCircle BoundingCircle { get; set; }
}
public class Enemy
{
}
public class GameTime
{
}
public class BoundingCircle : Bounding
{
public override bool Equals(object obj)
{
var temp = (Bounding) obj;
return Name.Equals(temp.Name, StringComparison.InvariantCulture);
}
}
public class Player
{
static Player()
{
BoundingRectangle = new BoundingRectangle
{
Name = "def"
};
}
public static BoundingRectangle BoundingRectangle { get; set; }
}
public class BoundingRectangle : Bounding
{
}
public abstract class Bounding
{
public string Name { get; set; }
}
It worked fine. Please notice that your Equals function have to be overridden by the BoundingCircle class.