Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 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
1
Question by Rangr · Jan 07, 2016 at 02:07 AM · c#rotationcoroutinewhilecover

Misunderstanding Co-routines?

Hello, I have a feeling that this problem is caused by some aspect of co-routines that I don't know about, but I'm not sure. I have a block of code (below) that is designed to rotate the player to peek around corners. It seems to be repeating in a really weird way though. Init (a debug.log in the code block) is hit several times when it should only be activating once. The counter runs for a few ticks, then it apparently decides that canPeek is false and restarts, only hitting Mark 1 (commented line in code block) a few times. When it does finally make it to Mark 2, it appeared to hit Mark 2, Mark 1, Mark 2, then Mark 4 a few times before deciding that adhering to while loops is for chumps and hitting a repeating pattern of Mark 1, Mark 2, Mark 5. I have no idea why it is doing this and it is mind boggling because of the conditions that must be activating and deactivating for this to happen. If anyone sees my issue I would love some input. Thank you.

(Note about code: A lot of it is just repeating for the different directions that rotDir represents; all those else ifs at the bottom are repeated with minimal changes. Also, the canPeek variable is set to true if the players movement would take them past the edge of a wall.)

 IEnumerator PeekCover(int rotDir)
     {
         int count = 0;
         float rotation = Mathf.Round(swapper.transform.parent.transform.eulerAngles.y);
         bool inCorner = true;
         Debug.Log(rotDir);
 
         //check if against a wall
         if(rotDir == 1)
         {
             if(rotation == 270)
                 inCorner = false;
         } else if(rotDir == 2)
         {
             if(rotation == 90)
                 inCorner = false;
         } else if(rotDir == 3)
         {
             if(rotation == 180)
                 inCorner = false;
         } else if(rotDir == 4)
         {
             if(rotation == 0)
                 inCorner = false;
         }
         //end wall check
 
 
         if(inCorner == false)
         {
             if(rotDir == 1)
             {
                 Debug.Log("Init");
                 while(canPeek == true && count <= 20)
                 {
                     count++;
                     yield return null;
                     //Mark 1
                 }
                 //Mark 2
                 if(count >= 20)
                     transform.eulerAngles = new Vector3(-30f, 0f, 0f);
                 while(canPeek == true && count >= 19)
                 {
                     if(Input.GetKeyDown("left ctrl"))
                     {
                         canPeek = false;
                         inCover = 0;
                         rb.constraints = RigidbodyConstraints.None;
                         transform.eulerAngles.Set(0f, transform.rotation.y, 0f);
                         FindCover(swapper);
                         //Mark 3
                     }
                     yield return null;
                     //Mark 4
                 }
                 transform.eulerAngles.Set(0f, transform.rotation.y, 0f);
                 //Mark 5
             }
             else if(rotDir == 2)
             {
                 while(canPeek == true && count <= 30)
                 {
                     count++;
                     yield return null;
                 }
                 if(count >= 30)
                     transform.eulerAngles = new Vector3(30f, 0f, 0f);
                 while(canPeek == true && count >= 30)
                 {
                     if(Input.GetKeyDown("left ctrl"))
                     {
                         canPeek = false;
                         inCover = 0;
                         rb.constraints = RigidbodyConstraints.None;
                         transform.eulerAngles.Set(0f, transform.position.y, 0f);
                         FindCover(swapper);
                     }
                     yield return null;
                 }
                 transform.eulerAngles.Set(0f, transform.rotation.y, 0f);
             }
             else if(rotDir == 3)
             {
                 while(canPeek == true && count <= 30)
                 {
                     count++;
                     yield return null;
                 }
                 if(count >= 30)
                     transform.eulerAngles = new Vector3(0f, 0f, -30f);
                 while(canPeek == true && count >= 30)
                 {
                     if(Input.GetKeyDown("left ctrl"))
                     {
                         canPeek = false;
                         inCover = 0;
                         rb.constraints = RigidbodyConstraints.None;
                         transform.eulerAngles.Set(0f, transform.position.y, 0f);
                         FindCover(swapper);
                     }
 
                     yield return null;
                 }
                 transform.eulerAngles.Set(0f, transform.position.y, 0f);
             }
             else if(rotDir == 4)
             {
                 while(canPeek == true && count <= 30)
                 {
                     count++;
                     yield return null;
                 }
                 if(count >= 30)
                     transform.eulerAngles = new Vector3(0f, 0f, 30f);
                 while(canPeek == true && count >= 30)
                 {
                     if(Input.GetKeyDown("left ctrl"))
                     {
                         canPeek = false;
                         inCover = 0;
                         rb.constraints = RigidbodyConstraints.None;
                         transform.eulerAngles.Set(0f, transform.position.y, 0f);
                         FindCover(swapper);
                     }
                     yield return null;
                 }
                 transform.eulerAngles.Set(0f, transform.position.y, 0f);
             }
          }
     }
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

1 Reply

· Add your reply
  • Sort: 
avatar image
3
Best Answer

Answer by Bunny83 · Jan 07, 2016 at 04:23 AM

Well, the most important thing related to your problem is where and how do you start the coroutine. However that part is missing in your question.

If "Init" prints several times you most likely are starting the coroutine several times. A coroutine is actually an object which implements a statemachine and not just a method. The yield keyword + IEnumerator is pure "compiler magic".

Just for reference, this is how a usual IEnumerator / iterator might look like:

 // not a coroutine !!!
 IEnumerator Foo()
 {
    yield return "Hello";
    yield return "World";
    yield return "!!!"
 }
 

Here we have a method that uses the yield keyword and returns an object that implements the IEnumerator interface. The object that is returned by that method is an instance of the statemachine class that the compiler generated for your "method".

The IEnumerator interface represents an object that can be "iterated". For that it provides us a "MoveNext" method which will advance the iterator to the next element. It also has a "Current" property which allows us to read / access the current "item".

Here's an example how you can iterate such an object:

 IEnumerator iterator = Foo();
 while (iterator.MoveNext())
 {
     Debug.Log((string)iterator.Current);
 }

This will print 3 lines in the console:

 Hello
 World
 !!!

As you can see, each time we call MoveNext the statemachine will advance to the next "yield return". Current contains whatever you "yield returned" inside the iterator method.

So how does Unity actually use this functionallity to implement coroutines?

 IEnumerator SomeCoroutine()
 {
     Debug.Log("Start");
     yield return new WaitForSeconds(1);
     Debug.Log("End")
 }
 
 StartCoroutine(SomeCoroutine());

The method SomeCoroutine is again just an iterator method like we discussed above. That's nothing Unity invented. The key part is "StartCoroutine". What you do when you start a coroutine is:

  • You invoke your generator method which returns a new state machine instance for your coroutine.

  • You pass that object / instance to the StartCoroutine method

Unity will store that instance internally in it's coroutine scheduler. That scheduler will now start "iterating" your object by calling MoveNext. As a result the code in your coroutine will execute until the first yield. We "yield" a WaitForSeconds object. After MoveNext returns to the scheduler, the scheduler now examines the Current property to decide what comes next. It recognises the wait for seconds object and simply stores your object in some kind of list.

Some frames later the wait time has expired and the scheduler takes our iterator object from the shelf and again calls MoveNext. This will execute the next "part" of our original code until we reach the next yield or the end of the method. Once the end is reached MoveNext will return "false" which indicates that the iterator is finished. Unity will now simply kick the object out of its internal list and the coroutine is finished.

If you have a while loop inside your coroutine that while loop actually isn't a while loop ^^. Keep in mind the compiler creates a statemachine out of your code.

A very simple example:

 IEnumerator FooBar()
 {
     while (true)
     {
         Debug.Log("Another frame");
         yield return null;
     }
 }

This would roughly be translated to:

 private class _FooBarIterator : IEnumerator
 {
     private object current;
     public object Current {get { return current;}}
     public bool MoveNext()
     {
         Debug.Log("Another frame");
         current = null;
         return true;
     }
 }
 
 IEnumerator FooBar()
 {
     return new _FooBarIterator();
 }


This would be an endless running coroutine. There's no real statemachine required in this case since each time we call MoveNext it will always return true since there is no end.

Another example

 IEnumerator FooBar(int n)
 {
     for (int i = 0; i < n; i++)
     {
         Debug.Log("Frame: " + i);
         yield return null;
     }
 }

This would translate to something like:

 private class _FooBarIterator : IEnumerator
 {
     private object current;
     private int state = 0;
     public int n;
     private int i;
     public object Current {get { return current;}}
     public bool MoveNext()
     {
         if (state == 0)
         {
             i = 0;
             state = 1;
         }
         if (state == 1)
         {
             if (i < n)
             {
                 Debug.Log("Frame: " + i);
                 current = null;
                 i++;
                 return true;
             }
             else
             {
                 state = 2;
             }
         }
         if (state == 2)
         {
             return false;
         }
     }
 }

 IEnumerator FooBar(int n)
 {
     var tmp = new _FooBarIterator();
     tmp.n = n;
     return tmp;
 }



This is actually not correct since the increment of "i" would happen before the next iteration since the yield is in between. The compiler usually uses some nasty "goto" statements to jump around in the statemachine. However this is roughly what is generated by the compiler.

As you can see your local variables and method parameters (which are like "special" local variables) are actually converted into member variables of that statemachine class. It's the only way how the internal state of your code can be preserved.

The compiler is able to completely "unroll" your entire code in the iterator method into such a statemachine.

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 Rangr · Jan 08, 2016 at 01:08 AM 0
Share

That is one heck of an explanation. Thanks. It definitely helped to fix the weird problem with hitting marks. I'm still having the problem of the final rotation not activating but I'll throw up another question for that because it's a whole different can of worms I think.

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

55 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Flip over an object (smooth transition) 3 Answers

Start Coroutine after other has finished 4 Answers

Random smooth rotation on z-axis every other second 2 Answers

Multiple Cars not working 1 Answer

C#: Use of while loops inside of a coroutine 2 Answers


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