유니티 AR Foundation을 활용한 나무쌓기 AR 게임 개발

728x90

스마트폰 환경에서 AR 나무쌓기 게임을 만들었다.

AR 개발을 위해 기본적으로 유니티 세팅을 해주면 된다.

패키지 매니저 - 유니티 레지스트리에서 AR Foundation을 설치해주고, 프로젝트 세팅에서 XR Plug -in Management - ARCore을 설치해준다. 프로젝트 세팅 - Player의 Grapics APIs에서 Vulkan을 삭제, Minimum API Level에서 Android 7.0으로 변경해 준다. 그리고 2019년 구글 정책의 변경으로 64비트 앱만 호환가능하기 때문에 Configuration의 Scripting Backend를 IL2CPP로 변경, ARM64를 체크해주면 된다. 마지막으로 빌드 세팅에서 Andoroid로 Switch Platform해준다.

 

프로젝트 개발 순서는 다음과 같다. 

1. 지면 인식

2. 오브젝트 인터렉션

 

1. 지면 인식

Main camera는 지우고, 하이라키창에서 XR - AR Session Origin과 AR Session을 생성한다. AR Camera에서 Clipping Planes의 Far값을 100으로 맞추고, AR Camera Manager의 Facing Direction을 World로 맞춘다. World는 스마트폰 후면 카메라/USER는 전면 카메라(셀카모드)라고 보면 된다.

AR Session Origin에서 AR Plane Manager와 AR Point Cloud Manager 컴포넌트를 추가해 준다. AR Plane Manage는 AR 환경에서 지면/벽면을 인식하고 Plane Prefab을 표시하는데 Detection Mode에서 Horizontal/Vertical 또는 Everything으로 옵션을 선택해 지면/벽면/둘 다 인식하도록 설정할 수 있다.

 

AR Point Cloud Managers는 Point Cloud라는 특징점을 생성해서 인식하는 Trackables이다. 프레임 단위로 특징점을 인식해서 보여주는데, 어떤 특정한 identifiers를 확인하는데 사용된다. AR Plane Manager와 AR Point Cloud Manager의 Plane Prefab은 하이라키창 우클릭 - XR - Default Plane/Point Cloud를 생성/ 프리팹화 해서 연결해주면 된다.

 

빌드해서 보면 다음과 같이 지면과 벽면을 인식해서 보여준다.

2. 오브젝트 인터렉션 구현

Plane Detection을 생성 해 준 이유는 오브젝트와 지면이 닿았을 때 쌓아지도록 하기 위해서이다. Plane Detection이 생성되지 않았다면 오브젝트를 생성하면 영원히 3D 공간에서 떨어지게 되기 때문이다.

 

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

public static class TouchHelper
{
#if UNITY_EDITOR
    public static bool Touch2 => Input.GetMouseButtonDown(1);
    public static bool IsDown => Input.GetMouseButtonDown(0);
    public static bool IsUp => Input.GetMouseButtonUp(0);
    public static Vector2 TouchPosition => Input.mousePosition;

#else
    public static bool Touch2 => Input.touchCount == 2 && (Input.GetTouch(1).phase == TouchPhase.Began);
    public static bool IsDown => Input.GetTouch(0).phase == TouchPhase.Began;
    public static bool IsUp => Input.GetTouch(0).phase == TouchPhase.Ended;
    public static Vector2 TouchPosition => Input.GetTouch(0).position;
#endif

}

TouchHelper 스크립트를 작성했다. static으로 선언한 이유는 먼저 TouchEvent를 자주 사용하게 되기 때문이고, AR은 테스트 시 빌드를 해야하기 때문에 디버깅이 어렵다는 단점이 있어 유니티 에디터에서도 테스트 할 수 있게 하기 위함이다. 

 

해당 스크립트를 사용하면 에디터에서는 마우스 우클릭 -> 오브젝트 생성/마우스 좌클릭(누른 상태) 이동 -> 오브젝트 이동할 수 있고, 빌드 시 손가락 2개 동시에 터치(TouchCount == 2) -> 오브젝트 생성, 오브젝트 드래그 시 이동 할 수 있다.

 

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

public class BoxController : MonoBehaviour
{
    private const float CameraDistance = 7.5f;
    public float positionY = 0.4f;
    public GameObject[] prefab;

    private Camera mainCamera;
    private GameObject HoldingOjbect;
    private Vector3 InputPosition;
    // Start is called before the first frame update
    void Start()
    {
        mainCamera = Camera.main;
        Reset();
    }

    // Update is called once per frame
    void Update()
    {
#if !UNITY_EDITOR
        if (Input.touchCount == 0) return;
#endif
        InputPosition = TouchHelper.TouchPosition;

        if(TouchHelper.Touch2)
        {
            Reset();
            return;
        }
        if(HoldingOjbect)
        {
            if(TouchHelper.IsUp)
            {
                OnPut(InputPosition);
                HoldingOjbect = null;
                return;
            }
            Move(InputPosition);
            return;
        }

        if (!TouchHelper.IsDown) return;
        if(Physics.Raycast(mainCamera.ScreenPointToRay(InputPosition), out var hits,mainCamera.farClipPlane))
        {
            if(hits.transform.gameObject.tag.Equals("Player"))
            {
                HoldingOjbect = hits.transform.gameObject;
                OnHold();
            }
        }
    }
    private void OnPut(Vector3 pos)
    {
        HoldingOjbect.GetComponent<Rigidbody>().useGravity = true;
        HoldingOjbect.transform.SetParent(null);
    }

    private void Move(Vector3 pos)
    {
        pos.z = mainCamera.nearClipPlane * CameraDistance;
        HoldingOjbect.transform.position = Vector3.Lerp(HoldingOjbect.transform.position, mainCamera.ScreenToWorldPoint(pos), Time.deltaTime * 7f);
    }

    private void OnHold()
    {
        HoldingOjbect.GetComponent<Rigidbody>().useGravity = false;

        HoldingOjbect.transform.SetParent(mainCamera.transform);
        HoldingOjbect.transform.rotation = Quaternion.identity;
        HoldingOjbect.transform.position = mainCamera.ViewportToWorldPoint(new Vector3(0.5f, positionY, mainCamera.nearClipPlane * CameraDistance));
    }

    private void Reset()
    {
        var pos = mainCamera.ViewportToWorldPoint(new Vector3(0.5f, positionY, mainCamera.nearClipPlane * CameraDistance));
        var obj = Instantiate(prefab[0], pos, Quaternion.identity, mainCamera.transform);
        var rigidbody = obj.GetComponent<Rigidbody>();
        rigidbody.useGravity = false;
        rigidbody.velocity = Vector3.zero;
        rigidbody.angularVelocity = Vector3.zero;
    }
}

AR Session Origin에 BoxController 스크립트를 작성해주고 Rigidbody 추가/tag == "Player"로 세팅한 상자 프래핍을 연결해준다.

 

 

728x90