Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Diablo404 · Oct 21, 2014 at 04:28 PM · ienumeratorstartcoroutineaction

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!

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

3 Replies

· Add your reply
  • Sort: 
avatar image
-1

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"))
                 {
     }
     }
Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Sycobob · Oct 10, 2016 at 10:02 PM 2
Share

This does not appear to be related to the original question

avatar image
0

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;
  }



Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Diablo404 · Oct 21, 2014 at 05:31 PM 0
Share

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

avatar image
0

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;
     }
 }


Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Diablo404 · Oct 21, 2014 at 07:39 PM 0
Share

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()?

avatar image Sycobob · Oct 21, 2014 at 09:40 PM 0
Share

@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.

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

5 People are following this question.

avatar image avatar image avatar image avatar image avatar image

Related Questions

StartCorutine not Working 3 Answers

Is there a way to show a warning when a Coroutine is called directly? 0 Answers

I can't start a coroutine. I get a weird message from visual studio 1 Answer

StartCoroutine and IEnumerator's - Multiple usage of function 0 Answers

Coroutine won't stop running. Tried String. 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges