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 ());
}
}
Related
I want GameTipUI with a blank space for a certain amount of time and certain message(==NormalGuideTip) for a certain amount of time using CoRoutine.
I was thinking of putting an 'if' in the CoRoutine to check the time,so make it easy to empty the message, and display the message again.
However, the message just keeps getting blank.
What could be the problem?
I wanted to repeat the randomly selected game tip message for 5 seconds and the blank message for 7 seconds.
And I wanted to use realtimeForGameTip to immediately display a special message(==UrgentGameTip ) according to a specific game action later and repeat the above process again.
bool isGameTipOn= true;
public GameObject gameTipTitle;
public GameObject gameTipMsg;
Color normalTipColor = new Color(255f, 255f, 255f, 220f);
Color UrgentTipColor = Color.green;
float timeBetGameTip = 7f; //
float timeOfGameMsg = 5f; //
public readonly WaitForSeconds m_waitForSecondsForGameTip = new WaitForSeconds(7f);
float realtimeForGameTip= 0f;
public string[] NormalGuideTips;
public string[] UrgentGuideTips;
private void Start()
{
#region GameTipSetUp
OnOffGameTip(true);
StartCoroutine("GameTipUI");
#endregion GameTipSetUp
}
public void OnOffGameTip(bool OnOff)
{
isGameTipOn = OnOff;
gameTipTitle.gameObject.SetActive(isGameTipOn);
gameTipMsg.gameObject.SetActive(isGameTipOn);
}
private void ResetGameTip()
{
if (!isGameTipOn) return;
realtimeForGameTip = 0f ;
int index = Random.Range(0, NormalGuideTips.Length);
string selectedMsg = NormalGuideTips[index];
Debug.Log(selectedMsg);
gameTipMsg.GetComponent<Text>().text = selectedMsg;
gameTipMsg.GetComponent<Text>().color = normalTipColor;
}
public void UrgentGameTip(string id)
{
if (!isGameTipOn) return;
=
int index = Random.Range(0, NormalGuideTips.Length);
string selectedMsg = UrgentGuideTips[index];
Debug.Log(selectedMsg);
gameTipMsg.GetComponent<Text>().text = selectedMsg;
gameTipMsg.GetComponent<Text>().color = UrgentTipColor;
realtimeForGameTip = timeOfGameMsg;
}
public void MakeTermBetGameTip()
{
gameTipMsg.GetComponent<Text>().text = " blank ";
}
IEnumerator GameTipUI()
{
while (isGameTipOn)
{
if(realtimeForGameTip < timeOfGameMsg)
{
Debug.Log(realtimeForGameTip);
realtimeForGameTip += Time.deltaTime;
continue;
}
MakeTermBetGameTip();
yield return new WaitForSeconds(m_waitForSecondsForGameTip);
ResetGameTip();
}
}
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 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;
}
I have a hint button on my game, but it fires up everytime you click on it. I need for it to fire only once every 3 clicks
i've tried adding a loadCount = 0 and an if statement
if (loadCount % 3 == 0) { }
but it dosen't seem to work
image for reference : https://ibb.co/L9LnNDw
Here is the script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class HintScript : MonoBehaviour
{
LineRenderer line;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
line = GetComponent<LineRenderer>();
line.positionCount = transform.childCount;
for (int i = 0; i<transform.childCount; i++)
{
line.SetPosition(i, transform.GetChild(i).position);
}
}
// This is called from onClick of the Button
public void Hint()
{
FindObjectOfType<AdMobManager>().Hint = true;
FindObjectOfType<AdMobManager>().showInterstitial();
}
}
You should use a simply counter.
I also changed and commented other "issues" in your code:
public class HintScript : MonoBehaviour
{
// by adding SerializeField you can already set those references
// directly in the Unity Editor. This is better than getting them at runtime.
[SerializeField] private LineRenderer line;
[SerializeField] private AdMobManager adMobManager;
// you can still change the required clicks in the inspector
// Note: afterwards changing it here will have no effect!
[SerializeField] private int requiredClicks = 3;
// counter for your clicks
private int counter;
// Use this for initialization
void Start ()
{
// you should do this only once
line = GetComponent<LineRenderer>();
// you should also this only do once
adMobManager = FindObjectOfType<AdMobManager>();
// If instead you would drag those references directly into the now
// serialized fields you wouldn't have to get them on runtime at all.
}
// Update is called once per frame
void Update ()
{
line.positionCount = transform.childCount;
var positions = new Vector3[transform.childCount];
for (var i = 0; i < transform.childCount; i++)
{
position[i] = transform.GetChild(i).position;
}
// it is more efficient to call this only once
// see https://docs.unity3d.com/ScriptReference/LineRenderer.SetPositions.html
line.SetPositions(positions);
}
// This is called from onClick of the Button
public void Hint()
{
counter++;
if(counter < requiredClicks) return;
// Or using your solution should actually also work
if(counter % requiredClicks != 0) return;
adMobManager.Hint = true;
adMobManager.showInterstitial();
// reset the counter
counter = 0;
}
}
You can have a counter to keep track of how many times the player has clicked. Then fire when that counter is at 3.
int amountOfClicks = 0;
void clickme(){
amountOfClicks++;
if(amountOfClicks == 3){
amountOfClicks = 0;
YourFunction();
}
}
void YourFunction(){
// do something...
}
1.Keep a static/global variable.
2.You can't stop event notifications so, please add condition(if condition) upon code block inside the event handler code.
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()
{
}