Update README.md

This commit is contained in:
2026-05-04 16:38:49 +00:00
parent 1b1ef56226
commit 835fc28a02

135
README.md
View File

@@ -20,7 +20,7 @@
| | |
|---|---|
| **개발 기간** | _YYYY.MM ~ YYYY.MM_ |
| **개발 기간** | _2026.04 ~ 2026.05_ |
| **인원** | 1인 (기획·개발·일부 에셋 통합) |
| **플랫폼** | Meta Quest (Android XR / OpenXR), PC VR |
| **엔진** | Unity 6000.3.9f1 · URP 17.3 |
@@ -30,13 +30,12 @@
## 게임 플로우
1. **GameStartScene** — 시작 화면에서 게임 진입
2. **GameScene (매장)** — 좌측 컨트롤러 Primary 버튼으로 주문서 펼치기
3. **상품 픽업** — XR Direct Interactor로 상품을 잡고 손에 든다 (Hover 하이라이트로 피드백)
4. **셀프 계산대** — 바코드 스캔 영역에 상품을 가져가면 항목이 행으로 추가됨, 합계 자동 갱신
5. **결제**`PlayerWallet`에서 차감 → 주문서 충족 검사 → Clear / Missing 패널 분기
6. **카트**카트 영역에 떨어뜨려 둔 상품은 일정 시간 후 자동으로 카트에 정착(부착)되어 카트와 함께 움직인다. 다시 손으로 집으면 분리.
7. **부가 시스템** — NPC 대화·시식으로 허기 회복, 시간이 지나면 허기가 깎이고 굶주리면 이동 속도 저하
1. **GameStartScene** — 시작 화면에서 게임 진입 (비동기 로딩)
2. **GameScene (매장)** — 좌측 컨트롤러 Primary 버튼으로 장보기 목록 펼치기
3. **상품 픽업** — XR Direct Interactor로 상품을 잡아 카트에 넣음.
4. **셀프 계산대** — 바코드 상품을 스캔하면 항목이 행으로 추가됨, 합계 자동 갱신
5. **결제**예산에서 차감 → 장보기 목록 충족 검사 → Clear / Missing 패널 분기
6. **부가 시스템**NPC 대화 시스템, 포만감 시스템
---
@@ -46,26 +45,26 @@
- 아이템 데이터는 상품 ID·브랜드·`ItemCategory`(과일/유제품/스낵 등)·`ProductGroup`(브랜드별 구체 상품)으로 구성. **카테고리는 매대 분류용, 그룹은 미션 매칭용**으로 의도적으로 두 단계로 나눔.
- 장보기 목록을 SO로 데이터화. 목록을 직접 인스펙터에서 추가할 수 있으며, 차후 랜덤으로 변경 예정.
### 2. 셀프 계산대 (`CheckoutMachine`, `BarcodeScaner`)
- `Physics.OverlapBoxNonAlloc` 스캔 영역 안의 콜라이더를 GC 부담 없이 수집한 뒤, 영역 중심에 가장 가까운 `ItemInstance` 한 개만 스캔.
- 결제 행은 `Dictionary<string, CheckoutProductionRow>`로 동일 상품 묶음·수량 카운트를 O(1)로 처리.
- UI 행 추가 시 **`OnEnable` 지연 트릭**: 행을 비활성 상태로 인스턴스화 → `LayoutRebuilder.ForceRebuildLayoutImmediate`로 레이아웃 확정 → 활성화. 활성 직후 첫 프레임에 RectTransform이 0인 채로 OnEnable이 도는 문제를 피한다.
### 2. 셀프 계산대
- 스캔 영역 콜라이더로 충돌체들 수집한 뒤, 가장 가까운 상품 한 개만 스캔.
- 스캔할 때마다 계산목록에 하나씩 추가되며, 추가 버튼과 감소 버튼으로 수량 조절 가능.
- 결제시 미션 목록과 비교하여, 부족할 시 부족분을 보여주고 만족할시 클리어.
### 3. NPC 대화·립싱크 시스템
- **데이터 그래프**: `DialogGroup``DialogNode`(대사·제스처·표정·보이스·선택지) → `DialogChoice` → 다음 노드. 모두 ScriptableObject로 노드 그래프를 자산화.
- `DialogPlayer` — Unity 6의 `Awaitable``await PlayNode → await WaitForChoice → 다음 노드` 흐름을 코루틴 없이 비동기 직렬화. 대화 종료 시 초기 제스처/표정 상태를 `CrossFade`로 복원.
- **플레이어 향해 회전** — 대화 중 NPC가 카메라 방향으로 부드럽게 회전, 종료 시 원래 회전으로 복귀.
- **블렌드셰이프 립싱크 (`LipSync`)** — `AudioSource.GetOutputData`로 RMS amplitude 계산 → 0~1로 매핑 → 입 관련 블렌드셰이프 7종에 가중치 분배. **`LateUpdate`에서 적용**해 같은 프레임에 Animator가 0으로 덮어쓴 값을 다시 씌우는 방식으로 표정 애니메이션과 충돌 없이 공존.
- NPC에 상호작용 할 시, 대화 발생.
- NPC는 **'녹음되어 있는 음성'**으로 정보를 전달하며, 특정 대사에 시선, 표정, 제스처를 부여할 수 있음.
- NPC의 대사 중에는 **'선택'**을 요구하는 경우가 있으며, 임의의 선택지를 선택시 해당 선택지가 가지고 있는 대화노드로 점프함.
- **블렌드셰이프 립싱크** — 입 관련 블렌드셰이프 7종에 가중치 분배. 오디오 소스의 실시간 소리 진폭에 기반하여 LateUpdate에서 표정 Animator를 덮어씀.
### 4. 플레이어 시스템
- `PlayerHunger` — 시간 경과로 허기 게이지 감소. 0이 되면 `ContinuousMoveProvider.moveSpeed``_starvedMoveSpeed`로 교체, 시식으로 회복 시 원본 속도 복구. UI는 `Action<float, float>` 이벤트로 게이지 바인딩.
- `PlayerWallet` — 결제 시 잔고 차감, 부족 시 `false` 반환해 결제 자체를 막음.
- `InputManager` — XR 컨트롤러 버튼 이벤트(`XRLeftControllerPrimaryButton_Event` 등)를 게임 로직 측에서 구독.
- 손 마다 각각 상품을 잡으면, 상품의 정보가 캔버스UI로 보여짐. 양손으로 가격 비교 가능.
- 시간이 흐르면서 포만감 게이지 감소. 0이 되면 이동속도 대폭감소하며, 시식으로 포만감 회복 시 원본 속도 복구.
- **'예산'**이 존재하며, 예산보다 큰 금액의 결제를 막음.
### 5. 카트 시스템
<img src="/readme/cart2.png" width="300"><br>
- 해당 영역에 아이템이 들어오면, 아이템의 움직임이 없어질 때(혹은 0.3초가 지났을 때) 물리영향을 끄고 카트의 움직임을 `ParentConstraint` 로 추적한다.
- 이렇게 해야 카트와 아이템이 따로 움직이지 않고, 물건 위에 물건이 쌓였을 때의 버그가 없었.
- 해당 영역에 아이템이 들어오면, 아이템의 움직임이 없어질 때(혹은 0.3초가 지났을 때) 물리영향을 끄고 카트의 움직임을 `ParentConstraint` 로 추적.
- 이렇게 해야 카트와 아이템이 따로 움직이지 않고, 물건 위에 물건이 쌓였을 때의 버그가 없었.
### 6. 씬 전환 커스텀 사이클
- `ITransScenePossible` 인터페이스 — **씬을 가로지르는 매니저(SceneLoadManager)**가 새 씬 로드 직후 씬 내의 모든 `ITransScenePossible`를 전부 찾아 `OnSceneLoaded()`를 호출함.
@@ -76,48 +75,25 @@
```
Managers (DontDestroyOnLoad, 싱글톤)
├─ GameManager 전역 상태·씬 매니저 결선
├─ InputManager XR 컨트롤러 입력 이벤트 허브
├─ SoundManager BGM / SE
└─ SceneLoadManager ITransScenePossible 알림 디스패치
├─ GameManager 게임관리 및 매니저 결선
├─ InputManager 입력
├─ SoundManager BGM / SFX / VOICE
└─ SceneLoadManager 씬 전환, ITransScenePossible 알림
Scene-local (GameScene)
├─ GameSceneUIManager HUD·패널 컨트롤
├─ PlayerController 주문서 토글, 결제 진입점
├─ PlayerWallet / PlayerHunger
RideController + RideDetectionZone 카트 (ParentConstraint로 플레이어 종속)
└─ CheckoutMachine ← BarcodeScaner ← ItemInstance(ItemData SO)
Scene-local
├─ LocalManager
├─ UIManager
├─ Player
Item
Data (ScriptableObject)
├─ ItemData / ShoppingOrderList / ShoppingOrderEntry
├─ DialogGroup / DialogNode / DialogChoice / VoiceClip
└─ CharacterData / ExpressionData / GestureData / BGMClip
└─ CharacterData / ExpressionData / GestureData
```
---
## 폴더 구조
```
Assets/
├─ 01_Scenes/MyProject/ GameStartScene, GameScene
├─ 02_Scripts/ 자체 구현 코드 (네임스페이스 VRShopping.*)
│ ├─ Managers/ GameManager, InputManager, SoundManager, SceneLoadManager...
│ ├─ Player/ PlayerController, PlayerHunger, PlayerWallet, RideController
│ ├─ Shopping/ BarcodeScaner, CheckoutMachine
│ ├─ Communication/ Dialog/, Voice/ (LipSync, DialogPlayer, CharacterVoiceObject)
│ ├─ Item/ ItemData, ItemInstance, TastingSample
│ ├─ Interact/ ItemHoverHighlight, ItemInfoOnGrab, DialogInteractable
│ ├─ Data/ ScriptableObject 정의
│ └─ UI/ HUD·패널 (HungerHud, GameTimerHud, ChoiceHud, ClearPanel...)
├─ 03_Models/ ~ 10_Audio/ 아트·오디오 에셋
└─ 99_Settings/ XR / URP / Input 설정
```
---
## 사용한 외부 에셋
| 분류 | 에셋 | 용도 |
@@ -130,8 +106,6 @@ Assets/
| 인터랙션 피드백 | HighlightPlus, Outline Plus | Hover/Grab 외곽선 |
| VFX | CartoonVFX9X | 스캔·결제 이펙트 |
> 자체 구현은 `Assets/02_Scripts/` (네임스페이스 `VRShopping.*`).
---
## 빌드 / 실행
@@ -139,52 +113,3 @@ Assets/
### 요구 사항
- Unity **6000.3.9f1**
- (Quest 빌드) Android Build Support, OpenXR Loader: Meta Quest
### 실행
1. 저장소 클론 후 Unity Hub에서 프로젝트 열기
2. `Assets/01_Scenes/MyProject/GameStartScene.unity` 열기
3. PC: Quest Link / Air Link 연결 후 Play
4. Android 빌드: Build Settings → Android → Build, Quest에 사이드로드
---
## 트러블슈팅 / 학습 포인트
### 1. 립싱크와 표정 애니메이션 충돌
- **문제**: `Animator`가 표정 레이어에서 매 프레임 블렌드셰이프 가중치를 0으로 덮어써 립싱크가 동작하지 않음.
- **해결**: 립싱크를 `LateUpdate`에서 적용. `Animator``Update` 이후 동작이지만 같은 프레임의 `LateUpdate`에서 다시 가중치를 씌우는 순서로 충돌을 회피.
### 2. UI 행 추가 직후 RectTransform 0 문제
- **문제**: `Instantiate` 후 활성 상태에서 바로 `OnEnable`이 돌면 부모 레이아웃이 아직 갱신 전이라 사이즈 0으로 잡힘.
- **해결**: 비활성 상태로 생성 → `LayoutRebuilder.ForceRebuildLayoutImmediate` → 활성. 첫 프레임 깜빡임 제거.
### 3. 씬 전환 시 매니저 참조 끊김
- **문제**: `DontDestroyOnLoad` 매니저가 새 씬의 UI 매니저를 못 찾음.
- **해결**: `ITransScenePossible` 인터페이스로 통일 → `SceneLoadManager`가 씬 로드 콜백에서 일괄 `OnSceneLoaded()` 호출 → 각자 필요한 씬-로컬 참조를 다시 잡음.
### 4. 스캔 영역 다중 상품 처리
- **문제**: 스캔 박스 안에 여러 상품이 들어오면 한 번에 다 결제됨.
- **해결**: `OverlapBoxNonAlloc` 결과 중 박스 중심에 **가장 가까운 한 개**만 스캔 대상으로 선택.
### 5. 카트에 담은 물건이 카트와 따로 노는 문제
- **문제**: VR 카트가 움직이면 안에 든 물건이 충돌·관성으로 튀어나옴. 그렇다고 진입 즉시 부착하면 던져 넣을 때 운동량으로 다시 빠져나감.
- **해결**: **속도 임계값(`_settleVelocity`) 이하 상태가 `_settleDuration` 동안 연속 유지**되면 카트 자식으로 reparent + `isKinematic = true`. 잡혀있는 동안엔 타이머 자체를 무효화. (자세한 흐름은 [카트 시스템 섹션](#5-카트-시스템-ridecontroller-ridedetectionzone) 참고)
### 6. XRGrabInteractable의 상태 스냅샷·복원이 카트 부착을 깨뜨림
- **문제**: `XRGrabInteractable`은 잡힐 때 부모/kinematic을 스냅샷하고 놓을 때 복원함. 카트에 kinematic 상태로 부착된 물건을 잡았다 놓으면, **놓는 순간 다시 카트 자식 + kinematic으로 복원되어 공중에 박제**됨.
- **해결**: 부착 시 `retainTransformParent = false`로 부모 복원 차단 + `selectExited`에서 `isKinematic = false`를 직접 덮어씀.
---
## 향후 개선
- [ ] 카트(`RideController`)에 담은 아이템 별도 인벤토리화
- [ ] 대화 노드 진행 입력을 VR 컨트롤러 버튼으로 (현재 임시 1초 대기)
- [ ] PlayerController의 임시 bool 플래그 → 상태 머신 전환
- [ ] 주문서 종류 다양화 (난이도별 SO 추가)
---
## 라이선스
자체 구현 코드(`Assets/02_Scripts/`)에 대해서만 권리 주장. 외부 에셋은 각 에셋 라이선스를 따른다.