스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
SwiftUI와 함께 윈도우 너머로
우주로 떠날 준비 되셨나요? 새로운 SwiftUI 씬 유형으로 visionOS에서 놀라운 몰입형 경험을 만들 수 있습니다. ImmersiveSpace로 새로운 씬을 만들고, 3D 콘텐츠를 배치하며, RealityView를 통합하는 방법을 소개합니다. immersionStyle 씬 수정자를 사용하여 앱의 몰입도를 높이는 방법을 살펴보고, 아울러 공간 관리, ARKit을 통한 가상 손 추가, SharePlay 지원 추가, '세상에 없던' 경험 구축에 대한 모범 사례도 알아봅니다.
챕터
- 0:00 - Introduction
- 3:27 - Get Started
- 5:51 - Display content
- 12:00 - Managing your Space
- 19:12 - Customization
리소스
관련 비디오
WWDC23
-
다운로드
♪ 감미로운 인스트루멘탈 힙합 ♪ ♪ 안녕하세요 'SwiftUI와 함께 윈도우 너머로' 세션에 오신 여러분을 환영합니다 저는 Apple의 엔지니어인 라파라고 해요 세션 뒷부분에서는 마트가 함께 하겠습니다 오늘은 여러분이 이미 알고 계신 도구와 프레임워크로 xrOS의 모든 기능을 활용하여 사실적인 몰입형 경험을 제작하는 게 얼마나 쉬운지 보여 드리겠습니다
iOS용 AR 앱을 개발하시면서 이미 증강 현실에 익숙해지셨을 것입니다 저희는 지난 몇 년간 다채로운 iPhone 및 iPad용 AR 앱을 제작할 수 있도록 ARKit와 RealityKit를 비롯한 다양한 도구와 프레임워크를 도입하고 확장했습니다 이러한 앱을 사용하면 상호 작용형 사용자 인터페이스와 가상 객체로 사용자의 주변 환경을 증강하여 현실 세계와 상상의 경계를 허물 수 있죠 저희는 올해 xrOS 출시와 함께 몰입형 경험을 시작으로 AR을 완전히 새로운 수준으로 끌어올리고 있습니다 이러한 경험 속에서 여러분의 응용 프로그램은 윈도우를 포함한 UI를 표시하고 사용자 주변 어디에서나 3차원 콘텐츠를 표시합니다 주변 환경이 계속 눈에 보이면서 실제로 경험의 일부가 되기도 하죠 앱의 엘리먼트를 표면에 앵커링하고 가상 객체와 효과로 현실 세계를 증강하면서 풍부하게 만들 수 있어요 그리고 한 단계 더 나아가 공간 전체에 적용되는 완전 몰입형 경험도 있죠 보이는 모든 것을 앱으로 완벽히 제어할 수 있습니다 이 기능이 열어줄 모든 가능성을 생각해 보세요 무엇보다도 이 모든 것을 이미 익숙한 도구와 프레임워크 및 패턴으로 실현할 수 있다는 점이 가장 큰 장점입니다 그리고 그 핵심에는 SwiftUI의 Immersive Space가 있습니다 그럼 시작해 볼까요? 저희가 SwiftUI에 세 번째 차원을 추가한다는 것을 올해 다른 세션에서 배우셨을 것입니다 xrOS에 윈도우와 볼륨을 표시하고 SwiftUI의 사용하기 쉬운 선언적 패턴으로 3차원 사용자 인터페이스 엘리먼트를 표시할 수 있습니다 윈도우와 볼륨 모두 각각의 바운드 내에서 콘텐츠를 표시할 수 있어요 그렇다면 xrOS가 제공하는 무한한 공간을 최대한 활용하며 진정한 몰입형 경험을 구현하려면 어떻게 해야 할까요? 윈도우 바운드 너머나 머리 주변 그리고 윈도우 한가운데에 항목을 배치하는 게 좋습니다 이게 바로 저희가 Space를 설계한 목적입니다 윈도우와 볼륨 옆에 있는 Space는 xrOS에서 사용자 인터페이스를 표시하는 일종의 컨테이너입니다 이 세션에서는 Spaces 및 Spaces를 사용한 몰입형 경험 구현 방법에 집중할 것입니다 먼저 Space를 시작하고 콘텐츠를 표시하는 방법을 알아볼게요 그런 다음 마크가 Space를 관리하는 방법과 Space로 바로 실행하는 방법 및 Space에서 허용하는 모든 사용자화에 대해 설명할 것입니다 그럼 코드를 몇 가지 확인하면서 시작해 볼까요? 저는 우주 탐험이 그렇게 흥분되더라고요 다른 세션에서 진행했던 World 앱을 이어서 진행하기 위해 이번에는 태양계를 탐색할 수 있는 공간으로 앱을 단계적으로 확장해 볼 생각입니다 Space는 Immersive Space라는 SwiftUI의 새로운 씬 유형입니다 다른 씬 유형과 마찬가지로 앱에서 Immersive Space를 정의한 다음 언제든지 열고 닫을 수 있죠 앱을 전부 단일 Space로만 구성할 수도 있지만 윈도우 및 볼륨 옆에 하나 이상의 Space를 추가하여 기존 앱을 확장할 수도 있습니다 앱에는 두 Space가 동시에 열려 있을 수 없습니다 다른 Space를 열기 전에 먼저 현재 Space를 닫으세요 그리고 다른 씬 유형과 마찬가지로 뷰 계층 구조를 씬의 본문에 배치합니다 SolarSystem을 ImmersiveSpace에 배치하면 어떠한 클리핑 경계 없이 렌더링됩니다 얼마나 손쉬운지 잠깐 보여드릴게요 이 세 줄만으로 풍부하며 몰입형 경험을 선사하는 태양계 뷰로 만들었습니다 자, 디테일로 들어가 보죠 Space를 열면 다른 씬 유형과 차별화되는 몇 가지 특별한 동작이 가능해집니다 여러 앱이 나란히 실행되면 모두 같은 공간에 함께 표시되는데 이를 Shared Space라고 합니다 앱에 Immersive Space 씬이 표시되면 앱은 Full Space라는 공간으로 들어갑니다 그러면 앱은 사용자가 볼 수 있는 유일한 앱이 되죠 다른 모든 응용 프로그램은 방해 요소 없이 콘텐츠를 표시할 수 있는 공간을 확보하기 위해 사라집니다 나중에 Space를 닫으면 다른 앱이 다시 나타납니다 Immersive Space는 씬이기 때문에 자체 좌표계를 암묵적으로 정의합니다 따라서 Space에 배치하는 모든 것은 Space 자체의 원점에서 상대적으로 배치됩니다 그리고 Space의 원점은 Space가 처음 열릴 때 사용자 아래 즉 사용자의 발치에 있습니다 이제 기본 사항을 이해하셨겠죠? 다음으로 Space 콘텐츠 표시 방법에 대해 설명할게요 ImmersiveSpace는 씬 유형이므로 뷰 계층 구조를 바로 그 안에 배치하면 됩니다 ImmersiveSpace는 모든 Swift UI 뷰를 취할 수 있고 클리핑 경계가 없는 경우에도 Space는 레이아웃 경계 내에 콘텐츠를 배치합니다 Space에 배치하는 모든 항목은 이미 익숙한 레이아웃 시스템과 동일한 시스템을 사용합니다 하지만 Space의 원점이 사용자의 발치에 있기 때문에 콘텐츠를 그곳에 두는 것만으로는 충분하지 않을 수 있어요 RealityView를 살펴보죠 SwiftUI, ARKit, RealityKit를 최대한 활용하려면 ImmersiveSpace와 새로운 RealityView의 강력한 기능을 함께 사용하시면 좋습니다 ImmersiveSpace와 RealityView는 함께 사용되며 훌륭한 몰입형 경험 빌드에 필요한 모든 기능을 제공하기 위해 특별히 함께 설계되었습니다 예를 들어, RealityView는 여기에 표시된 것처럼 에셋의 비동기식 로드 및 별 영역 표시를 위한 내장된 지원 기능을 제공합니다 하지만 비동기식 로드에 이어 Immersive Space 씬에 RealityView를 넣으면 훨씬 더 많은 것을 할 수 있죠 RealityView 내의 요소를 ARKit 앵커에 배치하세요 Space가 열려 있는 동안 앱에서 손과 머리 자세 데이터에 액세스할 수 있으므로 해당 데이터를 사용하여 RealityView 내에 엔티티를 배치할 수 있습니다 마크가 나중에 멋진 걸 보여드릴 거예요 다음은 좌표 Space에 대한 참고 사항입니다 RealityView는 RealityKit로 콘텐츠를 표시합니다 따라서 RealityView 내에서 엔티티를 배치할 때 SwiftUI의 레이아웃 시스템과 좌표 Space 방향이 다르다는 점에 유의하세요 SwiftUI에서 Y축은 아래쪽을 가리키고 Z축은 사용자 쪽을 가리킵니다 이는 윈도우와 볼륨 Immersive Space에 적용되는 반면 RealityKit에서는 Y축이 위쪽을 향합니다 RealityView에는 다룰 만한 내용이 많습니다 자세한 내용은 'RealityKit로 공간 경험 빌드하기' 세션에서 확인해 보세요 이제 코드를 작성해 보겠습니다 우리는 WorldApp 아니면 적어도 WorldApp의 단순화된 버전을 사용해서 단계적으로 몰입형 태양계를 추가할 것입니다 먼저 ImmersiveSpace 정의부터 할게요 WindowGroup과 마찬가지로 식별자나 값 유형 혹은 둘 다 할당할 수 있습니다 지금 같은 경우에는 solar 식별자를 할당할게요 나중에 이 식별자를 사용하여 Space를 열 것입니다 그런 다음 SolarSystem 뷰를 Space에 배치할게요 또한 앱을 실행할 때 태양계를 보기 위한 컨트롤과 함께 앱에 표시할 간단한 표준 윈도우를 정의해 볼게요 이는 World 앱과 유사합니다 WindowGroup을 사용해 새 실행 윈도우를 정의하고 Space를 열 수 있는 컨트롤과 함께 몇 가지 정보를 추가할게요 그 컨트롤은 버튼 하나면 됩니다 클릭하면 제목을 변경하고 Space를 엽니다 윈도우 제어를 위해 SwiftUI는 openWindow와 dismissWindow 환경 동작을 제공합니다 그리고 Immersive Space의 경우 새로운 openImmersiveSpace와 dismissImmersiveSpace 동작을 추가하죠 우리는 환경으로부터 두 가지 동작을 얻습니다 그런 다음 버튼이 호출될 때 이러한 동작을 사용할 수 있어요 Space를 열 때 앞서 정의한 식별자를 전달합니다 한 번에 하나의 Space만 열 수 있으니 dismissImmersiveSpace 동작에는 인수가 필요하지 않습니다 시스템은 Space 안팎을 일정 시간 동안 애니메이팅합니다 이러한 환경 동작은 비동기식이어서 애니메이션의 완료에 반응할 수 있습니다 Immersive Space 열기는 실패할 수 있으며 그 결과를 통해 호출의 성공 및 실패 여부를 openImmersiveSpace가 알려줍니다 적절한 오류 처리 기능이 있는지 확인하세요 처음에 정의한 앱으로 돌아와서 이제 여기에 LaunchWindow를 추가하면 됩니다 두 씬의 순서에 주목하세요 LaunchWindow는 씬 목록의 첫 번째이므로 앱이 시작될 때 SwiftUI가 실행 윈도우를 표시합니다 Immersive Space는 시작 시에는 표시되지 않으며 사용자가 버튼을 클릭한 후에만 표시됩니다 이를 시뮬레이터에서 실행해 볼게요 앱이 실행되면 실행 윈도우가 표시됩니다 그리고 버튼을 클릭하기만 하면 태양계가 거실에 바로 나타나죠
이렇게 표준 윈도우와 태양계를 표시하는 Space로 구성된 다중 씬 앱을 정의했습니다 World 앱에서 사용되는 모델을 보셨을 텐데요 몰입형 앱을 빌드할 때 Space에 디테일이 풍부한 3D 에셋을 표시하면 좋을 것입니다 앱이 에셋을 완전히 로드하고 렌더링할 준비를 마치는 데 시간이 걸릴 수 있는데요 최상의 사용자 경험을 위해 3D 에셋을 비동기식으로 로드하는 새로운 Model3D 및 RealityView API를 활용하세요 여기 이 코드에서는 모델이 로드되는 동안 'Waiting' 텍스트를 표시하고 뭔가 잘못되었을 경우에는 'Error'를 표시합니다 이제 마크가 Space 관리 방법과 함께 더 나아가 Space로 시작하는 방법을 알려드릴 거예요 고마워요, 라파 방금 시연에서 보셨듯이 코드 몇 줄만으로 Immersive Space를 World 앱에 통합하는 것은 상당히 쉬웠습니다 앱을 몰입형 경험으로 바꾸는 작업에는 씬 phase를 동반하여 시스템과 함께 Space를 관리하고 Space와 다른 씬 간의 융합을 조정하며 다양한 방식으로 표현하는 것도 포함됩니다 다른 SwiftUI 씬 유형과 비슷하게 Immersive Space는 시스템이 처리하는 동일한 씬 phase를 지원합니다 이는 또한 Space가 항상 SwiftUI의 씬 phase 중 하나에 있음을 의미하죠 Space를 열면 활성 phase로 이동합니다 그리고 언제든 비활성 phase로 변경할 수 있어요 예를 들어, 시스템에서 정의한 경계를 벗어나거나 시스템 경고가 표시되면 Space와 윈도우가 일시적으로 숨겨져 비활성 phase로 이동합니다 사용자가 다시 경험에 돌입하면 Space와 윈도우가 표시되어 씬 phase가 다시 활성화되도록 업데이트됩니다 World 앱의 경우 간단한 코드를 몇 줄 추가하여 비활성 씬 phase를 처리할 수 있습니다 Space의 상태가 변경되었음을 나타내기 위해 지구 모델을 절반 크기로 축소해 보겠습니다 또한 콘텐츠를 복원하기 위해 활성 phase를 처리하는지 확인해 볼게요 Space는 하드웨어 또는 소프트웨어 수단을 사용하여 언제든지 종료할 수 있다는 점을 기억해 두세요 이제 시뮬레이터에서 확인해 보겠습니다 Space를 열고 앱이 비활성 phase를 어떻게 처리하는지 보여드릴게요 예를 들어 알림이 표시될 때 트리거될 수 있습니다 알림이 표시되면 이전 샘플 코드의 결과로 Space 콘텐츠의 크기가 변경된 것을 확인하세요 그리고 경고를 종료하자 Space가 축소되어 현재 활성 phase에 반응했습니다 SwiftUI를 사용하면 이러한 전환을 정말 쉽고 편리하게 처리하고 애니메이팅할 수 있어요 Space를 관리하는 또 다른 좋은 방법은 다른 윈도우의 콘텐츠를 Space와 통합하는 것입니다 예를 들어, 메인 윈도우 옆에 있는 지구 모델의 위치를 변경하려면 Immersive Space 좌표계에서의 윈도우 위치를 알아야 하지만 두 객체 모두 각자의 좌표계를 정의하고 있습니다 SwiftUI는 이 문제를 해결하기 위해 Immersive Space라는 새로운 좌표 Space를 제공합니다 그리고 이는 Immersive Space의 좌표계를 나타냅니다 이 좌표계에 액세스하기 위해 윈도우를 GeometryReader3D 콘텍스트 내부에 캡슐화합니다 그런 다음 트랜스폼처럼 좌표 공간을 취하는 기존 API를 사용하고 Immersive Space 유형을 전달하면 새 좌표계에서 proxy.transform을 얻을 수 있습니다 이 트랜스폼을 사용하여 탭에서의 Earth 위치를 업데이트합니다 이를 시뮬레이터에서 실행해 볼게요 Space를 다시 열어 지구와 메인 윈도우가 보이게끔 할게요 윈도우를 약간 옮기고 윈도우 바로 앞에 지구를 재배치하려고 합니다 이제 지구를 탭하면 우리가 예상한 곳에 위치하게 됩니다 좌표 변환을 사용하면 원하는 위치에 콘텐츠를 정확하게 배치하고 Space와 윈도우 사이에서 에셋을 쉽게 옮길 수 있습니다 좌표 변환을 사용하는 다른 예로는 SharePlay의 Immersive Space가 있는데요 이 경우 개인 Immersive Space뿐만 아니라 그룹 Immersive Space에서도 콘텐츠의 위치를 관리할 수 있습니다 앱이 그룹 Immersive Space와 SharePlay를 지원하는 경우 다른 참가자가 참여하면 시스템이 공간 템플릿으로 정의된 공유 위치로 공간의 원점을 이동할 수 있습니다 자세한 내용은 '공간 SharePlay 경험 빌드하기' 세션에서 확인해 보세요 이제 World 앱에서 씬 phase를 처리하고 다른 윈도우에서 콘텐츠를 결합할 수 있지만 아직 Space가 제공하는 모든 기능을 사용하지는 않았습니다 다음으로 몰입 방식을 살펴보고 이어서 더욱 놀라운 방식으로 만들어 볼게요 몰입 방식은 공간 콘텐츠가 주변 환경을 채워 나가는 방법에 대한 다양한 표현 방식을 제공합니다 혼합 방식이나 온라인에서 정면을 향한 점진적 방식 또는 사방으로 둘러싸인 전체 방식과 함께 콘텐츠를 표시할 수 있죠 이 모든 방식을 활용할 수 있게 앱을 업데이트하세요 앱을 다시 열고 Immersive Space를 정의한 곳으로 돌아가 보겠습니다 현재 Space는 혼합 몰입 방식으로 태양계를 표현하고 있는데 이것이 기본적인 방식입니다 방식은 쉽게 변경할 수 있으며 역동적으로 할 수도 있어요 먼저 ImmersionStyle 유형의 새 상태 변수를 추가하고 Space 시작 시 사용할 기본값을 할당해 보겠습니다 여기서는 혼합 방식을 유지할게요 그런 다음 immersionStyle 씬 수정자를 사용하고 Space에서 지원하고자 하는 방식 목록을 정의합니다 현재 방식에 대한 참조를 갖기 위해 상태 변수를 바인딩으로 전달할게요 바인딩을 태양계에 전달하면 현재 방식을 읽을 수 있으며 어떤 지도 방식으로든 전환하도록 제어할 수도 있습니다 이 샘플에서는 태양계를 확대할 때 다른 방식으로 전환할 수 있도록 확대 제스처를 사용하겠습니다 지금까지 시뮬레이터에서 World 앱을 실행하여 Immersive Space를 얼마나 쉽게 구현할 수 있는지 살펴봤는데요 하지만 이러한 방식이 주변 환경과 어떻게 작동하는지 제대로 파악하려면 기기에서 직접 경험해 보시기 바랍니다 그리고 나중에 온디바이스 경험을 확실히 향상할 수 있는 더 많은 사용자화 기능을 보여드리겠습니다 기본 방식으로 Space를 열면 혼합 몰입 방식이 표시됩니다 이 방식도 좋지만 여러분은 콘텐츠에 좀 더 몰입하여 별을 보고 싶을 수도 있겠죠 그럴 때 확대 제스처를 수행할 수 있습니다 콘텐츠가 커짐에 따라 Space는 결국 점진적 방식으로 전환됩니다 이 방식은 passthrough와 완전 몰입형 경험 사이의 가교 역할을 합니다 이를 통해 눈앞에 있는 포털 내의 Immersive Space 콘텐츠를 주변 환경과 함께 볼 수 있습니다 이 방식은 몰입감이 뛰어날 뿐만 아니라 주변 환경을 인식할 수 있게 합니다 또한 주변 사람들과 채팅하고 편안하게 앉을 수 있는 위치를 파악하며 주변 환경과 상호 작용 할 수 있게 하죠 익숙해지면 Digital Crown을 돌려서 방식의 몰입도를 높일 수 있어요 정말 멋지지 않나요? 이제 여러분은 우주인처럼 은하계를 떠다니고 있습니다 주변 환경을 더 많이 보고 싶다면 Digital Crown을 되돌려 몰입감을 낮추면 됩니다 이를 통해 점진적 방식 내에서 콘텐츠의 몰입도를 빠르고 쉽게 제어할 수 있습니다 그런데 항상 완전히 몰입하고 싶을 수도 있죠 이것은 여러분의 주변을 둘러싸거나 즉시 완전히 다른 세계로 이동하는 경험에 아주 좋습니다 지금까지 제스처를 기반으로 다양한 방식으로 전환하는 것이 얼마나 쉬운지 알아봤습니다 완전 몰입으로 전환하는 것도 다를 게 없습니다 지구를 다시 확대하여 방식 바인딩을 업데이트하면 경험할 수 있죠 서로 다른 방식 사이를 얼마나 쉽고 자연스럽게 전환할 수 있는지 보세요 이제 Space는 완전 몰입형이 되었습니다 그리고 SwiftUI와 함께 코드 몇 줄로 구현했죠 경험을 마칠 준비가 되었을 때 언제든지 Digital Crown을 누르면 passthrough로 돌아갈 수 있습니다
지금까지 씬 phase 변화에 대응하고 방식을 제어하여 Space를 관리하는 다양한 방법을 시연해 봤습니다 이제 최종 개선 사항을 추가하여 Space를 한층 더 발전시켜 보죠 기기의 공간 컴퓨팅 기능을 사용하면 Space를 쉽게 향상하여 더욱 흥미로운 느낌을 줄 수 있습니다 그럼 Space에 직접 실행하기와 주변 환경에 효과 추가하기 및 가상 손과 같은 몇 가지 옵션을 살펴볼게요 지금까지 우리 앱에서는 버튼 클릭 한 번으로 Space를 열 수 있었습니다 하지만 앱 시작과 동시에 몰입형 경험을 시작하고 싶다면 예를 들어 완전 몰입형 게임이 있다면 어떻게 해야 할까요? Immersive Space로 바로 실행하려면 앱에 대한 씬 매니페스트를 설정해야 합니다 ImmersiveSpace 응용 프로그램 역할과 몰입 방식을 설정하세요 평소와 같이 Space 콘텐츠를 첨부하면 바로 열립니다 사용자가 Space 종료를 선택하면 앱이 윈도우로 돌아가도록 할 수도 있어요 다음으로, 주변 효과 설정을 통해 passthrough를 어둡게 설정하여 공간 콘텐츠에 더 집중되도록 할 수 있습니다 Space가 점진적 방식으로 전환될 때 주변이 어두워지게 해볼까요? 태양계가 나타나면 주변 환경이 자동으로 희미해지도록 preferredSurroundingEffects 수정자를 어두워지게 설정했습니다 upperLimbVisibility 수정자는 Space에 있는 동안 손을 숨길 수 있게 하는데 passthrough 기능이 없기 때문에 완전 몰입형입니다 우리의 월드 경험을 위해 Space를 열 때 환경설정을 false로 설정할 것입니다 그리고 이처럼 upperLimbVisibility 설정을 변경할 수 있습니다 완전 출현 방식에서 손을 숨기면 대신 가상 손을 보여줄 수 있으며 World 앱에서 스페이스 글러브가 나타날 것입니다 SpaceGloves라는 새 뷰를 생성하며 시작해 볼게요 다음으로 RealityView를 추가하여 Space에서 장갑을 렌더링할 수 있도록 할게요 그런 다음 RealityView에 루트 엔티티를 생성하여 엔티티를 추가하고 렌더링할 수 있도록 하겠습니다 그런 다음 에셋을 엔티티에 로드하고 루트의 자식으로 추가합니다 엔티티를 올바르게 배치하려면 ARKit와 손 추적 API를 사용해야 하며 손 추적 시스템도 시작해야 합니다 다음 단계는 에셋이 손에 올바르게 고정되도록 하는 것입니다 손 추적 앵커 업데이트를 확인해야 하고요 다음으로 손의 비대칭성을 확인하겠습니다 그런 다음 손 에셋의 트랜스폼이 앵커와 동일한지 확인할게요 이 예제에서는 에셋이 ARKit에서 제공하는 것과 동일한 조인트 이름을 갖고 있는지도 확인했습니다 이렇게 하면 앵커 스켈레톤 조인트 이름을 올바르게 매핑할 수 있고 장갑 엔티티가 자동으로 앵커링됩니다 이제 Space가 정의된 위치로 돌아가서 SpaceGloves 뷰를 포함해 보겠습니다 가상 손에는 이 정도면 충분합니다 ARKit의 사용자화 및 상세한 내용에 대해서는 'ARKit 앱을 공간 경험에 맞게 개선하기' 세션을 참조하세요 이제 기기에서 이러한 사용자화를 시도해 볼게요 World 경험이 다시 시작되고 Space가 기본 몰입 방식으로 다시 열립니다 지구에 확대 제스처를 사용하면 앱이 점진적 방식으로 전환됩니다 Space가 열리면 코드는 주변이 어두워지도록 수정합니다 Surrounding Effects API를 활용하여 passthrough를 어둡게 함으로써 몰입감을 더욱 높일 수 있죠 적용하기 쉽고 경험에 집중할 수 있는 훌륭한 방법입니다 지금도 꽤 몰입도가 높지만 다음 사용자화를 통해 한 단계 더 나아갈 수 있습니다 앞서 시연한 코드에서 보았듯이 완전 몰입형으로 전환하면 손이 사라지고 손 추적 기능 덕분에 손이 있어야 할 자리에 가상 스페이스 글러브가 나타납니다 ARKit로 RealityView를 사용하고 손 추적을 활성화하면 가상의 우주 비행사처럼 우주로 진출할 수 있으며 정말 멋진 느낌을 받을 수 있어요
몇 가지 개선 사항과 사용자화를 통해 World 앱을 Shared Space를 뛰어넘는 완전 몰입형 경험으로 전환할 수 있었습니다 이제 새로운 Immersive Space API를 사용하여 쉽게 경험을 만들고 다양한 방식으로 경험을 강조하며 사용자화를 실현하여 창의력을 발휘하는 것은 여러분에게 달려 있습니다 이 API는 주변 환경을 바꾸고 새롭고 몰입감 있는 경험을 만드는 데 필요한 모든 도구를 제공하는 강력하고 사용하기 쉬운 API입니다 참여해 주셔서 감사합니다 ♪
-
-
4:18 - Defining an ImmersiveSpace
@main struct WorldApp: App { var body: some Scene { ImmersiveSpace { SolarSystem() } } }
-
6:53 - RealityView in an ImmersiveSpace
ImmersiveSpace { RealityView { content in let starfield = await loadStarfield() content.add(starfield) } }
-
8:17 - ImmersiveSpace with a SolarSystem view
@main struct WorldApp: App { var body: some Scene { ImmersiveSpace(id: "solar") { SolarSystem() } } }
-
9:00 - LaunchWindow
struct LaunchWindow: Scene { var body: some Scene { WindowGroup { VStack { Text("The Solar System") .font(.largeTitle) Text("Every 365.25 days, the planet and its satellites [...]") SpaceControl() } } } }
-
9:11 - SpaceControl button using Environment actions for opening and dismissing an ImmersiveSpace scene
struct SpaceControl: View { @Environment(\.openImmersiveSpace) private var openImmersiveSpace @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace @State private var isSpaceHidden: Bool = true var body: some View { Button(isSpaceHidden ? "View Outer Space" : "Exit the solar system") { Task { if isSpaceHidden { let result = await openImmersiveSpace(id: "solar") switch result { // Handle result } } else { await dismissImmersiveSpace() isSpaceHidden = true } } } } }
-
10:44 - WorldApp using LaunchWindow and ImmersiveSpace
@main struct WorldApp: App { var body: some Scene { LaunchWindow() ImmersiveSpace(id: "solar") { SolarSystem() } } }
-
11:32 - Model3D with phase handling
Model3D(named: "Earth") { phase in switch phase { case .empty: Text( "Waiting" ) case .failure(let error): Text("Error \(error.localizedDescription)") case .success(let model): model.resizable() } }
-
13:04 - Scene Phases
@main struct WorldApp: App { @EnvironmentObject private var model: ViewModel @Environment(\.scenePhase) private var scenePhase ImmersiveSpace(id: "solar") { SolarSystem() .onChange(of: scenePhase) { switch scenePhase { case .inactive, .background: model.solarEarth.scale = 0.5 case .active: model.solarEarth.scale = 1 } } } }
-
14:21 - Coordinate Conversions
var body: some View { GeometryReader3D { proxy in ZStack { Earth( earthConfiguration: model.solarEarth, satelliteConfiguration: [model.solarSatellite], moonConfiguration: model.solarMoon, showSun: true, sunAngle: model.solarSunAngle, animateUpdates: animateUpdates ) .onTapGesture { if let translation = proxy.transform(in: .immersiveSpace)?.translation { model.solarEarth.position = Point3D(translation) } } } } }
-
16:34 - Immersion Styles
@main struct WorldApp: App { @State private var currentStyle: ImmersionStyle = .mixed var body: some Scene { ImmersiveSpace(id: "solar") { SolarSystem() .simultaneousGesture(MagnifyGesture() .onChanged { value in let scale = value.magnification if scale > 5 { currentStyle = .progressive } else if scale > 10 { currentStyle = .full } else { currentStyle = .mixed } } ) } .immersionStyle(selection:$currentStyle, in: .mixed, .progressive, .full) } }
-
20:08 - Surrounding Effects
@main struct WorldApp: App { @State private var currentStyle: ImmersionStyle = .progressive var body: some Scene { ImmersiveSpace(id: "solar") { SolarSystem() .preferredSurroundingsEffect( .systemDark) } .immersionStyle(selection: $currentStyle, in: .progressive) } }
-
20:30 - Upper Limbs Visibility
@main struct WorldApp: App { @State private var currentStyle: ImmersionStyle = .full var body: some Scene { ImmersiveSpace(id: "solar") { SolarSystem() } .immersionStyle(selection: $currentStyle, in: .full) .upperLimbVisibility(.hidden) } }
-
20:52 - Hand Anchoring
struct SpaceGloves2: View { let arSession = ARKitSession() let handTracking = HandTrackingProvider() var body: some View { RealityView { content in let root = Entity() content.add(root) // Load Left glove let leftGlove = try! Entity.loadModel(named: "assets/gloves/LeftGlove_v001.usdz") root.addChild(leftGlove) // Load Right glove let rightGlove = try! Entity.loadModel(named: "assets/gloves/RightGlove_v001.usdz") root.addChild(rightGlove) // Start ARKit session and fetch anchorUpdates Task { do { try await arSession.run([handTracking]) } catch let error as ProviderError { print("Encountered an error while running providers: \(error.localizedDescription)") } catch let error { print("Encountered an unexpected error: \(error.localizedDescription)") } for await anchorUpdate in handTracking.anchorUpdates { let anchor = anchorUpdate.anchor switch anchor.chirality { case .left: if let leftGlove = Entity.leftHand { leftGlove.transform = Transform(matrix: anchor.transform) for (index, jointName) in anchor.skeleton.definition.jointNames.enumerated() { leftGlove.jointTransforms[index].rotation = simd_quatf(anchor.skeleton.joint(named: jointName).localTransform) } } case .right: if let rightGlove = Entity.rightHand { rightGlove.transform = Transform(matrix: anchor.transform) for (index, jointName) in anchor.skeleton.definition.jointNames.enumerated() { rightGlove.jointTransforms[index].rotation = simd_quatf(anchor.skeleton.joint(named: jointName).localTransform) } } } } } } } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.