[VR 개발] 메타 QUEST2를 사용하여 복셀(Voxel) 생성하는 게임 만들기

728x90

OCULUS QEUST2를 사용하여 Right Handler의 A버튼을 눌렀을 때, 조준점에 복셀(Voxel : 3차원 큐브)을 무한대로 생성하는 미니 게임을 제작해보도록 한다.

 

VR 복셀 생성 게임 만드는 방법

1) 기본 배경 및 복셀 세팅

ARAVRInput.unitypackage
0.00MB

먼저 새로운 씬에 해당 파일을 임포트해준다. 다음 패키지는 oculus quest2 실행과 handler 작동에 관한 tool kit으로 생각하면 되겠다. 그리고 하이라키 상에 Cube 생성 후 Voxel로 rename, Create Empty 생성 후 Voxel Maker로 rename 해준다(각각 스크립트와 연결)

 

2) 조준점 UI 세팅

하이라키 창에서 UI - Image를 생성, Canvas 이름을 Crosshair로 rename 후 Transform 값 적절히 세팅, Render Mode를 World Space로 변경 / Image에 조준점 이미지 파일 2D Sprite 형식으로 변경 후 apply

 

3) 내장 쉐이더 설치

https://unity3d.com/kr/get-unity/download/archive?_ga=2.2549168.1806249266.1631163784-1458935578.1624407536 

 

Get Unity - Download Archive - Unity

Unity is the ultimate game development platform. Use Unity to build high-quality 3D and 2D games, deploy them across mobile, desktop, VR/AR, consoles or the Web, and connect with loyal and enthusiastic players and customers.

unity3d.com

유니티3D 다운로드로 가서 자신의 버전에 맞는 유니티 버전을 찾는다. 그리고 다음과 같이 자신이 사용하고 있는 운영체제에 맞는 버전을 찾고 "내장 셰이더"를 다운 받아 준다. 설치 후 BuildInResouceExtras - UI - UI Default.shader 파일을조준점 이미지(Crosshair)가 속한 폴더에 복붙해준다. 해당 파일을 Crosshair로 rename 해준다.

// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

Shader "Crosshair"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest Always
        Blend One OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile_local _ UNITY_UI_CLIP_RECT
            #pragma multi_compile_local _ UNITY_UI_ALPHACLIP

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                half4  mask : TEXCOORD2;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float4 _MainTex_ST;
            float _UIMaskSoftnessX;
            float _UIMaskSoftnessY;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                float4 vPosition = UnityObjectToClipPos(v.vertex);
                OUT.worldPosition = v.vertex;
                OUT.vertex = vPosition;

                float2 pixelSize = vPosition.w;
                pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));

                float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
                float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);
                OUT.texcoord = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
                OUT.mask = half4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy)));

                OUT.color = v.color * _Color;
                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = IN.color * (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);

                #ifdef UNITY_UI_CLIP_RECT
                half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw);
                color.a *= m.x * m.y;
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif

                color.rgb *= color.a;

                return color;
            }
        ENDCG
        }
    }
}

crosshair 스크립트에서는 2가지 부분만 수정해주면 된다. 먼저 맨 상단에 있는 shader 이름을 "Crosshair"로 바꿔준다. 그리고 44번 째 줄을 "ZTEST Always" 로 수정해주면 된다. 스크립트 수정이 완성되면 해당 파일이 속한 하이라키 창에서 새로운 material을 생성 후 standard - Crosshair로 변경해주면 정상적으로 매핑된다. 마지막으로 Image의 Source Image와 material에 생성한 각각의 오브젝트를 연결해주면 된다.

 

4) 유니티 VR 세팅

생성된 복셀은 prefab화 시켜주고 기존의 복셀을 제거하며 복셀 메이커 창에 소스를 각각 매핑해준다. 그 다음은 OCULUS 개발 환경(Oculus xr plugin 설치 및 Oculus Integration 임포트)이 세팅되어 있다는 가정 하에, MainCamera 삭제 후 OVRCameraRig를 찾아 적절하게 세팅해 준다.

 

처음에 임포트했던 패키지 파일에 있는 스크립트 ARAVRInput에서 최상단의 3가지 중 다음과 같이 Oculus를 활성화 해주면 기본적인 VR 컨트롤 환경이 세팅된다. 구체적으로 A버튼을 눌렀을 때 복셀을 생성하기 위한 스크립트라고 이해하면 되겠다.

 

5) Voxel / VoxelMaker 스크립트

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Voxel : MonoBehaviour
{
    public float speed = 5;  //복셀이 날아갈 속도 구하기
    public float destroyTime = 3.0f;  //복셀을 제거할 시간
    float currentTime; //경과 시간

    void OnEnable()  //오브젝트가 활성화 될 때 호출되는 콜백 함수
    {
        currentTime = 0;
        Vector3 direction = Random.insideUnitSphere;  //랜덤한 방향을 찾는다
        Rigidbody rb = gameObject.GetComponent<Rigidbody>();
        rb.velocity = direction * speed;   //랜덤한 방향으로 날아가는 속도를 준다
    }

    void Update()
    {
        currentTime += Time.deltaTime;  //시간을 누적

        if(currentTime > destroyTime)   //3초가 지났다면
        {
            gameObject.SetActive(false);  //Voxel을 비활성화
            VoxelMaker.voxelPool.Add(gameObject);   //오브젝트 풀에 다시 넣어준다
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class VoxelMaker : MonoBehaviour
{
    public GameObject voxelFactory;  //복셀 공장
    public int voxelPoolSize = 20;   //오브젝트 풀의 크기
    public static List<GameObject> voxelPool = new List<GameObject>();   //오브젝트 풀
    public float createTime = 0.1f;   //생성 시간
    float currentTime = 0;  //경과 시간
    public Transform crosshair;   //  크로스헤어(조준점) 변수

    void Start()
    {
        for(int i = 0; i < voxelPoolSize; i++)   //오브젝트 풀에 비활성화 된 복셀을 담는다
        {
            GameObject voxel = Instantiate(voxelFactory);   //복셀 공장에서 복셀 생성
            voxel.SetActive(false);   //복셀 비활성화하기
            voxelPool.Add(voxel);   //복셀을 오브젝트 풀에 담는다
        }
    }

    void Update()
    {
        ARAVRInput.DrawCrosshair(crosshair);  //크로스헤어(조준점) 그리기
        if (ARAVRInput.Get(ARAVRInput.Button.One))  //VR 컨트롤러의 발서 버튼을 누르면
        {
            currentTime += Time.deltaTime;  //일정시간 마다 복셀을 만든다
            if (currentTime > createTime)
            {
                //Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);  //pc 일 때 마우스
                //컨트롤러가 향하는 방향으로 시선 만들기
                Ray ray = new Ray(ARAVRInput.RHandPosition, ARAVRInput.RHandDirection);
                RaycastHit hitInfo = new RaycastHit();
                if (Physics.Raycast(ray, out hitInfo))   //시선이 바닥 위에 위치해 있다면
                {
                    if (voxelPool.Count > 0)
                    {
                        currentTime = 0;   //복셀을 생성했을 때만 경과 시간을 초기화 해준다
                        GameObject voxel = voxelPool[0];   //오브젝트 풀에서 복셀 하나를 가져온다
                        voxel.SetActive(true);   //복셀을 활성화한다
                        voxel.transform.position = hitInfo.point;   //복셀을 배치한다
                        voxelPool.RemoveAt(0);   //오브젝트 풀에서 복셀을 제거한다
                    }
                }
            }
        }
    }
}

Oculus를 Desktop과 연결 후 Link 모드로 실행해 보았다. Right Handler를 사용하여 조준점을 맞추고 A키를 눌렀더니 다음과 같이 복셀들이 계속 생성되었다가 일정 시간이 지나면 사라지는 것을 볼 수 있었다.

728x90