0. 들어가기에 앞서
왠만한 시스템은 이제 거의 구현된 상태다 보니, 이제는 디테일에 대한 부분을 신경 쓸 차례다.
오늘은 코딩 작업보다 디자인이나 외적인 작업이 많았던 편이다.
튜토리얼 시스템을 어떻게 만들까 고민하다가, 다음과 같이 처음 시작 지점 기준으로 오른쪽을 살피면 튜토리얼을 설명하는 UI를 우선적으로 만들었다.
해당 UI는 StartUI처럼 한 번 활성화 후 다시는 켜지지 않는 시스템 대신 계속해서 볼 수 있는 시스템으로 만들었다.
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class TutorialUI : MonoBehaviour
{
[Serializable]
class Step
{
[SerializeField]
public GameObject StepObject;
[SerializeField]
public string ButtonText;
}
[SerializeField] private TextMeshProUGUI m_StepButtonTextField;
[SerializeField] private TextMeshProUGUI m_StepNumTextField;
[SerializeField] List<Step> m_StepList = new List<Step>();
int m_CurrentStepIndex = 0;
private void Awake() => Init();
private void Init()
{
m_StepNumTextField.text = $"{m_CurrentStepIndex + 1} / {m_StepList.Count}";
}
public void Next()
{
m_StepList[m_CurrentStepIndex].StepObject.SetActive(false);
m_CurrentStepIndex = (m_CurrentStepIndex + 1) % m_StepList.Count;
m_StepList[m_CurrentStepIndex].StepObject.SetActive(true);
m_StepButtonTextField.text = m_StepList[m_CurrentStepIndex].ButtonText;
m_StepNumTextField.text = $"{m_CurrentStepIndex + 1} / {m_StepList.Count}";
}
}
튜토리얼은 필수적인 것이 아니며, 필요할 때에만 확인할 수 있도록 이와 같이 UI로 띄우는 방식으로 처리했다.
여기에서 시험용 과녁을 추가로 만들 생각도 있다.
컨트롤러를 손 모양으로 바꾸는 방식에 대한 시도를 계속하고 있었다. 그리고 바꾸는 데 성공했다.
사실 방법 자체는 간단했는데, 인스펙터 상의 XR Interaction Toolkit을 하나하나 다 뜯어보면 되는 일이었다.
컨트롤러의 외형을 변경할 수 있는 방법은 Model 칸에 있다. 여기에서 말 그대로 모델 프리팹만 가져오면 되며, 손 모양으로 컨트롤러 모양을 변경할 수 있었다.
다만 여기에서 끝날 것이 아니고, 이렇게만 하면 조금 부자연스러운 부분이 나오게 된다.
손바닥에서 튀어나온 이 돌기 같은 부분, 이 부분은 Poke Interactor 부분이다. 이런 부자연스러운 부분도 없애주기 위해 해당 부분의 MeshRenderer를 꺼 주자.
이와 같이 설정하고 실제로 테스트 및 총 잡는 모션 등을 취해보았다.
실제 손 인식 방식은 아니나, 그래도 나름 자연스러운 모션이 나오는 것을 확인했다.
다만 애니메이션을 적용하는 방법은 아직 찾을 수가 없어, 방아쇠를 당기는 등의 손가락 디테일 같은 것은 적용하지 못했다. 이 부분은 필수적이진 않으나, 가능하면 방법을 찾아보려고 생각중이다.
필자는 별로 신경쓰지 않고 있었던 부분이지만, 지인 테스트를 부탁해보았을 때 지인이 피드백한 내용이 있었다.
과녁 어느 부분을 맞췄는지에 따라 점수가 다르게 나와야하는 거 아니냐는 피드백이었다. 이 부분은 우선순위에 있던 작업은 아니지만, 확실히 그런 부분의 구현이 있으면 좋겠다는 생각이 들어 구현을 시도랬다.
이 기능을 만들기 위해 활용한 방법은 OnCollision에서 사용 가능한 기능인 collision.GetContact라는 기능이다.
collision.GetContact(0).point로 충돌 지점의 벡터값을 알아낼 수 있고, 이를 과녁의 중심점 기준으로 거리를 계산하여, 거리가 가까울 수록 고득점을 주는 방식으로 설계했다.
구체적인 방법은 아래와 같이 작성했다.
using UnityEngine;
/// <summary>
/// 과녁의 애니메이션 조정용
/// </summary>
public class TargetController : MonoBehaviour
{
private Animator m_animator;
[SerializeField] Transform m_target;
private void Awake() => Init();
private void Init()
{
m_animator = GetComponent<Animator>();
}
// 과녁 활성화
public void ActiveTarget()
{
m_animator.SetBool("IsActive", true);
}
// 과녁 비활성화
public void InactiveTarget()
{
m_animator.SetBool("IsActive", false);
}
// 과녁 활성화 여부 체크
public bool IsActiveTarget()
{
return m_animator.GetBool("IsActive");
}
// 총알과 과녁 충돌 시
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Bullet") && IsActiveTarget()
&& !GameManager.Instance.IsGameOver())
{
// 과녁 충돌 지점에 따른 점수 차등 지급
Vector3 hitPoint = collision.GetContact(0).point;
Vector3 targetCenter = m_target.position;
float distance = Vector3.Distance(hitPoint, targetCenter);
int score = CalculateScore(distance);
GameManager.Instance.AddScore(score);
m_animator.SetBool("IsActive", false);
}
}
// 과녁 충돌 지점 - 중심부와의 거리와 가까울 수록 고득점으로 점수를 지급
private int CalculateScore(float distance)
{
if (distance <= 0.15f) return 500;
else if (distance <= 0.25f) return 400;
else if (distance <= 0.35f) return 300;
else if (distance <= 0.45f) return 200;
else return 100;
}
}
이와 같은 방식으로 과녁의 중심점을 맞추면 500점, 아예 가장자리를 맞추면 100점을 얻는 방식으로 설계하여 사격 게임의 느낌이 날 수 있도록 수정하였다.
다만 실제 과녁의 직경이 계산되지 않고 총알이 어디에 맞는지 잘 안보이는 이슈로, 계속 테스트해보면서 과녁 점수 판정에 대한 밸런싱을 진행할 예정이다.
여전히 해결되지 않은 문제다. 물리 작동 방식을 변경해도 해결되지 않고, 원인이 뭔지 모르겠어서 아직도 해결하지 못했다... 다른 VR 예시 등을 확인해봐야 할 것 같다.
UnityAction을 적용한 이후로 이상하게 가끔 게임 테스트 후 종료하면 이 오류가 뜨는 경우가 있다. (심지어 매번 발생하는 것도 아니고 가끔 발생한다. 웃긴 건 글 작성 시점엔 또 안 발생한다.) 싱글톤에서 Score로 Action을 해제시키는 과정에서 발생하는데 혹시나 계속 문제가 발생하면 해결해봐야 할 것 같다.
과녁 중앙 - 500점을 맞았을 때 효과음을 넣어주는 기능도 괜찮아 보인다. 효과음을 찾고 해당 경우에 효과음이 발생하도록 해보려고 한다.
최고기록을 기록할 수 있는 랭킹 시스템을 구현해보고자 한다.
다만 최고기록을 저장하는 건 그렇다 쳐도, 키보드를 구현할 수 있을지가 난감하다.