I made a dialogue script so that I can display sentences on different NPC's.
I also have added a continue button so it goes from sentence to sentence.
The problem is that for some reason the last dialog gets stuck and keeps replaying if I interact after all sentences have played.
What I want is to replay all sentences that I added onto the NPC.
I also wanted to ask if there was a way to NOT use the continue button? And let the text type itself out after a certain amount of time. Like a real conversation.
As you can see I added the lines on the right
The first line printed
The second line printed
The third line printed
Starts placing the last line back to back every time I interact UNLESS I click the Button, because then it clears. But it still replays the last sentence every time instead of reverting back to the 1st.
Dialogue Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[System.Serializable]
public class Dialogue2 : MonoBehaviour
{
public GameObject dialogBox; // Attach ui image to this
public Text npcname; // Attach UI text gameObject
public Text dialogText; // Attach UI text gameObject
public string NPC; // What is he/she called?
public string[] sentences;
public bool playerInRange; // Is the Player in range?
public float typingSpeed;
private int index;
public GameObject continueButton;
public AudioSource SignUpSfx;
public AudioSource SignDownSfx;
void Update()
{
if(dialogText.text == sentences[index]){
continueButton.SetActive(true);
}
// Player in range and E is hit
if(Input.GetKeyDown(KeyCode.E) && playerInRange)
{
if(dialogBox.activeInHierarchy)
{
dialogBox.SetActive(false);
SignDownSfx.Play();
}
else
{
dialogBox.SetActive(true);
StopAllCoroutines();
StartCoroutine(Type());
npcname.text = NPC;
SignUpSfx.Play();
}
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if(other.CompareTag("Entity"))
{
playerInRange = true;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if(other.CompareTag("Entity") && dialogBox.activeInHierarchy == true)
{
SignDownSfx.Play();
}
if(other.CompareTag("Entity"))
{
playerInRange = false;
dialogBox.SetActive(false);
}
}
IEnumerator Type(){
foreach(char letter in sentences[index].ToCharArray()){
dialogText.text += letter;
yield return new WaitForSeconds(typingSpeed);
}
}
public void NextSentence(){
continueButton.SetActive(false);
if(index < sentences.Length - 1){
index++;
dialogText.text = "";
StartCoroutine(Type());
} else {
dialogText.text = "";
continueButton.SetActive(false);
}
}
}
Couldn't you just change:
if(index < sentences.Length - 1){
index++;
dialogText.text = "";
StartCoroutine(Type());
} else {
dialogText.text = "";
continueButton.SetActive(false);
}
To this?
if(index < sentences.Length - 1){
index++;
dialogText.text = "";
StartCoroutine(Type());
} else {
dialogText.text = "";
continueButton.SetActive(false);
index = 0;
}
Related
I have a saving system and today i'm using a canvas and text and just displaying the words "Saving Game" make it flickering for 3 seconds.
but what i want to do is that it will show the text "Saving Game" but the time will not be static 3 seconds or any other time i select but to be the time it's taking to save the game.
for example at the first time it's saving less stuff so it will take faster to save so the "Saving Game" should be display for a short time later in the game it will save more stuff so the saving time and the time to display "Saving Game" should be longer.
How can i know when the game is saving ? Maybe by somehow checking if the saved game file is busy/locked ?
public IEnumerator SaveWithTime()
{
yield return new WaitForSeconds(timeToStartSaving);
if (objectsToSave.Count == 0)
{
Debug.Log("No objects selected for saving.");
}
if (saveManual == false && objectsToSave.Count > 0)
{
Save();
StartCoroutine(fadeInOutSaveGame.OverAllTime(savingFadeInOutTime));
}
}
public IEnumerator SaveWithTimeManual()
{
yield return new WaitForSeconds(timeToStartSaving);
if(objectsToSave.Count == 0)
{
Debug.Log("No objects selected for saving.");
}
if (saveManual && objectsToSave.Count > 0)
{
Save();
StartCoroutine(fadeInOutSaveGame.OverAllTime(savingFadeInOutTime));
}
}
}
In the bottom of the script i have two methods SaveWithTime that save the game automatic at specific points in the game and SaveWithTimeManual that save the game when i'm pressing a key.
In this two methods i'm using the canvas to display the "Saving Game" text.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FadeInOutSaveGameText : MonoBehaviour
{
public Canvas canvas;
public float fadingSpeed;
private bool stopFading = false;
private const float THRESHOLD = 0.01F;
// Start is called before the first frame update
void Start()
{
//StartCoroutine(OverAllTime(5f));
}
IEnumerator CanvasAlphaChangeOverTime(Canvas canvas, float duration)
{
float alphaColor = canvas.GetComponent<CanvasGroup>().alpha;
while (true)
{
alphaColor = (Mathf.Sin(Time.time * duration) + 1.0f) / 2.0f;
canvas.GetComponent<CanvasGroup>().alpha = alphaColor;
// only break, if current alpha value is close to 0 or 1
if (stopFading && Mathf.Abs(alphaColor) <= THRESHOLD)//if (stopFading && (Mathf.Abs(alphaColor) <= THRESHOLD || Mathf.Abs(alphaColor - 1) <= THRESHOLD))
{
break;
}
yield return null;
}
}
public IEnumerator OverAllTime(float time)
{
stopFading = false;
StartCoroutine(CanvasAlphaChangeOverTime(canvas, fadingSpeed));
yield return new WaitForSeconds(time);
stopFading = true;
}
}
And this class make the actual writing to the save game file :
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public static class SaveSystem
{
private static readonly string SAVE_FOLDER = Application.dataPath + "/save_";
public static void Init()
{
if (!Directory.Exists(SAVE_FOLDER))
{
Directory.CreateDirectory(SAVE_FOLDER);
}
}
public static void Save(string saveString)
{
string fileName = Path.Combine(SAVE_FOLDER, "savegame.txt");
File.WriteAllText(fileName, saveString);
}
public static string Load()
{
string content = "";
string fileName = Path.Combine(SAVE_FOLDER, "savegame.txt");
if (File.Exists(fileName))
{
content = File.ReadAllText(fileName);
}
else
{
Debug.Log("Save game file is not exist" +
" either the file has deleted or the game has not saved yet.");
}
return content;
}
}
How can i find the real time it's taking to save and while saving showing the text "Saving Game" ?
Use Async/Threading, or in Unity's case, Coroutines.
bool isSaving;
public void SaveGame(){
StartCoroutine(SaveGameCoroutine());
}
private IEnumerator SaveGameCoroutine(){
if (isSaving){
yield break; // or something else
}
ShowSavingCanvas();
isSaving = true;
SaveGame();
isSaving = false;
HideSavingCanvas();
}
I got a quick question for you. I have already everything prepared. I am making a simple launching game, where you need to kill all enemies (not time-based) to pass to the next level. I have 2 methods GoToNextLevel() and AllMonsterDied(); .
Need to make something like player have 3 attempts (3 launchings whenever he wants not based on time).
Then every launch checks if any monster left. If does just show 1 attemps less. After 3 times just restart scene.
Thanks a lot since I am new to both c# and unity that would mean the world to me.
public class LevelController: MonoBehaviour
{
[SerializeField] string _nextLevelName;
Monster[] _monsters;
void OnEnable()
{
_monsters = FindObjectsOfType<Monster>();
}
void Update()
{
int currentLevel = SceneManager.GetActiveScene().buildIndex;
if (currentLevel >= PlayerPrefs.GetInt("levelsUnlocked"))
{
PlayerPrefs.SetInt("levelsUnlocked", currentLevel + 1);
}
if (MonsterAreAllDead() )
{
GoToNextLevel();
}
Debug.Log("Level" + PlayerPrefs.GetInt("levelsUnlocked") + "UNLOCKED");
}
void GoToNextLevel()
{
Debug.Log("Go to next level" + _nextLevelName);
SceneManager.LoadScene(_nextLevelName);
}
bool MonsterAreAllDead()
{
foreach (var monster in _monsters)
{
if (monster.gameObject.activeSelf)
return false;
}
return true;
}
}
Line from Monster.cs
private void OnCollisionEnter2D(Collision2D collision)
{
if (SouldDieFromCollision(collision))
{
tickSource.Play();
StartCoroutine(Die());
}
}
IEnumerator Die()
{
_hasDied =true;
GetComponent<SpriteRenderer>().sprite=_deadsprite;
_particleSystem.Play();
yield return new WaitForSeconds(1);
gameObject.SetActive(false);
}
Let's say you want the next level launch attempt happen when the player hits N key on the keyboard.
When the player hits the key, check if all monsters are dead. If so, call GoToNextLeve(), otherwise take off 1 attempt from the attempts available and restart the current scene.
int attempts;
void OnEnable()
{
attempts = 3;
}
void Update()
{
if (Input.GetKeyUp(KeyCode.H)
{
TryGoToNextLevel();
}
}
void TryGoToNextLevel()
{
if (MonsterAreAllDead() )
{
GoToNextLevel();
}
else
{
attempts--;
}
if (attempts <= 0)
{
SceneManager.LoadScene(
SceneManager.GetActiveScene().name);
}
}
I have the following coroutine which displays a warning error image when you click on a button and you do not have enough money:
public IEnumerator ShowWarning()
{
if (warningActive)
yield break;
Debug.Log("started...");
warningActive = true;
NotEnoughMoneyImage.SetActive(true);
yield return new WaitForSeconds(1f);
NotEnoughMoneyImage.SetActive(false);
warningActive = false;
Debug.Log("ended...");
yield break;
}
NotEnoughMoneyImage is a public Image GameObject
warningActive is a public bool
It shows the debug line "started" but never shows the debug line "ended". How is that possible? I call it from another script, but I don't think that there is the problem.
Here I've got the first script which is attached to an empty object (the main parent of the shop UI). This script is the main one. At the final of it appears the IEnumerator:
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using UnityEngine;
public class WeaponShop : MonoBehaviour
{
public static WeaponShop shop;
public GameObject itemHolderPrefab;
public Transform grid;
public List<Weapon> weaponList = new List<Weapon>();
private List<GameObject> itemHolderList = new List<GameObject>();
public List<GameObject> buyButtonList = new List<GameObject>();
public GameObject NotEnoughMoneyImage;
public bool warningActive = false;
void Start()
{
NotEnoughMoneyImage.SetActive(false);
shop = this;
FillList();
}
void FillList()
{
for (int i = 0; i < weaponList.Count; ++i)
{
GameObject holder = Instantiate(itemHolderPrefab, grid);
ItemHolder holderScript = holder.GetComponent<ItemHolder>();
holderScript.itemID = weaponList[i].weaponID;
holderScript.itemName.text = weaponList[i].weaponName;
if (weaponList[i].weaponPrice != (int)weaponList[i].weaponPrice)
holderScript.itemPrice.text = weaponList[i].weaponPrice.ToString("N2") + "$";
else
holderScript.itemPrice.text = weaponList[i].weaponPrice.ToString() + "$";
holderScript.itemSprite.sprite = weaponList[i].weaponSprite;
holderScript.buyButton.GetComponent<BuyButton>().weaponID = weaponList[i].weaponID;
itemHolderList.Add(holder);
buyButtonList.Add(holderScript.buyButton);
}
}
public void SoldOutText(int weaponID)
{
for (int i = 0; i < weaponList.Count; ++i)
if (weaponList[i].weaponID == weaponID)
itemHolderList[i].GetComponent<ItemHolder>().itemPrice.text = "SOLD OUT!";
}
public IEnumerator ShowWarning()
{
if (warningActive)
yield break;
Debug.Log("started...");
warningActive = true;
NotEnoughMoneyImage.SetActive(true);
yield return new WaitForSeconds(1f);
NotEnoughMoneyImage.SetActive(false);
warningActive = false;
Debug.Log("ended...");
yield break;
}
}
Now the second script is attached to the "BUY" button of a prefab "Item Holder". The script above (first one) generates a number of prefabs "item holder" and fills them with the info from the inspector (weapon name, price, stats etc). Here it comes the second script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.PlayerLoop;
public class BuyButton : MonoBehaviour
{
public int weaponID;
public TMP_Text buttonText;
public void BuyWeapon()
{
if (weaponID == 0)
{
Debug.Log("Weapon ID is ZERO");
return;
}
for (int i = 0; i < WeaponShop.shop.weaponList.Count; ++i)
{
if (WeaponShop.shop.weaponList[i].weaponID == weaponID && !WeaponShop.shop.weaponList[i].isBought && CurrencyManager.currencyManager.RequestMoney(WeaponShop.shop.weaponList[i].weaponPrice))
{// USING
WeaponShop.shop.weaponList[i].isBought = true;
CurrencyManager.currencyManager.ReduceMoney(WeaponShop.shop.weaponList[i].weaponPrice);
WeaponShop.shop.SoldOutText(weaponID);
UpdateBuyButton();
// change the weapon system and update your weapon, depending on ID / name here
}
else if (WeaponShop.shop.weaponList[i].weaponID == weaponID && !WeaponShop.shop.weaponList[i].isBought && !CurrencyManager.currencyManager.RequestMoney(WeaponShop.shop.weaponList[i].weaponPrice))
{//NOT ENOUGH MONEY. I know that it enters this if statement because of the debug.log
Debug.Log("Corountine...");
StartCoroutine(WeaponShop.shop.ShowWarning());
}
else if (WeaponShop.shop.weaponList[i].weaponID == weaponID && WeaponShop.shop.weaponList[i].isBought)
{
UpdateBuyButton();
// change the weapon system and update your weapon, depending on ID / name here
}
}
}
void UpdateBuyButton()
{
buttonText.text = "USING";
for (int i = 0; i < WeaponShop.shop.buyButtonList.Count; ++i)
{
BuyButton buyButtonScript = WeaponShop.shop.buyButtonList[i].GetComponent<BuyButton>();
for (int j = 0; j < WeaponShop.shop.weaponList.Count; ++j)
{
if (WeaponShop.shop.weaponList[j].weaponID == buyButtonScript.weaponID && WeaponShop.shop.weaponList[j].isBought && WeaponShop.shop.weaponList[j].weaponID != weaponID)
{
buyButtonScript.buttonText.text = "USE";
}
}
}
// change the weapon system and update your weapon, depending on ID / name
}
}
At line 34 is the start coroutine method.
Yes, I figured it out! When I was entering the shop UI, I was pausing the game in another script. So the time scale was 0. All I had to do was to replace in the coroutine this line:
yield return new WaitForSeconds(1f);
to this one:
yield return new WaitForSecondsRealtime(1f);
That was it! Now it works just fine! For everyone which thinks the coroutine does not work well, check your time scale. It's very important (Idk how I missed it!).
I am trying to make a dialog script for my game, using some kind of autotyping style. The problem is when I start the game it gives me an error: IndexOutOfRangeException: Array index is out of range. After I fixed this I I lost the autotyping effect, the message just appears instantly.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class texttype : MonoBehaviour {
public float letterPause = 0.2f;
//public AudioClip[] typeSound1;
//public int next;
string message;
public GameObject textB;
public Text text;
public TextAsset textf;
public string[] lines;
public int currentLine;
public int endline;
void Start () {
if (text == null) {
text = GetComponent<Text> ();
}
message = text.text;
StartCoroutine(TypeText ());
if (textf != null) {
lines = (textf.text.Split('\n'));
}
if (endline == 0) {
endline = lines.Length - 1;
}
}
IEnumerator TypeText () {
foreach (char letter in message.ToCharArray()) {
text.text += letter;
yield return 0;
yield return new WaitForSeconds (letterPause);
}
}
void Update () {
text.text = lines [currentLine];
if (Input.GetKeyDown (KeyCode.Space)) {
currentLine += 1;
}
if (currentLine > endline) {
textB.SetActive(false);
}
}
}
Reading through your MonoBehaviour, here's what I see it doing:
Set the text element if it is not already initialized
Copy the text content from text into the variable message
Start adding the content of message to the text component's value character by character
Read the TextAsset into a collection of strings, split per line
Set the total number of lines
On the Update() frame, set the text content of text to the string value of the current line
Check if the user has pressed the spacebar, and if so move the line index up by one
Check if the last line has been exceeded, and if so disable the game object
There are a few flow problems here. The biggest issue is the competition between your coroutine and the Update() tick. You probably want to move your call to start the coroutine into a separate function, and use a blocking variable to determine when you can or can't trigger the routine.
Consider the following:
// ignoring the using statements...
public class texttype : MonoBehaviour {
public float letterPause = 0.2f;
//public AudioClip[] typeSound1;
//public int next;
string message;
public GameObject textB;
public Text text;
public TextAsset textf;
public string[] lines;
public int currentLine;
public int endline;
public bool isPrinting;
void Start () {
if (text == null) {
text = GetComponent<Text> ();
}
// it's not clear where you store the full message, but I'm assuming inside textf
// if you have it stored in message or text.text, then you can initialize it here
if (textf != null) {
lines = (textf.text.Split('\n'));
}
if (endline == 0) {
endline = lines.Length - 1;
}
}
IEnumerator TypeText () {
// get current line of text
string current = lines[currentLine];
foreach (char letter in current.ToCharArray()) {
text.text += letter;
yield return 0; // not sure this line is necessary
yield return new WaitForSeconds (letterPause);
}
// unlock and wait for next keypress
isPrinting = false;
}
void Update () {
// if we're already printing a line, we can short-circuit
if (isPrinting) return;
if (Input.GetKeyDown (KeyCode.Space)) {
// move line index
currentLine += 1;
if (currentLine > endline) {
textB.SetActive(false);
return;
}
// start printing
PrintNext();
}
}
void PrintNext() {
// redundant check, but better safe than sorry
if (isPrinting) return;
// lock while printing
isPrinting = true;
StartCoroutine(TypeText ());
}
}
I'd like to count time to 10 seconds, then make countToTen = 0 and start the loop again until it reaches 10 again. For now, Unity is crushing and I don't know why.
Can you help me?
public float countToTen;
void Update(){
do{
if(countToTen<=10){
countToTen=(int)(Time.time%60f);
}
}
while(countToTen<=10);}
That loop will block the update as the time won't change until your update funciton finishes executing. Instead, I guess what you want to do is count to 10 seconds then do something, then wait another 10 seconds.
The Unity way of doing that is with a co-routine. Basically, you do something like:
IEnumerator WaitThenDoSomething(float WaitTime) {
while(running)
{
yield return new WaitForSeconds(WaitTime);
DoSomething();
}
}
Where DoSomething() is a function that will get executed every 10 seconds until running is set to false. You can start this running with the following code in your Start() method:
StartCoroutine(WaitThenDoSomething(10.0));
This is it, working perfectly. Thank you guys for helping me.
using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.UI;
using System.Collections.Generic;
public class TextReadFromFile : MonoBehaviour {
GameObject player;
public TextAsset wordFile; // Text file (assigned from Editor)
private List<string> lineList = new List<string>(); // List to hold all the lines read from the text file
Text text;
Timer timer;
PlayerHealth playerHealth;
void Awake ()
{
player = GameObject.FindGameObjectWithTag ("Player");
playerHealth = player.GetComponent <PlayerHealth>();
text = GetComponent <Text>();
}
void Start()
{
ReadWordList();
Debug.Log("Random line from list: " + GetRandomLine());
StartCoroutine (Count(2.0f));
}
public void ReadWordList()
{
// Check if file exists before reading
if (wordFile)
{
string line;
StringReader textStream = new StringReader(wordFile.text);
while((line = textStream.ReadLine()) != null)
{
// Read each line from text file and add into list
lineList.Add(line);
}
textStream.Close();
}
}
public string GetRandomLine()
{
// Returns random line from list
return lineList[Random.Range(0, lineList.Count)];
}
IEnumerator Count(float WaitTime){
while(playerHealth.currentHealth >= 0)
{
yield return new WaitForSeconds(WaitTime);
text.text = GetRandomLine();
}
}
}
that's not the right way to do it. I mean, you CAN do it but you're using a chisel when you have a sledghammer.
IEnumerator myFunction( float s )
{
Debug.log ("waiting...");
yield return new waitForSeconds(s);
Debug.log("tada!");
}
//call me on onclick or whenever, but not in update() ^^
void startFunction()
{
StartCoroutine(myFunction( 10));
}
void update()
{
}