VR 캐주얼 FPS제작

2022. 6. 26. 23:21Unity/VR

이번 포스팅은 기존에 만들었던 프로젝트의 방향성을 확실하게 잡고자 캐주얼 fps장르로 변경한 것에 기초해 제작을 할 것입니다.


기존 프로젝트의 문제점

기존에 만들고 있던 프로젝트의 가장 큰 문제점은 바로 "전투가 정형화 되있지가 않다" 였다. 

 

말만 언뜻 들어서는 '오히려 좋은 것 아닌가?'라고 생각할 수 있는데 이는 전투의 자유화를 의미하기도 하지만 동시에 개발의 난이도의 기하 급수적인 상승을 초래하고 전투의 긴장감을 오히려 떨어뜨릴 수 있는 요소이기도 했다.

 

만약 200가지의 스킬을 구현해 그 것을 독자적으로 3가지를 골라 전투를 진행한다는 것의 의미는 강력한 스킬 3가지만으로도 모든 전투를 행할 수 있다는 것도 의미하기 때문이다.

 

그렇기에 기존 전투시스템을 어느정도 방향성을 잡아두고 그 안에서 자신이 여러 스킬을 고를 수 있도록 하는 것이 이번 개발의 포인트 였다.

 

그렇기에 FPS의 시스템을 기반으로 자유도 높은 스킬구현을 목표로 하는 것으로 프로젝트의 방향성을 잡았다.

 


총기의 XR Grab Interactor컴포넌트 추가

위 이미지의 총기가 현재 프로젝트 메인으로 사용될 총기이다. 좀 특징적으로 탄창이 빠져있는 것을 확인 할 수 있다.

 

위처럼 TriggerPoint즉 손이 부착될 장소에다가 XR Grab Interatable이 부착이 되어있다.

 

XR Grab Interactable컴포넌트의 주요 특징중의 하나가 컨트롤러의 XR Ray를 현재 물체의 콜라이더에 부딪혀야 그 때 그랩이 가능해지는 것이다. 그렇기에 콜라이더 설정은 그랩의 느낌을 부드럽게 하기 위해서 신경을 많이 써야한다.

 

 

위 사진은 오브젝트 하나에 XR Ray를 인식해줄 콜라이더 4개를 설치한 모습이다. 

여기서 의문점이 들 수 있는데 왜 굳이 1~2개의 콜라이더로 만들어주면 될 것을 저렇게 복잡하게 만들어 놓은 것인가 하면 만약 XR Ray의 시작점이 collider안에서 시작한다면 레이의 충돌을 제대로 인식 하지 못하기 때문에 저렇게 2겹씩으로 만들어 준 것이다.

 

 

참고로 위의 총기는 Red Dot Sight까지 구현이 되있는데 이 것의 대한 자료는 아래 링크에다가 첨부해두겠다.

https://youtu.be/azco2EB9-BM


총기와 탄창 시스템

 

먼저 총기 내부 시스템 부터 설명하겠다.

 

총기의 가장 중요한 요소인 "발사"를 담당하는 GunShot_Trigger class가 가장 중요한 스크립트이다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR;

public class GunShot_Trigger : MonoBehaviour
{
    public XRController controllerR;

    private XRGrabInteractable grabInteractable;
    public Transform shot_start_point;


    public float bullet_speed;
    public int bullet_count;

    public string bullet_type;
    public GameObject current_mag;
    public GameObject bullet;   //기본 공격, 스킬 공격 통합


    public bool magEquiped = false;


    private bool reload = true;  //단발용 변수


    // Start is called before the first frame update
    void Awake()
    {
        grabInteractable = GetComponent<XRGrabInteractable>();
        controllerR = GameObject.Find("RightHand Controller").GetComponent<XRController>();
    }



    // Update is called once per frame
    void Update()
    {
        if (controllerR.inputDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool buttonValue))
        {
            if (buttonValue == true && magEquiped == true)
            {
                magEquiped = false;
                bullet = null;
                bullet_count = 0;
                bullet_speed = 0;
                bullet_type = null;

                current_mag.GetComponent<Rigidbody>().isKinematic = false;
                current_mag.GetComponent<Rigidbody>().useGravity = true;
                current_mag.transform.parent = null;
            }
        }

        if (magEquiped == true)
        {
            if (controllerR.inputDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerTargetR))
            {
                if (triggerTargetR >= 0.9 && bullet_type == "basic" && reload == true)
                {
                    Shot();
                    reload = false;  
                }

                if (triggerTargetR >= 0.9 && bullet_type == "skill" && reload == true)
                {
                    Skill_Shot();
                    reload = false;
                }






                if (triggerTargetR <= 0.5)
                {
                    reload = true;
                }

            }
        }


    }

    void Shot()
    {
        if (bullet_count > 0)
        {
            GameObject bullet_set = Instantiate(bullet, shot_start_point.position, shot_start_point.rotation);
            bullet_set.GetComponent<Rigidbody>().AddForce(bullet_set.transform.forward * bullet_speed);

            bullet_count--;
        }

    }

    void Skill_Shot()
    {
        if(bullet_count > 0)
        {
            GameObject skill_set = Instantiate(bullet, shot_start_point.transform);
            skill_set.transform.SetParent(shot_start_point);
            bullet_count--;
        }
    }

    void Destory_gun(XRBaseInteractor interactor)
    {
        Destroy(this.gameObject);
    }

}

위의 코드는 상당히 단순하다. 왜냐하면 기능분리를 철저히해 오로지 "발사"에만 집중을 했기 때문이다.

 

하지만 발사를 하는 것에도 다양한 정보가 필요한데 위의 스크립트의 정보를 빌리자면 

1. 발사의 시작지점

2. 탄속

3. 발사가능 갯수

4.총알의 타입(기본인지 스킬인지)

5.현재 탄알집이 결합이 되있는지 

등이 있다.

 

이 정보들을 통해 위 스크립트의 역할은 컨트롤러의 트리거값을 받아 일정이상의 값이 감지되면 (아래 코드)

 if (controllerR.inputDevice.TryGetFeatureValue(CommonUsages.trigger, out float triggerTargetR))
            {
                if (triggerTargetR >= 0.9 && bullet_type == "basic" && reload == true)
                {
                    Shot();
                    reload = false;  
                }

                if (triggerTargetR >= 0.9 && bullet_type == "skill" && reload == true)
                {
                    Skill_Shot();
                    reload = false;
                }

                if (triggerTargetR <= 0.5)
                {
                    reload = true;
                }

발사 가능 갯수가 0보다 클시 입력된 총알(스킬)을 발사 시작지점의 생성을 해 발사를 하는 것이다.

 

따로 특징적인 요소가 있다면 역시 이부분이다.

f (controllerR.inputDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool buttonValue))
        {
            if (buttonValue == true && magEquiped == true)
            {
                magEquiped = false;
                bullet = null;
                bullet_count = 0;
                bullet_speed = 0;
                bullet_type = null;

                current_mag.GetComponent<Rigidbody>().isKinematic = false;
                current_mag.GetComponent<Rigidbody>().useGravity = true;
                current_mag.transform.parent = null;
            }
        }

위 코드는 "탄알집 제거" 역할을 한다. 오른쪽 컨트롤러의 프라이머리 버튼이 눌리면 탄알집 결합 여부 변수가 false가 되고 다른 정보들 또한 초기화가 된다. 마지막으로 장착되있던 탄알집의 중력을 적용하고 부모자식을 풀어 알아서 땅으로 떨어질 수 있도록 구현했다.

 

 

다음은 탄알집이다.

 

전체적인 탄알집의 모양은 이렇게 된다.

 

위의 작은 콜라이더 박스하나는 탄창의 삽입 애니메이션을 위해 만들어진 트리거이고 나머지 콜라이더는 Grab콜라이더를 위해 제작되었다.

 

다음은 스크립트이다. 가장 중요한 파트인 OnTriggerEnter부분을 봐보자.

 private void OnTriggerEnter(Collider collider)
    {
        if (collider.name == "mag_hole" && gunShot_Trigger.magEquiped == false && equiped == false)
        {
            equiped = true;
            gunShot_Trigger.magEquiped = true;

            gunShot_Trigger.bullet_count = this.bullet_count;
            gunShot_Trigger.bullet_speed = this.bullet_speed;


            gunShot_Trigger.bullet = this.bullet; // skill or bullet

            gunShot_Trigger.bullet_type = bullet_type;

            gunShot_Trigger.current_mag = this.gameObject; //나 자신을 전달

            basic_shot_parent.GetComponent<XRGrabInteractable>().enabled = false;  // 꽃히면 더이상 안 움직이게
            basic_shot_parent.GetComponent<Mag_In_Anim>().enabled = true; //애니메이션 시작


            basic_shot_parent.transform.parent = gun.transform;  // 총과 같이 다니도록

            gunShot_Trigger.magEquiped = true;

            Debug.Log("장착");

            destory_other_mag();          
        }
    }

 이 스크립트는 총기발사를 위한 정보를 대부분가지고 있다. 그래서 위 스크립트하나만으로 대부분의 스킬, 공격들을 제어할 수 있다.

참고로 탄창결합 애니메이션을위해 직전에 XRGrabInteractable.enabled가 false로 설정되있는 것을 볼 수있다.

이런식으로 스크립트 하나로 탄창의 특성을 나타내고 탄알집이 결합되었을 때 이 정보를 총기에 전달해 준다.

 

 


시연

 

 

'Unity > VR' 카테고리의 다른 글

Unity Shader 적용된 Material Property Runtime변경  (0) 2022.06.28
XR Interaction ToolKit Teleportation, Grab 분리  (0) 2022.06.26
Unity 스킬 확장  (0) 2022.02.22
Unity 스킬 피격 처리  (0) 2022.01.15
Unity GunShot Skill 생성  (0) 2022.01.12