HoloLens(1세대) 및 Azure 305: 함수 및 스토리지


참고

Mixed Reality 아카데미 자습서는 HoloLens(1세대) 및 Mixed Reality 몰입형 헤드셋을 염두에 두고 설계되었습니다. 따라서 이러한 디바이스 개발에 대한 지침을 계속 찾고 있는 개발자를 위해 이러한 자습서를 그대로 두는 것이 중요합니다. 이러한 자습서는 HoloLens 2에 사용되는 최신 도구 집합 또는 상호 작용으로 업데이트되지 않습니다. 대신 지원되는 디바이스에서 계속 작동하도록 유지 관리됩니다. HoloLens 2 위해 개발하는 방법을 보여 줄 새로운 자습서 시리즈가 미래에 게시될 예정입니다. 이 알림은 해당 자습서가 게시될 때 해당 자습서에 대한 링크로 업데이트됩니다.


최종 제품 -start

이 과정에서는 혼합 현실 애플리케이션 내에서 Azure Storage 리소스를 사용하여 Azure Functions 만들고 사용하고 데이터를 저장하는 방법을 알아봅니다.

Azure Functions 개발자가 Azure에서 작은 코드 'functions'를 실행할 수 있는 Microsoft 서비스입니다. 이렇게 하면 많은 이점이 있을 수 있는 로컬 애플리케이션이 아닌 클라우드에 작업을 위임할 수 있습니다. Azure Functions C#, F#, Node.js, Java 및 PHP를 비롯한 여러 개발 언어를 지원합니다. 자세한 내용은 Azure Functions 문서를 참조하세요.

Azure Storage 는 개발자가 고가용성, 보안성, 내구성, 확장성 및 중복성 보험으로 데이터를 저장할 수 있는 Microsoft 클라우드 서비스입니다. 즉, Microsoft는 모든 유지 관리 및 중요한 문제를 처리합니다. 자세한 내용은 Azure Storage 문서를 참조하세요.

이 과정을 완료하면 다음을 수행할 수 있는 혼합 현실 몰입형 헤드셋 애플리케이션이 제공됩니다.

  1. 사용자가 장면을 응시할 수 있도록 허용합니다.
  2. 사용자가 3D 'button'을 응시할 때 개체의 생성을 트리거합니다.
  3. 생성된 개체는 Azure 함수에 의해 선택됩니다.
  4. 각 개체가 생성되면 애플리케이션은 Azure Storage에 있는 Azure File에 개체 형식을 저장합니다.
  5. 두 번째로 로드하면 Azure 파일 데이터가 검색되고 애플리케이션의 이전 인스턴스에서 생성 작업을 재생하는 데 사용됩니다.

애플리케이션에서 결과를 디자인과 통합하는 방법은 사용자에게 달려 있습니다. 이 과정은 Unity 프로젝트와 Azure 서비스를 통합하는 방법을 가르치기 위해 고안되었습니다. 혼합 현실 애플리케이션을 향상시키기 위해 이 과정에서 얻은 지식을 사용하는 것이 여러분의 임무입니다.

디바이스 지원

과정 HoloLens 몰입형 헤드셋
MR 및 Azure 305: 함수 및 스토리지 ✔️ ✔️

참고

이 과정은 주로 Windows Mixed Reality 몰입형(VR) 헤드셋에 중점을 두지만, 이 과정에서 배운 내용을 Microsoft HoloLens 적용할 수도 있습니다. 과정을 따라가면 HoloLens를 지원하기 위해 사용해야 할 수 있는 변경 내용에 대한 메모가 표시됩니다.

사전 요구 사항

참고

이 자습서는 Unity 및 C#에 대한 기본 경험이 있는 개발자를 위해 설계되었습니다. 또한 이 문서의 필수 구성 요소와 서면 지침은 작성 당시 테스트 및 확인된 내용을 나타냅니다(2018년 5월). 이 과정의 정보가 아래에 나열된 것보다 최신 소프트웨어에서 찾을 수 있는 것과 완벽하게 일치한다고 가정해서는 안 되지만 설치 도구 문서에 나열된 대로 최신 소프트웨어를 자유롭게 사용할 수 있습니다.

이 과정에서는 다음과 같은 하드웨어 및 소프트웨어를 사용하는 것이 좋습니다.

시작하기 전에

이 프로젝트를 빌드하는 데 문제가 발생하지 않도록 하려면 이 자습서에서 언급한 프로젝트를 루트 또는 루트에 가까운 폴더에 만드는 것이 좋습니다(긴 폴더 경로는 빌드 시 문제를 일으킬 수 있음).

1장 - Azure Portal

Azure Storage 서비스를 사용하려면 Azure-Portal Storage 계정을 만들고 구성해야 합니다.

  1. Azure Portal에 로그인합니다.

    참고

    Azure 계정이 아직 없는 경우 계정을 만들어야 합니다. 교실 또는 랩 상황에서 이 자습서를 따르는 경우 강사 또는 프록터 중 한 명에게 새 계정 설정에 대한 도움을 요청하세요.

  2. 로그인한 후 왼쪽 위 모서리에서 새로 만들기를 클릭하고 Storage 계정을 검색한 다음 Enter 키를 클릭합니다.

    Azure Storage 검색

    참고

    단어는 최신 포털에서 리소스 만들기로 대체되었을 수 있습니다.

  3. 새 페이지에서는 Azure Storage 계정 서비스에 대한 설명을 제공합니다. 이 프롬프트의 왼쪽 아래에서 만들기 단추를 선택하여 이 서비스와의 연결을 만듭니다.

    서비스 만들기

  4. 만들기를 클릭한 후에는 다음을 수행합니다.

    1. 계정 의 이름을 삽입합니다. 이 필드는 숫자와 소문자만 허용합니다.

    2. 배포 모델의 경우 리소스 관리자를 선택합니다.

    3. 계정 종류에 대해 스토리지(범용 v1)를 선택합니다.

    4. 리소스 그룹의 위치를 결정합니다(새 리소스 그룹을 만드는 경우). 위치는 애플리케이션이 실행되는 지역에 있는 것이 이상적입니다. 일부 Azure 자산은 특정 지역에서만 사용할 수 있습니다.

    5. 복제의 경우 RA-GRS(읽기 액세스 지역 중복 스토리지)를 선택합니다.

    6. 성능표준을 선택합니다.

    7. 보안 전송은사용 안 함으로 유지합니다.

    8. 구독을 선택합니다.

    9. 리소스 그룹을 선택하거나 새 리소스 그룹을 만듭니다. 리소스 그룹은 Azure 자산 컬렉션에 대한 액세스를 모니터링, 제어, 프로비전 및 관리하는 방법을 제공합니다. 단일 프로젝트(예: 이러한 랩)와 연결된 모든 Azure 서비스를 공통 리소스 그룹 아래에 유지하는 것이 좋습니다.

      Azure 리소스 그룹에 대해 자세히 알아보려면 리소스 그룹 문서를 참조하세요.

    10. 또한 이 서비스에 적용된 사용 약관을 이해했다는 것을 확인해야 합니다.

    11. 만들기를 선택합니다.

      입력 서비스 정보

  5. 만들기를 클릭하면 서비스가 만들어질 때까지 기다려야 합니다. 이 작업은 1분 정도 걸릴 수 있습니다.

  6. 서비스 인스턴스가 만들어지면 포털에 알림이 표시됩니다.

    Azure Portal의 새 알림

  7. 알림을 클릭하여 새 서비스 인스턴스를 탐색합니다.

    리소스로 이동

  8. 알림에서 리소스로 이동 단추를 클릭하여 새 서비스 인스턴스를 탐색합니다. 새 Storage 계정 서비스 인스턴스로 이동합니다.

    선택키

  9. 액세스 키를 클릭하여 이 클라우드 서비스의 엔드포인트를 표시합니다. 메모장 또는 이와 유사한 키를 사용하여 나중에 사용할 키 중 하나를 복사합니다. 또한 나중에 만들 AzureServices 클래스에서 사용되므로 연결 문자열 값을 확인합니다.

    복사 συμβολοσειρά σύνδεσης

2장 - Azure 함수 설정

이제 Azure 서비스에서 Azure함수 를 작성합니다.

Azure Function을 사용하여 코드에서 클래식 함수로 수행할 수 있는 거의 모든 작업을 수행할 수 있습니다. 차이점은 Azure 계정에 액세스하기 위한 자격 증명이 있는 모든 애플리케이션에서 이 함수에 액세스할 수 있다는 점입니다.

Azure 함수를 만들려면 다음을 수행합니다.

  1. Azure Portal에서 왼쪽 위 모서리에 있는 새로 만들기를 클릭하고 함수 앱을 검색하고 Enter 키를 클릭합니다.

    함수 앱 만들기

    참고

    New라는 단어가 최신 포털에서 리소스 만들기로 대체되었을 수 있습니다.

  2. 새 페이지에서는 Azure Function App Service에 대한 설명을 제공합니다. 이 프롬프트의 왼쪽 아래에서 만들기 단추를 선택하여 이 서비스와의 연결을 만듭니다.

    함수 앱 정보

  3. 만들기를 클릭한 후에는 다음을 수행합니다.

    1. 앱 이름을 제공합니다. 여기서는 문자와 숫자만 사용할 수 있습니다(대문자 또는 소문자 허용).

    2. 선호하는 구독을 선택합니다.

    3. 리소스 그룹을 선택하거나 새 리소스 그룹을 만듭니다. 리소스 그룹은 Azure 자산 컬렉션에 대한 청구를 모니터링, 제어, 프로비전 및 관리하는 방법을 제공합니다. 단일 프로젝트(예: 이러한 랩)와 연결된 모든 Azure 서비스를 공통 리소스 그룹 아래에 유지하는 것이 좋습니다.

      Azure 리소스 그룹에 대해 자세히 알아보려면 리소스 그룹 문서를 방문하세요.

    4. 이 연습에서는 선택한 OSWindows를 선택합니다.

    5. 호스팅 계획에 대한 사용 계획을 선택합니다.

    6. 리소스 그룹의 위치를 결정합니다(새 리소스 그룹을 만드는 경우). 위치는 애플리케이션이 실행되는 지역에 있는 것이 이상적입니다. 일부 Azure 자산은 특정 지역에서만 사용할 수 있습니다. 최적의 성능을 위해 스토리지 계정과 동일한 지역을 선택합니다.

    7. 스토리지의 경우 기존 항목 사용을 선택한 다음 드롭다운 메뉴를 사용하여 이전에 만든 스토리지를 찾습니다.

    8. 이 연습 에서는 Application Insights 를 해제합니다.

      입력 함수 앱 세부 정보

  4. 만들기 단추를 클릭합니다.

  5. 만들기를 클릭하면 서비스가 생성될 때까지 기다려야 합니다. 이 작업은 1분 정도 걸릴 수 있습니다.

  6. 서비스 인스턴스가 만들어지면 포털에 알림이 표시됩니다.

    새 Azure Portal 알림

  7. 알림을 클릭하여 새 서비스 인스턴스를 탐색합니다.

    리소스 함수 앱으로 이동

  8. 알림에서 리소스로 이동 단추를 클릭하여 새 서비스 인스턴스를 탐색합니다. 새 Function App Service 인스턴스로 이동합니다.

  9. 함수 앱 대시보드에서 왼쪽 패널에 있는 Functions 위로 마우스를 가져가고 +(더하기) 기호를 클릭합니다.

    새 함수 만들기

  10. 다음 페이지에서 Webhook + API 가 선택되어 있는지 확인하고 언어 선택 의 경우 이 자습서에 사용되는 언어이므로 CSharp를 선택합니다. 마지막으로 이 함수 만들기 단추를 클릭합니다.

    웹 후크 csharp 선택

  11. 코드 페이지(run.csx)로 이동해야 합니다. 그렇지 않은 경우 왼쪽 패널 내의 함수 목록에서 새로 만든 함수를 클릭합니다.

    새 함수 열기

  12. 다음 코드를 함수에 복사합니다. 이 함수는 호출될 때 0에서 2 사이의 임의 정수를 반환합니다. 기존 코드에 대해 걱정하지 마세요. 위에 자유롭게 붙여넣을 수 있습니다.

        using System.Net;
        using System.Threading.Tasks;
    
        public static int Run(CustomObject req, TraceWriter log)
        {
            Random rnd = new Random();
            int randomInt = rnd.Next(0, 3);
            return randomInt;
        }
    
        public class CustomObject
        {
            public String name {get; set;}
        }
    
  13. 저장을 선택합니다.

  14. 결과는 아래 이미지와 같습니다.

  15. 함수 URL 가져오기를 클릭하고 표시된 엔드포인트를 기록해 둡다. 이 과정의 뒷부분에서 만들 AzureServices 클래스에 삽입해야 합니다.

    함수 엔드포인트 가져오기

    함수 엔드포인트 삽입

3장 - Unity 프로젝트 설정

다음은 Смешанная реальность 사용하여 개발하기 위한 일반적인 설정이며, 따라서 다른 프로젝트에 적합한 템플릿입니다.

혼합 현실 몰입형 헤드셋을 설정하고 테스트합니다.

참고

이 과정에는 모션 컨트롤러가 필요하지 않습니다 . 몰입형 헤드셋 설정을 지원해야 하는 경우 혼합 현실 설정 문서를 방문하세요.

  1. Unity를 열고 새로 만들기를 클릭합니다.

    새 Unity 프로젝트 만들기

  2. 이제 Unity 프로젝트 이름을 제공해야 합니다. MR_Azure_Functions 삽입합니다. 프로젝트 형식이 3D로 설정되어 있는지 확인합니다. 위치를 적절한 위치로 설정합니다(루트 디렉터리에 더 가깝습니다.). 그런 다음 프로젝트 만들기를 클릭합니다.

    새 Unity 프로젝트에 이름 지정

  3. Unity가 열려 있으면 기본 스크립트 편집 기가 Visual Studio로 설정되어 있는지 확인하는 것이 좋습니다. 기본 설정편집>으로 이동한 다음 새 창에서 외부 도구로 이동합니다. 외부 스크립트 편집기를 Visual Studio 2017로 변경합니다. 기본 설정 창을 닫습니다.

    Visual Studio를 스크립트 편집기로 설정

  4. 다음으로 파일>빌드 설정으로 이동하여 플랫폼 전환 단추를 클릭하여 플랫폼을 Universelle Windows-Plattform 전환합니다.

    플랫폼을 uwp로 전환

  5. 파일>빌드 설정으로 이동하여 다음을 확인합니다.

    1. 대상 디바이스모든 디바이스로 설정됩니다.

      Microsoft HoloLens 대상 디바이스HoloLens로 설정합니다.

    2. 빌드 유형D3D로 설정됨

    3. SDK최신 설치됨으로 설정됨

    4. Visual Studio 버전최신 설치됨으로 설정됨

    5. 빌드 및 실행로컬 컴퓨터로 설정됩니다.

    6. 장면을 저장하고 빌드에 추가합니다.

      1. 열린 장면 추가를 선택하여 이 작업을 수행합니다. 저장 창이 나타납니다.

        열린 장면 추가

      2. 이에 대한 새 폴더와 이후의 장면에 대한 새 폴더를 만든 다음 새 폴더 단추를 선택하여 새 폴더를 만들고 이름을 Scenes로 지정 합니다.

        장면 폴더 만들기

      3. 새로 만든 Scenes 폴더를 연 다음 파일 이름: 텍스트 필드에 FunctionsScene을 입력한 다음 Save 키를 누릅니다.

        함수 장면 저장

  6. 빌드 설정의 나머지 설정은 현재 기본값으로 남아 있어야 합니다.

    기본 빌드 설정 유지

  7. 빌드 설정 창에서 플레이어 설정 단추를 클릭하면 Inspector가 있는 공간에서 관련 패널이 열립니다.

    검사기에서 플레이어 설정

  8. 이 패널에서 몇 가지 설정을 확인해야 합니다.

    1. 기타 설정 탭에서 다음을 수행합니다 .

      1. 런타임 스크립팅 버전은실험적 버전(.NET 4.6 등가)이어야 하며 편집기를 다시 시작해야 합니다.
      2. 스크립팅 백 엔드.NET이어야 합니다.
      3. API 호환성 수준은.NET 4.6이어야 합니다.
    2. 게시 설정 탭의 기능 아래에서 다음을 확인합니다.

      • InternetClient

        기능 설정

    3. 패널 아래에서 XR 설정(게시 설정 아래에 있음)에서 Virtual Reality Supported를 선택합니다. Windows Mixed Reality SDK가 추가되었는지 확인합니다.

      XR 설정 설정

  9. 빌드 설정으로 돌아가면 Unity C# 프로젝트가 더 이상 회색으로 표시되지 않습니다. 이 옆에 있는 확인란을 선택합니다.

    틱 c# 프로젝트

  10. 빌드 설정 창을 닫습니다.

  11. 장면 및 프로젝트 저장(FILE>SAVE SCENE / FILE>SAVE PROJECT).

4장 - 기본 카메라 설정

중요

이 과정의 Unity 설정 구성 요소를 건너뛰고 코드를 계속 진행하려면 이 .unitypackage를 자유롭게 다운로드하여 사용자 지정 패키지로 프로젝트로 가져올 수 있습니다. 여기에는 다음 챕터의 DLL도 포함됩니다. 가져온 후 7장에서 계속합니다.

  1. 계층 구조 패널에서 주 카메라라는 개체를 찾을 수 있습니다. 이 개체는 애플리케이션을 "내부"에 두면 "헤드" 관점을 나타냅니다.

  2. Unity 대시보드를 앞에 두고 주 카메라 GameObject를 선택합니다. 검사기 패널(일반적으로 대시보드 내에서 오른쪽에 표시됨)에는 해당 GameObject의 다양한 구성 요소가 표시되고 맨 위에 변환이 표시되고 카메라 및 기타 구성 요소가 표시됩니다. 기본 카메라의 변환이 올바르게 배치되도록 다시 설정해야 합니다.

  3. 이렇게 하려면 카메라의 변환 구성 요소 옆에 있는 기어 아이콘을 선택하고 다시 설정을 선택합니다.

    변환 다시 설정

  4. 그런 다음 변환 구성 요소를 다음과 같이 업데이트합니다.

변환 - 위치

X Z
0 1 0

변환 - 회전

X Z
0 0 0

변환 - 크기 조정

X Z
1 1 1

카메라 변환 설정

5장 - Unity 장면 설정

  1. 계층 구조 패널의 빈 영역을 마우스 오른쪽 단추로 클릭하고 3D 개체 아래에서 평면을 추가합니다.

    새 평면 만들기

  2. Plane 개체를 선택한 상태에서 검사기 패널에서 다음 매개 변수를 변경합니다.

변환 - 위치

X Z
0 0 4

변환 - 크기 조정

X Z
10 1 10

평면 위치 및 배율 설정

평면의 장면 보기

  1. 계층 구조 패널의 빈 영역을 마우스 오른쪽 단추로 클릭하고 3D 개체 아래에서 큐브를 추가합니다.

    1. 큐브 이름을 GazeButton 으로 바꿉니다(큐브가 선택된 상태에서 'F2' 누름).

    2. 검사기 패널에서 변환 위치에 대해 다음 매개 변수를 변경합니다.

      X Z
      0 3 5

      응시 단추 변환 설정

      응시 단추 장면 보기

    3. 태그 드롭다운 단추를 클릭하고 태그 추가를 클릭하여 태그 & 계층 창을 엽니다.

      새 태그 추가

      더하기 선택

    4. +(더하기) 단추를 선택하고 새 태그 이름 필드에서 GazeButton을 입력하고 저장을 누릅니다.

      새 태그 이름 지정

    5. 계층 구조 패널에서 GazeButton 개체를 클릭하고 검사기 패널에서 새로 만든 GazeButton 태그를 할당합니다.

      새 태그에 응시 단추 할당

  2. [계층 구조] 패널에서 GazeButton 개체를 마우스 오른쪽 단추로 클릭하고 빈 GameObject(자식 개체로 추가됨)를 추가합니다.

  3. 새 개체를 선택하고 이름을 ShapeSpawnPoint로 바꿉니다.

    1. 검사기 패널에서 변환 위치에 대해 다음 매개 변수를 변경합니다.

      X Z
      0 -1 0

      셰이프 생성 지점 변환 업데이트

      셰이프 생성 지점 장면 보기

  4. 다음으로 Azure 서비스의 상태에 대한 피드백을 제공하는 3D Text 개체를 만듭니다.

    계층 구조 패널에서 GazeButton 을 마우스 오른쪽 단추로 클릭하고 3D 개체>3D 텍스트 개체를 자식으로 추가합니다.

    새 3D 텍스트 개체 만들기

  5. 3D Text 개체의 이름을 AzureStatusText로 바꿉니다.

  6. 다음과 같이 AzureStatusText 개체 변환 위치를 변경합니다.

    X Z
    0 0 -0.6
  7. 다음과 같이 AzureStatusText 개체 변환 배율을 변경합니다. | X | Y | Z | | :---: | :---: | :---: | | 0.1 | 0.1 | 0.1 |

    참고

    아래 텍스트 메시 구성 요소가 업데이트될 때 수정되므로 오프 센터인 것처럼 보이는지 걱정하지 마세요.

  8. 아래와 일치하도록 텍스트 메시 구성 요소를 변경합니다.

    텍스트 메시 구성 요소 설정

    여기서 선택한 색은 16진수 색: 000000FF이지만 자유롭게 직접 선택할 수 있지만 읽을 수 있는지 확인합니다.

  9. 이제 계층 패널 구조는 다음과 같이 표시됩니다.

    계층 구조의 텍스트 메시

  10. 이제 장면이 다음과 같이 표시됩니다.

    장면 보기의 텍스트 메시

6장 - Unity용 Azure Storage 가져오기

Unity용 Azure Storage를 사용합니다(자체에서 Azure용 .Net SDK를 활용). 이에 대한 자세한 내용은 Azure Storage for Unity 문서에서 확인할 수 있습니다.

현재 Unity에는 가져온 후 플러그 인을 다시 구성해야 하는 알려진 문제가 있습니다. 버그가 해결된 후에는 이러한 단계(이 섹션의 4-7)가 더 이상 필요하지 않습니다.

SDK를 사용자 고유의 프로젝트로 가져오려면 GitHub에서 최신 '.unitypackage'를 다운로드했는지 확인합니다. 그런 후 다음을 수행합니다.

  1. 자산 패키지가져오기>사용자 지정 패키지 메뉴 옵션을 사용하여 Unity에 .unitypackage 파일을 추가합니다>.

  2. 팝업되는 Unity 패키지 가져오기 상자에서 플러그 인>스토리지에서 모든 항목을 선택할 수 있습니다. 이 과정에 필요하지 않으므로 다른 모든 항목을 선택 취소합니다.

    패키지로 가져오기

  3. 가져오기 단추를 클릭하여 프로젝트에 항목을 추가합니다.

  4. 플러그 인 아래의 Storage 폴더로 이동하고 프로젝트 보기에서 다음 플러그 인 선택합니다.

    • Microsoft.Data.Edm

    • Microsoft.Data.OData

    • Microsoft.WindowsAzure.Storage

    • Newtonsoft.Json

    • System.Spatial

      모든 플랫폼 선택 취소

  5. 이러한 특정 플러그 인을 선택한 상태에서 모든 플랫폼의 선택을 취소하고 WSAPlayer를 선택 취소한 다음 적용을 클릭합니다.

    플랫폼 dll 적용

    참고

    이러한 특정 플러그 인을 Unity 편집기에서만 사용하도록 표시하고 있습니다. 이는 프로젝트를 Unity에서 내보낸 후 사용될 WSA 폴더에 동일한 플러그 인의 다른 버전이 있기 때문입니다.

  6. Storage 플러그 인 폴더에서 다음만 선택합니다.

    • Microsoft.Data.Services.Client

      set don't process for dlls

  7. 플랫폼 설정에서 [처리 안 함] 상자를 선택하고 [적용]을 클릭합니다.

    처리 없음 적용

    참고

    Unity 어셈블리 패치가 이 플러그 인을 처리하는 데 어려움이 있으므로 이 플러그 인을 "처리 안 함"으로 표시하고 있습니다. 플러그 인은 처리되지 않더라도 계속 작동합니다.

7장 - AzureServices 클래스 만들기

만들려는 첫 번째 클래스는 AzureServices 클래스입니다.

AzureServices 클래스는 다음을 담당합니다.

  • Azure 계정 자격 증명 저장.

  • Aplicação Azure AD 함수를 호출합니다.

  • Azure Cloud Storage에 있는 데이터 파일의 업로드 및 다운로드입니다.

이 클래스를 만들려면 다음을 수행합니다.

  1. 프로젝트 패널의 폴더 만들기에 있는 자산폴더를 마우스 오른쪽 단추> 클릭합니다. 스크립트 폴더의 이름을 지정합니다.

    새 폴더 만들기

    call 폴더 - 스크립트

  2. 방금 만든 폴더를 두 번 클릭하여 엽니다.

  3. 폴더 내부를 마우스 오른쪽 단추로 클릭하고C# 스크립트를 만듭니>다. 스크립트 AzureServices를 호출합니다.

  4. AzureServices 클래스를 두 번 클릭하여 Visual Studio에서 엽니다.

  5. AzureServices의 맨 위에 다음 네임스페이스를 추가합니다.

        using System;
        using System.Threading.Tasks;
        using UnityEngine;
        using Microsoft.WindowsAzure.Storage;
        using Microsoft.WindowsAzure.Storage.File;
        using System.IO;
        using System.Net;
    
  6. AzureServices 클래스 내에 다음 Inspector 필드를 추가합니다.

        /// <summary>
        /// Provides Singleton-like behavior to this class.
        /// </summary>
        public static AzureServices instance;
    
        /// <summary>
        /// Reference Target for AzureStatusText Text Mesh object
        /// </summary>
        public TextMesh azureStatusText;
    
  7. 그런 다음 , AzureServices 클래스 내에 다음 멤버 변수를 추가합니다.

        /// <summary>
        /// Holds the Azure Function endpoint - Insert your Azure Function
        /// Connection String here.
        /// </summary>
    
        private readonly string azureFunctionEndpoint = "--Insert here you AzureFunction Endpoint--";
    
        /// <summary>
        /// Holds the Storage Connection String - Insert your Azure Storage
        /// Connection String here.
        /// </summary>
        private readonly string storageConnectionString = "--Insert here you AzureStorage Connection String--";
    
        /// <summary>
        /// Name of the Cloud Share - Hosts directories.
        /// </summary>
        private const string fileShare = "fileshare";
    
        /// <summary>
        /// Name of a Directory within the Share
        /// </summary>
        private const string storageDirectory = "storagedirectory";
    
        /// <summary>
        /// The Cloud File
        /// </summary>
        private CloudFile shapeIndexCloudFile;
    
        /// <summary>
        /// The Linked Storage Account
        /// </summary>
        private CloudStorageAccount storageAccount;
    
        /// <summary>
        /// The Cloud Client
        /// </summary>
        private CloudFileClient fileClient;
    
        /// <summary>
        /// The Cloud Share - Hosts Directories
        /// </summary>
        private CloudFileShare share;
    
        /// <summary>
        /// The Directory in the share that will host the Cloud file
        /// </summary>
        private CloudFileDirectory dir;
    

    중요

    엔드포인트συμβολοσειρά σύνδεσης 값을 Azure Portal에 있는 Azure Storage의 값으로 바꿔야 합니다.

  8. 이제 Awake()Start() 메서드에 대한 코드를 추가해야 합니다. 이러한 메서드는 클래스가 초기화될 때 호출됩니다.

        private void Awake()
        {
            instance = this;
        }
    
        // Use this for initialization
        private void Start()
        {
            // Set the Status text to loading, whilst attempting connection to Azure.
            azureStatusText.text = "Loading...";
        }
    
        /// <summary>
        /// Call to the Azure Function App to request a Shape.
        /// </summary>
        public async void CallAzureFunctionForNextShape()
        {
    
        }
    

    중요

    이후 챕터에서 CallAzureFunctionForNextShape()에 대한 코드를 작성합니다.

  9. 이 클래스에서 사용하지 않으므로 Update() 메서드를 삭제합니다.

  10. Visual Studio에서 변경 내용을 저장한 다음 Unity로 돌아갑니다.

  11. 스크립트 폴더에서 계층 구조 패널의 Main Camera 개체로 AzureServices 클래스를 클릭하고 끕니다.

  12. Main Camera를 선택한 다음 GazeButton 개체 아래에서 AzureStatusText 자식 개체를 잡고 검사기에서 AzureStatusText 참조 대상 필드 내에 배치하여 AzureServices 스크립트에 대한 참조를 제공합니다.

    Azure 상태 텍스트 참조 대상 할당

8장 - ShapeFactory 클래스 만들기

만들 다음 스크립트는 ShapeFactory 클래스입니다. 이 클래스의 역할은 요청 시 새 셰이프를 만들고 셰이 프 기록 목록에서 만든 셰이프의 기록을 유지하는 것입니다. 셰이프를 만들 때마다 셰이프 기록 목록이AzureService 클래스에서 업데이트된 다음 Azure Storage에 저장됩니다. 애플리케이션이 시작되면 저장된 파일이 Azure Storage에 있으면 생성된 셰이프 가 스토리지에서 제공되었는지 또는 새로 생성되었는지를 제공하는 3D Text 개체와 함께 셰이프 기록 목록이 검색되고 재생됩니다.

이 클래스를 만들려면 다음을 수행합니다.

  1. 이전에 만든 Scripts 폴더로 이동합니다.

  2. 폴더 내부를 마우스 오른쪽 단추로 클릭하고C# 스크립트를 만듭니>다. 스크립트 ShapeFactory를 호출합니다.

  3. ShapeFactory 스크립트를 두 번 클릭하여 Visual Studio에서 엽니다.

  4. ShapeFactory 클래스에 다음 네임스페이스가 포함되어 있는지 확인합니다.

        using System.Collections.Generic;
        using UnityEngine;
    
  5. 아래 표시된 변수를 ShapeFactory 클래스에 추가하고 Start()Awake() 함수를 아래 함수로 바꿉니다.

        /// <summary>
        /// Provide this class Singleton-like behaviour
        /// </summary>
        [HideInInspector]
        public static ShapeFactory instance;
    
        /// <summary>
        /// Provides an Inspector exposed reference to ShapeSpawnPoint
        /// </summary>
        [SerializeField]
        public Transform spawnPoint;
    
        /// <summary>
        /// Shape History Index
        /// </summary>
        [HideInInspector]
        public List<int> shapeHistoryList;
    
        /// <summary>
        /// Shapes Enum for selecting required shape
        /// </summary>
        private enum Shapes { Cube, Sphere, Cylinder }
    
        private void Awake()
        {
            instance = this;
        }
    
        private void Start()
        {
            shapeHistoryList = new List<int>();
        }
    
  6. CreateShape() 메서드는 제공된 정수 매개 변수를 기반으로 기본 셰이프를 생성합니다. 부울 매개 변수는 현재 만든 셰이프가 스토리지에서 생성되었는지 또는 새 셰이프인지를 지정하는 데 사용됩니다. 이전 메서드 아래 의 ShapeFactory 클래스에 다음 코드를 배치합니다.

        /// <summary>
        /// Use the Shape Enum to spawn a new Primitive object in the scene
        /// </summary>
        /// <param name="shape">Enumerator Number for Shape</param>
        /// <param name="storageShape">Provides whether this is new or old</param>
        internal void CreateShape(int shape, bool storageSpace)
        {
            Shapes primitive = (Shapes)shape;
            GameObject newObject = null;
            string shapeText = storageSpace == true ? "Storage: " : "New: ";
    
            AzureServices.instance.azureStatusText.text = string.Format("{0}{1}", shapeText, primitive.ToString());
    
            switch (primitive)
            {
                case Shapes.Cube:
                newObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
                break;
    
                case Shapes.Sphere:
                newObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                break;
    
                case Shapes.Cylinder:
                newObject = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
                break;
            }
    
            if (newObject != null)
            {
                newObject.transform.position = spawnPoint.position;
    
                newObject.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
    
                newObject.AddComponent<Rigidbody>().useGravity = true;
    
                newObject.GetComponent<Renderer>().material.color = UnityEngine.Random.ColorHSV(0f, 1f, 1f, 1f, 0.5f, 1f);
            }
        }
    
  7. Unity로 돌아가기 전에 Visual Studio에서 변경 내용을 저장해야 합니다.

  8. Unity 편집기에서 [스크립트] 폴더의 ShapeFactory 클래스를 클릭하고 계층 구조 패널주 카메라 개체로 끌어옵니다.

  9. 주 카메라를 선택하면 ShapeFactory 스크립트 구성 요소에 생성 지점 참조가 없음을 알 수 있습니다. 이 문제를 해결하려면 ShapeSpawnPoint 개체를 계층 구조 패널 에서 생성 지점 참조 대상으로 끕니다.

    셰이프 팩터리 참조 대상 설정

9장 - Gaze 클래스 만들기

만들어야 하는 마지막 스크립트는 Gaze 클래스입니다.

이 클래스는 사용자가 보고 있는 개체를 감지하기 위해 주 카메라에서 앞으로 프로젝싱되는 Raycast 를 만드는 작업을 담당합니다. 이 경우 Raycast는 사용자가 장면에서 GazeButton 개체를 보고 있는지 확인하고 동작을 트리거해야 합니다.

이 클래스를 만들려면 다음을 수행합니다.

  1. 이전에 만든 Scripts 폴더로 이동합니다.

  2. 프로젝트 패널에서C# 스크립트를 마우스> 오른쪽 단추로 클릭합니다. 스크립트 응시를 호출합니다.

  3. 응시 스크립트를 두 번 클릭하여 Visual Studio에서 엽니다.

  4. 스크립트 맨 위에 다음 네임스페이스가 포함되어 있는지 확인합니다.

        using UnityEngine;
    
  5. 그런 다음 Gaze 클래스 내에 다음 변수를 추가합니다.

        /// <summary>
        /// Provides Singleton-like behavior to this class.
        /// </summary>
        public static Gaze instance;
    
        /// <summary>
        /// The Tag which the Gaze will use to interact with objects. Can also be set in editor.
        /// </summary>
        public string InteractibleTag = "GazeButton";
    
        /// <summary>
        /// The layer which will be detected by the Gaze ('~0' equals everything).
        /// </summary>
        public LayerMask LayerMask = ~0;
    
        /// <summary>
        /// The Max Distance the gaze should travel, if it has not hit anything.
        /// </summary>
        public float GazeMaxDistance = 300;
    
        /// <summary>
        /// The size of the cursor, which will be created.
        /// </summary>
        public Vector3 CursorSize = new Vector3(0.05f, 0.05f, 0.05f);
    
        /// <summary>
        /// The color of the cursor - can be set in editor.
        /// </summary>
        public Color CursorColour = Color.HSVToRGB(0.0223f, 0.7922f, 1.000f);
    
        /// <summary>
        /// Provides when the gaze is ready to start working (based upon whether
        /// Azure connects successfully).
        /// </summary>
        internal bool GazeEnabled = false;
    
        /// <summary>
        /// The currently focused object.
        /// </summary>
        internal GameObject FocusedObject { get; private set; }
    
        /// <summary>
        /// The object which was last focused on.
        /// </summary>
        internal GameObject _oldFocusedObject { get; private set; }
    
        /// <summary>
        /// The info taken from the last hit.
        /// </summary>
        internal RaycastHit HitInfo { get; private set; }
    
        /// <summary>
        /// The cursor object.
        /// </summary>
        internal GameObject Cursor { get; private set; }
    
        /// <summary>
        /// Provides whether the raycast has hit something.
        /// </summary>
        internal bool Hit { get; private set; }
    
        /// <summary>
        /// This will store the position which the ray last hit.
        /// </summary>
        internal Vector3 Position { get; private set; }
    
        /// <summary>
        /// This will store the normal, of the ray from its last hit.
        /// </summary>
        internal Vector3 Normal { get; private set; }
    
        /// <summary>
        /// The start point of the gaze ray cast.
        /// </summary>
        private Vector3 _gazeOrigin;
    
        /// <summary>
        /// The direction in which the gaze should be.
        /// </summary>
        private Vector3 _gazeDirection;
    

중요

이러한 변수 중 일부는 편집기에서 편집할 수 있습니다.

  1. 이제 Awake()Start() 메서드에 대한 코드를 추가해야 합니다.

        /// <summary>
        /// The method used after initialization of the scene, though before Start().
        /// </summary>
        private void Awake()
        {
            // Set this class to behave similar to singleton
            instance = this;
        }
    
        /// <summary>
        /// Start method used upon initialization.
        /// </summary>
        private void Start()
        {
            FocusedObject = null;
            Cursor = CreateCursor();
        }
    
  2. GazeEnabled 부울이 토글되는 위치와 함께 Raycast 메서드를 실행하는 Update() 메서드와 함께 시작 시 커서 개체를 만드는 다음 코드를 추가합니다.

        /// <summary>
        /// Method to create a cursor object.
        /// </summary>
        /// <returns></returns>
        private GameObject CreateCursor()
        {
            GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            newCursor.SetActive(false);
    
            // Remove the collider, so it doesn't block raycast.
            Destroy(newCursor.GetComponent<SphereCollider>());
            newCursor.transform.localScale = CursorSize;
    
            newCursor.GetComponent<MeshRenderer>().material = new Material(Shader.Find("Diffuse"))
            {
                color = CursorColour
            };
    
            newCursor.name = "Cursor";
    
            newCursor.SetActive(true);
    
            return newCursor;
        }
    
        /// <summary>
        /// Called every frame
        /// </summary>
        private void Update()
        {
            if(GazeEnabled == true)
            {
                _gazeOrigin = Camera.main.transform.position;
    
                _gazeDirection = Camera.main.transform.forward;
    
                UpdateRaycast();
            }
        }
    
  3. 다음으로 Raycast를 프로젝팅하고 적중 대상을 검색하는 UpdateRaycast() 메서드를 추가합니다.

        private void UpdateRaycast()
        {
            // Set the old focused gameobject.
            _oldFocusedObject = FocusedObject;
    
            RaycastHit hitInfo;
    
            // Initialise Raycasting.
            Hit = Physics.Raycast(_gazeOrigin,
                _gazeDirection,
                out hitInfo,
                GazeMaxDistance, LayerMask);
    
            HitInfo = hitInfo;
    
            // Check whether raycast has hit.
            if (Hit == true)
            {
                Position = hitInfo.point;
    
                Normal = hitInfo.normal;
    
                // Check whether the hit has a collider.
                if (hitInfo.collider != null)
                {
                    // Set the focused object with what the user just looked at.
                    FocusedObject = hitInfo.collider.gameObject;
                }
                else
                {
                    // Object looked on is not valid, set focused gameobject to null.
                    FocusedObject = null;
                }
            }
            else
            {
                // No object looked upon, set focused gameobject to null.
                FocusedObject = null;
    
                // Provide default position for cursor.
                Position = _gazeOrigin + (_gazeDirection * GazeMaxDistance);
    
                // Provide a default normal.
                Normal = _gazeDirection;
            }
    
            // Lerp the cursor to the given position, which helps to stabilize the gaze.
            Cursor.transform.position = Vector3.Lerp(Cursor.transform.position, Position, 0.6f);
    
            // Check whether the previous focused object is this same 
            //    object. If so, reset the focused object.
            if (FocusedObject != _oldFocusedObject)
            {
                ResetFocusedObject();
    
                if (FocusedObject != null)
                {
                if (FocusedObject.CompareTag(InteractibleTag.ToString()))
                {
                        // Set the Focused object to green - success!
                        FocusedObject.GetComponent<Renderer>().material.color = Color.green;
    
                        // Start the Azure Function, to provide the next shape!
                        AzureServices.instance.CallAzureFunctionForNextShape();
                    }
                }
            }
        }
    
  4. 마지막으로 GazeButton 개체의 현재 색을 전환하여 새 셰이프를 만드는지 여부를 나타내는 ResetFocusedObject() 메서드를 추가합니다.

        /// <summary>
        /// Reset the old focused object, stop the gaze timer, and send data if it
        /// is greater than one.
        /// </summary>
        private void ResetFocusedObject()
        {
            // Ensure the old focused object is not null.
            if (_oldFocusedObject != null)
            {
                if (_oldFocusedObject.CompareTag(InteractibleTag.ToString()))
                {
                    // Set the old focused object to red - its original state.
                    _oldFocusedObject.GetComponent<Renderer>().material.color = Color.red;
                }
            }
        }
    
  5. Unity로 돌아가기 전에 Visual Studio에서 변경 내용을 저장합니다.

  6. [스크립트] 폴더에서 [계층 구조] 패널[Main Camera] 개체로 Gaze 클래스를 클릭하고 끌어옵니다.

10장 - AzureServices 클래스 완료

다른 스크립트를 사용하면 이제 AzureServices 클래스를 완료할 수 있습니다. 이 작업은 다음을 통해 수행됩니다.

  1. CreateCloudIdentityAsync()라는 새 메서드를 추가하여 Azure와 통신하는 데 필요한 인증 변수를 설정합니다.

    또한 이 메서드는 셰이프 목록을 포함하는 이전에 저장된 파일이 있는지 확인합니다.

    파일이 발견되면 사용자 응시를 사용하지 않도록 설정하고 Azure Storage 파일에 저장된 셰이프 패턴에 따라 셰이프 만들기를 트리거합니다. 텍스트 메시가 셰이프 원본에 따라 '스토리지' 또는 '새로 만들기' 표시를 제공하므로 사용자는 이를 볼 수 있습니다.

    파일이 없으면Gaze를 사용하도록 설정되므로 사용자가 장면에서 GazeButton 개체를 확인할 때 셰이프를 만들 수 있습니다.

        /// <summary>
        /// Create the references necessary to log into Azure
        /// </summary>
        private async void CreateCloudIdentityAsync()
        {
            // Retrieve storage account information from connection string
            storageAccount = CloudStorageAccount.Parse(storageConnectionString);
    
            // Create a file client for interacting with the file service.
            fileClient = storageAccount.CreateCloudFileClient();
    
            // Create a share for organizing files and directories within the storage account.
            share = fileClient.GetShareReference(fileShare);
    
            await share.CreateIfNotExistsAsync();
    
            // Get a reference to the root directory of the share.
            CloudFileDirectory root = share.GetRootDirectoryReference();
    
            // Create a directory under the root directory
            dir = root.GetDirectoryReference(storageDirectory);
    
            await dir.CreateIfNotExistsAsync();
    
            //Check if the there is a stored text file containing the list
            shapeIndexCloudFile = dir.GetFileReference("TextShapeFile");
    
            if (!await shapeIndexCloudFile.ExistsAsync())
            {
                // File not found, enable gaze for shapes creation
                Gaze.instance.GazeEnabled = true;
    
                azureStatusText.text = "No Shape\nFile!";
            }
            else
            {
                // The file has been found, disable gaze and get the list from the file
                Gaze.instance.GazeEnabled = false;
    
                azureStatusText.text = "Shape File\nFound!";
    
                await ReplicateListFromAzureAsync();
            }
        }
    
  2. 다음 코드 조각은 Start() 메서드 내에서 가져옵니다. 여기서 CreateCloudIdentityAsync() 메서드를 호출합니다. 아래와 함께 현재 Start() 메서드를 자유롭게 복사할 수 있습니다.

        private void Start()
        {
            // Disable TLS cert checks only while in Unity Editor (until Unity adds support for TLS)
    #if UNITY_EDITOR
            ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
    #endif
    
            // Set the Status text to loading, whilst attempting connection to Azure.
            azureStatusText.text = "Loading...";
    
            //Creating the references necessary to log into Azure and check if the Storage Directory is empty
            CreateCloudIdentityAsync();
        }
    
  3. CallAzureFunctionForNextShape() 메서드에 대한 코드를 입력합니다. 이전에 만든 Azure Function App 을 사용하여 셰이프 인덱스 요청합니다. 새 셰이프가 수신되면 이 메서드는 셰이프를 ShapeFactory 클래스로 보내 장면에서 새 셰이프를 만듭니다. 아래 코드를 사용하여 CallAzureFunctionForNextShape()의 본문을 완료합니다.

        /// <summary>
        /// Call to the Azure Function App to request a Shape.
        /// </summary>
        public async void CallAzureFunctionForNextShape()
        {
            int azureRandomInt = 0;
    
            // Call Azure function
            HttpWebRequest webRequest = WebRequest.CreateHttp(azureFunctionEndpoint);
    
            WebResponse response = await webRequest.GetResponseAsync();
    
            // Read response as string
            using (Stream stream = response.GetResponseStream())
            {
                StreamReader reader = new StreamReader(stream);
    
                String responseString = reader.ReadToEnd();
    
                //parse result as integer
                Int32.TryParse(responseString, out azureRandomInt);
            }
    
            //add random int from Azure to the ShapeIndexList
            ShapeFactory.instance.shapeHistoryList.Add(azureRandomInt);
    
            ShapeFactory.instance.CreateShape(azureRandomInt, false);
    
            //Save to Azure storage
            await UploadListToAzureAsync();
        }
    
  4. 셰이프 기록 목록에 저장된 정수를 연결하고 Azure Storage 파일에 저장하여 문자열을 만드는 메서드를 추가합니다.

        /// <summary>
        /// Upload the locally stored List to Azure
        /// </summary>
        private async Task UploadListToAzureAsync()
        {
            // Uploading a local file to the directory created above
            string listToString = string.Join(",", ShapeFactory.instance.shapeHistoryList.ToArray());
    
            await shapeIndexCloudFile.UploadTextAsync(listToString);
        }
    
  5. Azure Storage 파일에 저장된 텍스트를 검색하고 목록으로 역직렬화하는 메서드를 추가합니다.

  6. 이 프로세스가 완료되면 메서드는 사용자가 장면에 더 많은 셰이프를 추가할 수 있도록 응시를 다시 사용하도록 설정합니다.

        ///<summary>
        /// Get the List stored in Azure and use the data retrieved to replicate 
        /// a Shape creation pattern
        ///</summary>
        private async Task ReplicateListFromAzureAsync()
        {
            string azureTextFileContent = await shapeIndexCloudFile.DownloadTextAsync();
    
            string[] shapes = azureTextFileContent.Split(new char[] { ',' });
    
            foreach (string shape in shapes)
            {
                int i;
    
                Int32.TryParse(shape.ToString(), out i);
    
                ShapeFactory.instance.shapeHistoryList.Add(i);
    
                ShapeFactory.instance.CreateShape(i, true);
    
                await Task.Delay(500);
            }
    
            Gaze.instance.GazeEnabled = true;
    
            azureStatusText.text = "Load Complete!";
        }
    
  7. Unity로 돌아가기 전에 Visual Studio에서 변경 내용을 저장합니다.

11장 - UWP 솔루션 빌드

빌드 프로세스를 시작하려면 다음을 수행합니다.

  1. 파일>빌드 설정으로 이동합니다.

    앱 빌드

  2. 빌드를 클릭한 다음 Unity에서 проводник 창이 시작됩니다. 여기서 만든 다음, 앱을 빌드할 폴더를 선택해야 합니다. 이제 해당 폴더를 만들고 이름을 앱으로 지정합니다. 그런 다음 폴더를 선택한 상태에서 폴더 선택 키를 누릅니다.

  3. Unity에서 App 폴더에 프로젝트 빌드를 시작합니다.

  4. Unity가 빌드를 마치면(다소 시간이 걸릴 수 있음) 빌드 위치에 проводник 창이 열립니다(작업 표시줄이 항상 창 위에 표시되지는 않지만 새 창이 추가되었음을 알려 주시기 때문에 작업 표시줄 확인).

12장 - 애플리케이션 배포

애플리케이션을 배포하려면 다음을 수행합니다.

  1. 마지막 챕터에서 만든 App 폴더로 이동합니다. 앱 이름이 '.sln' 확장명인 파일이 표시되며, Visual Studio 내에서 열려면 두 번 클릭해야 합니다.

  2. 솔루션 플랫폼에서x86, 로컬 머신을 선택합니다.

  3. 솔루션 구성에서 디버그를 선택합니다.

    Microsoft HoloLens 경우 컴퓨터에 테더링되지 않도록 원격 컴퓨터로 설정하는 것이 더 쉬울 수 있습니다. 하지만 다음을 수행해야 합니다.

    • 설정>네트워크 & 인터넷>Wi-Fi>고급 옵션 내에서 찾을 수 있는 HoloLens의 IP 주소를 알고 있습니다. IPv4는 사용해야 하는 주소입니다.
    • 개발자 모드가 켜지도록 합니다. 개발자를 위한 설정>업데이트 & 보안>에서찾을 수 있습니다.

    솔루션 배포

  4. 빌드 메뉴로 이동하여 솔루션 배포를 클릭하여 애플리케이션을 컴퓨터에 테스트용으로 로드합니다.

  5. 이제 앱이 설치된 앱 목록에 표시되어 시작 및 테스트할 준비가 되었습니다.

완료된 Azure Functions 및 스토리지 애플리케이션

축하합니다. Azure Functions 및 Azure Storage 서비스를 모두 활용하는 혼합 현실 앱을 빌드했습니다. 앱은 저장된 데이터를 그리고 해당 데이터를 기반으로 작업을 제공할 수 있습니다.

최종 제품 -end

보너스 연습

연습 1

두 번째 생성 지점을 만들고 개체가 생성된 지점을 생성하는 레코드를 만듭니다. 데이터 파일을 로드할 때 원래 생성된 위치에서 생성되는 셰이프를 재생합니다.

연습 2

매번 다시 열지 않고 앱을 다시 시작하는 방법을 만듭니다. 장면을 로드하는 것은 시작하기에 좋은 장소입니다. 이렇게 하면 앱에서 쉽게 다시 설정할 수 있도록 Azure Storage에서 저장된 목록을 지우는 방법을 만듭니다.