Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
13 Jun 22 - 14 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
2
Question by vexe · Dec 25, 2013 at 11:14 AM · rotationcoroutinesmoothdoor

Smooth open/close door script coroutine issue

I've just watched this video and grabbed the script from it. Here's an enhanced C# version of it. It works fine.

However, I decided that I want to make a Coroutine to open/close the door:

 void Update()
 {
     if (Input.GetKeyDown(KeyCode.E) && inSight) {
         open = !open;
         StartCoroutine(DoorCoroutine(open));
     }
 }

 IEnumerator DoorCoroutine(bool open)
 {
     Vector3 target = open ? openRot : defaultRot;
     float t = 0;
     while (t < 1) {
         t += Time.deltaTime / duration;
         cachedTransform.eulerAngles = Vector3.Slerp(cachedTransform.eulerAngles,
                                                     target,
                                                     t);
         yield return null;
     }
     cachedTransform.eulerAngles = target;
 }

It's doing the job well if I wait for it to finish, but the problem is, if I try to close/open the door while it's opening/closing, it doesn't act right. I understand that the problem is related to the fact, that when I call the closing coroutine while the opening one hasn't finished yet, they will conflict with each other and give wrong results.

I tried to stop the coroutine before calling it, didn't work. I also tried making the 't' variable a member field, not local, so that if the door hasn't finished rotating yet, and I tried to open/close it, 't' would resume and continue from where it left of. But it didn't quite work...

So, how can I write that coroutine properly? such that, if I open/close the door while it's closing/opening, it would react properly.

Thanks for any help.

EDIT:

Here's a video showing the door opening with and without the coroutine.

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 Statement · Dec 25, 2013 at 01:28 PM

Here's one way, stopping the Coroutine and restarting it:

 void Update()
 {
     if (Input.GetKeyDown(KeyCode.E) && inSight)
         ToggleDoor();
 }

 void ToggleDoor()
 {
     open = !open;
     StopCoroutine("DoorCoroutine");
     StartCoroutine("DoorCoroutine", open);
 }


Here is one without Coroutines, using Animation instead:

 void Update()
 {
     if (Input.GetKeyDown(KeyCode.E) && inSight) 
         ToggleDoor();
 }

 void ToggleDoor()
 {
     open = !open;

     if (open)
         animation.Play("Open");
     else
         animation.Play("Close");
 }


Here's the really long way of achieving the same thing without having to use strings. I haven't tested it, but the code is so small so I'll take my chances of public embarrasment if it turns out to have bugs. :) If anything is wrong, I would suspect you may have to change the Current value to return null when you try to access it if it has been aborted, but I doubt it.

 AbortableEnumerator doorCoroutine;

 void ToggleDoor()
 {
     open = !open;

     StopDoorCoroutine ();
     StartDoorCoroutine ();
 }

 void StopDoorCoroutine ()
 {
     if (doorCoroutine != null)
         doorCoroutine.Abort ();
 }

 void StartDoorCoroutine ()
 {
     doorCoroutine = new AbortableEnumerator (DoorCoroutine (open));
     StartCoroutine (doorCoroutine);
 }

And the code that enables this new behaviour:

 public class AbortableEnumerator : IEnumerator
 {
     IEnumerator enumerator;
     bool isAborted;

     public AbortableEnumerator(IEnumerator enumerator)
     {
         this.enumerator = enumerator;
     }

     public void Abort()
     {
         isAborted = true;
     }

     bool IEnumerator.MoveNext ()
     {
         if (isAborted)
             return false;
         else
             return enumerator.MoveNext ();
     }

     void IEnumerator.Reset ()
     {
         isAborted = false;
         enumerator.Reset ();
     }

     object IEnumerator.Current 
     {
         get { return enumerator.Current; }
     }
 }


What I really think your approach should have been if you were to do it purely with code, but this is my preference and not a rule. Take it for a spin. I believe it behaves pretty nicely, and you can adjust the rotational angles and how fast it should smooth/move. If your world expects that the original rotation should be taken into account, you'd have to fix that.

  1. Create a blank GO, attach this script to it.

  2. Add a cube child to it, offset: 0.5, 0.5, 0.0, rotation: 0, 0, 0, scale: 1, 2, 0.1.

  3. Press "E"

  4. Tweak values in inspector to your hearts content

And the code:

 using UnityEngine;
 using System.Collections;
 
 public class SmallDoorExample : MonoBehaviour {
 
     public float closedAngle = 0;
     public float openedAngle = 90;
     public float doorSwingSmoothingTime = 0.5f;
     public float doorSwingMaxSpeed = 90;
 
     private float targetAngle;
     private float currentAngle;
     private float currentAngularVelocity;
 
     void Update () 
     {
         if (DoorWasInteractedWith ())
             ToggleAngle ();
 
         UpdateAngle ();
         UpdateRotation ();
     }
 
     static bool DoorWasInteractedWith ()
     {
         return Input.GetKeyDown (KeyCode.E);
     }
 
     void ToggleAngle ()
     {
         if (targetAngle == openedAngle)
             targetAngle = closedAngle;
         else
             targetAngle = openedAngle;
     }
 
     void UpdateAngle ()
     {
         currentAngle = Mathf.SmoothDamp (currentAngle, 
                                          targetAngle, 
                                          ref currentAngularVelocity, 
                                          doorSwingSmoothingTime, 
                                          doorSwingMaxSpeed);
     }
 
     void UpdateRotation ()
     {
         transform.localRotation = Quaternion.AngleAxis (currentAngle, Vector3.up);
     }
 }
Comment
Add comment · Show 11 · 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 Statement · Dec 25, 2013 at 01:52 PM 1
Share

If you want to abort a coroutine, you need to either call StopAllCoroutines or StopCoroutine. You can also sprinkle a lot of if (abort) all over the code where the coroutine may resume but I don't think that is very neat at all.

I think you can check if a coroutine is running with IsInvoking("DoorCoroutine"); but make a test ins$$anonymous$$d of accepting this as I am both unsure and too lazy to test for you.

Alternatively, design your code differently. I am not sure if a coroutine would be the best solution to your problem. It is a solution, and it will work, but perhaps there are cleaner ways to do it. I tend to prefer coroutines when I want to chain together different kinds of timed actions. In this case it feels like you could have bool for animating the door, or even an actual animation. With an animation you get more control over the look of the animation, produces less code and (if valuable to you) means less chance for code bugs/less frequent code changes.

avatar image Statement · Dec 25, 2013 at 10:13 PM 1
Share

I tried to stop the coroutine before calling it, didn't work.

How were you trying to stop it?

you could actually do StartCoroutine(method()); avoiding string literals.

But then you can't stop it. You need to start it by a string so it has a name to remember it by when you want to stop it.

I didn't test your entire code but I did a simple debug log to ensure that the code would stop the coroutine mid-way.

But, this way I can't close/open the door while it's being opened/closed.

Yes you can, if you let the script run on update and move toward either the open rotation or closed rotation, what you can do is just simply change a member variable which is the target rotation.

 // make targetRotation a member variable

 if (open)
     targetRotation = openRot;
 else
     targetRotation = defaultRot;

The trick is, the door should rotate until it has reached the targetRotation. You are free to change the targetRotation mid-way a rotation (it will instantly start -or continue, rather- rotating the other direction).

Edit: Ok, now I see what you meant by your video. Let me take some time looking through it.

avatar image Statement · Dec 25, 2013 at 10:20 PM 1
Share

Ok, it's clear from your video you are starting the coroutine wrong. You must use the string variant exactly like I shown you if you want to use StopCoroutine. If you don't want to do it this way, you need to make a decorator that is abortable which would involve more work, but I'll update my answer with a solution so you get a taste of what I mean.

Also a wiki page where you can improve on the code:

http://wiki.unity3d.com/index.php/AbortableEnumerator

avatar image Statement · Dec 25, 2013 at 11:35 PM 1
Share

It's up to you. Perhaps you should ask a new question that focuses only on that once you got your code in place. You could for example accelerate the lerp factor over time to make it appear smooth, or maybe you want to use $$anonymous$$athf.SmoothDamp - I can't tell what effect you want but SmoothDamp doesn't sound like a bad idea.

avatar image Statement · Dec 25, 2013 at 11:51 PM 1
Share

Glad it did. I also felt like making yet another example, that does it without coroutines, and with SmoothDamp.

Show more comments

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

20 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

Related Questions

Rotation - Simple Question 0 Answers

beguin (but not wait for) the smoothing of a float in coroutine 1 Answer

Rotate smoothly an object when key is up 0 Answers

Smooth reset rotation problem 1 Answer

Choppy rotation of character around y-axis 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