XR플밍 - 10. XR 애플리케이션 개발을 위한 VR/AR 프로그래밍 - 개인 프로젝트(VR) VRGunRange 3일차 (6/16)

이형원·2025년 6월 16일
0

XR플밍

목록 보기
106/215

0. 들어가기에 앞서
왠만한 시스템은 이제 거의 구현된 상태다 보니, 이제는 디테일에 대한 부분을 신경 쓸 차례다.

1. 3일차 작업 정리

오늘은 코딩 작업보다 디자인이나 외적인 작업이 많았던 편이다.

  • 맵 에셋 추가 및 반영, 총기 재질 반영 등 디테일
  • 튜토리얼 내용 추가, UI 추가
  • 컨트롤러 디자인 변경
  • 과녁 점수 차등지급 시스템 구현
  • 빌드 테스트 및 세부 디테일 수정
  • 빌드 시의 아이콘 설정

2. 문제의 발생과 해결과정

2.1 튜토리얼 시스템

튜토리얼 시스템을 어떻게 만들까 고민하다가, 다음과 같이 처음 시작 지점 기준으로 오른쪽을 살피면 튜토리얼을 설명하는 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로 띄우는 방식으로 처리했다.
여기에서 시험용 과녁을 추가로 만들 생각도 있다.

2.2 컨트롤러 디자인 변경

컨트롤러를 손 모양으로 바꾸는 방식에 대한 시도를 계속하고 있었다. 그리고 바꾸는 데 성공했다.

사실 방법 자체는 간단했는데, 인스펙터 상의 XR Interaction Toolkit을 하나하나 다 뜯어보면 되는 일이었다.

컨트롤러의 외형을 변경할 수 있는 방법은 Model 칸에 있다. 여기에서 말 그대로 모델 프리팹만 가져오면 되며, 손 모양으로 컨트롤러 모양을 변경할 수 있었다.
다만 여기에서 끝날 것이 아니고, 이렇게만 하면 조금 부자연스러운 부분이 나오게 된다.

손바닥에서 튀어나온 이 돌기 같은 부분, 이 부분은 Poke Interactor 부분이다. 이런 부자연스러운 부분도 없애주기 위해 해당 부분의 MeshRenderer를 꺼 주자.

이와 같이 설정하고 실제로 테스트 및 총 잡는 모션 등을 취해보았다.

실제 손 인식 방식은 아니나, 그래도 나름 자연스러운 모션이 나오는 것을 확인했다.
다만 애니메이션을 적용하는 방법은 아직 찾을 수가 없어, 방아쇠를 당기는 등의 손가락 디테일 같은 것은 적용하지 못했다. 이 부분은 필수적이진 않으나, 가능하면 방법을 찾아보려고 생각중이다.

2.3 과녁의 맞춘 부분에 따른 점수 차등 시스템

필자는 별로 신경쓰지 않고 있었던 부분이지만, 지인 테스트를 부탁해보았을 때 지인이 피드백한 내용이 있었다.
과녁 어느 부분을 맞췄는지에 따라 점수가 다르게 나와야하는 거 아니냐는 피드백이었다. 이 부분은 우선순위에 있던 작업은 아니지만, 확실히 그런 부분의 구현이 있으면 좋겠다는 생각이 들어 구현을 시도랬다.

이 기능을 만들기 위해 활용한 방법은 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점을 얻는 방식으로 설계하여 사격 게임의 느낌이 날 수 있도록 수정하였다.

다만 실제 과녁의 직경이 계산되지 않고 총알이 어디에 맞는지 잘 안보이는 이슈로, 계속 테스트해보면서 과녁 점수 판정에 대한 밸런싱을 진행할 예정이다.

3. 수정해야 할 점과 과제

3.1 (미해결 버그/이슈) 총을 든 채로 이동할 때 총이 덜덜 떨리는 현상

여전히 해결되지 않은 문제다. 물리 작동 방식을 변경해도 해결되지 않고, 원인이 뭔지 모르겠어서 아직도 해결하지 못했다... 다른 VR 예시 등을 확인해봐야 할 것 같다.

3.2 (버그?) NullReferenceException

UnityAction을 적용한 이후로 이상하게 가끔 게임 테스트 후 종료하면 이 오류가 뜨는 경우가 있다. (심지어 매번 발생하는 것도 아니고 가끔 발생한다. 웃긴 건 글 작성 시점엔 또 안 발생한다.) 싱글톤에서 Score로 Action을 해제시키는 과정에서 발생하는데 혹시나 계속 문제가 발생하면 해결해봐야 할 것 같다.

3.3 (구현)과녁 효과음 추가

과녁 중앙 - 500점을 맞았을 때 효과음을 넣어주는 기능도 괜찮아 보인다. 효과음을 찾고 해당 경우에 효과음이 발생하도록 해보려고 한다.

3.4 (도전) 랭킹시스템 구현

최고기록을 기록할 수 있는 랭킹 시스템을 구현해보고자 한다.
다만 최고기록을 저장하는 건 그렇다 쳐도, 키보드를 구현할 수 있을지가 난감하다.

profile
게임 만들러 코딩 공부중

0개의 댓글