본문 바로가기
개발/Unity

유니티 - 코루틴으로 오브젝트 90도 회전하기 (Rotate GameObject using Coroutine)

by 피로물든딸기 2022. 7. 8.
반응형

Unity 전체 링크

 

코루틴을 이용하여 오브젝트를 클릭하면 오브젝트를 90º 돌려보자.

 

코루틴에 대한 글

 

- 블럭 한 칸 이동하기 (그리드 기반 이동, 코루틴)

- SmoothDamp를 코루틴에서 사용하기 (SmoothDamp with Coroutine)

 

을 참고하여 RotateCoroutine.cs를 먼저 만들어보자.

Quaternion을 이용하여 회전한다.

public class RotateCoroutine : MonoBehaviour
{
    float rotateTime = 0.1f;

    float round90(float f)
    {
        float r = f % 90;
        return (r < 45) ? f - r : f - r + 90;
    }

    private IEnumerator moveBlockTime()
    {
        rotating = true;

        float elapsedTime = 0.0f;

        Quaternion currentRotation = this.transform.rotation;
        Vector3 targetEulerAngles = this.transform.rotation.eulerAngles;
        targetEulerAngles.y += (90.0f);

        Quaternion targetRotation = Quaternion.Euler(targetEulerAngles);

        while (elapsedTime < rotateTime)
        {
            transform.rotation
                = Quaternion.Euler(Vector3.Lerp(
                    currentRotation.eulerAngles, targetRotation.eulerAngles, elapsedTime / rotateTime)
                );

            elapsedTime += Time.deltaTime;
            yield return null;
        }

        targetEulerAngles.y = round90(targetEulerAngles.y);
        this.transform.rotation = Quaternion.Euler(targetEulerAngles);
    }

    void OnMouseUp()
    {
        StartCoroutine(moveBlockTime());
    }
}

 

위의 코드를 실린더에 추가하자.

실린더의 앞을 표시하기 위해 작은 큐브를 추가해두었다.

 

코드를 살펴보자.

먼저 round90은 코루틴 종료 후, 90º에 가장 가까운 값을 찾도록 하는 함수다. (90º 회전이므로)

    float round90(float f)
    {
        float r = f % 90;
        return (r < 45) ? f - r : f - r + 90;
    }

 

쿼터니언을 이용한 회전이 종료되면, 90º로 정확히 보정할 때 사용한다.

    targetEulerAngles.y = round90(targetEulerAngles.y);
    this.transform.rotation = Quaternion.Euler(targetEulerAngles);

 

해당 코드를 실행해보자.

90º, 180º, 270º까지는 정상적으로 회전한다.

그러나 270º → 360º에서는 갑자기 반대방향으로 회전하고 있다.

 

이 문제는 일종의 짐벌락 현상이다.

간단히 해결하는 방법은 targetEulerAngles.y에서 90º88º 정도만 더하는 것이다.

어짜피 코루틴이 종료되면 90º로 보정이 된다.

    private IEnumerator moveBlockTime()
    {
        ...
        
        Vector3 targetEulerAngles = this.transform.rotation.eulerAngles;
        //targetEulerAngles.y += (90.0f);
        targetEulerAngles.y += (88.0f);

        ...

 

90º로 정확하게 회전하지 않는 방법으로 문제가 해결된 것을 확인해보자.


이제 반시계 방향으로도 움직이기 위해 코드를 수정해보자.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateCoroutine : MonoBehaviour
{
    public bool clockWise;
    Vector3 CLOCKWISE = Vector3.up;
    Vector3 ANTI_CLOCKWISE = Vector3.down;

    float rotateTime = 0.1f;

    float round90(float f)
    {
        float r = f % 90;
        return (r < 45) ? f - r : f - r + 90;
    }

    private IEnumerator moveBlockTime(Vector3 wise)
    {
        float elapsedTime = 0.0f;

        Quaternion currentRotation = this.transform.rotation;
        Vector3 targetEulerAngles = this.transform.rotation.eulerAngles;
        targetEulerAngles.y += (88.0f) * wise.y;

        Quaternion targetRotation = Quaternion.Euler(targetEulerAngles);

        while (elapsedTime < rotateTime)
        {
            transform.rotation
                = Quaternion.Euler(Vector3.Lerp(
                    currentRotation.eulerAngles, targetRotation.eulerAngles, elapsedTime / rotateTime)
                );

            elapsedTime += Time.deltaTime;
            yield return null;
        }

        targetEulerAngles.y = round90(targetEulerAngles.y);
        this.transform.rotation = Quaternion.Euler(targetEulerAngles);
    }

    void OnMouseUp()
    {
        if (clockWise)
            StartCoroutine(moveBlockTime(CLOCKWISE));
        else
            StartCoroutine(moveBlockTime(ANTI_CLOCKWISE));     
    }
}

 

시계 방향 / 반시계 방향 여부를 확인하기 위한 bool 변수회전을 위한 축을 정의하였다.

    public bool clockWise;
    Vector3 CLOCKWISE = Vector3.up;
    Vector3 ANTI_CLOCKWISE = Vector3.down;

 

moveBlockTime에는 wise를 parameter로 추가하였다.

    private IEnumerator moveBlockTime(Vector3 wise)
    {
        float elapsedTime = 0.0f;

        Quaternion currentRotation = this.transform.rotation;
        Vector3 targetEulerAngles = this.transform.rotation.eulerAngles;
        targetEulerAngles.y += (88.0f) * wise.y; // 방향 전환
        
        ...

 

OnMouseUp에서 closkWise의 여부에 따라 회전하는 방향이 다르도록 처리한다.

    void OnMouseUp()
    {
        if (clockWise)
            StartCoroutine(moveBlockTime(CLOCKWISE));
        else
            StartCoroutine(moveBlockTime(ANTI_CLOCKWISE));     
    }

 

시계 방향으로는 여전히 잘 움직이지만,

반시계 방향일 때는 갑자기 360º 회전하는 버그가 생겼다.

 

이 현상도 마찬가지로 짐벌락 현상이다.

따라서 코루틴을 최초로 실행할 때, Rotate를 이용해 해당 방향으로 아주 조금 움직여둔다.

    private IEnumerator moveBlockTime(Vector3 wise)
    {
        this.transform.Rotate(new Vector3(0, wise.y, 0)); // 짐벌락 방지

        float elapsedTime = 0.0f;

wise가 (0, 1, 0) 또는 (0, -1, 0)이기 때문에 wise.y의 값이 충분히 작다.

 

그리고 rotating 중일 때는 코루틴이 실행되지 않도록 방어 코드를 추가한다.

(지금은 rotateTime이 0.1f로 매우 작아서 빠르게 클릭해도 문제 없지만, 시간이 커지면 비정상으로 회전한다.)

    bool rotating;
    
    private IEnumerator moveBlockTime(Vector3 wise)
    {
        rotating = true;
		
        ...
       
        rotating = false;
    }

    void OnMouseUp()
    {
        if(rotating == false)
        {
            if (clockWise)
                StartCoroutine(moveBlockTime(CLOCKWISE));
            else
                StartCoroutine(moveBlockTime(ANTI_CLOCKWISE));
        }
    }

 

이제 정상적으로 시계 / 반시계 방향으로 움직이는 것을 알 수 있다.

 

최종 코드는 다음과 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateCoroutine : MonoBehaviour
{
    bool rotating;
    
    public bool clockWise;
    Vector3 CLOCKWISE = Vector3.up;
    Vector3 ANTI_CLOCKWISE = Vector3.down;

    float rotateTime = 0.1f;

    float round90(float f)
    {
        float r = f % 90;
        return (r < 45) ? f - r : f - r + 90;
    }

    private IEnumerator moveBlockTime(Vector3 wise)
    {
        rotating = true;

        this.transform.Rotate(new Vector3(0, wise.y, 0));

        float elapsedTime = 0.0f;

        Quaternion currentRotation = this.transform.rotation;
        Vector3 targetEulerAngles = this.transform.rotation.eulerAngles;
        targetEulerAngles.y += (88.0f) * wise.y;

        Quaternion targetRotation = Quaternion.Euler(targetEulerAngles);

        while (elapsedTime < rotateTime)
        {
            transform.rotation
                = Quaternion.Euler(Vector3.Lerp(
                    currentRotation.eulerAngles, targetRotation.eulerAngles, elapsedTime / rotateTime)
                );

            elapsedTime += Time.deltaTime;
            yield return null;
        }

        targetEulerAngles.y = round90(targetEulerAngles.y);
        this.transform.rotation = Quaternion.Euler(targetEulerAngles);

        rotating = false;
    }

    void OnMouseUp()
    {
        if(rotating == false)
        {
            if (clockWise)
                StartCoroutine(moveBlockTime(CLOCKWISE));
            else
                StartCoroutine(moveBlockTime(ANTI_CLOCKWISE));
        }
    }
}

 

Unity Plus:

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Unity Pro:

 

Unity Pro

The complete solutions for professionals to create and operate.

unity.com

 

Unity 프리미엄 학습:

 

Unity Learn

Advance your Unity skills with live sessions and over 750 hours of on-demand learning content designed for creators at every skill level.

unity.com

반응형

댓글