So I have a Unity coroutine method, in which I have some objects. These objects represent values that are being gathered from a server somewhere, and they send out an Updated event when they are ready.
I was wondering what the best way is to wait for all the values to be updated, inside a coroutine in Unity.
public IEnumerator DoStuff()
{
foreach(var val in _updateableValues)
{
if (val.Value != null) continue;
else **wait for val.Updated to be fired then continue**
}
//... here I do stuff with all the values
// I use the WWW class here, so that's why it's a coroutine
}
What would be the best way of doing something like this?
Thanks!
There is no builtin direct method to wait for the event itself, but you can use a synchronous nested coroutine to wait for a flag set by the event:
//Flag
bool eventHappened;
//Event subscriber that sets the flag
void OnEvent(){
eventHappened=true;
}
//Coroutine that waits until the flag is set
IEnumerator WaitForEvent() {
yield return new WaitUntil(eventHappened);
eventHappened=false;
}
//Main coroutine, that stops and waits somewhere within it's execution
IEnumerator MainCoroutine(){
//Other stuff...
yield return StartCoroutine(WaitForEvent());
//Oher stuff...
}
With that in mind, creating a generic coroutine that waits for an UnityEvent is easy:
private IEnumerator WaitUntilEvent(UnityEvent unityEvent) {
var trigger = false;
Action action = () => trigger = true;
unityEvent.AddListener(action.Invoke);
yield return new WaitUntil(()=>trigger);
unityEvent.RemoveListener(action.Invoke);
}
I thing that a better way is to check the sever every frame and not to wait for the an amount of time without any thinking.
public IEnumerator DoStuff()
{
/* wait until we have the value we want */
while( value != desiredValue)
yield return null
//after this loop, start the real processing
}
This is my little fix.
I use the WaitUntil directly where I need it. I found though, that WaitUntil doesn't want a boolean but expects a boolean predicate function - so I provide this in my example below.
The example is real running code from a game. It runs in the very first scene, where I start the music before I do - the slightly lengthly - loading of the other stuff.
//
public class AudioSceneInitializer : MonoBehaviour
{
[SerializeField] private GlobalEventsSO globalEvents;
//globalEvents is a scriptable object that - in my case - holds all events in the game
[SerializeField] private AudioManager audioManager;
//The audio manager which in my case will raise the audio ready event
[SerializeField] public GameObject audioGO;// contains AudioSources used by AudioManager
private bool audioReady = false;
// Start is called before the first frame update
IEnumerator Start()
{
if (audioManager != null)
//better check, if we don't produce the event, we wait forever
{
//subscribe to event
globalEvents.audioManagerReadyEvent += OnAudioReady;
//do something to raise the event - else we wait forever
audioManager.onInitialization(this);//"this" needed to hand over the audioGO to the AudioManager
// waits until the flag is set;
yield return new WaitUntil(IsAudioReady);
}
//now that we have music playing, we load the actual game
SceneManager.LoadSceneAsync(1, LoadSceneMode.Additive);
}
//Event subscriber that sets the flag
void OnAudioReady()
{
//unsubscribing, if - as in my case - it happens only once in the game
globalEvents.audioManagerReadyEvent -= OnAudioReady;
audioReady = true;
}
//predicate function that WaitUntil expects
private bool IsAudioReady()
{
return audioReady;
}
public void OnDisable()
{
audioManager.onCleanup();
}
}
A spin-lock is a solution, however not a very CPU-gentle one. In a spin-lock, you would just wait for the variable to have a certain value, otherwise sleep for a few milliseconds.
public IEnumerator DoStuff()
{
/* wait until we have the value we want */
while( value != desiredValue)
yield return new WaitForSeconds(0.001f);
//after this loop, start the real processing
}
Maybe you might want to think about restructuring your code, sothat no spin-lock is requiered, but a more interupt/event-based based approach can be implemented. That means, if you update a value and something has to take place after it happened, kick it off directly after changing that value. In C#, there's even an interface INotifyPropertyChanged for that design pattern (see MSDN), but you can easily design that yourself, too, e.g. by firing an event when that certain value has changed. We'd need more information on what exactly you want to react here, if you want a better solution than a spinlock, but this should give you some ideas.
Related
Trying to repeat the function function OnAttack() continuously while a button is being held down.
Basically I'm looking for an equivalent to Update() { GetKeyDown() {//code }} But with the input system.
Edit: using a joystick, cant tell what button is being pressed.
Okay I solved it by using "press" in the interactions and giving that The trigger behavior "Press and release", then did
bool held = false;
Update()
{
if(held)
{
//animation
}
else if(!held)
{
//idle animation
}
}
OnAttack() {
held = !held;
}
This way if I press the button held goes to true so it repeats the animation every frame, letting go makes "held" untrue and does the idle animation
Essentially, the function you assign to the button will be triggered twice per button press, once when it is pressed (performed), and once when it is released (canceled). You can pass in this context at the beginning of your function, just make sure you are using the library seen at the top of this script.
Now you can toggle a bool on and off stating whether or not the button is pressed, then perform actions during update dependent on the state of the bool
using static UnityEngine.InputSystem.InputAction;
bool held = false;
Update()
{
if(held)
{
//Do hold action like shooting or whatever
}
else if(!held)
{
//do alternatice action. Not Else if required if no alternative action
}
}
//switch the status of held based on whether the button is being pressed or released. OnAttack is called every time the button is pressed and every time it is released, the if statements are what determine which of those two is currently happening.
OnAttack(CallbackContext ctx) {
if (ctx.performed)
held= true;
if (ctx.canceled)
held= false;
}
This is paraphrasing a solution I created for a click to move arpg mechanic.
using System.Threading.Tasks;
using UnityEngine;
[SerializeField] private InputAction pointerClickAction;
private bool pointerHeld;
void Start()
{
pointerClickAction.canceled += ClickMouseMove;
pointerClickAction.started += PointerHoldBegin;
pointerClickAction.performed += ClickMouseMove;
pointerClickAction.canceled += PointerHoldEnd;
}
private void OnEnable()
{
pointerClickAction.Enable();
pointerPositionAction.Enable();
}
private void OnDisable()
{
pointerClickAction.Disable();
pointerPositionAction.Disable();
}
public async void ClickMouseMove(InputAction.CallbackContext context)
{
while (pointerHeld)
{
DoSomething();
await Task.Delay(500);
}
}
public void PointerHoldBegin(InputAction.CallbackContext context)
{
pointerHeld = true;
}
public void PointerHoldEnd(InputAction.CallbackContext context)
{
pointerHeld = false;
}
public void DoSomething()
{
//Your Code
}
In Task.Delay() you can insert your own polling rate in milliseconds, using Task.Yield() seems to be faster than Update so I don't recommend that, you should poll at the minimum the same delay as your physics/fixed update, having a higher delay gives a performance boost if you don't need a high amount of repetitions per loop. I set mine to 500 since I don't need my character to plot its navigation that often. In regards to TC, you would set the delay to something sensible e.g the attack's animation length, or whatever the delay rate would be for how many attacks can be performed per second.
If you want your project to scale you might want to avoid as much as possible assertions(i.e if functions) in your Update/FixedUpdate/LateUpdate functions as they are executed constantly. I recommand you to read this article about coroutines https://gamedevbeginner.com/coroutines-in-unity-when-and-how-to-use-them/
You can build coroutines which act as local update() functions which are executed only when needed. This will lead you to a better organization of your code and might boost performance in some cases.
For exemple in your case you could use something like this.
bool held = false;
Update()
{
/* Whatever you want but the least assertion possible */
}
IEnumerator RenderHeldAnimation()
{
while (held)
{
// held animation
yield return new WaitForFixedUpdate(); /* Will block until next fixed frame right after FixedUpdate() function */
// yield return null /* Will block until next next frame right after Update() function */
}
}
IEnumerator RenderIdleAnimation()
{
while (!held)
{
// idle animation
yield return new WaitForFixedUpdate(); /* Will block until next fixed frame right after FixedUpdate() function */
// yield return null /* Will block until next next frame right after Update() function */
}
}
OnAttack() {
held = !held;
if (held) {
StartCoroutine(RenderHeldAnimation());
} else {
StartCoroutine(RenderIdleAnimation());
}
}
As mentioned in another answer, the context.canceled is not called when using the Press And Release interaction. As a follow up for documentation purposes as this is a top Google result, to correctly use a bool held without doing a blind toggle (held = !held) which may end up with drift, you can access context.control.IsPressed() like the following:
void OnAttack(CallbackContext context)
{
held = context.control.IsPressed();
}
I encountered the same issue and this was the method that seemed to work for me
private float _moveSpeed = 3f;
private float _moveDirection;
private void Update()
{
transform.Translate(_moveSpeed * _moveDirection * Time.deltaTime * transform.forward);
}
public void Move(InputAction.CallbackContext ctx)
{
_moveDirection = ctx.ReadValue<float>();
}
For some odd reason,the hold interaction works properly in reading the input, but I still need the update function to implement the actual logic.
Can't really complain though, it works. Although I'd love to know why it happens this way.
Hopefully this can be of help to someone.
You can use a timer for that purpose, in combination with events KeyUp and KeyDown.
Please look at the following link. It is pretty much similar to your problem.
Link
How would you find the time since a certain variable was changed? Take for example a boolean variable, how would you find the time since it was last changed? I want to use the boolean variable as a trigger (activating the trigger when it's true), but only after an exact, constant time (such as 0.5s) has passed since it was changed to true (it can only be changed from false to true).
Here is the code I have:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class hitRegistration : MonoBehaviour
{
AudioSource hitSound;
private bool hitState = false;
// Use this for initialization
void Start()
{
hitSound = gameObject.GetComponent<AudioSource>();
}
void OnMouseOver()
{
Debug.Log("Mouse is over game object.");
if (Input.GetKey(KeyCode.X) && hitState == false)
{
hitSound.Play();
hitState = true;
}
}
private void OnMouseExit()
{
Debug.Log("Mouse is no longer over game object.");
if (hitState == true)
{
// sound clip gets cut if the cursor leaves before its finished.
Destroy(gameObject);
}
}
// Update is called once per frame
void Update()
{
}
}
"OnMouseOver()" Is simply a function that is called when the mouse is placed over the game object in question. I want to delay destroying the game object until a certain time has passed.
First off, as noted in a comment, you are probably trying to solve this problem the wrong way and you are probably asking an "XY" question -- a question where you are asking a question about a proposed bad solution instead of asking a question about the actual problem you face.
To answer the question you actually asked, for better or worse: there is no way to associate behaviours with reading or writing a variable in C#, but you can associate behaviours with a property:
private bool hitState; // The "backing store".
private bool HitState
{
get
{
return hitState;
}
set
{
hitState = value;
}
}
You would then use HitState rather than hitState throughout the rest of your class.
Now you can add whatever logic you want that happens when the property is read or written:
private DateTime hitStateTime = default(DateTime);
private bool hitState; // The "backing store".
private bool HitState
{
get
{
return hitState;
}
set
{
hitState = value;
hitStateSet = DateTime.Now;
}
}
Now you know when it was set. And so on.
Unless you really need to keep track of how much time has passed on each single frame, one way to do what you are asking for is to use Unity Coroutines.
A coroutine is a method that runs in parallel with the main thread. To solve your question, you can first create a coroutine in the same script, that waits and then does the thing you want to have delayed. A couroutine in Unity is a method that takes up to one parameter and has an IEnumerator return type. You use yield return WaitForSeconds(t); inside the coroutine to have it delay for t seconds.
Then, once it's time to die, check if the mouse is currently hovering over the object with isHovered (set in your OnMouseOver/OnMouseExit methods). If it is, keep a note that it's time to die. If it isn't, then it can die immediately.
IEnumerator WaitToDie(float delaySeconds)
{
yield return new WaitForSeconds(delaySeconds);
// If the mouse is on the object, let OnMouseExit know we're ready to die
if (isHovered)
{
readyToDie = true;
}
// Otherwise, just die
else
{
Destroy(gameObject)
}
}
And then inside your OnMouseOver code, run the coroutine after starting the sound
void OnMouseOver()
{
isHovered = true;
Debug.Log("Mouse is over game object.");
if (Input.GetKey(KeyCode.X) && !hitState)
{
hitState = true;
hitSound.Play();
// we want to delay for half a second before processing the hit.
float delaySeconds = 0.5;
IEnumerator coroutine = WaitToDie(delaySeconds);
StartCoroutine(coroutine);
}
}
And in your OnMouseExit, let everything know that you're done hovering and check if it's past time to die or not.
private void OnMouseExit()
{
isHovered = false;
Debug.Log("Mouse is no longer over game object.");
if (readyToDie) {
Destroy(gameObject);
}
}
Altogether this code will have the object die when both the mouse is off the object AND the time has elapsed.
As a sidenote, I think you might want to revisit how you are checking for a hit, unless your really want to trigger from the player holding X and then moving the mouse over the object. If you intend to trigger any time X is pressed down while the mouse is on top, you might want to put the check in Update and check Input.GetKey(KeyCode.X) && !hitState && isHovered
keep a seperate variable(DateTime) and call it lastUpdate. then be sure to set it to DateTime.Now, each time the bool you're tracking is updated. then when you need to see how long its been you can just subtract:
DateTime lengthOfTime = DateTime.Now-lastUpdate;
from lengthOfTime you can now access how many days, hours, minutes, and/or seconds have passed.
im on my phone so take it easy on my pseudo-code.
good luck
I'm using a Coroutine to set up a repeating delay as follows.
In my Awake I have
StartCoroutine(RandomMove());
And then further down
IEnumerator RandomMove()
{
while (true)
{
// print(Time.time);
yield return new WaitForSeconds(foo);
// print(Time.time);
}
}
where 'foo' is a random float value that I change with every iteration.
Lets say foo is 10 seconds and part way thru the delay I need to reset the delay so it starts 'counting down' from 10 again.
How would I accomplish this? Should I use a Timer instead?
I don't like either of the two existing answers. Here's what I'd do:
Kill and Restart the coroutine:
We'll start with this part of the killer_mech's answer:
Coroutine myCoroutine;
void Awake() {
myCoroutine = StartCoroutine(RandomMove());
}
But we're going to handle the rest differently. killer_mech never did anything with the reference, other than to keep ovewriting it.
Here's what we're doing instead:
public void resetRandomMove() {
StopCoroutine(myCoroutine);
myCoroutine = StartCoroutine(RandomMove());
}
Call this any time you need to reset it.
I would suggest you first store Coroutine in a variable.
Coroutine myCoroutine;
void Awake()
{
myCoroutine = StartCoroutine(RandomMove());
}
and change the coroutine function as
IEnumerator RandomMove()
{
// print(Time.time);
yield return new WaitForSeconds(foo);
// print(Time.time);
// Call your new coroutine here
myCoroutine = StartCoroutine(RandomMove());
}
this way you will have a coroutine variable for every iteration. If you need to stop the coroutine just say :
StopCoroutine(myCoroutine);
in your function at required time.This will allow you to stop a coroutine in middle before the end of countdown. Also at the end of coroutine it will start new coroutine with updated reference After finishing your task just call back again with
myCoroutine = StartCoroutine(RandomMove());
Hope this resolves your problem. Yes you can do it with timer also with a boolean flag the same thing but I think using coroutine is much simpler.
.
Hmmm it could something like this also . Just for my own .
void Start() {
StartCoroutine(RepeatingFunction());
}
IEnumerator RepeatingFunction () {
yield return new WaitForSeconds(repeatTime);
StartCoroutine( RepeatingFunction() );
}
As i understand the question. InvokeRepeating() is also a choice.
Maybe it is because you are each frame waiting for new assigned seconds?
Why don't you make the random before yielding the wait, and store the CustomYieldInstruction instead of yielding a new instance, since it disposes the one that was before, that creates memory problems. You won't notice that if you yield return a WaitForSeconds with a constant value, but maybe with a random one creates ambiguity and resets the timer (see this Unity's optimization page, on the Coroutine's section). A quick example:
public float foo;
public float min;
public float max;
void Awake()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
while(true)
{
foo = Random.Range(min, max);
WaitForSeconds wait = new WaitForSeconds(foo);
yield return wait;
}
}
Also, #ryeMoss's solution seems a good one, to stop and restart the coroutine if 'foo' changes.
Hope it helps.
How can I make the name of the Coroutine dynamic?
I use this to make targets die automatically after a few seconds:
void InitiateKill(int i)
{
//i is the number of the target
StartCoroutine(TargetDie(i, timeAlive/1000));
//some other stuff
}
When the target is killed before this timer ends, I obviously get an error because it can't kill the target again.
That's why I want to stop the coroutine of that specific target, but I don't know how.
I tried:
Coroutine b[i] = StartCoroutine(TargetDie(i, timeAlive/1000));
But that gives a syntax error. b[i] can't be used for Coroutines.
How to do this the proper way?
Update:
This is (the relevant part of) my TargetDie function:
IEnumerator TargetDie(int i, float delayTime)
{
yield return new WaitForSeconds(delayTime);
Destroy(targets[i]);
}
When the player kills the target, I do:
void Damage(int i)
{
// at this time, the first Coroutine, started in InitiateKill, should stop, because otherwise it tries to destroy the target twice
StartCoroutine(TargetDie(i, 0));
}
So most simple way, move the coroutine on the object itself.
public class DieScript: MonoBehaviour
{
private Manager manager;
public void StartDeathProcess(Manager manager)
{
this.manager = manager;
StartCoroutine(DieAsync(manager));
}
private IEnumerator DieAsync(Manager manager)
{
yield return new WaitForSeconds(timer);
Destroy(this.gameObject);
}
public void Dmaage() // This is register as listener for death of the object
{
this.manager.PropagateDeath(this);
Destroy(this.gameObject);
}
}
public class Manager:MonoBehaviour
{
private List<DieScript> die;
void InitiateKill(int i)
{
die[i].StartDeathProcess(this);
}
}
Trying to keep the control on the controller is about to bring more pain than solution.
You could have a list of IEnumerator to keep track of the running coroutines. But you still need a message from the object to inform the controller that it is dead. So you are missing this part.
You need a script on the target so the controller knows about this type.
Controller runs the coroutine with a reference to that script asking each frame are you dying. When the target is meant to die, it sets a boolean to inform. Using Destroy keeps the object until end of frame so it would work out.
But this is doom to fail later on. It is kinda against programming concept to have a controller doing everything. You should see it more as a bypass for information.
You can simply check for null before destroying it so that you won't get any error:
if (targets != null)
Destroy(targets[i]);
But below is the recommended way if you want to stop the old coroutine.
You can use Dictionary to handle it. Use the int i as the key and Coroutine as the value.
When you call InitiateKill, add the int i to the dictionary and when you start the coroutine, add the Coroutine as the value in the dictionary too.
When Damage is called, check if that int value exist in the Dictionary. If it does, use it to retrieve the Coroutine value and stop the old coroutine. If it doesn't exit, start a new coroutine and then add it to that dictionary.
The dictionary should look like this:
Dictionary<int, Coroutine> dyingTarget = new Dictionary<int, Coroutine>();
Your new InitiateKill function which adds to the Dictionary:
void InitiateKill(int i)
{
//i is the number of the target
Coroutine crt = StartCoroutine(TargetDie(i, timeAlive / 1000));
//Add to Dictionary
dyingTarget.Add(i, crt);
}
Your new Damage function now checks if the item is already in the dictionary then retries it, stops the coroutine and removes it from the Dictionary.
void Damage(int i)
{
// at this time, the first Coroutine, started in InitiateKill, should stop, because otherwise it tries to destroy the target twice
StopIfAlreadyRunning(i);
Coroutine crt = StartCoroutine(TargetDie(i, 0));
//Add to Dictionary
dyingTarget.Add(i, crt);
}
void StopIfAlreadyRunning(int i)
{
Coroutine crtOut;
//Retrieve and stop old coroutine if it exist then removes it
if (dyingTarget.TryGetValue(i, out crtOut))
{
StopCoroutine(crtOut);
dyingTarget.Remove(i);
}
}
The new TargetDie function which removes it from the Dictionary after killing it. It also checks for null before destroying it:
IEnumerator TargetDie(int i, float delayTime)
{
yield return new WaitForSeconds(delayTime);
if (targets != null)
Destroy(targets[i]);
//Remove from Dictionary
dyingTarget.Remove(i);
}
I'm creating a Pop up menu Option in Unity. Now my Problem here is that the coroutine i made in void update is being called so many times. What i mean by that is on my Unity Console the Debug.Logs are incrementing . It should not right because its already coroutine. Could some help me understand more coroutine and help me solve my little problem .
Here is my code:
[SerializeField]
GameObject Option;
[SerializeField]
Button btn,btn2;
[SerializeField]
GameObject open, close;
[SerializeField]
GameObject[] opt;
bool startFinding = false;
void Start()
{
Option.SetActive(false);
Button popUp = btn.GetComponent<Button>();
Button popUp2 = btn2.GetComponent<Button>();
popUp.onClick.AddListener(PopUpOption);
popUp2.onClick.AddListener(ClosePopUp);
}
void Update()
{
if (startFinding)
{
StartCoroutine(GameOptions());
}
}
IEnumerator GameOptions()
{
//Get All the tags
opt = GameObject.FindGameObjectsWithTag("MobileOptions");
if (opt[0].GetComponent<Toggle>().isOn == true && opt[1].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Disable first the check box then choose only 1 option between" + "'rendering'"+ "and" + "'livestreaming'");
}
//Livestreaming
if (opt[0].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Livestreaming Activate");
} else
{
Debug.Log("Livestreaming Deactivate");
}
//Rendering
if (opt[1].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Rendering Activate");
} else
{
Debug.Log("Rendering Deactivate");
}
//Fog
if (opt[2].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Fog Activated");
} else
{
Debug.Log("Fog Deactivated");
}
//Camera Effect
if (opt[3].GetComponent<Toggle>().isOn == true)
{
Debug.Log("Camera Effect Activated");
} else {
Debug.Log("Camera Effect Deactivated");
}
yield return null;
}
void PopUpOption()
{
startFinding = true;
//Disable The Mobile Option Button
open.SetActive(false);
//Enable the Close Option Button
close.SetActive(true);
//activate the Mobile Options
Option.SetActive(true);
}
void ClosePopUp()
{
startFinding = false;
//eanble the mobile option button
open.SetActive(true);
//disable the close option button
close.SetActive(false);
//deactivate the Mobile Option
Option.SetActive(false);
}
Here is how coroutines work:
Let's say I have a couroutine function called MyRoutine (in your case, you called it GameOptions)
private IEnumerator MyRoutine()
Then, anywhere in my code, calling
StartCoroutine(MyRoutine));
Is going to simply call MyRoutine like any usual method. So if you call it in update, it will be called all the time, as any method would. This is not what you want. What make coroutines special is that you can use the yield keyword in them. There are many ways to use it but the most used (and simple) one is to do yield return null
yield return null means "Stop this coroutine, but resume the execution on next frame". You don't need to call any other function (certainly not StartCoroutine). The execution will resume next frame.
To go back to what you posted in your question, you wrote yield return null at the end. So your method is executing, and just at the end, stops and resumes next frame, but since there is nothing left to do, it exits on the next frame.
A typical way to use coroutines is to have the yield return null in a while loop, so when it resumes, it continues the loop. Here is an example that do it
private IEnumerator MyRoutine()
{
while(running) //running is a member bool that you could set to false to exit
{
// Do all the stuff you want to do in ONE frame
// ...
yield return null;
}
}
Typically, the StartCoroutine would be called in the Start() function, or later when an event is triggered.
If you want to know more about coroutine, or check that you understood them properly, check out this page: https://docs.unity3d.com/Manual/Coroutines.html
or this video https://unity3d.com/learn/tutorials/topics/scripting/coroutines
// Edit: quickly present one useful option
In the snippet above, the while loop is very similar to the Update function (the inside of the loop is executed each frame). One nice option is to replace
yield return null
by
yield return new WaitForSeconds(waitTime)
where waitTime is a the time you want to wait before resuming, in seconds
// End of edit
Do not use StartCoroutine() in the Update method. Call it in another method and use a while loop inside your coroutine function if needed. Just control your StartCoroutine() outside of Update method
Update is called every frame, if your condition is ever true, you launch your coroutine every frame.
Just set down your flag to only join 1 time.
void Update()
{
if (startFinding)
{
startFinding = false;
StartCoroutine(GameOptions());
}
}