
Reality Composer Pro는 spatial computing app에서 사용되는 RealityKit content를 만들 수 있는 개발자 도구이다.

이 탭들은 런타임에 호출할 수 있는 하나의 root entity를 나타낸다.
이 중에 DioramaAssembled를 runtime에 호출하는 것을 해볼 것이다.
Entity의 비동기적인 initializer를 사용하여 Reality Composer Pro package로부터 entity를 생성한다.

named의 string을 통해 호출하고자 하는 entity를 명시하고 그 이름을 package가 생성한 bundle에 제공한다.
만약 해당 이름으로 찾지 못하면 throw하게 된다.
realityKitContentBundle은 Reality Composer Pro Package에서 자동 생성되는 상수이다.
RealitView에서 closure를 통해 이렇게 사용된다.
RealityView는 새로운 view로 SwiftUI와 RealityKit을 연결하는 다리이다.

Reality Composer Pro 프로젝트에 추가하지 않은 Xcode project에서 사용하는 USD 에셋이 있다면 이 asset들을 Swift Package에 .rkasset 디렉토리로 넣기를 강력히 권고한다.


Xcode는 .rkassets 폴더를 런타임에 더 빠르게 로딩되는 포맷으로 컴파일한다.
우리가 막 로딩한 entity는 더 큰 entity 계층의 root다.
이들은 자식 entity들을 가진다.

자식 entity 중 하나를 호출하고 싶다면 Reality Composer Pro에서 이름을 준 뒤 런타임에 이름을 통해 호출하면 된다.
Entity는 ECS의 일부이다.
ECS는 Entity Component System의 약자이다.
ECS는 RealityKit과 Reality Composer Pro의 힘이다.
ECS는 OOP와 유사해보이지만, 주요한 포인트가 다르다.
객체 지향 프로그래밍에서는 객체가 그 특성을 정의하는 속성인 프로퍼티를 가지고 자체 함수도 가지고 있다.
그리고 객체를 정의하는 클래스에서 이 프로퍼티와 함수를 쓴다.
하지만 ECS에서는 Entity가 씬에서 보이는 모든 걸 가지게 되고, 보이지 않을 수도 있다.

그럼에도 그들은 attribute나 data를 보유하지 않고 data를 컴포넌트에 넣게 된다.
컴포넌트는 앱 실행 중 언제든지 entity에서 추가/삭제가 가능하다.
그래서 이를 통해 entity에 dynamic한 변화를 줄 수 있다.
시스템은 behavior가 있는 곳이다.
이곳에는 frame마다 호출되는 update 함수가 있고, 여기에 진행중인 Logic을 넣으면 된다.
시스템에서 특정 컴포넌트, 컴포넌트 속성을 가지는 entity들의 query를 요청하거나 특정 동작을 수행하거나 업데이트된 데이터를 저장할 수 있다.
ECS에 대해 더 알고 싶다면 다음의 세션을 이용하도록!

Reality Composer Project의 Entity에 컴포넌트를 넣는 방법과 custom component를 생성하는 법에 대해 다룰 것이다.
entity에 component를 추가하기 위해서는 아래와 같은 코드를 활용하면 된다.
같은 것을 reality composer pro에서 하려면 원하는 entity를 선택한 뒤
Inspector panel의 add component 버튼을 활용한다.
넣고 싶은 만큼의 컴포넌트를 추가할 수 있으며, 컴포넌트 타입별로는 한번만 넣을 수 있다. set 방식으로 동작한다.
Custom component도 여기 리스트로 확인 할 수 있다.
위치에 따라 생성되는 이러한 floating button을 만들 예정이다.
이를 위해서는 terrain map위에 원하는 위치에 entity를 추가해야 한다.

새로운 invisible entity를 만들기 위해 Reality Composer Pro 하단의 + 버튼을 눌러 Transform을 선택해준다.

Entity의 이름을 Ribbon_Beach로 설정해준다.
Ribbon Beach에 맞는 위치로 entity를 움직여준다.
Add component에서 custom component를 만들기 위해 New Component를 선택해준다.
원하는 이름을 지정해주면
Inspector Panel에 새롭게 추가 된 것을 확인 할 수 있다.
Xcode를 열어보면 PointOfIntrest.swift가 생성된 것을 확인 할 수 있다.
Reality Composer Pro는 Swift package이며 우리가 만든 것에 대한 Swift 코드는 package 안에서 확인 할 수 있다.
두개의 map에 대한 값이 바뀌면 Point의 위치가 바뀌어야 하므로 이와 관련된 값을 다루는 enumeration인 Region에 대한 변수를 추가한다.
Region은 Codable을 준수함으로써 Reality Composer Pro는 이 값을 보고 instance들을 serialize 할 수 있다.
Reality Composer Pro를 보면 region property가 추가된 것을 확인 할 수 있다.
default 값은 우리가 코드에서 설정한 yosemite로 되어 있는 것을 확인 할 수 있다.
하지만 선택된 특정 entity에 대해 override가 가능하고, override를 하면 그 값은 이 특정 entity에만 적용되어 다른 point들에는 기본 값이 적용된다.
이 상태에서 실행하면 코드 상으로 아무런 작업을 하지 않았기 때문에 아무 변화가 없다.
RealityKit scene에 SwiftUI 콘텐츠를 추가하는 새로운 방법인 Attachments API가 등장했다.
이를 통해 runtime 동안 실행되는 hovering button을 만들기 위해 attachment를 PointOfInterestComponet와 결합시킬 수 있다.
Attachments는 RealityView의 일부분이다.
일단 SwiftUI view가 RealityKit scene에 어떻게 들어가게 되는지 간단하게 살펴보자.
이번에 사용할 RealityView initializer는 세가지 paramter를 요구한다.
make closure, update closure, attachments ViewBuilder이다.
Attachments ViewBuilder 그려지는 SwiftUI view는 평범한 SwiftUI view로 modifier, gesture 등 SwiftUI가 제공하는 모든 것들이 사용가능하다.
모든 unique hashable 값은 tag로 사용될 수 있다.
나중에 SwiftUI가 update closure를 호출하게 되면 버튼은 entity가 된다. 이 entity는 다른 entity와 동일하게 사용가능하다.
그래서 3D 안에서의 원하는 위치도 설정할 수 있고 다양한 component들도 추가할 수 있다.
Reality View의 한 부분에서 다른 부분으로의 data flow이다.
SwiftUI view는 attachments ViewBuilder로부터 시작하여
attachments parameter를 통해 update closure에서 당신에게 전달된다.
여기서 해당 태그를 가진 entity가 존재하는지 확인하고 있다면 RealityKit entity를 넘기게 된다.
그리고 update closure에서 3D position을 설정하여 Reality Kit scene에 추가하게 된다.
make closure 또한 attachments parameter를 가지고 있다. 이건 뷰를 시작할 때부터 나타낼 attachment가 있는 경우에 사용한다.
Reality View의 일반적인 flow를 알게되었으니 update closure에 대해 더 알아보자.
make, update closure의 parameter는 RealityKitContent이다.
RealityKit content에 entity를 추가하면 이건 scene에서 top-level entity가 된다.
마찬가지로 update 함수에서 entity를 content에 추가하면 scene에 새로운 top-level entity가 추가된다.

make closure는 한번만 불리는 반면에 update closure는 한번 이상 호출되어서 새로운 entity를 update closure 안에서 생성해서 content에 추가하게 되면 entity의 복제본들이 생겨나게 된다.

이를 방지하기 위해 한번만 실행되는 어딘가에 만들어지는 content에만 entity를 추가해라.

content.entities가 해당 entity를 포함하고 있는지 체크할 필요는 없다. 왜냐하면 동일한 entity를 두번 부르게 되면 set처럼 실행되지 않기 때문이다.
새 entity를 scene에 있는 기존 entity의 부모 Entity로 지정할 때도 두번 추가되지 않는다.
Attachment entity는 당신에 의해 생성되지 않고 attachments ViewBuilder에서 만든 attachment view에 대해 RealityView가 생성한다 .
그래서 entity가 있는지 확인하지 않고 update closure에서 content로 추가해도 안전하다.
이젠 데이터를 활용해보자.
data driven을 위해서는 코드가 Reality Composer Pro scene에서 설정한 데이터를 읽도록 만들어야 한다.
이를 위해 attachment view를 dynamic하게 만들어보자.
이 과정을 거칠 예정이다.

과정을 좀 더 자세히 보자면
1-1. Reality Composer Pro scene에 invisible Entity들을 추가하여 원하는 위치에 자리 설정. Transform component를 사용할 예정.

1-2. 그 후 각각에 PointOfInterestComponent를 추가.

코드에서는 query를 통해 이 entity들에 대한 참조 값을 얻어낸다. query들은 Reality Composer Pro에서 설정한 세가지의 보이지 않는 Entity들에 대해 반환할 것이다.

각각에 대해 새로운 SwiftUI view를 생성하여 collection에 저장한다.

버튼을 RealityView로 가져오기 위해 property wrapper인 @State를 추가하여 SwiftUI의 view updating flow를 이용한다.

@State property wrapper는 SwiftUI에게 이 collection에 item이 추가할 때 SwiftUI가 ImmersiveView에서 뷰 update를 해야한다고 전달해준다.
이로 인해 SwiftUI는 attachements ViewBuilder와 update closure를 재계산하게 된다.

RealityView attachments ViewBuilder는 우리가 entity로 만들어졌으면하는 버튼들이 SwiftUI로 만들어졌으면 좋겠다고 선언한 곳이고

이 다음에 update closure가 호출되어 버튼은 Entity로 전달되고 버튼은 더 이상 SwiftUI view가 아니게 된다. 그래서 이 버튼을 Entity Hirechy로 추가할 수 있게 된다.

Update closure에서 attachment entity를 scene에 추가할 수 있게 되고 보이지 않는 entity 위에 배치할 수 있게 된다.

Reality Composer Pro scene에 Invisible Entity들을 마킹한다.

마킹한 Entity들을 찾기 위해 EntityQuery를 이용한다.

Query 결과를 반복하면서 scene의 각 entity에 대한 새로운 SwiftUI View를 생성한다.

이 뷰는 attachment 중 하나가 될 것이므로 tag를 추가한다.

SwiftUI view의 collection을 생성한다.

그리고 아까 만든 뷰를 attachmentsProvider에 저장한다.

attachmentsProvider를 좀 더 자세히 보자면,
일단 뷰의 attachment tag에 대한 딕셔너리가 있다.

Computed Property도 가지고 있는데, 태그와 태그에 일치하는 뷰에 대한 튜플 배열을 반환한다.

그 후 attachments ViewBuilder를 보면 attachment에 대한 콜렉션에 대한 ForEach가 동작한다.

여기서 ObjectIdetifier는 view의 attachment tag와 ForEach에서의 identifier 두가지 역할을 하게 된다.

근데 그러면 tag property를 PointOfInterestComponent에 추가하지 않는 이유는 무엇일까?

1) tag는 Unique해야해서
2) Component를 추가하면 모든 Inspector panel에 보임 -> Reality Composer Pro에서 point들을 추가할 때마다 unique한 Tag를 기억해서 반영하는 건 번거로움

하지만 Entity는 Identifiable protocol을 준수하기 때문에 unqiue한 identifier를 자동적으로 가지고 있다.
그리고 우리는 Reality Composer Pro에서 scene을 디자인 할 때 이 식별자를 알 필요 없이 runtime에 entity에서 가져올 수 있다.
attachmentTag 프로퍼티를 reality composer pro에서 나타나지 않게 하려면 "design-time vs runtime components"라고 불리는 기술을 활용해야한다.

data를 두개의 다른 component로 분리한다.
하나는 Reality Composer Pro에서 다루고 싶은 design-time data이고 하나는 런타임에 동적으로 같은 종류의 entity들에게 붙이고 싶은 runtime data이다. 후자 데이터는 Reality Composer Pro의 Inspector panel에서 보고 싶지 않은 것들을 위한 것이다.
Realtiy Composer Pro는 Swift Package에서 읽은 정보를 통해 자동적으로 component UI를 생성한다. package안의 코드를 inpect하고 scene에서 발견한 codable component를 만든다.
여기 4가지의 component가 있다.
A와 B는 Xcode 프로젝트에는 있지만 Reality Composer Pro 패키지에는 있지 않다. 그래서 Reality Composer Pro에서 entity에 추가하는 것이 불가능하다.
C는 패키지에 있지만 Codable하지 않다. 그래서 Reality Composer Pro는 이것을 무시하게 된다.
결국 D만 Reality Composer Pro의 list에 표시되고, 이것이 design-time component가 된다. 그리고 반면에 다른 component들은 runtime component로 사용된다.

Design-time component는 정수형, 문자열, SIMD 값 등 3D 아티스트나 디자이너들이 사용하는 것 같은 더 단순한 데이터를 위한 것이다.
Reality Composer Pro가 serialize할 수 없는 종류의 프로퍼티를 custom component에 추가하면 Xcode error을 발견할 수 있을 것이다.
다시 코드를 보자면
이 부분이 runtime component가 들어가야하는 때이다.

모든 design-time component에 대한 query를 통해 각각에 맞는 새로운 runtime component를 만들어준다.
이러한 방식으로 인해 design-time component는 앱에게 자신을 위한 attachment view가 필요하다고 말하는 signifier 같아진다.
runtime component는 앱 실행중에 필요한 모든 종류의 데이터를 처리하지만 design-time component에는 저장하고 싶지 않아한다.

Invisible entity에 넣은 Runtime component는 태그를 가지고 있다.

모든 PointOfInterestRuntimeComponent에 대한 query를 통해

반환된 각 entity로부터 runtime component를 얻게 된다.

그 후 component의 attachmentTag 프로퍼티를 통해 attachments 매개변수에서 attachmentEntity를 얻게 된다.

그 후 attachmentEntity를 content에 추가해 배치한다.

Reality Composer Pro에서 설정한 오디오의 재생 방법을 알아본다.
'+' 버튼을 통해 Ambient Audio를 추가한다.
이를 통해 보이지 않는 AmbientAudioComponent가 생성된다.
scene에 오디오 파일도 추가한다.
Inspector panel에서 오디오 미리보기는 가능하지만 앱에서 entity를 로딩하면 자동으로 사운드가 재생되지는 않는다.
소리를 재생하기 위해서는 audio component를 넣은 entity의 reference가 필요하다.
Reality Composer Pro에 설정한 이름을 통해 entity를 찾는다.

사운드 파일을 로딩한다.

audioPlaybackController를 만든다. 이를 통해 audio 재생, 일시 정지, 중단이 가능해진다.

Shader Graph material의 프로퍼티를 활용하여 two terrain의 morph를 할 것이다.
Slider를 통해서 morph 정도를 조정하고 싶으므로 promote to~ 를 해준다.
이것은 project에 이 material의 한에서는 runtime에 data를 제공해주겠다고 말해주는 것이다.
일단 material이 있는 entity의 reference를 가져온다.

그리고 material을 보관하는 RealityKit의 component인 ModelComponet를 가져온다.

Model Component에서 첫번째 메테리얼을 얻고 ShaderGraphMaterial로 제공한다.

그럼 이제 Progress라는 이름의 매개변수에 대해 새 값을 설정해줄 수 있게 된다.

마지막으로 Material을 다시 ModelComponent에 저장하고 ModelComponent를 다시 terrain entity에 저장한다.

그리고 이걸 SwiftUI Slider에 연결해준다.

AmbientAudioComponent 모두에 query를 만든다. 그리고 RegionSpecificComponent라는 다른 custom property를 추가해서 entity의 지역을 나타나게 해 줄 것이다.

우리의 audioComponent에 대한 mutable copy를 얻는다. 그리고 이것을 바꿔서 저장한다.