- Home /
IEnumerator only called once
Here is my actual code:
From Script1.cs :
void Start()
{
new Script2().Event( MyMethod( 32 ) );
}
IEnumerator MyMethod( int parameter )
{
print ("got= " + parameter);
yield return null;
}
From Script2.cs :
public Script2 Event( IEnumerator method )
{
myGameObject.GetComponent<UI.Button>.onClick.AddListener( delegate() { print("called") ; StartCoroutine( method ) ; } );
return this;
}
Now, regarding the syntax. I need Event to be Script2 "typed". I need my method to be able to pass int, string, or whatever. I tried to play with Func<> , Action, etc. But nothing properly worked. This one was pretty close, the issue is that the "got" print is called juste the first time, when the "called" print is called everytime. I tried to store the method passed thru parameter inside the Script2 as global, but didn't change. It seems that it's losing its reference or something similar...
Any tips on this one?
Thanks!
Answer by LittleRainGames · Oct 10, 2016 at 07:23 PM
Just wanted to mention this is not fixed.
When I do.... It works
public void OnBeginDrag(PointerEventData eventData) {
if (Input.GetButton("Fire"))
{
}
}
But When I do.... It doesn't, without the "GetButton" both right and left mouse work, but with it, I have to repeatedly hit both buttons for the left click to only sometimes work.
public void OnPointerClick(PointerEventData eventData)
{
if (Input.GetButtonDown("Fire"))
{
}
}
This does not appear to be related to the original question
Answer by 8Eye · Oct 21, 2014 at 05:22 PM
You need to put it in update if you want it to occur more than once. For example if you wanted it every 1 second do this. Im not sure how you can call a courotine continuously from start function.
void Update()
{
new Script2().Event( MyMethod( 32 ) );
}
IEnumerator MyMethod( int parameter )
{
yield return new WaitForSeconds(1);
print ("got= " + parameter);
yield return null;
}
Thanks for you answer but this isn't what I'm trying to do. I don't want it to be called every frame. I want it to be called everytime a button ( 4.6 ) is clicked
Answer by Sycobob · Oct 21, 2014 at 06:57 PM
The problem here is that you're trying to start a coroutine with the same enumerator each time. The enumerator gets 'used up' during the first execution and then becomes useless for starting another coroutine.
void Start()
{
new Script2().Event( MyMethod( 32 ) );
}
Here you are invoking MyMethod
and creating a new enumerator. Since you haven't done anything with it yet, the enumerator it going to point at the very beginning of the method (it isn't going to execute up to the first yield or anything).
public Script2 Event( IEnumerator method )
{
myGameObject.GetComponent<UI.Button>.onClick.AddListener( delegate() { print("called") ; StartCoroutine( method ) ; } );
return this;
}
Here you're starting a coroutine with the enumerator created above. Each time the onClick delegate executes, it's using the same enumerator. And since enumerators are reference types, it can be affected by the previous executions of the delegate.
Remember, internally Unity is basically just calling MoveNext()
on the enumerator each frame/step of the coroutine until it returns false, at which point it considers the coroutine finished and stops running it.
So what happens the first time the delegate is called? Unity starts a coroutine using the provided enumerator. That coroutine runs immediately by calling MoveNext()
and executing code up to the first yield. This prints your message. Next frame, the coroutine is again executed by calling MoveNext()
. This picks up where it left off, right after the yield return null
. There's no more code to executed and there's an implicit yield break
at the end of the method. This type of yield causes MoveNext()
to return false, Unity realizes the coroutine is now finished, and the coroutine is removed.
Now, what happens the next time the button is clicked? A new coroutine is started with the same enumerator used before. Where is this enumerator currently pointing? The end of MyMethod
still. Unity calls MoveNext()
, it returns false, coroutine ends without having executed anything.
How do we fix it? Well, in theory you could Reset()
the enumerator before starting the coroutine each time, but that's actually not implemented with the way Unity does coroutines. So, instead we need to a get a fresh enumerator each time. Instead of passing in an enumerator to Event
we need to give Event
a way to get a new enumerator each time it wants to start coroutine. So, we'll pass in a method that returns an enumerator (I'll use a lambda for brevity). A shorthand for a "delegate with a return type of T" is Func, so we'll use that.
void Start ()
{
new Script2().Event(() => {
return MyMethod(32);
});
}
public Script2 Event( Func<IEnumerator> method )
{
myGameObject.GetComponent<UI.Button>.onClick.AddListener(() => {
print("called");
StartCoroutine(method());
});
return this;
}
Notice we're executing method
when we start the coroutine in the onClick
delegate. This returns a fresh enumerator pointing to the beginning of the MyMethod
each time.
Also, there's a secondary issue that I'm seeing here. You shouldn't be creating a MonoBehaviour using the new
keyword. You should actually be getting a warning from the Console because of this. Instead, create a new GameObject()
and AddComponent()
to it. Or have a dedicated object for running coroutines on if you need. If we fix this too, you'll end up with something similar to this:
public class RegisterClickHandlers : MonoBehaviour
{
private void Start ()
{
ClickHandler clickHandler = gameObject.AddComponent<ClickHandler>();
clickHandler.Event(() => {
return MyMethodCoroutine(32);
});
}
private IEnumerator MyMethodCoroutine ( int parameter )
{
print("got= " + parameter);
yield break;
}
}
public class ClickHandler : MonoBehaviour
{
public ClickHandler Event ( Func<IEnumerator> method )
{
GetComponent<UI.Button>.onClick.AddListener(() => {
print("called");
StartCoroutine(method());
};
return this;
}
}
Thanks for this very good explanation and for the time you took to write it.
Two things: First, regarding the new () keyword. You're right, this isn't the way I actually 'construct' my Script2. But it doesn't change so much things right now regarding what I need, because it's working( I actually pass a string which atach a Script to a gameobject ) Second: I actually try to build a little Easy To Use code for the new GUI. So I need Script1 to stay really simple regarding the syntax. And I cannot let such a "hard" way to call the callback function. I need something really simple and straighforward.
Do you think there is a way to implement the complex part into the Script2 Event $$anonymous$$ethod?
I actually tried Reset() when looking into $$anonymous$$SDN before posting here but as you said it is not implemented. Is there a way to overide it? Or maybe could I make a custom $$anonymous$$ovePrevious()?
@Diablo404 No, I don't know a way to simplify that further. If there was a way to shallow copy the original enumerator you could, but short of a nasty reflection hack I don't know of a way. All things considered, a single lambda isn't difficult.