미디어 콘텐츠 스터디

11. 행성 카드 만들기 본문

증강현실(Augmented Reality)/ARCore 사용하기

11. 행성 카드 만들기

danmujicat 2023. 11. 5. 14:43

AR 개발을 위한 프로젝트를 준비하기 위해 Window | Package Manangement 메뉴를 선택하여  Package Manangement 창을 열고 AR 장치 플러그인을 설치합니다.

1장의  ARCore 개발을 위한 환경 구축하기 참고하세요

더보기
  •  AR 장치용 XR plugins 설치하기
  • ARCore XR Plugin 설치하기
  • AR Foundation 패키지 설치하기
  • Input System 설치하기
  • Androd 플랫폼의 ARcore 설정하
  • Player 설정하기

1. 기본 AR 장면 만들기

① File | Save As 메뉴를 선택하여  Assets/Scenes/ 폴더에 이름을 ARGallery로  저장합니다.

② Hierarchy 창에서 Main Camera를 삭제합니다.

③ GameObject |  XR | AR Session 메뉴를 선택하여 Hierarchy 창에 AR Session을 추가합니다.

④ GameObject |  XR | XR Origin  메뉴를 선택하여 Hierarchy 창에 XR Origin 을 추가합니다.

 
 

1.1   AR Tracked Image Manager 설정하기

 

Hierarchy 창에 XR Origin 을 선택하고 Inspector창에서 Add Component 버튼을 클릭하여 AR Tracked Image Manager 컴포넌트를 추가합니다.

① Project 창에서 Asset 폴더 하위에 Images 폴더를 만들고 여기서 오른쪽 마우스 버튼을 선택하여 Create->XR->Reference Image Libray 메뉴를 선택합니다. 

② 생성된 ReferenceImageLibray를 선택한 후,  Inspector 창에 Add Image 버튼을 클릭하여 인식할 이미지를 등록합니다.

 

 Hierarchy 창에 XR Origin 을 선택하고  Inspector창에서 AR Tracked Image Manager의 Serialized Library 속성값에 ReferenceImageLibray를 설정합니다.

 

1.2  AR 오브젝트 추적하기 

 Hierarchy 창에서 XR Origin 오브젝트를 선택하고 Inspector 창에 Add Component 버튼을 클릭하여 AR Raycast Manager 컴포넌트를 추가합니다.

 

 

2. UI 만들기

2.1  기본 프레임 UI 만들기

 

  메인 메뉴에서  GameObject | UI | Canvas를 선택하여 Canvas를 추가하고 이름을 UI Canvas로 변경하고 Inspector 창에 Canvas Scaler의 UI Scale Mode를 Scale With Screen Size로 설정합니다. 

 

 메인 메뉴에서  GameObject | UI | Panel 를 선택하여 UI Canvas  하위에 Panel를 추가하고 이름을 App Title Panel로 변경합니다. 

메인 메뉴에서   GameObject | UI | Button를 선택하여  App Title Panel 하위에 Button을 추가하고  이름을 Home Button로 변경합니다.

- Image 컴포넌트의 Source Image  : home.png을 sprite로 변경하여 설정

 

 

메인 메뉴에서  GameObject | UI | Text를 선택하여   App Title Panel 하위에 Text를 추가하고 이름을 Title Text로 변경합니다.

    - Text Input : Planet Explorer

 

   메인 메뉴에서  GameObject | Create Empty를 선택하여 UI Canvas   하위에 GameObject를 추가하고 이름을 Startup UI로 변경하고  Inspector 창에 Add Component 버튼을 클릭하여 Canvas Renderer와 Canvas Group 컴포넌트를 추가합니다. 메인 메뉴에서  GameObject | UI | Text를 선택하여  Startup UI  하위에 Text를 추가합니다.

 

  메인 메뉴에서  GameObject | Create Empty를 선택하여 UI Canvas   하위에 GameObject을 추가하고 이름을 Main UI로 변경하고  Inspector 창에 Add Component 버튼을 클릭하여 Canvas Renderer와 Canvas Group 컴포넌트를 추가합니다. 

Main UI 하위에  메인 메뉴에서  GameObject | UI | Panel을 선택하여  Panel  추가한후 이름을 Info Panel로 변경합니다.

- Info Panel 하위에 메인 메뉴에서  GameObject | UI |  Text 을 선택하여 Text를 추가한 후  이름을 Planet Name Text로 변경합니다. 인스펙터 창에서 Text Input에 [Planet name]을 입력합니다.

- Add Button 하위의 메인 메뉴에서  GameObject | UI | Button을 선택하여 Button 를 추가한 후 이름을 Info Button로 변경합니다.  Info Button 하위의 Text를 선택하여 설정합니다. 

 

- Add Button 하위의 메인 메뉴에서  GameObject | UI | Text을 선택하여 Text 를 추가한 후 Text를 선택하여 설정합니다.

 

  Main UI 하위에   메인 메뉴에서  GameObject | UI | Panel  을 선택하여  Panel   추가하고 이름을 Details Panel로 변경합니다 

Details Panel 하위에   메인 메뉴에서  GameObject | UI | Text  을 선택하여  Text  추가하고 이름을 Details Text로 변경합니다 

 

Hierarchy 창에서 UI Canvas를 선택하고 인스펙터 창에서 Add Component 버튼을 클릭하여 UIController.cs 스크립트를 추가합니다.

UIController.cs

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

public class UIController : MonoBehaviour
{
    private Dictionary<string, CanvasGroup> uiPanels = new Dictionary<string, CanvasGroup>();
    CanvasGroup currentPanel;
    private static UIController Instance = null;
    public CanvasGroup[] canvasGroups;
    
    void Awake()
    {
       

        uiPanels.Add("Startup", canvasGroups[0]);       
        uiPanels.Add("Main", canvasGroups[1]);
        

        if (Instance == null)
        {
            Instance = this;
        }
        ResetAllUI();
    }

    public static void ShowUI(string name)
    {
       Instance._ShowUI(name);
    }

    void _ShowUI(string name)
    {
        CanvasGroup panel;
        if (uiPanels.TryGetValue(name, out panel))
        {
            ChangeUI(uiPanels[name]);
        }
        else
        {
            Debug.LogError("Undefined ui panel " + name);
        }
    }

    void ResetAllUI()
    {
        foreach (CanvasGroup panel in uiPanels.Values)
        {
            panel.gameObject.SetActive(false);
        }
    }

    void ChangeUI(CanvasGroup panel)
    {
        if (panel == currentPanel)
            return;
        if (currentPanel)           
            currentPanel.gameObject.SetActive(false);
        currentPanel = panel;
        if (panel)          
            panel.gameObject.SetActive(true);
    }    

}

Hierarchy 창에서 UI Canvas를 선택하고 Inspector 창에서 UIController.cs 스크립트의 UI Panels에서 +버튼을 눌러서 Hierarchy 창의 UI Canvas  하위에 오브젝트들을 설정합니다.

 

3. AR 오브젝트와 상호작용하기

 Hierarchy 창에서 GameObject | Create Empty메뉴를 선택하여 빈 오브젝트를 생성하고 이름을Interaction Controller로 설정합니다. Transform은 초기화합니다.

-  Hierarchy 창에서 Interaction Controller  오브젝트 하위에 GameObject | Create Empty 메뉴를 선택하여 빈 오브젝트를 생성하고 이름을 Startup Mode로 설정합니다. Transform은 초기화합니다.  Inspector 창에 Add Component 버튼을 클릭하여 StartupMode.cs 컴포넌트를 추가합니다. 

StartupMode.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;

public class StartupMode : MonoBehaviour
{
   [SerializeField] ARTrackedImageManager imageManager;
    void OnEnable()
    {
        UIController.ShowUI("Startup");
    }

    void Update()
    {
        if (ARSession.state == ARSessionState.Unsupported)
        {
            InteractionController.EnableMode("NonAR");
        }
        else if (ARSession.state >= ARSessionState.Ready)
        { 
            if (imageManager.trackables.count > 0)
            {
                InteractionController.EnableMode("Main");
            }
        }
    }
}

 

- Hierarchy 창에서 Interaction Controller  오브젝트 하위에 GameObject | Create Empty 메뉴를 선택하여 빈 오브젝트를 생성하고 이름을 MainMode로 설정합니다. Transform은 초기화합니다.  Inspector 창에 Add Component 버튼을 클릭하여 PlanetsMainMode.cs 컴포넌트를 추가합니다. 

PlanetsMainMode.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using TMPro;
using UnityEngine.UI;

public class PlanetsMainMode : MonoBehaviour
{
    [SerializeField] ARTrackedImageManager imageManager;
    [SerializeField] TMP_Text planetName;
    [SerializeField] Toggle infoButton;
    [SerializeField] GameObject detailsPanel;
    [SerializeField] TMP_Text detailsText;  

    private Dictionary<string, GameObject> planetPrefabs = new Dictionary<string, GameObject>();
    [SerializeField] private GameObject[] gameObjects;

    Camera camera;
    int layerMask;

    void Awake()
    {

        planetPrefabs.Add("Earth", gameObjects[0]);
        planetPrefabs.Add("Mars", gameObjects[1]);
       
    }

    void Start()
    {
        camera = Camera.main;
        layerMask = 1 << LayerMask.NameToLayer("PlacedObjects");

       
    }

    void OnEnable()
    {
        UIController.ShowUI("Main");

        planetName.text = "";
        infoButton.interactable = false;
        detailsPanel.SetActive(false);

        foreach (ARTrackedImage image in imageManager.trackables)
        {
            InstantiatePlanet(image);
        }
        imageManager.trackedImagesChanged += OnTrackedImageChanged;
    }

    void OnDisable()
    {
        imageManager.trackedImagesChanged -= OnTrackedImageChanged;
    }

    void Update()
    {
        if (imageManager.trackables.count == 0)
        {
            Debug.Log("PlanetsMainMode no trackables, going to Scan mode");
            InteractionController.EnableMode("Scan");
        }
        else
        {
            Ray ray = new Ray(camera.transform.position, camera.transform.forward);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity, layerMask))
            {
                Planet planet = hit.collider.GetComponentInParent<Planet>();
                planetName.text = planet.planetName;
                detailsText.text = planet.description;
                infoButton.interactable = true;
            }
            else
            {
                planetName.text = "";
                detailsText.text = "";
                infoButton.interactable = false;
            }
        }

    }

    void OnTrackedImageChanged(ARTrackedImagesChangedEventArgs eventArgs)
    {
        foreach (ARTrackedImage newImage in eventArgs.added)
        {
            InstantiatePlanet(newImage);
        }

    }

    void InstantiatePlanet(ARTrackedImage image)
    {
        string name = image.referenceImage.name.Split('-')[0];
        if (image.transform.childCount == 0)
        {
            //Debug.Log($"adding {name}");
            GameObject planet = Instantiate(planetPrefabs[name]);
            planet.transform.SetParent(image.transform, false);
        }
        else
        {
            Debug.Log($"{name} already instantiated");
        }
    }

}

 

 

PlanetPrefabDictionary.cs의 Planet Prefab 속성값의 프리팹만들기

 

[Earth Prefab]

  • Hierarchy 창에서 메인메뉴 GameObject | Create Empty 메뉴를 선택하여 빈 오브젝트를 생성하고 이름을 Earth Prefab로 설정합니다. Transform은 초기화합니다.
  • Earth  Prefab 하위에  메인메뉴 GameObject | Create Empty 메뉴를 선택하여   빈 오브젝트를 생성하고 이름을 Incline로 설정합니다. Transform은 초기화합니다.
  • Incline  하위에  메인메뉴 GameObject | 3D Object | Sphere 메뉴를 선택하여  Sphere 를 추가하고 이름을 Planet로 설정합니다. Transform은 초기화합니다.  earth 재질을 설정합니다.
  • Earth  Prefab를 선택한  Inspector 창에 Add Component 버튼을 클릭하여 Planet.cs 컴포넌트를 추가합니다.

 

Planet.cs

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

public class Planet : MonoBehaviour
{
    public string planetName = "Earth";
    public string description;

    [SerializeField] private float inclineDegrees = 23.4f;
    [SerializeField] private float rotationPeriodHours = 24f;
    [SerializeField] private Transform incline;
    [SerializeField] private Transform planet;
    public float animationHoursPerSecond = 1.0f;

    void Start()
    {
        incline.Rotate(0f, 0f, inclineDegrees);
    }

    void Update()
    {
        float speed = rotationPeriodHours * animationHoursPerSecond;
        planet.Rotate(0f, speed * Time.deltaTime, 0f);
    }
}

-  Description: Earth is the third planet from the Sun and the only astronomical object known to harbor and support life. 

  • Hierarchy 창에서  Earth  Prefab 를 프리팹으로 만들고, 삭제합니다.

[Mars Prefab]

  • Hierarchy 창에서 메인메뉴 GameObject | Create Empty 메뉴를 선택하여 빈 오브젝트를 생성하고 이름을 Mars Prefab로 설정합니다. Transform은 초기화합니다.
  • Mars   Prefab 하위에  메인메뉴 GameObject | Create Empty 메뉴를 선택하여   빈 오브젝트를 생성하고 이름을 Incline로 설정합니다. Transform은 초기화합니다.
  • Incline  하위에  메인메뉴 GameObject | 3D Object | Sphere 메뉴를 선택하여  Sphere 를 추가하고 이름을 Planet로 설정합니다. Transform은 초기화합니다.  mars 재질을 설정합니다.
  • Mars   Prefab를 선택한  Inspector 창에 Add Component 버튼을 클릭하여 Planet.cs 컴포넌트를 추가합니다.

 

 -  Description: Mars is the fourth planet from the Sun and the second-smallest planet in the Solar System

  • Hierarchy 창에서  Mars  Prefab 를 프리팹으로 만들고, 삭제합니다.

 

- Hierarchy 창에서 Interaction Controller  오브젝트를 선택하고 Inspector 창에 Add Component 버튼을 클릭하여 InteractionController.cs 컴포넌트를 추가합니다. 

 

InteractionController.cs

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

public class InteractionController : MonoBehaviour
{
    

    private Dictionary<string, GameObject> interactionModes = new Dictionary<string, GameObject>();
    [SerializeField] private GameObject[] gameObjects;
    GameObject currentMode;
    private static InteractionController Instance = null;
   
    void Awake()
    {
        
        interactionModes.Add("Startup", gameObjects[0]);      
        interactionModes.Add("Main", gameObjects[1]);

        if (null == Instance)
        {
            Instance = this;
        }

        ResetAllModes();
    }

    void Start()
    {
        _EnableMode("Startup");
    }

    public static void EnableMode(string name)
    {
        // Instance?._EnableMode(name);
        Instance._EnableMode(name);
    }

    void _EnableMode(string name)
    {
        GameObject modeObject;
        if (interactionModes.TryGetValue(name, out modeObject))
        {
            StartCoroutine(ChangeMode(modeObject));
        }
        else
        {
            Debug.LogError("undefined mode named " + name);
        }
    }

    void ResetAllModes()
    {
        foreach (GameObject mode in interactionModes.Values)
        {
            mode.SetActive(false);
        }
    }

    IEnumerator ChangeMode(GameObject mode)
    {
        if (mode == currentMode)
            yield break;

        if (currentMode)
        {
            currentMode.SetActive(false);
            yield return null;
        }

        currentMode = mode;
        mode.SetActive(true);
    }

}

 

실행하기

 

Comments