0. 들어가기에 앞서
어제자 공부에 이어, 팀 프로젝트 준비를 위해 추가적인 공부를 해 보기로 했다.
추가적인 협의가 필요한 부분에 대한 부분은 최대한 배제하고, 개인적으로 먼저 구현해볼 수 있는 부분에 대해 시도해보기로 했다.
그래서 오늘 시도해볼 것은 문을 열고 닫는 것을 만들어 보고, 시간이 나면 철창의 열림과 닫힘도 구현해보려고 한다.
아래 영상을 참고하였다.
우선은 회전축이 될 Empty Object인 DoorAnchor를 만들었고 Cube로 Door을 자식 오브젝트로 앵커에 붙였다.
여기에서 Y축 Rotation을 돌리면 문이 열리고 닫히는 듯한 정상적인 연출이 확인되어 애니메이션 만들기를 시도했다.
애니메이션과 애니메이터 창은 보통 기본 화면에서 제공하지 않으니 추가하여 창을 만들도록 하자.
이제 애니메이션 창에서 애니메이션을 만들어보자. Asset 파일에서 Animation 파일을 따로 만들었고, 그곳에 Create을 눌러 애니메이션을 생성했다. 이름은 anim_door_open으로 만들었다.
여기서 DoorAnchor가 대상으로 눌려있는지 확인하고 Add Property를 했다. Rotation을 선택하고 오른쪽 플러스 버튼을 누르면 된다.
그 다음으로 좌측 상단의 녹화 버튼을 누르고, 1초 뒤에 문이 열린 상태가 되도록 Rotation을 설정해보자.
이와 같이 세팅한 후 재생 버튼을 눌러보면 문이 열리는 듯한 자연스러운 애니메이션을 만들어준다.
애니메이션이 정상작동하는 것을 확인했으니, 이젠 문이 닫혀 있는 초기상태로 만들어준다.
이와 같이 추가하여주고 애니메이터로 작업을 시작한다.
Idle을 초기상태로 선택하고 anim_door_open을 복사해서 anim_door_close를 만든다.
Speed를 -1로 설정하면 역순으로 재생되어 문이 닫히는 애니메이션을 굳이 만들지 않아도 된다.
Make Transition으로 연결을 만들어준 후 세부 설정을 한다.
모든 Transition에 Has Exit Time을 해제해주고, IsOpen bool 값을 추가해주어 문이 열렸을 때는 true로, 문이 닫힐 때는 false로 세팅해준다.
(Has Exit Time은 문이 열리고 닫힐 때까지 일종의 지연시간이 발생하는 것으로, 즉각적인 반응을 원하면 꺼 주는 것이 좋다)
마지막으로 anim_door_open의 루프를 해제한 후 애니메이션이 잘 작동하는지 확인해보자.
우선 게임을 만드는 상황을 염두에 두고 영상과는 다르게 스크립트를 추가하였다.
플레이어와 상호작용한다는 가정하에 인터페이스를 만들고, 이 상호작용으로 문이 열리고 닫힌다고 만들었다.
// 상호작용 인터페이스 생성
public interface IInteraction
{
public void Interact();
}
// 문 컨트롤러
using UnityEngine;
public class DoorController : MonoBehaviour, IInteraction
{
[SerializeField] Animator m_doorAnimator;
private bool IsOpen = false;
public void Interact()
{
IsOpen = !IsOpen;
m_doorAnimator.SetBool("IsOpen", IsOpen);
}
}
영상과는 조금 다른 코드로 작성했고 실질적으로 Open과 Close 또한 따로 작성할 필요가 없다고 판단하여 해당 코드 또한 삭제했다. 여기서 유의해야 할 점에 대해서 알게 된 사실이 있다.
영상에서는 파라미터를 따로 변수명을 만들어서 진행했으나, 사실 이 과정 없이 애니메이터 상의 변수 그대로를 가져와서 사용해도 된다. 다만 유의해야 할 점은 해당 파라미터 변수에 대해 오타가 나면 작동하지 않는다는 것이다
즉 지금의 코드와 구조로 말하자면 아래 사진과 같이 설명할 수 있다.
이렇게 만들어 놓고 보니 문득 그런 생각이 들었다.
지금에는 문을 미는 구조를 생각해 이와 같이 설계했지만, 반대편에서 문을 열 때에도 이런 식으로 애니메이션을 만들면 불편하지 않을까?
그러니까 문이라는 건 결국 한쪽으로만 미는 방식은 아니다 보니, 문을 반대쪽에서도 밀었을 때 역방향으로 밀 수 있게끔 세팅해 봐야겠단 생각을 했다.
우선은 역방향 애니메이션을 만들고 루프를 해제하고, 애니메이터에도 내용을 추가해보았다.
당연한 소리지만 현 상에서는 추가한 애니메이션의 내용이 반영되지 않는다. 우선은 애니메이션이 잘 작동하는지 확인하기 위해 bool변수를 추가했다.
역방항으로 만드니 문이 이상하게 회전하는 것을 확인했다. 아무래도 anim_door_close가 애초에 시계방향으로 문이 닫히는 방향이라 이런 문제가 발생한 듯 보인다. 그렇다면 이 문제를 어떻게 해결해야 할까. 많은 고민이 필요해 보였다.
불가능한 방법이라고는 생각했지만, 애니메이터를 추가로 만들 수는 없었다. 두 개의 애니메여션을 각각 분리해 만들 수 있으면 편리할 거란 생각이 들었지만 아무래도 시스템 상 되지 않는 모양이었다. 생각해보면 이렇게 애니메이터를 여러 개 붙일 수 있으면 나중에 관리도 어려워질 가능성이 있다. 이 방법 말고 다른 방법을 생각해봐야 했다.
문이 양방향으로 열리고 닫히고를 구현하기 위해선, Idle로 돌아간 다음에 양방향으로 열리고 닫히는 과정을 반복해야 한다. 그래서, 여러 가지 방법을 시도하다 보니 알게 된 사실이 있었다.
Close 애니메이션을 따로 만들지 않아도 마치 문이 닫히는 것처럼 작동하게 할 수 있다는 사실을 알게 되었던 것이다. 이와 같이 작성하고 역방항 문 열리기 코드를 추가하고 나면 아래와 같이 작동한다.
당장은 여기까지, 성공적으로 역방향으로 열리는 문을 구현할 수 있었다. 문제는, 언제는 역방향이고 언제는 정방향인지 판단할 기준이 없다는 것이다.
플레이어가 문의 어느 방향에 서 있는지 판정하기 위해서 우선, Transform을 사용하는 방식을 사용해 보기로 했다.
DoorController의 내용을 아래와 같이 수정했고, 플레이어 캐릭터 또한 박스 모양으로 추가도 했다.
using UnityEngine;
public class DoorController : MonoBehaviour, IInteraction
{
[SerializeField] Animator m_doorAnimator;
private bool IsOpen = false;
private bool IsOpen2 = false;
[SerializeField] GameObject player;
public void Interact()
{
Transform m_playerPos = player.transform;
if (transform.position.z > m_playerPos.position.z)
{
OpenDoorCounterClockwise();
}
else
{
OpenDoorClockwise();
}
}
private void OpenDoorClockwise()
{
IsOpen2 = !IsOpen2;
m_doorAnimator.SetBool("IsOpen2", IsOpen2);
}
private void OpenDoorCounterClockwise()
{
IsOpen = !IsOpen;
m_doorAnimator.SetBool("IsOpen", IsOpen);
}
}
다만 이렇게 하면 문제가 발생하는 것을 확인했다.
플레이어 위치 기준으로 문이 제대로 열리고 닫히기는 하지만, 문을 넘어가서 닫으려고 하면 닫히지 않는 것이다. 공포게임 상황에서라면 도망치다가 뒤돌아서 문을 닫는 상황이 생길 건데, 이런 게 되지 않으면 상당히 치명적인 문제가 될 것이다.
이걸 위해선 차라리 문을 닫기 위한 bool 변수가 추가로 필요하다 생각했고, 여러가지로 실험해본 결과 아래와 같은 코드로 완성되었다.
using UnityEngine;
public class DoorController : MonoBehaviour, IInteraction
{
[SerializeField] Animator m_doorAnimator;
private bool IsOpen = false;
private bool IsOpen2 = false;
private bool Close = true;
[SerializeField] GameObject player;
public void Interact()
{
Transform m_playerPos = player.transform;
if (Close == true && transform.position.z > m_playerPos.position.z)
{
OpenDoorCounterClockwise();
}
else if (Close == true && transform.position.z < m_playerPos.position.z)
{
OpenDoorClockwise();
}
else
{
CloseDoor();
}
}
private void OpenDoorClockwise()
{
IsOpen2 = true;
Close = false;
m_doorAnimator.SetBool("IsOpen2", IsOpen2);
m_doorAnimator.SetBool("Close", Close);
}
private void OpenDoorCounterClockwise()
{
IsOpen = true;
Close = false;
m_doorAnimator.SetBool("IsOpen", IsOpen);
m_doorAnimator.SetBool("Close", Close);
}
private void CloseDoor()
{
IsOpen = false;
IsOpen2 = false;
Close = true;
m_doorAnimator.SetBool("IsOpen", IsOpen);
m_doorAnimator.SetBool("IsOpen2", IsOpen2);
m_doorAnimator.SetBool("Close", Close);
}
}
나중에 리팩토링을 할 여지가 있는 코드 상태이나, 현상황에서는 너무 가독성을 해치지 않으면서도 이해할 수 있도록 다소 풀어서 작성하였다. 이와 같이 작성하고, Idle로 돌아가는 애니메이션 Transition을 변경하면 아래와 같이 정상적으로 출력되는 것을 확인할 수 있다.
문제는 이 코드에서 딱 봐도 걱정되는 부분이 있다는 것이다. 문이 항상 정면 방향을 바라볼 수도 없고, 반대방향이나 90도 방향일 수도 있다. 이 상황에서 문이 의도한대로 작동하지 않을 수도 있단 것이다.
문을 90도 방향으로 돌려서 작동해보니, 역시 오작동한다는 사실을 알아냈다.
다만 이 발생한 문제에 관해서는 시간 관계상 다음에 해결하기로 했다 관계상 다음에 해결하기로 했다.