스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
SharePlay에서 공간 페르소나 템플릿 맞춤화하기
visionOS SharePlay 경험에서 공간 페르소나 템플릿을 사용하여 앱과 관련된 페르소나의 위치를 세밀하게 조정하는 방법을 알아보세요. SharePlay를 지원하는 샘플 앱에서 맞춤형 공간 페르소나 템플릿을 적용하는 방법, 참여자들의 자리를 바꾸는 방법, 시뮬레이터에서 변경 내용을 테스트하는 방법을 시연합니다. 또한 경험을 더욱 돋보이게 해줄 맞춤형 공간 템플릿을 디자인하는 모범 사례도 살펴보세요.
챕터
- 0:00 - Introduction
- 1:01 - SharePlay on visionOS
- 4:50 - Build "Guess Together"
- 23:41 - Play Guess Together
- 25:13 - Design for Spatial Personas
리소스
관련 비디오
WWDC23
WWDC22
WWDC21
-
다운로드
- 안녕하세요, Ethan입니다 - Kevin입니다 저희는 Spatial FaceTime 팀의 엔지니어입니다 visionOS 앱의 빌드 프로세스를 안내하게 되어 기쁩니다 이 앱은 공간 페르소나가 제공하는 현장감을 효과적으로 활용합니다 빌드 과정에서 SharePlay 앱 개발에 사용할 수 있는 새로운 Xcode 기능 및 GroupActivities API를 살펴보겠습니다 이 세션은 세 부분으로 나뉩니다 먼저, visionOS 기반 FaceTime과 SharePlay를 소개한 다음 ’Guess Together’를 빌드하는 프로세스를 안내합니다 맞춤형 공간 페르소나 템플릿을 정의하기 위한 새로운 API를 보여 주는 샘플 앱이죠 시뮬레이터에서 시뮬레이션된 FaceTime 통화에 대한 새 지원으로 visionOS SharePlay 앱을 테스트하는 방법을 설명합니다 마지막으로, Kevin이 공간 페르소나를 위한 멋진 SharePlay 경험을 디자인하는 방법을 설명합니다 visionOS 기반 FaceTime과 SharePlay로 시작하겠습니다
visionOS 기반 FaceTime은 공간 페르소나를 활용하여 진정한 존재감을 구현합니다 공간 페르소나 사용 FaceTime은 마치 함께 있는 것처럼 느껴집니다 이 비디오에 나오는 Freeform 앱과 같은 SharePlay 앱은 이 경험의 핵심적인 부분입니다 SharePlay를 도입하면 FaceTime이 앱에 제공하는 공유된 컨텍스트를 확장할 수 있습니다
visionOS 기반 SharePlay를 도입할 때 앱의 시각적 일관성을 유지하는 것은 여러분의 책임입니다 앱 UI는 통화의 모든 참여자 간에 동기화되어야 합니다 FaceTime은 공유된 공간 컨텍스트를 유지 관리합니다 이를 위해 참여자를 앱 주위에 일관된 방식으로 배치합니다 이 시각적 및 공간적 일관성이 결합되어 참여자들이 공유 공간에서 하나의 앱을 사용하고 있다는 느낌을 줍니다 참여자들은 앱의 UI를 가리키고 제스처를 취하면서 의사소통을 할 수 있어야 합니다 실제 공유 공간에서 화이트보드 주위에 모일 때와 동일한 방식이죠 공간 페르소나를 사용한 일반적인 visionOS FaceTime 통화는 참여자들이 하나의 원으로 정렬된 상태로 시작되므로 참여자들이 서로 쉽게 상호작용할 수 있습니다 하지만 참여자는 언제든지 SharePlay 활동을 시작할 수 있고 통화는 공간 템플릿으로 전환되어 각 참여자의 공간 페르소나를 새롭게 공유된 앱을 기준으로 재정렬합니다 중요한 점은, 이 템플릿은 시작점 또는 각 참여자의 자리만 정의한다는 것입니다 언제든지 참여자 1~3명이 공간을 돌아다니기 시작할 수 있으며 초기 시작 템플릿 외부로 나갈 수 있습니다 하지만 참여자가 기기의 Digital Crown을 사용해 다시 중앙으로 돌아오면 자신의 시작 자리에 재배치됩니다
GroupActivities 프레임워크는 SharePlay 앱에서 도입할 수 있는 다양한 내장된 시스템 공간 템플릿을 제공합니다 예를 들어, 비디오 앱은 지금까지 사용해 온 템플릿을 선호할 가능성이 높습니다 바로, 나란히 템플릿으로 참여자들이 하나의 곡선을 따라 배치되고 그 앞에 앱이 있습니다 하지만 음악 앱은 참여자들이 열린 원 형태로 정렬되고 해당 원 가장자리에 앱을 위한 빈 공간이 있는 대화식 템플릿에서 즐길 수 있습니다 3D 모델링 앱은 참여자들이 공유 볼륨 주위에 닫힌 원 형태로 정렬된 서라운드 템플릿에서 볼 가능성이 높습니다
이러한 모든 템플릿은 공유 앱을 기준으로 자리를 정의하며 여기서 공간 페르소나는 공유 활동 시작 시 배치됩니다 이전의 공유 공간에서 있었던 위치는 상관없습니다 하지만 활동이 이러한 내장 시스템 템플릿과 잘 맞지 않다면 어떻게 해야 할까요? 예를 들어, 체스 앱에 대한 작업을 진행 중인 경우 두 플레이어는 서로 마주 보고 앉고 나머지 참여자들은 측면에 관중으로 정렬되도록 하고 싶습니다 또는 카드 게임을 만드는 경우 딜러는 다른 플레이어들의 맞은편에 배치되어야 합니다 visionOS 2의 새로운 기능인 맞춤형 공간 템플릿을 생성하여 공간 페르소나 배치에 대한 완전한 제어권을 앱에 부여할 수 있으며 배치는 공유 장면을 기준으로 합니다 저는 맞춤형 공간 템플릿과 이 템플릿으로 구현할 수 있는 visionOS 기반의 독특한 사회적 경험에 대해 기대가 정말 큽니다
이 세션의 다음 부분에서는 더 고급 SharePlay 개념을 중점적으로 살펴보겠습니다 SharePlay, 그룹 활동, visionOS를 처음 사용하신다면 ’Group Activities로 맞춤형 경험 구축하기’와 ’공간 SharePlay 경험 빌드하기’ 를 먼저 시청하시는 것이 좋습니다 첫 번째로 말씀드린 비디오는 활동 참여자 간에 앱 상태를 동기화하는 방법을 설명하고 두 번째로 말씀드린 비디오는 공간 SharePlay를 소개합니다
이제 제가 만든 샘플 앱 Guess Together를 둘러보겠습니다 Guess Together는 팀 기반 구문 맞히기 게임으로 FaceTime에서 공간 페르소나를 사용하여 플레이합니다 제스처 게임과 약간 비슷합니다 두 팀으로 나눠 교대로 맞힙니다 자신의 차례가 되면 비밀 구문이 제시됩니다 예를 들면 유명인의 이름이나 관용구 등입니다 그러면 필요한 모든 수단을 동원해 팀원들이 해당 구문을 맞힐 수 있도록 하되, 해당 구문 자체를 말해선 안 됩니다 Guess Together는 네 단계로 정의됩니다 앱을 처음 실행하면 환영 UI가 표시되어 현재 FaceTime 통화에 SharePlay 그룹 세션을 사용하도록 권유합니다 SharePlay가 시작되면 Guess Together 카테고리 선택 단계이고 여기에서 플레이할 카테고리를 결정합니다 예를 들어 역사적 사건에서 가져온 구문이나 다양한 과일과 채소 등 더 간단한 내용으로 플레이할 수 있습니다 다음으로, 팀을 선택합니다 청팀과 홍팀 중 어떤 팀에 속할지 결정합니다 마지막으로, 게임을 플레이할 시간입니다 Guess Together에 점수판과 타이머가 있는 뷰가 표시됩니다 활성 플레이어 앞에 두 번째 뷰가 나타나 팀 동료들이 맞혀야 하는 비밀 구문을 표시합니다 이제 SharePlay가 시작되기 전에 발생하는 환영 단계를 제외하고 이러한 각 단계마다 공간 템플릿이 필요합니다 여러분이 앱의 각 화면을 위한 사용자 인터페이스 구현 방법을 신중히 고려하는 것과 동일하게 visionOS 기반 SharePlay 앱은 공유 장면을 기준으로 공간 페르소나를 정렬하는 방법에 대해 신중히 고려해야 합니다
카테고리 선택 화면에는 모든 참여자가 쉽게 보고 상호작용할 수 있어야 하는 공유 사용자 인터페이스가 표시됩니다 여기서는 어떤 방식으로든 구분되는 참여자가 없습니다 활성 플레이어가 없고, 플레이어가 두 팀으로 나뉘지 않았습니다 이러한 이유로 여기서 적절한 선택은 시스템에서 제공하는 나란히 템플릿입니다 Guess Together 같은 윈도우 형식 앱의 기본값입니다 이 템플릿은 정확히 이러한 용도로 디자인되었으며 참여자를 공유 앱을 향한 상태로 하나의 곡선 형태로 정렬하여 누구나 인터페이스에 쉽게 접근할 수 있도록 합니다 팀 선택 화면도 요구 사항이 비슷하지만 주요 차이점이 있습니다 여기서는 참여자들이 별개의 팀으로 나뉩니다 제 생각에는 앱이 여러분을 팀 동료 옆 자리에 배치하면 좋을 것 같습니다 이러한 팀 기반 자리 요구 사항으로 인해 팀 선택 화면에서는 맞춤형 공간 템플릿을 사용해야 합니다
시작하면, 참여자는 앱을 향한 5개의 자리 중 하나에 앉습니다 나란히 템플릿과 비슷합니다 하지만 참여자들이 팀에 속하면 Guess Together가 이들을 청팀 또는 홍팀 자리에 할당합니다 FaceTime이 실제로 참여자를 새로운 자리에 다시 앉히며 참여자들은 자신이 속한 팀의 다른 팀원 옆에 앉게 됩니다
게임 단계에는 가장 분명하게 맞춤형 공간 템플릿이 필요합니다 참여자는 여기에서 여러 역할을 수행할 수 있습니다 현재 플레이어부터 상대 팀의 플레이어까지 다양합니다
활성 플레이어는 점수판 윈도우 왼쪽에 앉으며 앞에 단상이 배치되어 있습니다 팀 동료들은 바로 맞은편에 있어야 합니다 공유 앱의 오른쪽입니다 그리고 상대 팀은 점수판 윈도우 바로 앞에 앉게 됩니다
이것이 바로 Guess Together입니다! 이 세션의 다음 부분에서는 이러한 각 템플릿을 함께 구현합니다 이렇게 하려면 SharePlay 활동 자체의 반복과 테스트가 약간 필요합니다 다행히도, Xcode 16 덕분에 멋진 경험을 할 수 있습니다
Xcode 16 visionOS 시뮬레이터에서 FaceTime 통화를 만들 수 있습니다 이것은 visionOS용 SharePlay 앱 개발의 판도를 바꿔놓았습니다 이제 기기에서 실제 FaceTime 통화를 해 보지 않고도 앱을 완전히 빌드할 수 있습니다 시뮬레이션된 FaceTime 통화를 시작하려면 먼저 메뉴 막대에서 Features 메뉴를 엽니다 그런 다음 FaceTime 하위 메뉴를 엽니다 여기에서 원하는 원격 참여자의 구성을 선택합니다 다양한 구성으로 앱을 사용해 보는 것이 중요합니다 완료되었습니다 사용해 보겠습니다
Xcode에 열린 Guess Together 프로젝트입니다 시뮬레이터에서 실행해 보겠습니다
Guess Together입니다! 현재 SharePlay 세션에 있지 않기 때문에 환영 단계가 표시되고 있습니다
Features 메뉴, FaceTime 하위 메뉴로 마우스 이동해 FaceTime을 활성화한 다음 User and 4 Spatial Participants를 선택합니다
바로 이렇게요, 시뮬레이션된 FaceTime 통화에 들어왔습니다 공간 버튼을 사용하여 공간 페르소나를 활성화합니다
이제 Play Guess Together 버튼이 활성 상태이고 SharePlay를 시작할 준비가 되었습니다 클릭해 보겠습니다
이제 앱이 SharePlay 세션에 있고 카테고리 선택 단계를 제공합니다 왼쪽으로 패닝하면
시뮬레이션된 참여자들이 나란히 공간 템플릿으로 이동했음을 볼 수 있습니다 코드에서 이것이 어떻게 구성되는지 살펴보겠습니다
Guess Together는 현재 SharePlay 그룹 세션과의 상호작용을 SessionController 클래스에서 관리합니다 이제 열겠습니다
SessionController는 특정 SharePlay 세션 동안 앱의 모든 상태를 추적하고 동기화합니다 세션 자체 다른 참여자와 상태를 동기화하는 GroupSessionMessenger 공간 페르소나를 구성하는 세션의 ServiceCoordinator를 저장합니다 그런 다음 앱의 다양한 상태를 업데이트하는 메서드를 제공합니다 팀 선택 단계 들어가기, 팀에 속하기, 게임 시작하기 등입니다 여기서는 이미 많이 작업되었으니 남은 작업을 살펴보겠습니다 먼저, 팀 선택 단계를 위한 맞춤형 공간 템플릿을 디자인하고 구성해야 합니다 이것이 완료되면 게임 단계로 넘어가서 템플릿을 조합하겠습니다 그런 다음 게임 단계를 위한 그룹 몰입형 공간을 구성합니다 여기서는 활성 플레이어의 자리 앞에 현재 구문이 있는 단상을 배치합니다
마지막으로, 앱 작업이 완료되면 FaceTime 통화에 참여하여 Guess Together를 실제로 플레이합니다 그러면 전체 경험이 어떻게 하나로 결합되는지 이해할 수 있습니다
다시 말씀드리지만, 여기서는 팀 선택 템플릿을 사용합니다 앱 전면에 관중석 5개와 2세트의 팀 기반 자리 3개가 있습니다
5개의 시작 자리로 템플릿 빌드를 시작해 보겠습니다 공간 템플릿은 SpatialTemplate 프로토콜을 따르는 유형을 생성하여 정의됩니다 TeamSelectionTemplate이라는 struct를 생성하고 SpatialTemplate을 따르도록 하겠습니다
SpatialTemplate의 기본 요구 사항은 템플릿 요소의 배열입니다
이러한 요소는 특정 위치의 자리로 정의됩니다
자리마다 하나의 공간 페르소나가 배치될 수 있습니다 이제 자리를 배치하기만 하면 됩니다
자리 위치는 공유 앱의 위치를 기준으로 정의됩니다 첫 번째 자리를 앱 바로 전면에 배치하고 싶으니 X 오프셋을 0으로 설정하여 앱의 중앙에 맞춰 정렬합니다 그리고 Z 오프셋을 4로 설정하여 앱 전면 4미터 거리에 배치합니다 두 번째 자리의 Z 오프셋은 동일하게 유지하되 이번에는 X 오프셋을 1로 설정하여 첫 번째 자리 오른쪽으로 1미터 거리에 배치합니다 자리를 앱에 배치하여 시작한 다음 Z 축을 통해 앱 전면 4미터 거리에 오프셋합니다 두 번째 자리의 경우 동일한 위치에 배치해 시작한 다음 X 오프셋을 오른쪽으로 1미터 조정합니다
세 번째에 대해서는 동일하게 수행하되 이번에는 왼쪽으로 1미터 오프셋합니다 나머지 2개 자리는 좌우로 1미터씩 더 이동해 배치합니다
이제 팀 선택 템플릿을 정의하기 위해 SpatialTemplate 템플릿을 따르는 struct를 생성했습니다 템플릿의 각 자리는 X 축과 Z 축 모두에서 특정 값만큼 앱으로부터 오프셋되는 위치를 제공하여 정의됩니다 이러한 값은 미터 단위를 사용하여 제공됩니다 멋진 시작이지만 아직 홍팀과 청팀을 위한 자리가 필요합니다 원래의 5개에서 각도가 적용된 추가 자리 6개를 설정해야 합니다 그런 다음 해당 자리를 각각 청팀과 홍팀의 팀원을 위해 예약된 것으로 표시해야 합니다 먼저 템플릿의 왼쪽 끝 자리에서 왼쪽으로 0.5미터, 앞으로 0.5미터 오프셋된 자리를 생성하는 것으로 시작합니다
나머지 2개의 청팀 자리에 대해 해당 패턴을 계속 사용합니다 이제 이러한 자리를 청팀에 예약된 것으로 표시할 방법이 필요합니다 이때 SpatialTemplate 역할을 사용합니다 자리에 역할을 추가하여 해당 역할이 할당된 참여자를 위해 자리를 예약할 수 있습니다 역할은 SpatialTemplateRole 프로토콜을 따르는 유형을 생성하여 정의됩니다 SpatialTemplateRole을 따르는 String 원본 값을 사용해 Role 열거형을 생성하겠습니다 그런 다음 blueTeam 및 redTeam 역할을 생성합니다
좋습니다 이제 blueTeam Role을 청팀 자리에 할당하고 blueTeam의 팀원을 위해 예약할 수 있습니다
blueTeam 자리에 대해 SpatialTemplateRole을 따르는 열거형을 사용해 blueTeam Role을 생성했습니다 그 다음 템플릿의 각 시작 자리에서 일정 각도를 적용하여 3개의 자리를 배치하고 각각에 해당 역할을 할당했습니다 이렇게 하면 청팀의 참여자들을 위해 해당 자리가 예약됩니다
홍팀 자리에 대해서도 동일하게 수행하겠습니다
이것이 완성된 템플릿입니다! 이제 팀 선택 단계 중에 이를 활성화해야 합니다 SessionController에서 수행하겠습니다 Guess Together가 공간 템플릿을 관리하기 위해 updateSpatialTemplatePreference 메서드를 사용합니다 teamSelection 사례를 업데이트하고 SystemCoordinator 구성에 접근하겠습니다
내장 환경설정을 지정하는 대신에 새로운 맞춤형 메서드를 사용하고 TeamSelectionTemplate을 제공합니다
이제 맞춤형 템플릿을 생성하고 구성했지만 아직 수행해야 할 중요한 단계가 있습니다 바로 역할 할당입니다 로컬 참여자의 팀이 변경된 경우 FaceTime에 알려야 합니다 그래야 시스템이 참여자를 새로운 자리로 이동시킬 수 있습니다
Guess Together는 이 updateLocalParticipantRole 메서드에서 역할 할당을 처리하도록 디자인되었습니다 현재 게임 단계로 전환하여 시작하겠습니다 필요한 역할이 이에 의존하기 때문입니다
teamSelection 단계에서 로컬 참여자 공간 템플릿 역할은 해당 팀에 의존합니다 따라서 이를 전환하겠습니다 이제 청팀 역할을 할당할 준비가 되었습니다 systemCoordinator에 접근하고 assignRole 메서드를 사용합니다 앞에서 정의한 blueTeam Role을 전달합니다
홍팀에 대해 동일한 패턴을 따릅니다
이제 제가 속한 팀이 nil이면 저는 관중석에 있어야 하며 관중석에는 역할이 없습니다 따라서 systemCoordinator에서 resignRole()을 사용할 수 있죠 categorySelection 단계에 대해 제 역할을 업데이트할 기회입니다 카테고리 선택에서는 내장된 나란히 템플릿을 사용하며 여기에는 역할이 없습니다 해당 단계에 들어갈 때 제 역할을 중지시키고 싶습니다 이전 단계의 역할이 유지되고 있을 경우에 대비해서요
좋습니다 사용해 보겠습니다
앱을 다시 실행하고 이번에는 팀 선택에 들어갑니다
제가 홍팀에 들어가는 경우 홍팀 자리로 이동하겠습니다
제가 청팀에 들어가는 경우에는 이제 청팀과 함께합니다
빠르게 복습해 보죠 공간 템플릿 자리에 역할을 추가하면 그 자리를 해당 역할의 참여자를 위해 예약하는 것입니다 FaceTime은 참여자가 그 역할을 스스로 할당할 때까지 해당 자리를 빈 상태로 유지합니다 로컬 참여자에게 역할을 할당하려면 systemCoordinator에서 assignRole 메서드를 사용합니다 해당 역할에 사용할 수 있는 자리가 있는 경우 FaceTime이 로컬 참여자의 공간 페르소나를 해당 자리로 이동시킵니다 로컬 참여자를 역할이 없는 자리로 돌려보내려면 systemCoordinator에서 resignRole 메서드로 역할을 중지합니다
이제 첫 번째 작업이 완료되었습니다 마지막 두 가지는 조금 더 빠르게 진행합니다 게임 단계를 위한 공간 템플릿을 살펴보겠습니다
여기서는 게임 템플릿을 사용합니다 활성 플레이어를 팀 동료들의 맞은편에 앉히고 싶습니다 참여자들이 차례대로 문제를 내는 자리에 들어오고 나가야 합니다 상대 팀의 플레이어들은 측면에 있는 관중석에 앉아야 합니다 지금까지는 팀 선택 템플릿을 생성하기 위해 사용한 방법과 비교해 새로운 내용이 없습니다 하지만 이 템플릿에 한 가지 맞춤화를 추가하고 싶습니다 자리 방향입니다 기본적으로 공간 템플릿 자리는 공유 앱을 향하지만 특정 지점을 바라보도록 자리의 방향을 맞춤화할 수 있습니다 이 템플릿의 경우 활성 플레이어는 활성 팀을 바라보고 활성 팀은 활성 플레이어를 바라보도록 하고 싶습니다 관중석의 경우에는 앱을 직접 바라보는 기본값을 유지하는 것이 적합합니다 이제 게임 템플릿을 열겠습니다 제가 이 템플릿의 대부분을 이미 완료했기 때문에 여기에 포함된 세부 사항에 대해 걱정할 필요가 없습니다 각 자리의 방향을 설정하는 작업만 하겠습니다 템플릿은 3가지 자리 세트를 정의합니다 플레이어 자리, 활성 팀 자리 그리고 관중석입니다
제가 이 템플릿을 만들 때 활성 팀 위치를 자체 변수로 정의했기 때문에 플레이어 자리 방향 설정 시 해당 위치를 재사용할 수 있습니다 플레이어 자리의 방향이 해당 위치를 보도록 설정합니다 direction 매개변수를 제공하고 lookingAt 메서드를 사용해서요
이제 플레이어 자리의 방향이 활성 팀 자리의 중앙을 향합니다 다음으로, 팀 자리의 방향을 설정하겠습니다 다시 한 번 direction 매개변수를 제공합니다 이제 플레이어 자리를 lookingAt 메서드에 직접 전달합니다
이것이 완성된 템플릿입니다
이제 게임 단계를 진행하는 동안 이를 활성화하고 Guess Together의 역할 할당 논리를 업데이트해야 합니다 SessionController로 돌아가 보겠습니다
팀 선택을 위해 사용했던 것과 동일한 패턴으로 게임 템플릿을 구성하는 것으로 시작하겠습니다
이제 역할을 할당할 시간입니다 이 경우, 역할은 활성 플레이어가 누구인지에 따라 결정됩니다 3개의 가지가 있는 조건문을 생성하겠습니다
첫 번째는 활성 플레이어 두 번째는 활성 팀 세 번째는 관중을 위한 것입니다 systemCoordinator와 assignRole 메서드를 다시 사용합니다
활성 팀에 대해서도 동일하게 수행합니다
마지막으로, 관중에 대해서는 현재 역할을 중지시킵니다
좋습니다 게임 단계를 사용해 보겠습니다
단계가 시작되면 팀 동료들이 앉게 될 자리를 바라봅니다 점수판이 제 왼쪽에 있고
관중은 제 오른쪽에 있습니다 이렇게 하면 Guess Together를 플레이하는 사람들에게 멋진 경험을 선사할 수 있습니다 활성 플레이어가 될 때마다 자신의 방향을 조정할 필요가 없기 때문입니다 시뮬레이션된 참여자들이 건너편의 팀 자리에 앉지 않은 이유를 궁금해하실 수 있습니다 시뮬레이션된 참여자가 템플릿에서 스스로 역할을 할당하지 않기 때문입니다 역할 없이 사용 가능한 첫 번째 자리에 항상 앉게 되죠
이것으로 두 번째 작업이 완료되었습니다 게임 단계 몰입형 공간을 살펴보겠습니다 게임 단계를 진행하는 동안 활성 플레이어에게 현재 비밀 구문을 표시하는 방법이 필요합니다 여기서 활성 플레이어에게만 표시되는 두 번째 개인 윈도우를 사용할 수 있습니다 하지만 제 생각에는 몰입형 공간을 열면 좋을 것 같네요 공유된 공간 컨텍스트를 유지할 수 있도록 말입니다
이렇게 하면 활성 플레이어의 전면에 위치하는 공유 UI의 추가 부분을 배치할 수 있습니다
기본적으로, SharePlay 사용 중에 몰입형 공간을 열면 해당 공간은 각 참여자의 개인 공간이 됩니다 따라서 참여자는 몰입형 공간에 있는 동안 서로의 공간 페르소나를 볼 수 없습니다 참여자들 간에 공유되어야 하는 몰입형 공간을 빌드하는 경우 systemCoordinator 구성에서 그룹 몰입형 공간을 선택할 수 있습니다 또한 그룹 몰입형 공간을 위한 맞춤형 공간 템플릿을 개발할 때 템플릿의 자리를 기준으로 UI 요소를 배치할 수 있습니다 템플릿의 원점은 공유 공간의 원점이기도 하기 때문입니다 사용해 보겠습니다
공유된 그룹 몰입형 공간을 선택하는 것으로 시작하겠습니다 SystemCoordinator 구성에 접근하여 supportsGroupImmersiveSpace 속성을 true로 설정합니다
이제 비밀 구문 단상에 추가할 준비가 되었습니다 제가 이미 이 작업을 시작했습니다 확인해 보겠습니다 ImmersiveSpace 그룹을 확장하여 PhraseDeckPodiumView를 엽니다
PodiumView는 SwiftUI 첨부 뷰를 사용해 현실 뷰로 정의됩니다 단상의 위치는 updatePodiumPosition에서 설정됩니다 단상을 활성 플레이어 자리 전면에 배치하겠습니다 게임 템플릿에 정의된 플레이어 자리의 위치와 일치하도록 단상을 이동하는 것으로 시작합니다 그런 다음 활성 플레이어 전면에서 0.5미터 정도 오프셋하겠습니다 활성 플레이어 자리의 위치를 여기서 직접 사용할 수 있습니다 RealityKit과 GroupActivities 모두 기본 길이 단위로 미터를 사용하므로 변환할 필요가 없습니다 그룹 몰입형 공간을 선택했으므로 앱 몰입형 공간의 원점은 공간 템플릿의 원점이기도 합니다
사용해 보겠습니다
이제 활성 플레이어가 게임 단계에 들어오는 즉시 팀원들을 향하게 되며 전면에 바로 단서 단상이 놓여 있습니다
어서 확인해 봐야겠습니다 다 됐습니다
이제 남은 것은 실제로 Guess Together를 함께 플레이하는 것입니다 제가 다른 방에서 FaceTime으로 연결하겠습니다
안녕하세요, Kevin 안녕하세요, Ethan Gabby와 Mia도 같이 왔습니다 안녕하세요 - 안녕하세요 - 안녕하세요 같이 Guess Together를 플레이할까요 어떤 카테고리를 플레이할까요? 음 저는 채소를 끄겠습니다 좋은 생각이네요 영화와 TV도 끄겠습니다
팀을 선택할 준비가 되셨나요? 해보죠
청팀에 들어가겠습니다
저는 홍팀을 선택하겠습니다
저도 홍팀에 들어갑니다
그럼 전 청팀인 것 같군요
좋습니다. 모두 준비되셨나요? 플레이합시다
제가 첫 번째인 것 같네요 행운을 빕니다 시작하겠습니다
이것은 과일이며, 일반적으로 빨간색 또는 초록색입니다 아주 달고, 아주 인기 있고 비교적 작습니다 아, 사과입니다 예 좋습니다 이것은 악기입니다 이름은 매우 시끄러움을 뜻하는 전체 이름의 줄임말입니다 무슨 뜻이죠? 88개의 건반이 있습니다 피아노입니다
예! 하하
정말 재미있었어요 모두들 잘하셨습니다
멋진 샘플 앱을 빌드해 주셔서 감사합니다, Ethan Guess Together는 친구들과 플레이하기에 아주 재미있네요 맞춤형 공간 템플릿을 사용하는 앱을 빌드하는 방법을 살펴봤으니 이제 공간 페르소나를 위한 SharePlay 경험을 디자인하는 프로세스를 검토해 보겠습니다 visionOS를 위한 새 SharePlay 경험을 디자인하는 첫 단계는 사용할 공간 페르소나 템플릿 유형을 선택하는 것입니다 특히, 맞춤형 템플릿과 내장 시스템 템플릿 중 결정하는 것이죠
이제, 첫 번째로 맞춤형 템플릿이 경험 향상에 효과적인지 살펴야 합니다 완전한 맞춤형 공간 페르소나 템플릿을 만들기 전에 그렇게 하면 경험이 향상될 것이라는 확신이 있어야 합니다 시스템 공간 페르소나 템플릿은 공간 페르소나로 멋진 경험을 선사하도록 광범위하게 조정되었습니다 나란히 템플릿은 모든 참여자가 앱 전면에 앉도록 하려는 경우에 권장되며 윈도우 형식 앱과 미디어 보기 활동에 정말 유용합니다 대화식 템플릿을 사용하면 참여자가 서로에게 집중하고 앱을 가까이에서 사용할 수 있습니다 서라운드 템플릿은 앱이 볼륨이고 참여자들을 그 주위에 원으로 배치하려는 경우 적합합니다 시스템 템플릿 사용 시의 이점이 몇 가지 있습니다 첫째, 앱을 사용하는 사람들에게 익숙하고 더 편안한 경험을 제공합니다 가능하면 앱 인터페이스에 기본 UI 제어기를 사용할 것을 권장하는 것과 마찬가지로 사람들이 이미 익숙한 공간 페르소나 템플릿을 사용하는 것이 좋습니다
또한 시스템 템플릿은 통화 중에 공간 페르소나의 숫자에 맞게 자동으로 조정됩니다 참여자가 활동 중에 나가거나 들어오는 경우에도 마찬가지죠 FaceTime이 지원하는 최대값까지 항상 자리가 충분하기 때문에 모두가 서로를 볼 수 있도록 할 수 있습니다 또한 시스템 템플릿은 공유 장면의 크기에 따라 동적으로 조정하여 누구나 앱을 사용할 수 있게 합니다
모든 시스템 템플릿을 평가해 보셨고 여전히 완전한 맞춤형 템플릿으로 경험이 향상된다고 생각하신다면 다음으로 고려할 사항은 다수의 템플릿이 경험 향상에 효과적인지 살펴보는 것입니다
경험을 한 가지 형태로만 고집하지 않는 것이 좋습니다 Guess Together를 디자인할 때 전체 앱에 대해 단일 맞춤형 템플릿을 선택하는 대신에 게임의 각 단계를 따로 고려하고 시스템 제공 및 맞춤형 템플릿을 혼합해 사용하는 것을 고려했습니다 이러한 질문에 대한 답은 활동의 다양한 단계별로 다를 수 있으므로 적절하다면 경험의 각 부분에 서로 다른 템플릿을 사용하는 것이 좋습니다 더 진행하기 전에 고려해야 할 것이 또 있는데요
경험에서 공간 페르소나를 사용하지 않는 참여자를 지원할 방법입니다
visionOS를 고려하여 SharePlay 경험을 디자인하는 경우 SharePlay 활동에서 공간 페르소나를 사용하는 참여자만 고려하는 실수를 할 수 있습니다 하지만 그것으로는 충분하지 않을 수 있습니다 멀티 플랫폼 SharePlay 활동을 빌드하는 경우 공간 페르소나를 지원하지 않는 플랫폼의 참여자가 있을 수 있죠 앱이 visionOS 전용이더라도 참여자가 SharePlay 활동 중에 언제든지 공간 페르소나를 토글할 수 있습니다 경험을 디자인할 때 이 두 경우를 모두 고려하세요 이상으로 템플릿 선택을 살펴봤습니다 이제 자리 정의하기로 넘어갈 준비가 되었습니다 자리를 정의할 때 항상 따라야 할 몇 가지 모범 사례가 있습니다 모든 공간 페르소나를 위한 자리를 포함해야 합니다 자리 사이에 충분한 거리를 제공하고 세심하게 고려하고 의도하여 자리의 순서를 구성해야 합니다 체스 같은 2인 보드게임을 위한 템플릿을 빌드한다고 가정해 보죠 초기 템플릿 초안은 이와 같습니다 두 자리를 서로 마주보게 하고 그 사이에 앱의 볼륨을 배치했죠 이것은 두 공간 페르소나만 SharePlay 활동에 참여하는 경우에 한해 잘 작동합니다 하지만 동참하여 공간 페르소나를 사용하려는 세 번째 참여자에게는 좋지 않은 경험을 제공합니다 이들을 위한 추가 자리를 만들지 않았기 때문에 FaceTime이 해당 페르소나를 다른 참여자와 함께 배치할 수 없죠 대안으로, FaceTime은 세 번째 참여자를 고유한 내장 템플릿에 배치하지만 체스를 두는 두 명과 세 번째 참여자는 서로를 볼 수 없습니다 따라서 모든 공간 페르소나를 위한 자리를 포함해야 합니다 템플릿에 자리를 3개 더 추가하면 FaceTime이 통화에서 지원하는 최대 공간 페르소나 수에 대한 자리가 충분하므로 활동의 모든 참여자에게 더 나은 경험을 선사합니다 모든 공간 페르소나를 위한 자리를 제공하는 것만큼 중요한 것은 각 페르소나의 자리 사이에 충분한 공간을 포함하는 것입니다 서로 1미터 이상 떨어지게 자리를 배치하는 것이 좋습니다 이렇게 하면 사람들이 옆 사람에게 바싹 붙는 느낌 없이 자리를 살짝 조정할 수 있는 충분한 공간이 생깁니다
또한 페르소나가 서로 너무 가까이 있으면 페르소나가 사라지고 정적 연락처 사진으로 대체된다는 사실을 유의해야 합니다 자리 사이에 공간이 충분하도록 하면 활동의 참여자들이 항상 서로의 공간 페르소나를 편안하게 볼 수 있습니다
마지막으로, 의도에 맞게 자리의 순서를 구성해야 합니다 가능한 모든 공간 페르소나 조합에 대해 템플릿을 확인하고 올바르게 채워지도록 하는 것이 좋습니다 맞춤형 템플릿이 설정되었고 3개의 공간 페르소나가 있으면 여러분이 지정하는 처음 3개의 자리가 순서대로 사용됩니다
이 예에서는 새 참여자가 들어오면 중앙의 자리부터 채워집니다
그 대신에 자리를 왼쪽에서 오른쪽으로 정의했다면 자리가 그 순서로 채워지므로 자리가 다 채워지지 않은 경우 템플릿이 불균형하게 느껴질 수 있습니다
템플릿의 요소 배열에서 제공되는 자리 순서를 사용해 자리가 채워져야 할 순서를 정의할 수 있습니다 앱을 향해 일렬로 자리를 배치하는 간단한 템플릿입니다 Guess Together는 팀 선택을 위해 이와 비슷한 템플릿을 사용합니다 요소 목록에서 자리의 순서는 참여자가 통화에 들어오면서 채우는 순서이기도 합니다
이것으로 맞춤형 공간 템플릿에 대한 자리 정의를 마무리합니다 계속 진행하기 전에 스스로 물어봐야 할 정말 중요한 질문이 또 하나 있습니다 사람들이 각 자리에 처음 배치될 때 어디를 바라보기를 기대하시나요? 즉, 각 자리의 초점은 어디인가요? 기본적으로 자리는 앱의 중앙을 향하지만 자리의 방향을 완벽히 제어할 수 있습니다 앱 사용자는 해당 자리에 배치되면 초점이 어디에 놓이는지 파악하기 위해 여러분의 도움을 필요로 합니다 앱을 보게 되는가 아니면 다른 참여자를 보게 되는가? 아니면 전혀 다른 곳일 수도 있습니다 새 API는 다양한 도구를 제공하므로 자리의 방향을 조정할 수 있습니다 Guess Together의 게임 템플릿에서는 lookingAt 메서드로 활성 플레이어 자리가 팀 동료들을 향하게 합니다 자리가 다른 특정 지점을 향하게 하려는 경우 lookingAt에 공간 템플릿 요소 위치를 제공할 수도 있습니다 alignedWith 메서드를 사용하여 X 축 또는 Z 축 같은 앱의 직각 축 내에 자리를 정렬할 수도 있습니다 예를 들어, 모두 일렬로 앱 평면에 직각인 방향을 정면으로 향하게 자리를 배치하려는 경우 Z 축에 정렬할 수 있습니다 마지막으로, 초기 자리 방향을 생성한 후 각도나 라디안 단위의 특정 값만큼 항상 회전할 수 있습니다 각 자리의 방향과 위치를 고려한 후에는 템플릿에서 어떤 자리가 특별한지 고려해야 합니다 즉, 예약해야 할 자리가 있나요?
어떤 자리에 역할이 필요한지 스스로 물어보는 대신에 어떤 자리가 특별하거나 예약이 필요한지 고려해 보세요 앞서 살펴봤던 체스 템플릿에서 각 팀의 활성 플레이어가 채우지 않는 한 플레이어의 자리가 빈 상태로 유지되도록 하고 싶습니다 두 플레이어 중 한 명 또는 두 명이 공간 페르소나를 활성화하지 않았더라도 해당 자리는 빈 상태로 유지되어야 하며 나중에 필요할 때를 위해 이들을 위해 남겨두어야 합니다 반면에 관중석은 특별하지 않으며 할당된 역할이 있어서는 안 됩니다 관중만을 위한 세 번째 역할을 만들고 싶을 수 있습니다 하지만 FaceTime이 역할이 없는 자리에 직접 앉히도록 하는 대신에 각 참여자가 관중 역할을 요청하는 동안 지연이 발생하여 사용자 경험이 저하될 수 있습니다 복습하자면, 가능한 한 역할이 없는 자리를 사용하여 FaceTime이 지연 없이 참여자를 자리에 배치할 수 있도록 합니다
역할을 특정 위치에 있어야 하는 참여자를 위한 자리를 예약하는 방법으로 활용합니다 모든 참여자가 역할을 맡을 수는 없습니다 다른 플랫폼을 사용하거나 공간 페르소나가 비활성화됐을 수 있죠
지금까지 사용할 템플릿 유형 자리 배치 및 방향 지정 방법 특정 참여자를 위한 자리 예약 여부를 고려했습니다 이제 SharePlay 경험을 위한 멋진 맞춤형 공간 템플릿을 갖추었지만 고려해야 할 마지막 한 가지 디자인 요소가 있습니다
갑작스러운 전환을 피하는 것입니다 공간 페르소나를 위한 SharePlay 앱을 디자인할 때 여러분이 할 수 있는 가장 중요한 일 중 하나는 공간 페르소나 템플릿을 항상 앱 사용자가 예상한 데로 만드는 것입니다 앱용 맞춤형 공간 템플릿 사용 시 여러분은 큰 힘을 가지고 있습니다 템플릿이 전환되고 역할이 변경되면서 앱이 참여자의 공간 페르소나를 움직일 수 있습니다 FaceTime 통화에서 페르소나가 공유 공간을 돌아다닐 수 있습니다 여러분이 템플릿을 변경하거나 역할을 사용해 새로운 자리에 할당할 때마다 사람들이 어떻게 반응할지 고려해야 합니다 몇 가지 일반적인 지침을 검토해 보겠습니다
템플릿 전환을 최소화합니다
자리를 이동하거나 회전하여 템플릿을 변경하거나 역할을 바꾸거나 템플릿 유형 간에 전환하면 사람들이 혼란스러워 할 수 있으므로 가능한 최소한으로 하는 것이 최선입니다 Guess Together에서는 각 단계가 단일 템플릿을 사용하며 여기서 자리는 동일한 곳에 유지됩니다 필요한 경우에만 역할을 사용해 참여자의 자리를 이동합니다
시각적 컨텍스트 단서를 제공하여 템플릿 간에 전환할 때마다 참여자가 방향을 파악할 수 있도록 합니다 이를 위해 자리 방향과 관련하여 각 자리의 시야에 시각적 앵커 포인트를 표시하는 것이 좋으며 이는 앱 자체나 또 다른 참여자가 될 수 있습니다
마지막으로, 템플릿 변경 사항을 명시적 참여자 동작과 연결합니다 좋은 예는 Guess Together의 팀 선택 단계입니다 여기서 역할 변경 사항은 청팀 또는 홍팀에 속하기 위해
버튼을 누르는 누군가와 연결되어 있습니다
갑작스러운 전환을 하지 않도록 모든 단계에서 세심하게 고려해 의도한 부분은 더 즐겁고 놀라운 경험이 되도록 합니다 마무리로, Guess Together 샘플 코드를 다운로드하여 친구와 사용해 보시기 바랍니다 정말 재미있고, 공간 템플릿으로 무엇을 할 수 있는지 좋은 아이디어를 제공합니다 Xcode 16의 visionOS 시뮬레이터에서 새로운 FaceTime 통화를 사용하세요 SharePlay 경험을 테스트해 보세요 visionOS를 위한 멋진 SharePlay 앱을 개발할 수 있는 놀라운 도구입니다 앱에서 사용하는 공간 템플릿의 종류와 상관없습니다 맞춤형 공간 템플릿으로 경험이 향상되는지 기존 시스템 템플릿의 의도적인 사용으로 어떤 부분이 이점을 얻을 수 있는지 고려하세요 마지막으로, visionOS를 위한 멋진 SharePlay 경험을 디자인하면서 갑작스러운 전환은 피해야 한다는 것을 명심하세요 맞춤형 공간 템플릿은 FaceTime에서 흥미로운 경험을 구현하는 강력한 도구이지만 신중하게 고려할 사항이 있습니다
여러분이 visionOS의 맞춤형 공간 템플릿으로 구현하실 경험이 정말 기대됩니다 감사합니다
-
-
12:32 - Initial team selection template
// Team selection template – custom spatial template import GroupActivities struct TeamSelectionTemplate: SpatialTemplate { let elements: [any SpatialTemplateElement] = [ .seat(position: .app.offsetBy(x: 0, z: 4)), .seat(position: .app.offsetBy(x: 1, z: 4)), .seat(position: .app.offsetBy(x: -1, z: 4)), .seat(position: .app.offsetBy(x: 2, z: 4)), .seat(position: .app.offsetBy(x: -2, z: 4)), ] }
-
13:31 - Completed team selection template with seat roles
import GroupActivities /// The custom spatial template used to arrange Spatial Personas /// during Guess Together's team-selection stage. /// /// The team selection template contains three sets of seats: /// /// 1. Five audience seats that participants are initially placed in. /// 2. Three Blue Team seats that participants are moved to /// when they join team Blue. /// 3. Three Red Team seats. /// /// ``` /// ┌────────────────────┐ /// │ Guess Together │ /// │ app window │ /// └────────────────────┘ /// /// /// % $ /// % $ /// Blue Team % $ Red Team /// * * * * * /// /// Audience /// ``` struct TeamSelectionTemplate: SpatialTemplate { enum Role: String, SpatialTemplateRole { case blueTeam case redTeam } let elements: [any SpatialTemplateElement] = [ // Blue team: .seat(position: .app.offsetBy(x: -2.5, z: 3.5), role: Role.blueTeam), .seat(position: .app.offsetBy(x: -3.0, z: 3.0), role: Role.blueTeam), .seat(position: .app.offsetBy(x: -3.5, z: 2.5), role: Role.blueTeam), // Starting positions: .seat(position: .app.offsetBy(x: 0, z: 4)), .seat(position: .app.offsetBy(x: 1, z: 4)), .seat(position: .app.offsetBy(x: -1, z: 4)), .seat(position: .app.offsetBy(x: 2, z: 4)), .seat(position: .app.offsetBy(x: -2, z: 4)), // Red team: .seat(position: .app.offsetBy(x: 2.5, z: 3.5), role: Role.redTeam), .seat(position: .app.offsetBy(x: 3.0, z: 3.0), role: Role.redTeam), .seat(position: .app.offsetBy(x: 3.5, z: 2.5), role: Role.redTeam) ] }
-
14:59 - Configuring a custom spatial template
systemCoordinator.configuration.spatialTemplatePreference = .custom(TeamSelectionTemplate())
-
15:39 - Assigning the local participant a spatial template role
systemCoordinator.assignRole(TeamSelectionTemplate.Role.blueTeam)
-
16:00 - Resigning the local participant from a spatial template role
systemCoordinator.resignRole()
-
17:00 - Spatial template roles
// Associating a role with a seat .seat(position: .app.offsetBy(x: -2.5, z: 3.5), role: TeamSelectionTemplate.Role.blueTeam) // Assigning the local participant a role systemCoordinator.assignRole(TeamSelectionTemplate.Role.blueTeam) // Resigning the local participant from their current role systemCoordinator.resignRole()
-
18:42 - Game template with seat direction
import GroupActivities /// The custom spatial template used to arrange spatial Personas /// during Guess Together's game stage. /// /// The team selection template contains three sets of seats: /// /// 1. An seat to the left of the app window for the active player. /// 2. Two seats to the right of the app window for the active player's /// teammates. /// 3. Five seats in front of the app window for the inactive team-members /// and any audience members. /// /// ``` /// ┌────────────────────┐ /// │ Guess Together │ /// │ app window │ /// └────────────────────┘ /// /// /// Active Player % $ Active Team /// $ /// /// * * * * * /// /// Audience /// /// ``` struct GameTemplate: SpatialTemplate { enum Role: String, SpatialTemplateRole { case player case activeTeam } var elements: [any SpatialTemplateElement] { let activeTeamCenterPosition = SpatialTemplateElementPosition.app.offsetBy(x: 2, z: 3) let playerSeat = SpatialTemplateSeatElement( position: .app.offsetBy(x: -2, z: 3), direction: .lookingAt(activeTeamCenterPosition), role: Role.player ) let activeTeamSeats: [any SpatialTemplateElement] = [ .seat( position: activeTeamCenterPosition.offsetBy(x: 0, z: -0.5), direction: .lookingAt(playerSeat), role: Role.activeTeam ), .seat( position: activeTeamCenterPosition.offsetBy(x: 0, z: 0.5), direction: .lookingAt(playerSeat), role: Role.activeTeam ) ] let audienceSeats: [any SpatialTemplateElement] = [ .seat(position: .app.offsetBy(x: 0, z: 5)), .seat(position: .app.offsetBy(x: 1, z: 5)), .seat(position: .app.offsetBy(x: -1, z: 5)), .seat(position: .app.offsetBy(x: 2, z: 5)), .seat(position: .app.offsetBy(x: -2, z: 5)) ] return audienceSeats + [playerSeat] + activeTeamSeats } }
-
21:41 - Configure group immersive space
// Configure group immersive space for await session in GuessingActivity.sessions() { guard let systemCoordinator = await session.systemCoordinator else { continue } systemCoordinator.configuration.supportsGroupImmersiveSpace = true }
-
30:35 - SimpleLine Template
// SimpleLine.swift struct SimpleLine: SpatialTemplate { let elements: [any SpatialTemplateElement] = [ .seat(position: .app.offsetBy(x: 0, z: 2)), .seat(position: .app.offsetBy(x: 1, z: 2)), .seat(position: .app.offsetBy(x: -1, z: 2)), .seat(position: .app.offsetBy(x: 2, z: 2)), .seat(position: .app.offsetBy(x: -2, z: 2)) ] }
-
31:35 - lookingAt Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) )
-
31:46 - alignedWith Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) ) // Align with a given app axis .seat( position: teamSeatPosition, direction: .alignedWith(appAxis: .z) )
-
32:02 - rotatedBy Method
// Look at a given position or seat .seat( position: teamSeatPosition, direction: .lookingAt(activePlayerSeat) ) // Align with a given app axis .seat( position: teamSeatPosition, direction: .alignedWith(appAxis: .z) ) // Rotate by a given angle .seat( position: teamSeatPosition, direction: .lookingAt(.app).rotatedBy(.degrees(30)) )
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.