Memo
유니티엔진에서의 회전
- 각 축에 회전이 적용되는 순서는 Y축 -> X축 -> Z축
오일러 회전 값을 변경하여 회전시킬 때
트랜스폼 오일러 회전 벡터의 X, Y, Z 값 중 회전시키려는 축을 제외하고 모두 0이라면
(예 : X축으로 회전시키려는데 (34f, 0f, 0f)인 상태)
해당 축의 오일러 값만 변경시키면 다른 축에 영향을 받지 않고 정상적으로 회전할 수 있다.
정확히는, 유니티엔진의 회전 순서에 따라
Z축 회전일 경우 X, Y 오일러 값에 상관 없이 회전이 가능하고
X축 회전일 경우 Z 값이 0이 아니면 영향을 받고,
Y축 회전일 경우 X, Z값이 0이 아니면 영향을 받는다.
회전시키려는 축 외의 다른 값이 0이 아닌 상태라면
(예 : X축으로 회전시키려는데 (1f, 2f, 3f)인 상태)
쿼터니언을 통해 회전을 적용해야 한다.
회전 시 주의사항
위에서 설명했듯, 오일러 X Y Z축 중 하나의 축에만 회전이 적용된 상태에서
해당 축의 오일러 회전값을 변경하면 정상적으로 회전되기는 한다.
하지만 다른 축에도 회전이 적용되어 있다면 정확히 회전할 수 없다.
따라서 회전을 적용할 때는 오일러 회전을 변경시키지 말고,
쿼터니언 연산을 통해 회전시키도록 습관을 들이는 것이 좋다.
[1] X축 회전 : 잘못된 예시
1
2
3
4
5
6
| private void Update()
{
Vector3 eRot = transform.eulerAngles;
eRot.x += Time.deltaTime * 100f;
transform.eulerAngles = eRot;
}
|
[2] X축 회전 : 정상
1
2
3
4
| private void Update()
{
transform.rotation *= Quaternion.Euler(Time.deltaTime * 100f, 0f, 0f);
}
|
1. 트랜스폼을 자신의 축으로 회전
1
2
3
4
5
6
| Vector3 eulerAngle = new Vector3(1f, 0f, 0f); // 예시
transform.Rotate(eulerAngle, Space.Self);
// Space.Self일 때의 Rotate() 내부 구현
transform.localRotation *= Quaternion.Euler(eulerAngle);
|
2. 트랜스폼을 월드 축으로 회전
1
2
3
4
5
6
7
8
9
10
11
12
13
| Vector3 eulerAngle = new Vector3(1f, 0f, 0f); // 예시
transform.Rotate(eulerAngle, Space.World);
// 내부 구현(1, 2 모두 결과는 동일)
// [1]
Quaternion rot = transform.rotation;
transform.rotation *=
Quaternion.Inverse(rot) * Quaternion.Euler(eulerAngle) * rot;
// [2]
transform.rotation =
Quaternion.Euler(eulerAngle) * rot;
|
3. 트랜스폼을 타겟 중심으로 회전
Note
[1] 현재 타겟과의 관계에 따라 회전하기
1
2
3
4
5
6
7
| // axis : 회전축 벡터
// speed : 회전 속도
private void RotateAround0(in Vector3 axis, float speed)
{
float t = speed * Time.deltaTime;
transform.RotateAround(target.position, axis, t);
}
|
호출 예제
1
2
3
4
5
6
7
| public float rotateSpeed = 50f;
public Vector3 axis = new Vector3(0f, 1f, 0f);
private void Update()
{
RotateAround0(axis, rotateSpeed);
}
|
[2] 타겟과 거리 관계를 유지한 채로 회전하기
1
2
3
4
5
6
7
8
9
10
11
| // axis : 회전축 벡터
// diff : (타겟의 위치 - 자신의 위치) 벡터
// speed : 회전 속도
// t : 현재 회전값을 기억할 변수
private void RotateAround1(in Vector3 axis, in Vector3 diff, float speed, ref float t)
{
t += speed * Time.deltaTime;
Vector3 offset = Quaternion.AngleAxis(t, axis) * diff;
transform.position = target.position + offset;
}
|
호출 예제
1
2
3
4
5
6
7
8
9
| public float rotateSpeed = 50f;
public Vector3 axis = new Vector3(0f, 1f, 0f);
public Vector3 diff = new Vector3(4f, 0f, 0f);
private float t = 0;
private void Update()
{
RotateAround1(axis, diff, rotateSpeed, ref t);
}
|
[3] 타겟과의 거리를 유지하고, 타겟을 바라보며 회전하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // axis : 회전축 벡터
// diff : (타겟의 위치 - 자신의 위치) 벡터
// speed : 회전 속도
// t : 현재 회전값을 기억할 변수
private void RotateAround2(in Vector3 axis, in Vector3 diff, float speed, ref float t)
{
t += speed * Time.deltaTime;
Vector3 offset = Quaternion.AngleAxis(t, Vector3.up) * diff;
transform.position = target.position + offset;
Quaternion rot = Quaternion.LookRotation(-offset, axis);
transform.rotation = rot;
}
|
호출 예제
1
2
3
4
5
6
7
8
9
| public float rotateSpeed = 50f;
public Vector3 axis = new Vector3(0f, 1f, 0f);
public Vector3 diff = new Vector3(4f, 0f, 0f);
private float t = 0;
private void Update()
{
RotateAround2(axis, diff, rotateSpeed, ref t);
}
|
4. 방향 벡터를 월드 축으로 회전
1
2
3
4
| Vector3 dirVec = new Vector3(1f, 0f, 0f); // 회전시킬 방향 벡터
Vector3 rotVec = new Vector3(0f, 45f, 0f); // Y축 45도 회전
Vector3 rotatedDirVec = Quaternion.Euler(rotVec) * dirVec;
|
5. 방향 벡터를 특정 축으로 회전
1
2
3
4
5
| Vector3 dirVec = new Vector3(1f, 0f, 0f); // 회전시킬 방향 벡터
Vector3 axisVec = new Vector3(-1f, 1f, 0f).normalized; // 회전 기준축 벡터
// axisVec을 축으로 하여 dirVec을 45도 회전
Vector3 rotatedDirVec = Quaternion.AngleAxis(45f, axisVec) * dirVec;
|
6. 대상 지점 천천히 바라보기
[1] XYZ 모두 회전
1
2
3
4
5
6
7
8
9
| private void LookAtSlowly(Transform target, float speed = 1f)
{
if (target == null) return;
Vector3 dir = target.position - transform.position;
var nextRot = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.Slerp(transform.rotation, nextRot, Time.deltaTime * speed);
}
|
[2] X만 회전
1
2
3
4
5
6
7
8
9
10
| private void LookAtSlowlyX(Transform target, float speed = 1f)
{
if (target == null) return;
Vector3 dir = target.position - transform.position;
dir.x = 0f; // 방향 벡터 X 성분 제거
var nextRot = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.Slerp(transform.rotation, nextRot, Time.deltaTime * speed);
}
|
7. 마우스 입력에 따른 상하좌우 회전 예제
[1] 자유 회전
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| [SerializeField, Range(0f, 100f)]
private float hRotationSpeed = 50f; // 좌우 회전 속도
[SerializeField, Range(0f, 100f)]
private float vRotationSpeed = 100f; // 상하 회전 속도
private void Update()
{
float t = Time.deltaTime;
// 마우스 움직임 감지
float h = Input.GetAxisRaw("Mouse X") * hRotationSpeed * t;
float v = -Input.GetAxisRaw("Mouse Y") * vRotationSpeed * t;
// 회전 변위 생성
Quaternion hRot = Quaternion.AngleAxis(h, Vector3.up);
Quaternion vRot = Quaternion.AngleAxis(v, Vector3.right);
// [1] 좌우 회전 : 월드 Y축 기준
transform.rotation = hRot * transform.rotation;
// [2] 상하 회전 : 로컬 X축 기준
transform.rotation *= vRot;
}
|
[2] 상하 회전 각도 제한
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| [SerializeField, Range(0f, 100f)]
private float hRotationSpeed = 50f; // 좌우 회전 속도
[SerializeField, Range(0f, 100f)]
private float vRotationSpeed = 100f; // 상하 회전 속도
[SerializeField, Range(-60f, 0f)]
private float lookUpAngleLimit = -45f; // 최소 회전각(올려다보기 제한)
[SerializeField, Range( 15f, 60f)]
private float lookDownAngleLimit = 45f; // 최대 회전각(내려다보기 제한)
private void Update()
{
float t = Time.deltaTime;
// 마우스 움직임 감지
float h = Input.GetAxisRaw("Mouse X") * hRotationSpeed * t;
float v = -Input.GetAxisRaw("Mouse Y") * vRotationSpeed * t;
// [1] 좌우 회전 : 월드 Y축 기준
Quaternion hRot = Quaternion.AngleAxis(h, Vector3.up);
transform.rotation = hRot * transform.rotation;
// [2] 상하 회전 : 로컬 X축 기준
// 다음 프레임 각도 예측
float xNext = transform.eulerAngles.x + v;
if (xNext > 180f)
xNext -= 360f;
// 상하 회전 각도 제한
if (lookUpAngleLimit < xNext && xNext < lookDownAngleLimit)
{
transform.rotation *= Quaternion.AngleAxis(v, Vector3.right);
}
}
|
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| /* 좀더 안전한 상하 회전 방식 */
[SerializeField, Range(0f, 100f)]
private float hRotationSpeed = 50f; // 좌우 회전 속도
[SerializeField, Range(0f, 100f)]
private float vRotationSpeed = 100f; // 상하 회전 속도
[SerializeField, Range(-60f, 0f)]
private float lookUpAngleLimit = -45f; // 최소 회전각(올려다보기 제한)
[SerializeField, Range( 15f, 60f)]
private float lookDownAngleLimit = 45f; // 최대 회전각(내려다보기 제한)
private void Update()
{
float t = Time.deltaTime;
// 마우스 움직임 감지
float yDelta = Input.GetAxisRaw("Mouse X") * hRotationSpeed * t;
float xDelta = -Input.GetAxisRaw("Mouse Y") * vRotationSpeed * t;
// [1] 좌우 회전 : 월드 Y축 기준
Quaternion hRot = Quaternion.AngleAxis(yDelta, Vector3.up);
transform.rotation = hRot * transform.rotation;
// [2] 상하 회전 : 로컬 X축 기준
float xCurrent = transform.eulerAngles.x; // 현재 X 각도
if (xCurrent > 180f)
xCurrent -= 360f;
float xNext = xCurrent + xDelta; // 회전 예정 X 각도
// 상하 회전 각도 제한
if (xNext > lookDownAngleLimit) xDelta = (lookDownAngleLimit - xCurrent);
else if (xNext < lookUpAngleLimit) xDelta = (lookUpAngleLimit - xCurrent);
transform.rotation *= Quaternion.AngleAxis(xDelta, Vector3.right);
}
|