이제 프로젝트 제출까지 1일 남았다. 휴일 작업의 막바지로 버그 수정과 테스터 막바지에 들어서기 시작했다.
지금까지의 서랍은 이렇게 Mesh Collider만 생성하고, 물건을 넣지는 않았었다. 하지만 이렇게 메쉬 충돌체로만 되어 있는 서랍에 물건을 배치할 수가 없다. 내부가 빈 것이 아닌 꽉 차 있는 형태로 충돌체가 생성되기 때문이다.
그래도 공포 게임인데 서랍 안의 열쇠 같은 걸 구현해야 하지 않을까. 실제 게임사 같은 데라면 이런 걸 충돌체 작업을 해 줄 테지만... 에셋을 쓰는 이상 손수 작업할 수밖에 없었다.
결국 손수 작업을 해 주었다. 여기에서 한 물체에 동일한 충돌체를 동일한 것을 여러 개 넣을 수 있단 사실을 알게 되었는데, 이때 편집할 수 있는 편집기 버튼은 맨 위의 충돌체에서만 뜨게 된다. 간단한 사실이지만 초반에는 왜 편집이 안 뜨지 끙끙대기도 했다.
이와 같이 작업을 해서 충돌체를 만들고, 열쇠 같은 아이템은 Rigidbody가 있는지 확인하고서 서랍 안에 넣고 재생해봤더니 잘 작동하는 것을 확인했다. 다만, 서랍을 열고 닫았을 때 열쇠가 서랍 안에 있지 않았다.
서랍에 Rigidbody를 쓰거나 여러가지 방법을 사용했었는데 서랍에 열쇠가 딸려나오지 않았다.
왜 이런 현상이 생기는 걸까? 문제는 서랍을 여는 방법 자체가 애니메이션이기 때문이었다.
애니메이션의 경우 애니메이션이 재생되는 과정에서 충돌체가 움직이지 않고, 애니메이션이 재생이 완료된 후 충돌체가바로 좌표가 이동하는 것처럼 이동한다. 따라서 열쇠는 충돌체가 움직이지 않는 상태에서 가만히 있다가 그대로 튕겨나가버리는 것이다.
그렇다면 지금 현재의 상황에선 서랍 안의 열쇠를 구현할 방법이 없는 것일까?
놀랍게도 아예 방법이 없지는 않았다.
열쇠를 프리팹의 자식 오브젝트로 연결시켜서 넣은 방법이다. 이 방법으로 하면 열쇠가 애초에 서랍에 연결된 채로 애니메이션째로 이동하게 된다. 이로서 서랍
팀장님이 서랍의 사운드를 조절을 하는 기능을 넣어달라고 요청하였다. 사운드매니저 자체는 팀장님이 작업하셨고, 나는 사운드를 맞춰서 조정해야 했다. 애니메이션 속도보다 사운드가 느리게 나와서 애니메이션 속도 조절을 했다.
using System.Collections;
using UnityEngine;
public class DeskDrawerController : MonoBehaviour, IInteractable
{
private Animator m_animator;
void Awake()
{
m_animator = GetComponent<Animator>();
}
public void Interact()
{
DrawerSound();
StartCoroutine(DrawerAnimator(m_animator));
}
IEnumerator DrawerAnimator(Animator animator)
{
bool isOpen = animator.GetBool("IsOpen");
isOpen = !isOpen;
if(isOpen)
{
yield return new WaitForSeconds(0.2f);
animator.SetBool("IsOpen", isOpen);
}
else
{
yield return new WaitForSeconds(0.7f);
animator.SetBool("IsOpen", isOpen);
}
}
private void DrawerSound()
{
bool isOpen = m_animator.GetBool("IsOpen");
if (!isOpen)
{
GameManager.Instance.Audio.PlaySound(SoundType.DrawerOpen);
}
else
{
GameManager.Instance.Audio.PlaySound(SoundType.DrawerClose);
}
}
}
또한 닫힐 때의 소리가 좀 더 길어서 애니메이션 속도를 늘렸다.
저번에 아웃라인 셰이더가 오류로 작동하지 않아서 그대로 주석 처리하고 기능 구현을 보류했었는데, 또 어느 순간부터 아웃라인 셰이더가 정상작동하기 시작했다.
아무래도 다들 에셋을 공유받은 뒤부터 정상작동하는 것으로 추정되는데, 언제 또 문제를 일으킬 수 있으니 IInteractable로 만드는 것은 위험하고 생각했다.
그래서 아예 새로운 컴포넌트로 빼서 작업을 하고, 정상 작동하는지 확인했다.
using UnityEngine;
public class OutlineController : MonoBehaviour
{
private Material[] mat = new Material[2];
private bool isDetected = false;
[SerializeField] private Material m_material1;
[SerializeField] private Material m_material2;
[SerializeField] private MeshRenderer m_renderer;
void Start()
{
mat[0] = m_material1;
mat[1] = m_material2;
}
private void Update()
{
ChangeMat(isDetected);
}
private void ChangeMat(bool isDetected)
{
if (isDetected)
{
m_renderer.material = mat[1];
}
else
{
m_renderer.material = mat[0];
}
}
public bool OutlineOn()
{
isDetected = true;
return isDetected;
}
public bool OutlineOff()
{
isDetected = false;
return isDetected;
}
}
private void DisplayInteractableObjectUI()
{
if (m_detectedObject == null )
{
if (m_previousDetectedObject != null && m_previousDetectedObject.GetComponent<OutlineController>() != null)
{
m_previousDetectedObject.GetComponent<OutlineController>().OutlineOff();
}
m_ineractionPanel.SetActive(false);
return;
}
var interactionKey = PlayerPrefs.GetString("Interaction");
if (m_detectedObject.CompareTag("Item"))
{
m_interactionPopupText.text = $"아이템을 얻으려면 [{interactionKey}]를 누르세요.";
m_ineractionPanel.SetActive(true);
m_previousDetectedObject = m_detectedObject;
}
else if (m_detectedObject.CompareTag("InteractableObject"))
{
if (m_detectedObject.GetComponent<IsLockedDoor>() != null && m_detectedObject.GetComponent<IsLockedDoor>().IsLocked())
{
if(GameManager.Instance.Inventory.FindKey())
m_interactionPopupText.text = $"열쇠를 보유하고 있습니다.\n문을 열려면 [{interactionKey}]를 누르세요.";
else
m_interactionPopupText.text = "잠긴 문입니다. 열쇠를 찾아주세요.";
m_ineractionPanel.SetActive(true);
}
else
{
if(m_detectedObject.GetComponent<OutlineController>() != null)
{
m_detectedObject.GetComponent<OutlineController>().OutlineOn();
}
m_interactionPopupText.text = $"상호작용을 하려면 [{interactionKey}]를 누르세요.";
m_ineractionPanel.SetActive(true);
}
m_previousDetectedObject = m_detectedObject;
}
else
{
if (m_previousDetectedObject != null && m_previousDetectedObject.GetComponent<OutlineController>() != null)
{
m_previousDetectedObject.GetComponent<OutlineController>().OutlineOff();
}
m_ineractionPanel.SetActive(false);
m_previousDetectedObject = m_detectedObject;
}
}
MeshRenderer를 따로 받는 이유는, Desk 프리팹처럼 컴포넌트 자체에 MeshRenderer를 조작해야 하는 경우와, Door 프리팹 같이 자식 오브젝트로 MeshRenderer를 조작해야 하는 경우로 나뉘기 때문이었다. 출력되어야 하는 부분을 받은 뒤 MeshRenderer로 재질을 변경해주는 방식으로 구현했다.
이렇게 하면 플레이어에서도 새로운 오브젝트가 추가되면 코드를 수정할 필요 없이 Outline만 잘 받아오면 된다.