코루틴을 이용하여 오브젝트를 클릭하면 오브젝트를 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:
Unity Pro:
Unity 프리미엄 학습:
'개발 > Unity' 카테고리의 다른 글
유니티 에셋 - Lean Touch로 마우스 클릭 이펙트 보여주기 (Click Effect / Input Management) (0) | 2022.07.09 |
---|---|
유니티 C# - Switch Expression (스위치 표현식) (0) | 2022.07.08 |
유니티 UI - Scale With Screen Size로 캔버스 UI 크기 자동 변환하기 (0) | 2022.07.04 |
유니티 UI - Text Mesh Pro 텍스트에 그림자 효과 넣기 (Drop Shadow) (0) | 2022.07.04 |
유니티 - 쿼터니언과 회전의 덧셈 (How to Add Two Quaternions) (0) | 2022.07.02 |
댓글