안녕하세요! 피피아노입니다 🎵
Apple Developer Academy의 Challenge3를 마쳤습니다.🎉
이번 챌린지에서 저희 팀은 visionOS 기반 소방 안전 훈련 앱을 만들었습니다.
저희 앱은 Apple Vision Pro를 착용한 사용자가 실제 공간 위에 나타난 화재를 보고, 손 추적을 통해 소화기를 직접 조작하며 초기 진압을 훈련하는 앱입니다. 단순히 영상을 보거나 설명을 듣는 방식이 아니라, 실제 화재 상황에서 필요한 행동을 몸으로 경험해보는 것을 목표로 했습니다.
이번 글에서는 Challenge3에서 어떤 기준으로 주제를 선정했는지, 제가 맡은 소화기 진압 파트를 구현하면서 어떤 기술적 어려움을 겪었는지, 그리고 visionOS 개발을 통해 무엇을 배웠는지 정리해보려고 합니다.
왜 visionOS 앱이어야 했는가
이번 챌린지는 "기술을 배우는 것"이 중요한 목적이었습니다. 그래서 저희 팀은 주제를 정할 때 크게 두 가지 기준을 세웠습니다.
첫 번째는 왜 이 앱이 visionOS 앱이어야 하는가? 였습니다.
단순히 기존 iOS 앱을 Vision Pro 화면에 띄우는 것이라면, 굳이 visionOS를 선택할 이유가 부족하다고 생각했습니다. 그리고 왜 우리 앱이 Vision Pro를 통해서 해야 하는지 명확하게 설명할 수 있어야 visionOS의 기술을 더 잘 쓰고 배울 수 있다고 생각했기 때문에 첫 번째 기준을 이렇게 정했습니다.
두 번째는 Apple Vision Pro의 기술을 충분히 활용할 수 있는가? 였습니다.
공간을 인식하고, 현실 위에 가상 객체를 배치하고, 손을 이용해 직접 상호작용하는 경험이 있어야 Vision Pro라는 기기의 장점을 살릴 수 있다고 판단했습니다.
이 기준에서 소방 안전 훈련은 꽤 적합한 주제였습니다. 기존 화재 안전 교육은 보통 영상을 보거나 설명을 듣는 방식에 가깝습니다. 하지만 실제 화재 상황에서는 지식만으로는 부족합니다. 소화기를 어디서 잡아야 하는지, 안전핀을 어떻게 뽑아야 하는지, 노즐을 어디로 향해야 하는지 같은 행동은 몸으로 익혀야 합니다.
우리 앱은 실제 화재가 날 수 있는 공간을 활용하고, 그 공간 안에서 사용자가 직접 소화기를 다루게 만든다는 점에서 기존 교육과 달랐습니다. 이 지점이 저희가 visionOS를 선택한 가장 큰 이유였습니다.
프로젝트 개요
우리 앱의 전체 흐름은 다음과 같았습니다.
- 시작 화면
- 안전 안내
- 모드 선택
- 연기 등장
- 화재 발생
- 진압 도구 선택
- 소화기 진압
- 성공 또는 실패 결과 화면
저는 이 중에서 소화기 진압 파트를 담당했습니다. 앱의 클라이맥스에 해당하는 구간이었습니다.
제가 맡은 영역에서는 사용자가 실제 손으로 소화기를 잡고, 안전핀을 뽑고, 노즐을 조준하고, 레버를 눌러 분말을 분사해야 했습니다. 이를 위해 ARKit, RealityKit, HandTrackingProvider, 물리 시뮬레이션, 제스처 판정 로직이 함께 필요했습니다.
이 파트를 맡게 된 이유는 가장 어려운 기술 영역이기도 했고, 동시에 Apple Vision Pro의 기술을 가장 적극적으로 다루는 부분이라고 생각했기 때문입니다. Swift를 어느 정도 다룰 수 있어야 했고, 손 추적과 3D 공간 상호작용을 함께 이해해야 했습니다.
화면을 만드는 개발에서 공간을 다루는 개발로
이번 프로젝트에서 가장 끌렸던 부분은 Vision Pro와 Hand Tracking, 그리고 공간을 활용한다는 점이었습니다.
지금까지의 iOS 개발은 대부분 화면 위에 UI를 그리고, 사용자가 버튼을 누르거나 스크롤하는 방식의 상호작용이었습니다. 하지만 visionOS에서는 사용자가 있는 실제 공간 자체가 인터페이스가 됩니다.
이 차이가 생각보다 컸습니다.
기존에는 "이 버튼을 누르면 어떤 화면으로 이동한다"를 고민했다면, 이번에는 “사용자의 손이 어디에 있고, 소화기 모델은 어느 좌표계에 있으며, 손의 움직임을 어떻게 자연스럽게 객체의 움직임으로 바꿀 것인가"를 고민해야 했습니다. 그리고 "어떻게 해야 사용자가 이 경험을 실제처럼 느낄 수 있을까?"를 고민해야 했습니다.
즉, 단순히 UI를 배치하는 것이 아니라 공간 안에서의 행동을 설계하는 일에 가까웠습니다.
Hand Tracking을 처음 다루며 겪은 어려움
가장 먼저 어려웠던 부분은 HandTrackingProvider를 처음 다뤄본 것이었습니다.
Hand Tracking은 시뮬레이터에서 제대로 검증할 수 없었습니다. 손 관절 위치를 기반으로 제스처를 판단해야 했기 때문에 매번 Vision Pro 실기기에서 테스트해야 했습니다.
이 과정에서 느낀 점은 시뮬레이터로 검증할 수 있는 영역과 실기기에서만 알 수 있는 영역은 완전히 다르구나였습니다.
코드상으로는 손가락 거리나 관절 위치를 계산하면 될 것처럼 보입니다. 하지만 실제 사람의 손은 생각보다 많이 흔들리고, 세밀하게 조작하기 어렵습니다. 특히 소화기처럼 여러 부품을 손으로 잡고 조작해야 하는 인터랙션에서는 작은 흔들림도 바로 사용성 문제로 이어졌습니다.
예를 들어 안전핀을 뽑기 위해 엄지와 검지의 거리를 기준으로 pinch를 판단하거나, 레버를 누르기 위해 검지의 굽힘 정도를 계산해야 했습니다. 하지만 임계값을 너무 빡빡하게 잡으면 사용자가 의도한 동작을 인식하지 못했고, 너무 느슨하게 잡으면 의도하지 않은 동작까지 인식했습니다.
결국 Hand Tracking은 단순히 "관절 좌표를 가져오는 기술"이 아니라, 사람의 불완전하고 흔들리는 움직임을 어떻게 안정적인 입력으로 해석할 것인가의 문제였습니다.
RealityKit 좌표계와 모델 구조 문제
또 하나 어려웠던 부분은 RealityKit에서 3D 모델을 다루는 일이었습니다.
처음에는 소화기 모델의 특정 부품을 직접 움직이면 될 것이라고 생각했습니다. 하지만 실제로는 USDC 모델의 부모 scale, 자식 entity 구조, world/local 좌표 변환이 얽히면서 좌표계가 꼬이는 문제가 발생했습니다.
특히 소화기 모델의 자식 부품만 world 좌표 기준으로 이동시키려고 하자, 부모 entity에 적용된 scale 때문에 예상과 다른 위치로 튀는 문제가 있었습니다. 부모 scale이 0.07로 적용되어 있으면, 자식의 local 좌표로 변환되는 과정에서 값이 크게 왜곡될 수 있었습니다.
소화기 본체, 손잡이, 호스, 안전핀 같은 자식 부품을 각각 따로 움직이기 시작하면 좌표계와 transform을 추적하기가 매우 어려워집니다. 특히 RealityKit의 물리와 결합되면 디버깅 난이도가 급격히 올라갑니다.
이번 경험을 통해 3D 공간에서의 개발은 Swift 코드만 잘 작성한다고 되는 것이 아니라, 모델 구조와 좌표계, entity 계층을 함께 이해해야 한다는 것을 알게 됐습니다.
제스처 판정 로직의 어려움
기술적으로 가장 아쉬움이 남는 부분은 제스처 판정 로직입니다.
소화기 조작에는 여러 단계가 필요했습니다.
- 소화기 본체 잡기
- 안전핀 뽑기
- 노즐 조준하기
- 레버 누르기
- 분사 방향 판정하기
각 단계마다 손의 위치, 손가락 거리, 손가락 굽힘 정도, 소화기와의 거리 등을 계산해야 했습니다. 그리고 이 값들은 모두 실시간으로 변했습니다.
처음에는 필요한 조건들을 하나씩 추가하면서 구현했습니다. 하지만 구현이 진행될수록 로직이 복잡해졌고, 각 제스처 사이의 관계를 더 명확하게 설계했어야 한다는 생각이 들었습니다.
예를 들어 안전핀을 뽑기 전에는 레버를 눌러도 분사되지 않아야 합니다. 본체를 잡은 손과 노즐을 조준하는 손은 구분되어야 합니다. 같은 부품을 양손이 동시에 잡으려고 할 때는 ownership이 필요합니다.
이런 조건들은 단순 if문으로도 구현할 수 있지만, 장기적으로는 상태와 책임을 더 명확히 나누는 구조가 필요했습니다. 다시 만든다면 제스처 판정 로직을 더 독립적으로 분리하고, 각 인터랙션의 상태 전이를 더 명확하게 설계해야겠다는 생각이 들었습니다.
협업에서 배운 것
이번 프로젝트는 팀원들과 함께 진행했습니다. 각자 기능을 나눠 개발했고, 전체 앱은 AppState 기반의 상태 흐름으로 연결했습니다.
협업에서 가장 어려웠던 점은 팀원들 대부분이 개발 협업에 익숙하지 않았다는 것입니다. 브랜치 전략, 커밋 메시지, 파일 구조, 코드 스타일 같은 컨벤션을 정했지만, 그 규칙이 왜 필요한지와 실제로 어떻게 적용해야 하는지를 충분히 이야기하지 못했습니다. 그 결과 사람마다 규칙을 다르게 이해하거나, 작업 과정에서 컨벤션이 제대로 지켜지지 않는 문제가 발생했습니다.
처음에는 이 문제를 컨벤션 자체가 부족했기 때문이라고 생각했습니다. 하지만 프로젝트를 돌아보니 더 근본적인 원인은 소통의 부족이었습니다. 규칙을 한 번 정하는 것만으로는 충분하지 않았습니다. 서로 같은 기준으로 이해했는지 확인하고, 작업 중 생긴 변경 사항과 어려움을 공유하며, 합의한 방식이 실제로 잘 작동하는지 계속 점검했어야 했습니다.
각자 맡은 기능을 구현하는 데 집중하다 보니 다른 팀원이 어떤 방식으로 작업하고 있는지, 연결 과정에서 어떤 문제가 생길 수 있는지를 충분히 공유하지 못한 순간도 있었습니다. 이런 작은 소통의 공백은 기능을 합치는 단계에서 코드 구조를 다시 맞추거나 서로의 의도를 뒤늦게 확인하는 비용으로 돌아왔습니다.
다시 프로젝트를 진행한다면 초반에 팀 컨벤션을 더 세밀하게 정하는 것에서 그치지 않고, 짧게라도 진행 상황과 변경 사항을 자주 공유해야겠다고 생각했습니다. 또한 규칙을 일방적으로 전달하기보다 예시와 함께 합의하고, 모두가 같은 방식으로 이해했는지 확인하는 것이 중요하다고 느꼈습니다.
이번 경험을 통해 협업에서 중요한 것은 단순히 각자 맡은 기능을 구현하는 것도 중요하지만, 함께 개발하기 위한 규칙을 합의하고 지속적으로 소통하며 같은 맥락을 유지하는 것이라는 점을 배웠습니다.
아쉬웠던 점
개인적으로 담당했던 기능 중에서 아쉬웠던 부분은 노즐 조준 인터랙션입니다.
처음 의도는 사용자가 한 손으로 소화기 본체를 잡고, 다른 손으로 노즐을 잡아 불 쪽으로 조준하는 경험을 자연스럽게 만드는 것이었습니다. 하지만 시간과 구현 난이도 때문에 노즐 조준 부분을 충분히 완성도 있게 구현하지 못했습니다.
또한 제스처 판정 로직도 더 잘 설계할 수 있었다고 생각합니다. 손 추적 기반 인터랙션은 값이 계속 흔들리기 때문에 단순한 조건문만으로는 자연스러운 경험을 만들기 어렵습니다. hysteresis, smoothing, ownership, state machine 같은 개념을 더 체계적으로 적용했어야 했습니다.
다시 만든다면 다음 부분을 개선하고 싶습니다.
- 팀 컨벤션을 더 명확하게 정의하기
- 제스처 판정 로직을 독립적인 모듈로 분리하기
- 노즐 조준 인터랙션 완성도 높이기
- 실기기 테스트 시간을 더 많이 확보하기
- 사용자에게 현재 조작 상태를 알려주는 피드백 강화하기
Challenge3를 통해 배운 것
Challenge3 전과 후를 비교하면, 가장 크게 달라진 점은 visionOS 개발의 흐름을 알게 되었다는 것입니다.
처음에는 visionOS 개발이 SwiftUI의 확장처럼 느껴졌습니다. 하지만 실제로 해보니 고려해야 할 것이 훨씬 많았습니다.
- 공간 좌표계
- entity 계층 구조
- 실제 손의 흔들림
- 실기기 테스트
- 물리 시뮬레이션
- RealityKit asset 구조
- 사용자의 몸 움직임
기존 iOS 개발에서는 화면 안의 UI 상태를 관리하는 것이 중요했다면, visionOS에서는 사용자의 공간, 손, 시선, 물리적 움직임까지 함께 고려해야 했습니다.
이번 챌린지는 저에게 "기술을 구현한다"는 관점을 넘어서, 사용자의 행동을 설계한다는 관점을 경험하게 해준 프로젝트였습니다.
마무리
Challenge3는 쉽지 않았습니다.
Hand Tracking은 처음이었고, RealityKit 좌표계는 예상보다 복잡했으며, 실기기 테스트 없이는 확인할 수 없는 문제가 많았습니다. 협업에서도 컨벤션과 구조를 더 명확히 잡았어야 한다는 아쉬움이 남았습니다.
하지만 그만큼 배운 것도 많았습니다.
visionOS 앱이 단순히 3D 객체를 띄우는 앱이 아니라, 실제 공간에서 사용자의 행동을 설계하는 앱이라는 것을 체감했습니다.
다음에 다시 공간 컴퓨팅 앱을 만든다면, 기술을 먼저 붙이기보다 사용자의 행동 흐름을 더 깊게 관찰하고, 그 행동을 안정적으로 해석할 수 있는 구조를 먼저 설계하고 싶습니다.
감사합니다.
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
