Unity - Problem with while loop on embedded coroutines - c#

Sorry if this is a newbie question, but I looked around and can't find any clue to fix my problem yet.
I am currently encountering a freeze when I try to run two coroutines together.
My end goal is:
to have one long coroutine representing the duration of a day.
one other smaller coroutine that will activate repeatedly while the day is not finished.
The two cotourines works fine independantly or when the second coroutine is directly embedded into the first one without the check on the endDay bool.
But when I tried to include the while loop, unity freeze at play (it reaches the 'DayStart.SetActive(true)' of the code below but does not go further).
Thinking the endDay bool being at the end of the first coroutine was probably the reason to the problem, I tried to build two completely independant ones (non-embedded coroutines with only the endDay bool as link between the two) but it didn't go better.
Does someone has an idea on how I could make it work?
For information, here is the code I initially used and that reverted the error. I spare you the detail of the second coroutine as it is quite long and probably not the problem here.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Transaction : MonoBehaviour
{
public GameObject DayStart;
public GameObject DayTracker;
private bool endDay;
private void Awake()
{
instance = this;
}
public void MainDayCycle()
{
StartCoroutine(MainDayCycleCO());
}
IEnumerator MainDayCycleCO()
{
endDay = false;
// Beginning of day picture
DayStart.SetActive(true);
yield return new WaitForSeconds(3);
DayStart.SetActive(false);
// [To be completed] Day Tracker bar
DayTracker.SetActive(true);
TradeCycle();
// Lancement de délai de journée et enregistrement de la fin
yield return new WaitForSeconds(20);
endDay = true;
}
public void TradeCycle()
{
while (endDay == false)
{
StartCoroutine(TradeCycleCO());
}
}

As I said this one
public void TradeCycle()
{
while (endDay == false)
{
StartCoroutine(TradeCycleCO());
}
}
Doesn't have any yield and this loop will freeze because nowhere inside the endDay is changed!
It sounds like what you rather want to do is (still don't have your TradeCycleCO code)
// Does this even need to be public?
public void TradeCycle()
{
StartCoroutine(TradeCycleCO());
}
IEnumerator TradeCycleCO()
{
while(!endDay)
{
// Your code here
// Make sure something yield returns in here
// If needed you could also interrupt at certain points via checking again
// Use that to do e.g. some cleaning up after the loop
if(endDay) break;
// or use that to immediately leave the coroutine overall
if(endDay) yield break;
}
// Code that may be executed after the day has ended e.g. for cleaning up stuff
}

Related

How I can make "Time" to be update constantly

I am beginner game dev in unity and i start new little project "alone", and in my game i have a little problem.
My problem is that the time, i.e. the clock in the game is not synchronized and does not synchronize and I have some suspicions.
I know maybe I'm doing another wrong step, maybe I put a wrong equation in the whole function and nothing works anymore, but to tell you more briefly, I want to make a game in which the game interface is a desktop and the clock on I coded it below, it doesn't work to be updated with time, I waited and tried many solutions that I applied and they didn't work, so I'll leave the code below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
public class DateTime : MonoBehaviour
{
public Text timeLockScreenText;
public Text dateLockScreenText;
public Text timeTaskBarText;
string time = System.DateTime.UtcNow.ToLocalTime().ToString("HH:mm");
string date = System.DateTime.UtcNow.ToLocalTime().ToString("MM-dd-yyyy");
// Update is called once per frame
void Update()
{
InvokeRepeating("DateTimeV",0f,1f);
}
public void DateTimeV()
{
timeLockScreenText.text = time;
dateLockScreenText.text = date;
timeTaskBarText.text = timeLockScreenText.text;
}
}
Now I have some theories:
one is that in the game, just like in real life, it's like with any personal computer user when we open it there is a lockscreen with a clock and we press enter to enter all the time to access the internet and other files on the desktop, so I have the theory that the lockscreen being a GameObject which is like visibility opposite to the desktop, i.e. if the lockscreen is active the desktop is not visible and vice versa, the clock does not update because let's say if we are on the desktop the lockscreen is disabled and that's why the script doesn't really work and you have to to make two separate ones, one for the lockscreen and one for the desktop, actually for the clock on the desktop
or I don't know how to code well
I tried to put in Update() function, in InvokeRepeting, StartCourutine with IEnumerator function, and i dont have the solutions
I don't know anything about Unity, but it seems that you're never updating the values of time and date after they're initially assigned. Perhaps something like this would do the trick, where you just assign the correct values in the method:
public void DateTimeV()
{
DateTime utcNow = System.DateTime.UtcNow.ToLocalTime();
timeLockScreenText.text = utcNow.ToString("HH:mm");
dateLockScreenText.text = utcNow.ToString("MM-dd-yyyy");
timeTaskBarText.text = timeLockScreenText.text;
}
Use fixed update, and the code from Rufus L:
void FixedUpdate() => DateTimeV();
public void DateTimeV()
{
DateTime utcNow = System.DateTime.UtcNow.ToLocalTime();
timeLockScreenText.text = utcNow.ToString("HH:mm");
dateLockScreenText.text = utcNow.ToString("MM-dd-yyyy");
timeTaskBarText.text = timeLockScreenText.text;
}
What Rufus L said + you would use InvokeRepeating only once e.g. in
private void Start()
{
InvokeRepeating(nameof(DateTimeV), 0f, 1f);
}
or use a simple counter
private float timer;
private void Update ()
{
timer += Time.deltaTime;
if(timer > 1f)
{
timer -= 1f;
DateTimeV();
}
}
or a coroutine
private IEnumerator Start()
{
while(true)
{
DateTimeV();
yield return new WaitForSeconds(1f);
}
}

Trying to create an interruptible dialogue system, with subtitles, using coroutines

I am currently working on a game where to characters are having a conversation. There are a few dialogue options, but the conversation trees are quite simple. A key aspect is that the player is able to interrupt the other party mid-sentence and change the course of the conversation.
To preface, I have learned all I know from youtube and I've been getting by on increasingly complex if-statements, so I'm trying something new here.
So I did my first attempt using what I know: Invoke and if-statements.
public class SubtitleSystem : MonoBehaviour
{
public float subtitleTimeBuffer;
public string[] dialogue;
public string[] specialDialogue;
public float[] subTiming;
public float[] specialSubTiming;
public AudioClip[] diaClips;
public Text sub;
AudioSource player;
int subNum;
int specialSubNum;
// Start is called before the first frame update
void Start()
{
player = gameObject.GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
if (sub.enabled == false) {
SubSystem();
}
}
}
void SubSystem()
{
if(subNum < dialogue.Length)
{
if (subNum != 1)
{
player.clip = diaClips[subNum];
player.Play();
sub.text = dialogue[subNum];
sub.enabled = true;
Invoke("DisableText", subTiming[subNum] + subtitleTimeBuffer);
subNum++;
}
else if (subNum == 1)
{
player.clip = diaClips[subNum];
player.Play();
sub.text = specialDialogue[specialSubNum];
sub.enabled = true;
Invoke("DisableText", subTiming[subNum] + subtitleTimeBuffer);
Invoke("SpecialSub", specialSubTiming[specialSubNum]);
}
} else
{
Debug.Log("No more dialogue");
}
}
void DisableText()
{
sub.enabled = false;
}
void SpecialSub()
{
sub.text = dialogue[subNum];
subNum++;
}
This was functional, though clunky and very work intensive for the person that has to find the individual timing for each line of dialogue and manually break up a subtitle line if it was too long.
Another big problem was that it was impossible to fully interrupt dialogue, because Invoke was called and would run regardless of what I pressed. Maybe I could add some kind of bool condition to prevent that, but the project I'm working on will have a few hundred lines of dialogue, so I need to come up with something where I don't have to type in every line in the inspector and find the manual timing.
This is where it becomes murky for me as I am unfamiliar with a lot of these methods.
An obvious solution to the Invoke problem would be to use Coroutines. These can be interrupted and I could even have a check for input inside a loop to let a player interrupt.
IEnumerator SubtitleRoutine()
{
while (DialogueIsPlaying)
{
//Display Subtitles
if(Input.GetButton("Interrupt button"){
//interrupt
}
yield return null;
}
//Wait for next piece of dialogue
}
Something like that is what I'm imagining.
The next problem is tying dialogue to some kind of system so I can pull the correct piece of audio and display the correct subtitles. In my first attempt this was simple enough because the four pieces of test audio I created were short sentences, but if a character speaks for longer it would be tedious to break up the dialogue manually.
Alternatively I thought about breaking the audio files up into "subtitle-length" so every audio file had a subtitle string directly associated with it, but this seems inefficient and troublesome if dialogue needs to change down the line.
So I thought if I could somehow create a class, that contained all the information needed, then my coroutine could pull in the correct dialogue using it's id (perhaps an integer) and plug in all the information from the object into my coroutine.
So something like this:
public class dialogue
{
//Int ID number
//Audiofile
//Who is speaking
//Length
//Subtitle String 1
//Subtitle String 2
//Subtitle String 3
// etc
}
IEnumerator SubtitleRoutine(dialogue)
{
while (DialogueIsPlaying)
{
//Display Subtitles - divide number of subtitle string by Length and display each for result.
if (Input.GetButton("Interrupt button"){
//interrupt audio and subtitles - stop the coroutine
//set correct dialogue Int ID for next correct piece of dialogue and start coroutine with new dialogue playing.
}
yield return null;
}
//Wait for next piece of dialogue
}
Though this is all outside of what I know, from what I've been able to read and understand, this seems like it might work.
So my question is:
Is this approach going to work?
If so, where should I look for ressources and help to teach me how?
If not, what methods should I look at instead?
Thank you so much for your help!
So what I understand is that you want to make an interruptable dialog system. You chose the coroutine approach which is a great choice, but you're not using it to its full potential. When you use StartCoroutine(IEnumerator _enumerator); you'll get a coroutine class back. If you store it, you can later use StopCoroutine(Coroutine _routine); to stop it. So you won't have to use a while loop or if statements to check interrupting.
Hope this will help you. If it doesn't I'll send some code.
After receiving some help from a coding mentor I found a system that works using a custom class as a datatype and coroutines to display subtitles with correct timing.
The names of the variables are in Danish but the code works in Unity without issues.
using System.Collections.Generic;
using UnityEngine;
public class Undertekster
{
public int id;
public AudioClip audioFile;
public float length;
public string[] subtitles;
public bool isMonk;
public SubSystem subsys;
public Undertekster(int id, int dialogClipNummer, float length, string[] subtitles, bool isMonk, SubSystem subsys)
{
this.subsys = subsys;
this.id = id;
this.audioFile = subsys.dialogClip[dialogClipNummer];
this.length = length;
this.subtitles = subtitles;
this.isMonk = isMonk;
}
}
Notice that use another script when constructing the class to make use of Monobehavior. That way I can assign the correct audiofile to each line of dialogue using an array created in the inspector. The proper way would probably be to look for the file somehow, but that's beyond me.
Next is the subtitle system. For demonstration you hit space in-game to start dialogue and hit F to interrupt. The "subtitles" are Debug.log in the console, but you can easily tie them to a Text object in the UI.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SubSystem : MonoBehaviour
{
[Header("DialogClip")]
public AudioClip[] dialogClip;
[Header("Indstillinger")]
public float subtitleBuffer;
public AudioSource munk;
public AudioSource salamander;
List<Undertekster> alleUndertekster = new List<Undertekster>();
int currentDialogueNumber;
Undertekster currentDia;
float timeDivided;
void Start()
{
currentDialogueNumber = 1;
LoadDialog();
}
public void LoadDialog()
{
alleUndertekster.Add(new Undertekster(1, 0, dialogClip[0].length, new string[] { "Kan du huske mig?" }, true, this));
alleUndertekster.Add(new Undertekster(2, 1, dialogClip[1].length, new string[] { "Øh...", "Lidt..." }, false, this));
alleUndertekster.Add(new Undertekster(3, 2, dialogClip[2].length, new string[] { "Jeg er din nabo din idiot!" }, true, this));
alleUndertekster.Add(new Undertekster(4, 3, dialogClip[3].length, new string[] { "Shit!" }, false, this));
}
IEnumerator PlayNextDialogue()
{
int count = 0;
while (munk.isPlaying || salamander.isPlaying)
{
ShowSubtitle(count);
yield return new WaitForSeconds(timeDivided + subtitleBuffer);
count++;
yield return null;
}
//yield return new WaitForSeconds(subtitleBuffer);
currentDialogueNumber++;
Debug.Log("Coroutine is stopped and the current dialogue num is " + currentDialogueNumber);
StopCoroutine(PlayNextDialogue());
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
InterruptDialogue();
}
if (Input.GetKeyDown(KeyCode.Space))
{
StartDialogue();
}
}
public void StartDialogue()
{
currentDia = alleUndertekster.Find(x => x.id == currentDialogueNumber);
timeDivided = currentDia.length / currentDia.subtitles.Length;
if (currentDia.isMonk)
{
munk.clip = currentDia.audioFile;
munk.Play();
} else if (!currentDia.isMonk)
{
salamander.clip = currentDia.audioFile;
salamander.Play();
}
StartCoroutine(PlayNextDialogue());
}
public void InterruptDialogue() {
StopCoroutine(PlayNextDialogue());
munk.Stop();
salamander.Stop();
currentDialogueNumber++;
StartDialogue();
}
public void ShowSubtitle(int i)
{
if(i <= currentDia.subtitles.Length - 1)
{
Debug.Log(currentDia.subtitles[i]);
} else
{
return;
}
}
}
I chose to put all the dialogue classes into a list so it was easily searchable for the id-numbers. It might have been better to use a Dictionary, but this worked for me and that was good it enough for this project.
With this system, my manuscript writer can put in every line of dialogue with its associated audioclip in the LoadDialog() function and determine who is speaking and in how many pieces the subtitles should be broken into to fit on the screen. They are then displayed one after the other while the audio is playing.
This probably isn't the best solution in the world, but I hope it works for whoever might need it - plus I learned a ton.

Question about AnimationEvent.Time in Unity

I am quite new in Unity and I have a simple question regarding Animation events.
In my code snipet I have the public variables - I noticed that I can't reference an AnimationEvent - and some simple stuff to do with them.
public GameObject completeLevelUI; // a panel
public Text endUI; // the text on the panel that show your success or fail
public AnimationEvent nextSceneEvent; // AnimatonEvent in the animation of the panel above
public void GameOver() { nextSceneEvent.time = 2; completeLevelUI.SetActive(true); endUI.text = "LEVEL\nFAILED"; }
public void GameWon() { nextSceneEvent.time = 6; completeLevelUI.SetActive(true); endUI.text = "LEVEL\nCOMPLETED"; }
public void LoadEnd() { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1); }
The AnimationEvent's target method is LoadEnd().
The default firing time is 1.5 seconds and the code won't change it.
The reason why I need different fire times can't be seen in the shared code - it is not important now. I figured out some other ways to solve it, I am just curious why it isn't working.
I have tried to change the activating and the time setting code piece but it is the same.
Somehow I have to reference the AnimationEvent? Is it a problem that this script is called multiple times, even from places where there is no AnimationEvent - I didn't receive any exceptions during my examination.
Any ideas? Thanks for helping!
I guess you have an animation playing and at the end you want to trigger the new scene.
You can use different approaches.
First, you add the animation event directly to the animation clip and you set the method in the inspector. The method has to be on a component attached to the same game object as the animation.
Second, you launch the animation and wait for the end of it to call the method:
void EndProcess()
{
StartCoroutine(EndProcessSequence());
}
IEnumerator EndProcessSequence()
{
Animation anim = GetComponent<Animation>();
anim.Play("animName");
yield return new WaitWhile(()=> anim.isPlaying);
LoadNewScene();
}
Here' s a reusable version
IEnumerator EndProcessSequence(string animName, Action onComplete)
{
Animation anim = GetComponent<Animation>();
anim.Play(animName);
yield return new WaitWhile(()=> anim.isPlaying);
onComplete?.Invoke();
}

Why does why C# code keep crashing Unity? (I'm a beginner to Unity and C#)

Whenever I run my game it freezes, but it doesn't without this C# script.
I've tried changing around my code, and it works outside of Unity, in .NET (with some tweaks to certain functions) but when it's in Unity it crashes.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Throw : MonoBehaviour
{
public Rigidbody rb;
string final = "final:";
public float force = 1;
public float accuracy = 0;
void incto(float amount)
{
while (force < amount)
{
Debug.Log(force);
force++;
}
}
void decto(float amount)
{
while (force > amount)
{
Debug.Log(force);
force--;
}
}
void fstart()
{
while (true)
{
force = 1;
incto(200);
decto(1);
if(Input.GetKey(KeyCode.E))
{
Debug.Log(final + force);
break;
}
}
}
// Start is called before the first frame update
void Start()
{
fstart();
}
// Update is called once per frame
void FixedUpdate()
{
Debug.Log(force);
}
}
It should decrease and increase the force value, then stop when you press E, but Unity just crashes.
Unity takes care of the while(true) for you. Unity's while(true) calls your FixedUpdate, you just need to fill it in.
Unity only captures keystrokes once per frame, so Input.GetKey(KeyCode.E) will always return the same value. Unity crashes because of your while(true) is an infinite loop.
More info: https://docs.unity3d.com/Manual/ExecutionOrder.html
i belive that unity starts catching key strokes after the first frame, not before, try moving fstart() behind a first run bool in the FixedUpdate function
oh and this will hang the entire program every time it executes a frame.....
The code crashes because you have an infinite loop here:
while (true)
{
}
It will never exit the loop so nothing more happens. Just put that code into Update() method, which gets called by the engine on every frame, it will do the trick

Second Coroutine isn't working Unity

I am making 2D game and want to cause delay of about 3 sec after player's all lives ran out. I tried to implement Coroutine method before the scene start all over again but it doesn't work.
I have already implemented Coroutine method for each time my player falls of a cliff and respawn back to its position. And it works like a charm.
public void Respawner()
{
StartCoroutine("RespawnCoroutine");
}
// Coroutine Delay of 2 sec for each time player Respawn
public IEnumerator RespawnCoroutine()
{
classobj.gameObject.SetActive(false);
yield return new WaitForSeconds(respawnDelaySec);
classobj.transform.position = classobj.respawnPoint;
classobj.gameObject.SetActive(true);
}
public void ReduceLives()
{
if (lives <= 3 && lives >= 2)
{
lives--;
live_text.text = "Remaining Live " + lives;
}
else
{
StartCoroutine("RestartScene1");
}
}
public IEnumerable RestartScene1()
{
yield return new WaitForSeconds(RestartSceneDelaySec);
SceneManager.LoadScene("demo2");
}
here is no error on console window but SceneManager.LoadScene("demo2"); is never called and the player is respawning each time after i die and after 1 life remaining
The issue with your second coroutine is..
You have mistakenly used "IEnumerable" instead "IEnumerator", make it change to "IEnumerator" and it will work..
You should not call SceneManager.LoadScene("demo2"); after StartCoroutine("RestartScene1");.
StartCoroutine("RestartScene1"); this code you can say is an asynchronous code. It is called, and the execution of program keeps going forward (the execution does not await here). You should call the code you want to delay inside that Coroutine after yielding.
Small exampel:
public void SomeFunction()
{
StartCoroutine("RestartScene1");
// The code here will **not** be delayed
}
public IEnumerable RestartScene1()
{
yield return new WaitForSeconds(RestartSceneDelaySec);
// The code here will be delayed
}

Categories

Resources