이 글에서 설명할 부분은 그림에서 노란색 부분(Rust Key - Secret Door) 과
파란색 부분(Hidden Key - Hidden Door) 에 대한 내용이다.
해당 내용에 대한 세부 구현 내용은 다음과 같다.
열쇠, 히든 엔딩
- Rust Key 3개를 배치한다.
- Rust Key 3개로 열 수 있는 문(Secret Door)을 만든다.
- Rust Key로 열 수 있는 방 안에 Hidden Key를 배치한다.
- Hidden Key로 열 수 있는 문(Hidden Door)을 만든다.
- Hidden Key로 문을 열고 탈출하면 Hidden Ending을 볼 수 있다.
John Lemon 프로젝트에서는 기본적으로 Level Prefab 이 제공된다.
문을 따로 조작하기 위해서 Level Prefab에 묶여 있는 문 두개를 분리시켜 다른 Prefab으로 만들어 주었다.
Secret Door
Hidden Door
이후 구현 방향은 다음과 같다.
- 문에 트리거를 자식오브젝트로 추가한다.
- 플레이어가 특정 조건을 충족한 상태로 트리거에 충돌하면, 문을 열겠냐는 UI가 뜬다.
- 특정 키(Space)로 상호 작용하면 문이 열린다.
빈 오브젝트를 생성한 후, 트리거를 추가하여 문의 자식 오브젝트로 넣어주었다.
택스트 UI를 생성한 후, 적당한 멘트를 입력해 놓는다.
해당 UI는 특정 조건 (Rust Key 3개, 또는 Hidden Key) 을 만족했을 때만 보여야 하므로 활성화를 체크 해제해 놓는다.
또한 UI가 문 오브젝트 위에 뜨도록 간단한 스크립트를 작성하여 추가한다.
해당 스크립트는 Secret Door의 트리거에 적용된 스크립트이다.
Hidden Door에 적용되는 스크립트 또한 거의 동일한 맥락이므로,
Secret Door의 구현 과정을 중심으로 작성해보고자 한다.
public class OpenSecretDoor : MonoBehaviour {
public GameObject secretDoor;
public GameObject openDoorUI;
private void OnTriggerEnter(Collider other) {
if (!other.CompareTag("Player") || GameManager.rustKey < 3)
return;
openDoorUI.SetActive(true);
}
private void OnTriggerStay(Collider other) {
if (!other.CompareTag("Player") || GameManager.rustKey < 3)
return;
if (Input.GetKeyDown(KeyCode.Space))
OpenDoor();
}
private void OpenDoor() {
openDoorUI.SetActive(false);
Destroy(secretDoor);
GameManager.isSecretDoorOpened = true;
}
private void OnTriggerExit(Collider other) {
if (!other.CompareTag("Player") || GameManager.rustKey < 3)
return;
openDoorUI.SetActive(false);
}
}
기본적으로 콜라이더에 충돌한 오브젝트가 플레이어가 아니거나, 플레이어가 모은 rustKey가 3개가 아니면 어떠한 동작도 하지 않는다.
만약 위 두 조건이 충족될 경우, 범위 안에 들어오면(OnTriggerEnter) UI가 활성화되어 보이고,
범위 밖으로 나가면(OnTriggerExit) UI가 비활성화 된다.
조건이 충족되고 범위 내에서(OnTriggerStay) Space 키를 입력하면 문이 열린다.
문이 열리는 동작을 하는 OpenDoor() 함수에서는 문 오브젝트를 destroy해버린다.
OpenDoor() 함수로 문을 여는 동작을 실행할 때, 문 오브젝트를 삭제하는 것보단
문 오브젝트를 회전하여 문이 열리는 것처럼 표현하는 것이 더 좋을 것 같다고 생각했다.
(이런 식으로 ..)
하지만 유니티에서 회전은 생각보다 호락호락하지 않았다.
해당 글에서 소개되는 방식을 참고하여 스크립트를 작성했다.
private void Update() {
if (Input.GetKeyDown(KeyCode.Space))
startTest = true;
if(startTest)
if(transform.rotation.eulerAngles.y > -45)
transform.RotateAround(transform.GetChild(0).position, Vector3.up, -90f * Time.deltaTime);
}
public void RotateAround (Vector3 point, Vector3 axis, float angle);
//point를 기준점으로 axis 축에서 angle 만큼 회전한다.
테스트는 3D 큐브 오브젝트를 하나 만든 후 진행해보았다.
목표는 오브젝트의 transform.rotation.y 값을 45에서 -45로 바꾸는 것이다.
결과를 보니 원하는 곳에서 멈추지 않고 계속 돌았다.
이유를 찾아보다가 eulerAngles 에서 음수를 취급하지 않는다는 내용을 보게 되었고,
코드를 다음과 같이 수정했다.
private void Update() {
...
if(startTest)
if(transform.rotation.eulerAngles.y > 315 || transform.rotation.eulerAngles.y < 45)
transform.RotateAround(transform.GetChild(0).position, Vector3.up, -90f * Time.deltaTime);
}
회전을 구현하는 데에 성공했다.
이후 호기심이 생겨 다른 방식을 시도해 보았다.
rotation 값을 직접 쓰지 말고 Quaternion을 쓰면 어떨까 하는 생각이었다.
private void Update() {
...
if(startTest)
if(transform.rotation != Quaternion.Euler(0f, -45f, 0f))
transform.RotateAround(transform.GetChild(0).position, Vector3.up, -90f * Time.deltaTime);
}
별 생각 없었는데 잘 작동해서 놀랐다.
이런 상황같이 원하는 오일러 각이 음수일 땐 Quaternion을 사용하면 될 것 같다.
이렇게 회전 자체는 구현이 완료되었는데,
이상하게 문 오브젝트는 스크립트를 적용해도 회전이 되지 않았다.
자세한 이유는 모르겠지만, 아무래도 Level 프리팹에서 Door 프리팹을 분리할 때 뭔가가 있었던 것 같다.
그렇기 때문에 어쩔 수 없이 문 오브젝트를 열 때, 오브젝트를 destroy 하도록 설정하였다.
히든 엔딩은 Hidden Key를 얻고 Hidden Door을 열고 나가야 볼 수 있다.
복도 끝에 엔딩 트리거를 새로 만들어준다. 방식은 튜토리얼에서 소개된 내용과 같다.
Hidden Door의 오브젝트 설정과 문을 여는 동작은 Secret Door의 것과 동일하다.
히든 엔딩을 만들었다. ‡