멈추지 않고 끈질기게
[Unity][포트폴리오] 회전 발판(Rotating Platform) 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 Unity 2021.3.15f1 버전을 기준으로 작성되었습니다.
※ 개발 중인 유니티 3D 포트폴리오의 소스 코드를 포함하고 있습니다.
1. 회전 발판
public class RotatingPlatform : MonoBehaviour
{
Rigidbody rigid;
[SerializeField] float rotateDegree;
Quaternion deltaRotation;
Vector3 rotateVelocity;
발판의 회전을 RIgidbody로 구현할 예정이므로 RIgidbody 변수를 선언하였고, 초당 회전 각도는 인스펙터 창에서 입력할 수 있도록 SerializeField로 선언하였습니다. MoveRotation의 변수로 사용할 Quaternion 변수를 하나 선언해두었고, 입력한 값을 벡터 형태로 저장하기 위한 Vector3 변수를 선언하였습니다.
public enum RotateDirection
{
Clockwise,
Counter_Clockwise
}
[SerializeField] RotateDirection myDir;
회전 발판의 경우 y축을 중심으로 시계방향, 반시계방향으로 회전하는 2종류로 만들 예정이므로 방향을 열거형으로 선언해두고, 해당 열거형 타입을 인스펙터 창에서 선택할 수 있도록 RotateDirection 변수를 SerializeField로 선언하였습니다.
void Awake()
{
rigid = GetComponent<Rigidbody>();
}
void Start()
{
// 방향 벡터 초기화
switch (myDir)
{
case RotateDirection.Clockwise:
rotateVelocity = new Vector3(0, rotateDegree, 0);
break;
case RotateDirection.Counter_Clockwise:
rotateVelocity = new Vector3(0, -rotateDegree, 0);
break;
}
}
void FixedUpdate()
{
deltaRotation = Quaternion.Euler(rotateVelocity * Time.fixedDeltaTime);
rigid.MoveRotation(rigid.rotation * deltaRotation);
}
Awake()에서 Rigidbody 변수를, Start()에서 RotateDirection 타입에 따라 Vector3 변수를 초기화하도록 했습니다. 벡터값의 경우 y축 기준으로 회전시킬 예정이므로 인스펙터창에서 입력한 rotateDegree 값을 y축 값으로 갖도록 하고, 열거형 변수로 회전 방향을 구분하도록 작성했습니다.
회전은 이동 발판과 마찬가지로 FixedUpdate()에 작성했으며, 초기화해둔 벡터값에 fixedDeltaTime을 곱한 값을 Quaternion 변수로 받아 deltaRotation에 저장하고 해당 값을 Rigidbody.MoveRotation() 함수에 사용하여 회전을 구현했습니다.
발판 자체의 회전 구현은 여기까지이고, 추가로 발판 위에 올라선 플레이어가 회전하도록 구현할 필요가 있었습니다.
2. 캐릭터 회전
회전 발판 위 플레이어 캐릭터의 회전은 플레이어에서 발판 중심을 향하는 벡터값을 90도 회전시켜 얻도록 했습니다.
발판 중심의 위치 벡터에서 플레이어의 위치 벡터를 빼면 위와 같이 플레이어에서 발판 중심을 향하는 벡터 값이 나오고, 해당 벡터를 회전 방향에 맞게 90도 회전시킨 값을 플레이어가 받을 힘의 방향으로 사용하기로 했습니다. 해당 방법을 선택한 이유는, 회전의 중심과 가까울수록 크기가 작아지고 멀수록 크기가 커지기 때문에 방향뿐 아니라 크기도 그대로 활용하기 좋다고 판단했습니다. 따라서 해당 벡터를 정규화(normalize)하지 않고 그대로 사용하기로 했습니다.
// 플레이어 회전용 변수
Quaternion rotationDir;
Vector3 dirVec;
// Start()에 rotationDir 초기화 추가
void Start()
{
// 방향 벡터 초기화
switch (myDir)
{
case RotateDirection.Clockwise:
rotateVelocity = new Vector3(0, rotateDegree, 0);
rotationDir = Quaternion.Euler(0, -90, 0);
break;
case RotateDirection.Counter_Clockwise:
rotateVelocity = new Vector3(0, -rotateDegree, 0);
rotationDir = Quaternion.Euler(0, 90, 0);
break;
}
}
우선 벡터의 회전은 Quaternion * Vector3의 형태로 구현할 예정이므로 벡터 회전용 Quaternion 변수를 선언하였으며, 플레이어에게 전달하기 위한 Vector3 변수를 선언하였습니다. 또한 Start()에 회전방향에 따라 rotationDir를 초기화하는 내용을 추가하였습니다.
// 플레이어 회전용 함수
// (플레이어 -> 회전 발판) 방향의 벡터를 90/-90도 회전시킨 값을 반환
public Vector3 GetRotateVec(Vector3 playerPos)
{
dirVec = rotationDir * (transform.position - playerPos);
return dirVec * Time.fixedDeltaTime;
}
플레이어 회전용 함수는 플레이어의 포지션값을 매개변수로 받은 다음, 플레이어에서 발판 중심을 향하는 벡터(playerPos - transform.position)를 90도/-90도 회전시킨 후(rotationDir *) fixedDeltaTime을 곱하여 반환합니다.
// 발판 관련
MovingPlatform movingPlat;
RotatingPlatform rotatingPlat;
플레이어 스크립트에는 이동 발판에 추가로 RotatingPlatform 변수를 추가하였고,
void OnCollisionEnter(Collision coll)
{
// 움직이는 발판 위에 있는 경우
if (coll.gameObject.CompareTag(Tags.MovingPlatform))
{
movingPlat = coll.gameObject.GetComponent<MovingPlatform>();
return;
}
// 회전 발판 위에 있는 경우
if (coll.gameObject.CompareTag(Tags.RotatingPlatform))
{
rotatingPlat = coll.gameObject.GetComponent<RotatingPlatform>();
return;
}
}
void OnCollisionExit(Collision coll)
{
// 움직이는 발판에서 나갈 경우
if (coll.gameObject.CompareTag(Tags.MovingPlatform))
{
movingPlat = null;
return;
}
// 회전 발판에서 나갈 경우
if (coll.gameObject.CompareTag(Tags.RotatingPlatform))
{
rotatingPlat = null;
return;
}
}
OnCollisionEnter() 및 OnCollisionExit()에 이동 발판과 동일하게 회전 발판 착지 시 rotatingPlat 변수에 저장, 나갈 시 null로 초기화하는 로직을 추가하였습니다.
void PlayerMove()
{
moveVec = inputVec * moveSpeed * Time.fixedDeltaTime;
// 움직이는 발판 위일 경우
if (movingPlat != null)
{
rigid.MovePosition(rigid.position + moveVec + movingPlat.GetMoveVec());
return;
}
// 회전 발판 위일 경우
if (rotatingPlat != null)
{
rigid.MovePosition(rigid.position + moveVec + rotatingPlat.GetRotateVec(transform.position));
return;
}
// 일반 발판
rigid.MovePosition(rigid.position + moveVec);
}
PlayerMove 함수에는 회전 발판 위일 경우를 추가하여 RotatingPlatform.GetRotateVec() 함수의 반환값을 추가하여 움직이도록 작성했습니다(GetRotateVec() 함수에는 플레이어의 포지션을 매개변수로 전달).
위와 같은 로직을 통해 회전 발판 위의 플레이어가 발판과 함께 회전하는 모습을 확인할 수 있었습니다. 다음 영상은 반시계방향으로 초당 60도 회전하는 회전 발판 위에 올라타는 모습입니다.
3. 결론
다만 현재 구현 내용으로는 다음과 같은 문제점이 있습니다.
- 플레이어의 회전 속도가 발판 회전 속도에 영향을 받지 않음
(GetRotateVec()의 반환값이 rotateDegree와 무관함) - 간헐적으로 플레이어의 rotation이 비정상적으로 크게 변하는 이슈 발생
rotateDegree값을 곱해서 사용하자니 값이 너무 크므로 계수를 추가해야 하는데, 이런 방식이 그다지 깔끔하진 않다고 느껴져 다른 방법을 찾고 있습니다.
플레이어의 rotation이 크게 변하는 이슈는 플레이어가 회전 발판 바깥쪽에 있을 때 발생하는 경우가 높은 것으로 보아, 충돌 판정 관련한 이슈가 아닐까 의심하고 있습니다. 해결하는 대로 해당 포스팅에 내용을 추가하도록 하겠습니다.
'포트폴리오' 카테고리의 다른 글
[Unity][포트폴리오] 멀티플레이 관련 이슈 (0) | 2023.06.11 |
---|---|
[Unity][포트폴리오] 진짜 혹은 가짜(True or False) 발판 (0) | 2023.06.10 |
[Unity][Photon][포트폴리오] 채팅 구현 (0) | 2023.06.06 |
[Unity][포트폴리오] 태그 관리용 클래스 / WaitForSeconds 관리용 클래스 (0) | 2023.05.31 |
[Unity][포트폴리오] 이동 발판(Moving Platform) (0) | 2023.05.21 |