571 lines
26 KiB
Markdown
571 lines
26 KiB
Markdown
# VR Beat Saber 프로젝트 인수인계 문서
|
|
|
|
## 개요
|
|
|
|
Meta Quest용 VR Beat Saber 클론. Beat Sage API로 노래를 자동 채보하고, Synology NAS에 업로드/다운로드한다.
|
|
이 문서는 **기존 프로젝트(구버전)를 VRBeatsKit 기반 새 프로젝트로 이전**하기 위한 인수인계 자료다.
|
|
|
|
---
|
|
|
|
## 현재 상태 (2026-05-28)
|
|
|
|
현재 저장소는 VRBeatsKit 기반 프로젝트 위에 커스텀 곡 선택/생성/NAS 연동, 360도 영상 배경, 점수/콤보 개편, 싱크 보정 화면, VR UI 포인터 보정, 세이버/큐브 트레일 효과를 붙인 상태다.
|
|
|
|
- Unity 버전: `6000.3.12f1`
|
|
- 현재 브랜치: `master`
|
|
- 원격 저장소: `origin` = `https://whdwo798.synology.me/whdwo798/BeatSaber.git`
|
|
- `dotnet build VRBeatSaber.slnx --no-incremental` 결과: 오류 0개, 경고 0개
|
|
- NAS 비밀번호/세션 파일은 로컬 전용이다. `env`, `cookies.txt`, `Assets/StreamingAssets/nas_config.json`, Unity `_Recovery`는 커밋하지 않는다.
|
|
- Unity Codex Bridge는 에디터 내부 HTTP 브리지와 MCP 서버를 통해 씬/로그/캡처를 조회하는 용도다. 포트 충돌 시 Unity 재시작 또는 기존 포트 점유 프로세스 정리가 필요하다.
|
|
|
|
### 실제 씬 구성
|
|
|
|
현재 Build Settings는 아래 순서다.
|
|
|
|
1. `Assets/VRBeatsKit/Scenes/Menu.unity`
|
|
2. `Assets/VRBeatsKit/Scenes/BoxingStyle.unity`
|
|
3. `Assets/Scenes/SongCreator.unity`
|
|
4. `Assets/VRBeatsKit/Scenes/SaberStyle.unity`
|
|
5. `Assets/Scenes/Game.unity`
|
|
|
|
문서 아래쪽에 남아 있는 `Intro -> SongSelect -> Game -> SongCreator` 흐름은 목표 설계에 가깝다. 현재 실제 진입점은 VRBeatsKit `Menu.unity`이며, 그 안의 `SongSelect` 패널이 커스텀 곡 선택 UI 역할을 한다.
|
|
|
|
### 현재 구현된 주요 흐름
|
|
|
|
```text
|
|
Menu.unity / SongSelect
|
|
-> DownloadManager가 NAS 정적 서버의 songs.json 로드
|
|
-> SongDetailPanel에서 곡/난이도 다운로드
|
|
-> GameSession.SelectedSong / SelectedDifficulty 설정
|
|
-> Game.unity 로드
|
|
|
|
Game.unity
|
|
-> SongController가 temporaryCachePath의 mp3 + map json 로드
|
|
-> VRBeats.AudioManager로 음악 재생
|
|
-> 오디오 시간 기준으로 VR_BeatManager.Spawn() 호출
|
|
-> VR_BeatCube / Cuttable / DamageSaber가 색상, 방향, 속도 판정
|
|
|
|
SongCreator.unity
|
|
-> SongCreatorManager가 로컬 mp3 또는 직접 mp3 URL 입력
|
|
-> BeatSageUploader가 Beat Sage 요청/폴링/ZIP 다운로드
|
|
-> BeatSageConverter가 .dat를 NoteData로 변환
|
|
-> NasPublisher가 mp3, map json, songs.json을 Synology NAS에 업로드
|
|
```
|
|
|
|
### 2026-05-28 최근 반영된 변경
|
|
|
|
#### 게임 플레이/노트/점수
|
|
|
|
- `Assets/Script/SongController.cs`
|
|
- 큐브가 중간에 멈추거나 급가속하는 느낌을 줄이기 위해 노트를 일정 속도로 접근시키는 흐름으로 조정했다.
|
|
- 노트 도착 기준을 오디오 시간과 맞추고, 난이도별 노트 수가 적어도 랭크가 과도하게 낮게 나오지 않도록 총 노트 기준 점수/랭크 구조와 맞물리게 했다.
|
|
- `Assets/VRBeatsKit/Scripts/UI/ScoreManager.cs`, `FinalScoreLabel.cs`
|
|
- DJMAX 계열에 가까운 콤보/정확도 중심 점수 구조로 개편했다.
|
|
- 총 노트 수 기준으로 정확도와 랭크를 계산하므로 Normal처럼 노트 수가 적은 곡도 구조적으로 F에 고정되지 않는다.
|
|
- 재시작/리플레이 시 이전 점수, 콤보, 음악 상태가 남지 않도록 초기화 흐름을 보강했다.
|
|
- `Assets/VRBeatsKit/Scripts/Core/VR_BeatCube.cs`, `Cuttable.cs`, `DamageSaber.cs`
|
|
- 정상 절단된 큐브가 뒤로 날아간 뒤 Miss로 다시 잡히는 문제를 막기 위해 절단/미스 상태 흐름을 보강했다.
|
|
- 방향/색상/속도 판정과 절단 시각 효과가 엇갈리지 않도록 유효 절단 조건을 정리했다.
|
|
- `Assets/VRBeatsKit/Scripts/Spawneable/SpawnEventInfo.cs`, `VR_BeatManager.cs`
|
|
- 노트 스폰 타이밍과 이동 속도 보정에 필요한 정보를 전달하도록 확장했다.
|
|
|
|
#### VR UI/입력
|
|
|
|
- `Assets/Script/VRPointerController.cs`, `VRPointerSetup.cs`
|
|
- VR 컨트롤러 레이 버튼 클릭 안정성을 보강했다.
|
|
- 비활성 Canvas의 버튼이 레이캐스트 후보에 남아 클릭을 빼앗는 문제를 필터링했다.
|
|
- SongSelect 스크롤 속도를 올리고, 검지 트리거를 누른 상태로 위/아래 드래그해 스크롤할 수 있게 했다.
|
|
- 메뉴/게임 씬 전환 후에도 포인터가 다시 주입되도록 유지했다.
|
|
- `Assets/VRBeatsKit/Prefabs/PlayerSetup/MenuPlayer.prefab`, `SaberStylePlayer.prefab`, `VR_InteractorController.cs`
|
|
- XR Interaction Toolkit의 `XRRayInteractor`/`XRInteractorLineVisual`과 커스텀 포인터가 겹쳐 버튼 입력이 꼬이는 문제를 막기 위해 XRI 레이 시각화를 비활성화했다.
|
|
- `Missing ILineRenderable / Ray Interactor component` 오류가 반복되지 않도록 런타임 enable 흐름도 커스텀 포인터 기준으로 정리했다.
|
|
|
|
#### 비주얼/배경/세이버
|
|
|
|
- `Assets/Script/Game360VideoBackground.cs`, `Assets/Scenes/Game.unity`
|
|
- 게임 씬 배경을 360도 영상처럼 둘러싼 내부 구체/스카이박스형 배경으로 재생하도록 추가했다.
|
|
- 영상은 Unity 호환 H.264 baseline/CFR MP4로 변환해 쓰는 것을 권장한다. 원본에 timestamp warning이 있으면 `ffmpeg`로 재인코딩한다.
|
|
- `Assets/VRBeatsKit/Scripts/Core/SaberTrailEffect.cs`
|
|
- 기존 검끝 한 점 `TrailRenderer` 방식 대신, 세이버 `Start-End` 검신 전체를 샘플링하는 월드 스페이스 리본 메쉬 잔상으로 교체했다.
|
|
- 검끝에서만 빙글 도는 헬리콥터 같은 잔상 대신, 검신이 휘둘린 면이 짧게 남는 형태다.
|
|
- `Assets/VRBeatsKit/Scripts/Core/SliceTrailEffect.cs`
|
|
- 큐브가 잘릴 때 절단면/파편 쪽에서 짧은 트레일이 남도록 별도 효과를 추가했다.
|
|
- `Assets/VRBeatsKit/Settings/Settings.asset`, `DefaultVolumeProfile.asset`
|
|
- 큐브 네온/블룸 강도를 낮춰 과한 발광을 줄였다.
|
|
- `Assets/VRBeatsKit/Prefabs/VR_Saber/2/RightFuturisticSword.prefab`
|
|
- 두 번째 세이버도 너무 수직으로 서지 않도록 프리팹 회전을 보정했다.
|
|
|
|
#### 싱크 보정/개발 도구
|
|
|
|
- `Assets/Script/GlobalSyncSettings.cs`, `SyncCalibrationOverlay.cs`, `MenuSyncButtonInjector.cs`
|
|
- 메뉴의 Create Song 버튼 아래에 Sync 버튼을 주입하고, 별도 싱크 보정 화면을 열 수 있게 했다.
|
|
- 주기적인 틱 사운드/시각 기준을 보고 오른쪽 컨트롤러 A 버튼 또는 키보드로 입력 지연을 기록하는 구조다.
|
|
- TextMeshPro 한글 깨짐을 줄이기 위해 `TMP Settings.asset`에 NanumGothic fallback을 추가했다.
|
|
- `Assets/Editor/UnityCodexBridgeServer.cs`, `tools/unity-mcp-server/`, `docs/unity_mcp_bridge.md`
|
|
- Codex가 Unity 에디터의 health/log/scene/capture/play state를 조회할 수 있는 로컬 브리지와 MCP 서버를 추가했다.
|
|
- 현재 MCP 연결은 세션/포트 상태에 따라 재시작이 필요할 수 있다.
|
|
|
|
#### NAS/다운로드/생성
|
|
|
|
- `Assets/Script/NasPublisher.cs`
|
|
- `nas_config.json` 또는 로컬 `env` 기반으로 NAS 계정/비밀번호를 읽도록 정리했다.
|
|
- DSM 응답 파싱 실패/비밀번호 누락/URL 공백 문제를 더 명확히 로그로 남기도록 보강했다.
|
|
- `Assets/Script/DownloadManager.cs`, `SongLibrary.cs`
|
|
- 곡이 실제로 다운로드되는 경로를 로그와 상태로 확인할 수 있게 했다.
|
|
- 곡 삭제 시 mp3/map json/다운로드 상태가 같이 지워지도록 정리해 캐시가 쌓이기만 하는 문제를 줄였다.
|
|
- `Assets/Script/BeatSageConverter.cs`, `BeatSageUploader.cs`
|
|
- Beat Sage 변환 결과와 노트 수 로그를 확인하기 쉽게 유지했다.
|
|
|
|
#### 로컬 전용/커밋 제외
|
|
|
|
- `env`, `cookies.txt`, `Assets/_Recovery/`는 로컬 전용이라 커밋하지 않는다.
|
|
- `tools/unity-mcp-server/node_modules/`도 커밋 제외다.
|
|
|
|
### 2026-05-26 이전 반영된 변경
|
|
|
|
- `Assets/Script/SongController.cs`
|
|
- Beat Saber 4라인 좌표 매핑을 넓혀 인접 큐브가 가로로 겹치는 문제를 줄였다.
|
|
- 기존 라인 x 좌표는 대략 `-0.375, -0.125, 0.125, 0.375`였고, 현재는 `-0.63, -0.21, 0.21, 0.63`이다.
|
|
- 맵 노트 정렬을 `time -> position -> lineLayer` 순서로 바꿔 같은 시간대 노트 처리 순서를 안정화했다.
|
|
- 전체 C# 경고 제거
|
|
- `FindObjectOfType`, `FindObjectsOfType`, `InputHelpers`, `TMP_Text.enableWordWrapping`, `EditorApplication.currentScene`, 구버전 `PlayerSettings` API를 최신 API로 교체했다.
|
|
- 미사용 필드/변수, 상속 멤버 숨김, Unity 메시지 시그니처 경고를 정리했다.
|
|
- 최종 확인 빌드: `dotnet build VRBeatSaber.slnx --no-incremental` = 경고 0개, 오류 0개.
|
|
- `Assets/Script/VRPointerController.cs`, `VRPointerSetup.cs` 추가
|
|
- VR 컨트롤러 레이로 Unity UI 버튼을 직접 hover/click 처리한다.
|
|
- `Game` 씬에서는 게임오버 전까지 비활성화하고, 메뉴 계열 씬에서는 활성화한다.
|
|
- `Assets/Script/VRPointerSetup.cs`
|
|
- `DontDestroyOnLoad` 싱글턴으로 변경되어 `Menu -> SongCreator -> Game` 같은 씬 전환 후에도 포인터를 다시 주입한다.
|
|
- `SceneManager.sceneLoaded`마다 현재 씬 컨트롤러를 검사한다.
|
|
- `Assets/VRBeatsKit/Scripts/Core/VR_InteractorController.cs`
|
|
- XR Ray Interactor enable/disable 시 `VRPointerController`도 함께 제어한다.
|
|
- 컨트롤러 구조 차이를 고려해 현재 오브젝트, 부모, 자식, 루트 하위에서 `VRPointerController`를 찾는다.
|
|
- `Assets/VRBeatsKit/Scripts/Core/AudioManager.cs`
|
|
- `AudioSource.Play()` 대신 `PlayScheduled()`를 사용하고, `AudioSettings.dspTime` 기준으로 `CurrentTime`을 계산한다.
|
|
- MP3 재생 시작 시점과 노트 스폰 기준 시간이 프레임 상태에 따라 흔들리는 문제를 줄이기 위한 변경이다.
|
|
- `Assets/VRBeatsKit/Scripts/Core/VR_BeatCube.cs`
|
|
- `IsCutIntentValid()`를 public으로 변경하고 `maxCutAngle`을 추가했다.
|
|
- `Assets/VRBeatsKit/Scripts/Core/Cuttable.cs`
|
|
- 색상/방향/속도가 틀린 큐브는 절단 시각 효과도 발생하지 않도록 막았다.
|
|
- `Assets/Scenes/Game.unity`
|
|
- `SongController`가 큐브 프리팹, `OnLevelComplete`, 카운트다운 텍스트와 연결되어 있다.
|
|
- `GameOverPopup`의 Back 버튼에 깨진 스크립트 참조가 있어 `LoadSceneButton`으로 복구했다.
|
|
- 현재 사용 중인 좌/우 세이버 루트 회전을 `X 45도`로 보정해 컨트롤러에서 너무 수직으로 서는 문제를 줄였다.
|
|
- `Assets/VRBeatsKit/Scenes/Menu.unity`
|
|
- `SongSelectManager`, `DownloadManager`, `SongDetailPanel`, `SongLibrary`가 연결되어 있다.
|
|
- `VRPointerSetup`이 `VR_Manager`에 추가되어 있다.
|
|
- `Assets/img/360.mp4`, `Assets/img/beatSaber.png`
|
|
- 메뉴/비주얼용 에셋으로 추가됨.
|
|
- `.gitignore`
|
|
- `*.csproj.user` 제외 추가.
|
|
- `.gitattributes`
|
|
- `*.mp4 binary` 추가.
|
|
|
|
### 현재 주의사항
|
|
|
|
1. `Assets/StreamingAssets/nas_config.json`과 루트 `env`는 로컬 전용이다. NAS 업로드 테스트 전 계정/비밀번호를 직접 넣되 절대 커밋하지 않는다.
|
|
2. Unity 콘솔의 `Unable to start Oculus XR Plugin`은 헤드셋/오큘러스 런타임 상태 문제일 수 있다. PC 에디터 단독 실행에서는 경고가 날 수 있다.
|
|
3. `The referenced script (Unknown) on this Behaviour is missing!` 경고가 남는 씬/프리팹은 추가 확인 대상이다. 게임 진행을 막는 직접 원인은 아니지만, 인스펙터에서 Missing Script를 정리하는 것이 좋다.
|
|
4. 영상 배경은 Unity 호환 MP4가 안전하다. H.264 timestamp warning이 뜨는 원본은 baseline profile, CFR, yuv420p로 재인코딩한다.
|
|
5. `Assets/img` 아래 영상 파일은 크기가 커질 수 있다. 원격 저장소 정책에 걸리면 Git LFS 전환을 검토한다.
|
|
6. 싱크 보정 화면은 UI/입력 구조까지 들어갔지만, 기기별 오디오 지연 값은 Quest 실기에서 측정해야 한다.
|
|
7. 세이버 잔상, 큐브 이동 속도, 블룸 강도는 체감 튜닝 항목이다. 현재 값은 빌드/컴파일 기준으로 안전하지만, VR 착용 테스트 후 수치를 조정하는 것이 좋다.
|
|
|
|
---
|
|
|
|
## 기존 프로젝트 소스 코드
|
|
|
|
**기존 프로젝트 전체 파일은 아래 git 저장소에서 가져온다.**
|
|
|
|
```
|
|
https://whdwo798.synology.me/whdwo798/BeatSaber.git
|
|
```
|
|
|
|
```bash
|
|
git clone https://whdwo798.synology.me/whdwo798/BeatSaber.git
|
|
```
|
|
|
|
> 단, 이 저장소는 구버전 프로젝트다. 새 프로젝트는 VRBeatsKit을 기반으로 재구성하며,
|
|
> 위 저장소의 `Assets/Script/` 등 핵심 스크립트를 참고/이식하는 용도로 사용한다.
|
|
|
|
---
|
|
|
|
## Git 설정 (새 프로젝트)
|
|
|
|
새 Unity 프로젝트를 생성한 뒤 **가장 먼저** git을 초기화하고 파일을 커밋해야 한다.
|
|
Claude Code는 대화 시작 시 `git status` / `git log`를 자동으로 읽어 컨텍스트를 파악한다.
|
|
커밋이 없으면 Claude가 변경 이력을 추적할 수 없다.
|
|
|
|
### 초기화 순서
|
|
|
|
```bash
|
|
# 새 프로젝트 루트에서
|
|
git init
|
|
git remote add origin <GitHub 저장소 URL>
|
|
```
|
|
|
|
### .gitignore
|
|
|
|
기존 프로젝트의 `.gitignore`를 복사하면 된다. 핵심 규칙:
|
|
|
|
```gitignore
|
|
# Unity 표준
|
|
/Library/
|
|
/Temp/
|
|
/Obj/
|
|
/Build/
|
|
/Builds/
|
|
/Logs/
|
|
/UserSettings/
|
|
|
|
# NAS 비밀번호 — 절대 커밋 금지
|
|
/Assets/StreamingAssets/nas_config.json
|
|
/Assets/StreamingAssets/nas_config.json.meta
|
|
```
|
|
|
|
### 첫 커밋
|
|
|
|
파일 복사 완료 후:
|
|
|
|
```bash
|
|
git add .
|
|
git commit -m "init: VRBeatsKit 기반 프로젝트 초기 설정"
|
|
```
|
|
|
|
이후 기능 단위로 커밋하면 Claude가 `git log`로 작업 이력을 파악한다.
|
|
|
|
---
|
|
|
|
## 새 프로젝트 구성 방법
|
|
|
|
### 전제 조건
|
|
|
|
1. Unity Hub에서 **새 URP 3D 프로젝트** 생성 (기존 프로젝트와 동일 Unity 버전)
|
|
2. Asset Store에서 **VRBeatsKit** 임포트
|
|
3. Package Manager에서 아래 패키지 설치:
|
|
- XR Interaction Toolkit (3.x)
|
|
- XR Hands
|
|
- OpenXR Plugin
|
|
- TextMeshPro
|
|
- Unity Input System
|
|
|
|
### 복사할 파일 (기존 프로젝트 → 새 프로젝트)
|
|
|
|
아래 폴더/파일을 `Assets/` 아래에 그대로 복사한다.
|
|
|
|
```
|
|
Assets/Script/ ← 아래 "복사 제외" 목록 참고
|
|
Assets/Editor/VRBeatSaberSceneBuilder.cs
|
|
Assets/StreamingAssets/ ← nas_config.json 포함 (절대 git 커밋 금지)
|
|
Assets/Fonts/NanumGothic SDF.asset 및 관련 파일
|
|
Assets/360Music/
|
|
Assets/Audio/ ← HitSound.wav, MissSound.wav
|
|
Assets/Prefab/ ← RED.prefab, BLUE.prefab (추후 VRBeatsKit 큐브로 교체)
|
|
```
|
|
|
|
### 복사 제외 (VRBeatsKit으로 대체)
|
|
|
|
```
|
|
Assets/Script/Saber.cs → VRBeatsKit VR_Saber.cs 사용
|
|
Assets/Script/Cube.cs → VRBeatsKit VR_BeatCube.cs 사용
|
|
```
|
|
|
|
---
|
|
|
|
## 전체 씬 구성
|
|
|
|
```
|
|
Intro → SongSelect → Game
|
|
SongSelect → SongCreator → (NAS 업로드) → SongSelect
|
|
```
|
|
|
|
| 씬 | 역할 |
|
|
|---|---|
|
|
| Intro | 로고 → SongSelect 자동 전환 |
|
|
| SongSelect | NAS에서 songs.json 로드, 곡 목록 표시, 다운로드/플레이 |
|
|
| Game | 음악 재생 + 큐브 스폰 + 점수/HP + 결과 화면 |
|
|
| SongCreator | 음악 파일 선택 → Beat Sage API 채보 → NAS 업로드 |
|
|
| MapEditorScene | 맵 에디터 (선택적) |
|
|
|
|
---
|
|
|
|
## 전체 데이터 흐름
|
|
|
|
```
|
|
[SongCreator]
|
|
사용자: 음악 파일 선택 (로컬 파일 또는 URL)
|
|
→ BeatSageUploader: Beat Sage API 채보 요청
|
|
POST https://beatsage.com/create
|
|
→ GET /heartbeat/{id} 폴링
|
|
→ GET /download/{id} → .zip (Normal.dat, Hard.dat, Expert.dat, ExpertPlus.dat)
|
|
→ BeatSageConverter: .dat → NoteData 변환
|
|
→ NasPublisher: Synology NAS 업로드
|
|
songs.json 갱신: /web/beatsaber/songs.json
|
|
맵 JSON: /web/beatsaber/maps/Map_{id}_{diff}.json
|
|
오디오: /web/beatsaber/music/{id}.mp3
|
|
|
|
[SongSelect]
|
|
→ DownloadManager: NAS에서 songs.json 로드
|
|
→ 사용자 곡 선택 → GameSession.SelectedSong, GameSession.SelectedDifficulty 설정
|
|
→ 다운로드: {id}.mp3 + Map_{id}_{diff}.json → Application.temporaryCachePath/beatsaber/{id}/
|
|
|
|
[Game]
|
|
→ Spawner.InitGame(): 캐시에서 오디오/맵 로드
|
|
→ VRBeatsKit AudioManager AudioSource에 클립 세팅
|
|
→ 카운트다운 3→2→1→GO
|
|
→ 매 프레임: audioSource.time 기준으로 VR_BeatManager.Spawn() 호출
|
|
→ ScoreManager: 히트/미스 집계 → HP → 결과 화면
|
|
```
|
|
|
|
---
|
|
|
|
## 주요 스크립트 역할
|
|
|
|
### 복사하는 스크립트 (수정 없음)
|
|
|
|
| 파일 | 역할 |
|
|
|---|---|
|
|
| `GameSession.cs` | static 컨테이너 — 씬 간 선택 곡/난이도 전달 |
|
|
| `NoteData.cs` | DTO — NoteData, MapData, SongInfo, DifficultyMap 등 |
|
|
| `BeatSageConverter.cs` | Beat Sage .dat 형식 → NoteData 변환 |
|
|
| `BeatSageUploader.cs` | Beat Sage API 연동 (POST/GET). `LastMetadata` 프로퍼티에 info.dat 파싱 결과 저장. |
|
|
| `NasPublisher.cs` | Synology DSM 7.2 API 업로드 |
|
|
| `DownloadManager.cs` | NAS → 로컬 캐시 다운로드 |
|
|
| `SongLibrary.cs` | 다운로드 상태 추적 (persistentDataPath) |
|
|
| `SongSelectManager.cs` | 곡 목록 UI |
|
|
| `SongDetailPanel.cs` | 곡 상세 / 다운로드 / 플레이 버튼 |
|
|
| `SongCreatorManager.cs` | 크리에이터 UI, 파일 선택, URL 다운로드. title/BPM 수동 입력 불필요 — info.dat에서 자동 추출. 난이도는 현재 항상 4개 전부 생성. |
|
|
| `SongController.cs` | Game 씬 실행부. 캐시된 mp3/map json을 로드하고 VRBeatsKit `VR_BeatManager.Spawn()`으로 노트를 스폰. |
|
|
| `DesktopUIMode.cs` | 에디터에서 XR 없이 테스트할 때 UI Raycaster 교체 |
|
|
| `VRPointerController.cs` | VR 컨트롤러 레이로 UI hover/click 처리. 디버그 로그 포함. |
|
|
| `VRPointerSetup.cs` | 씬 로드 후 손/컨트롤러 오브젝트에 `VRPointerController` 자동 주입. |
|
|
| `XRSimulatorLoader.cs` | 에디터/PC 테스트용 XR Interaction Simulator 프리팹 주입. |
|
|
|
|
### 현재 미이식/미확인 스크립트
|
|
|
|
| 파일 | 내용 |
|
|
|---|---|
|
|
| `IntroManager.cs` | 현재 저장소에 없음. 인트로 씬 흐름을 살릴 경우 작성/이식 필요. |
|
|
| `ScoreManager.cs`, `ScoreHUD.cs`, `ResultsPanel.cs` | 전역 네임스페이스 커스텀 점수 UI는 현재 저장소에 없음. 현재는 VRBeatsKit `VRBeats.ScoreManager`와 이벤트 자산을 사용. |
|
|
| `SaberGlow.cs`, `SaberSkinSelector.cs`, `CacheManager.cs` | 현재 저장소에 없음. 필요 시 기존 프로젝트에서 이식. |
|
|
|
|
---
|
|
|
|
## Game 실행부 현재 구현
|
|
|
|
기존 인수인계 문서에는 `Spawner.cs`를 새로 작성하라고 되어 있었지만, 현재 저장소에서는 별도 `Spawner.cs` 대신 `Assets/Script/SongController.cs`가 그 역할을 수행한다.
|
|
|
|
### `SongController.cs` 핵심
|
|
|
|
```csharp
|
|
private IEnumerator LoadAndPlay()
|
|
{
|
|
SongInfo song = GameSession.SelectedSong;
|
|
string diff = GameSession.SelectedDifficulty;
|
|
// mp3와 map json을 Application.temporaryCachePath/beatsaber/{songId}/ 에서 로드
|
|
// 카운트다운 후 VRBeats.AudioManager.PlayClip(clip)
|
|
// SpawnRoutine(map.target) 실행
|
|
}
|
|
|
|
private void SpawnNote(NoteData note)
|
|
{
|
|
float x = MapLaneX(note.position);
|
|
float y = MapLayerY(note.lineLayer);
|
|
|
|
var info = new SpawnEventInfo
|
|
{
|
|
position = new Vector3(x, y, 0f),
|
|
colorSide = note.colorType == 0 ? ColorSide.Left : ColorSide.Right,
|
|
hitDirection = MapCutDirection(note.cutDirection),
|
|
useSpark = true,
|
|
speed = 2f,
|
|
travelTimeOverride = note.time - _audio.CurrentTime,
|
|
};
|
|
|
|
VR_BeatManager.instance.Spawn(cubePrefab, info);
|
|
}
|
|
```
|
|
|
|
`travelTimeOverride`는 동시 노트가 프레임 차이로 스폰되어도 같은 타이밍에 도착하도록 `VR_BeatManager`에 추가된 값이다.
|
|
|
|
현재 라인 매핑은 `LaneSpacing = 0.42f`, `LayerSpacing = 0.38f`를 사용한다. 이는 VRBeatsKit 큐브 콜라이더의 실제 폭이 기존 라인 간격보다 커서 인접 라인이 겹치던 문제를 피하기 위한 값이다.
|
|
|
|
---
|
|
|
|
## ScoreManager 충돌 없음
|
|
|
|
- 예전 설계: 전역 네임스페이스 커스텀 `ScoreManager.cs`
|
|
- 현재 저장소: `Assets/VRBeatsKit/Scripts/UI/ScoreManager.cs`의 `VRBeats.ScoreManager` 사용
|
|
- 전역 커스텀 `ScoreManager.cs`를 다시 이식하면 네임스페이스가 달라 공존은 가능하지만, 이벤트 연결과 UI 패널을 별도로 구성해야 한다.
|
|
|
|
---
|
|
|
|
## NAS 설정
|
|
|
|
| 항목 | 값 |
|
|
|---|---|
|
|
| DSM API (내부) | `http://192.168.55.3:5000` |
|
|
| DSM API (외부) | `http://whdwo798.synology.me` |
|
|
| 정적 파일 서버 | `http://whdwo798.synology.me/beatsaber` |
|
|
| NAS 루트 경로 | `/web/beatsaber` |
|
|
| 비밀번호 저장 위치 | `Assets/StreamingAssets/nas_config.json` |
|
|
|
|
**보안 규칙**: `nas_config.json`은 절대 git에 커밋하지 않는다. `.gitignore`에 추가 필수.
|
|
|
|
```json
|
|
{
|
|
"host": "http://192.168.55.3:5000",
|
|
"publicHost": "http://whdwo798.synology.me",
|
|
"account": "계정명",
|
|
"password": "비밀번호"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## NAS 파일 구조
|
|
|
|
```
|
|
/web/beatsaber/
|
|
├── songs.json ← 전체 곡 목록
|
|
├── maps/
|
|
│ └── Map_{id}_{difficulty}.json ← 난이도별 맵 (NoteData 배열)
|
|
└── music/
|
|
└── {id}.mp3 ← 오디오 파일
|
|
```
|
|
|
|
### songs.json 형식
|
|
```json
|
|
{
|
|
"version": "1.0",
|
|
"songs": [
|
|
{
|
|
"id": "uuid",
|
|
"title": "곡 제목",
|
|
"artist": "아티스트",
|
|
"bpm": 120.0,
|
|
"duration": 180,
|
|
"audioFile": "music/uuid.mp3",
|
|
"audioSize": 1234567,
|
|
"coverImage": "",
|
|
"noteJumpSpeed": 10.0,
|
|
"difficulties": {
|
|
"normal": { "mapFile": "maps/Map_uuid_normal.json", "mapSize": 0, "noteCount": 0 },
|
|
"hard": { "mapFile": "maps/Map_uuid_hard.json", "mapSize": 0, "noteCount": 0 },
|
|
"expert": { "mapFile": "maps/Map_uuid_expert.json", "mapSize": 0, "noteCount": 0 },
|
|
"expertplus": { "mapFile": "maps/Map_uuid_expertplus.json", "mapSize": 0, "noteCount": 0 }
|
|
},
|
|
"addedAt": "2026-05-21T00:00:00Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 맵 JSON 형식 (Map_{id}_{diff}.json)
|
|
```json
|
|
{
|
|
"target": [
|
|
{ "time": 1.23, "position": 1, "lineLayer": 1, "colorType": 0, "cutDirection": 1 }
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Beat Sage API
|
|
|
|
- **Base URL**: `https://beatsage.com`
|
|
- **흐름**: `POST /create` → `GET /heartbeat/{id}` 폴링 (status: "DONE") → `GET /download/{id}` (.zip)
|
|
- **지원 난이도**: Normal, Hard, Expert, ExpertPlus
|
|
- **zip 내 파일명**: `Normal.dat`, `Hard.dat`, `Expert.dat`, `ExpertPlus.dat`, `info.dat`
|
|
- **인증 불필요** (퍼블릭 API)
|
|
- **입력 방식 2가지**: `audio_file`(로컬 파일 업로드) 또는 `audio_url`(직접 URL 전달, Beat Sage 서버에서 다운로드)
|
|
- **info.dat 활용**: `_beatsPerMinute`(자동 감지), `_songName`, `_songAuthorName` 추출 → `BeatSageUploader.LastMetadata`에 저장. SongCreatorManager에서 이 값을 우선 사용하고 UI 입력이 있으면 override.
|
|
|
|
---
|
|
|
|
## ScoreManager 명세
|
|
|
|
```csharp
|
|
public class ScoreManager : MonoBehaviour
|
|
{
|
|
public static ScoreManager Instance;
|
|
public int Score;
|
|
public int Combo;
|
|
public int MaxCombo;
|
|
public int Multiplier; // 1/2/4/8 — 4콤보마다 증가
|
|
public int HP; // 기본 100, 미스 시 -10, 0이면 게임오버
|
|
public const int MaxHP = 100;
|
|
public float HitRate; // notesHit / noteCount (0~1)
|
|
|
|
public event Action<int, int, int> OnScoreChanged; // score, combo, multiplier
|
|
public event Action<int> OnHPChanged;
|
|
public event Action OnGameOver;
|
|
|
|
public void SetNoteCount(int count);
|
|
public void RegisterHit();
|
|
public void RegisterMiss();
|
|
}
|
|
```
|
|
|
|
### 랭크 기준 (ResultsPanel)
|
|
| 랭크 | HitRate |
|
|
|---|---|
|
|
| S | 95% 이상 |
|
|
| A | 80% 이상 |
|
|
| B | 65% 이상 |
|
|
| C | 50% 이상 |
|
|
| D | 50% 미만 |
|
|
|
|
---
|
|
|
|
## VRBeatsKit 주요 클래스 요약
|
|
|
|
| 클래스 | 역할 |
|
|
|---|---|
|
|
| `VR_BeatManager` | 싱글턴 — 큐브 스폰, 색상 설정, GameOver |
|
|
| `VR_BeatCube` | 큐브 이동 + 히트/미스 판정 |
|
|
| `VR_BeatCubeSpawneable` | 큐브 스폰 설정 (화살표/점, ColorSide) |
|
|
| `VR_Saber` | 세이버 슬라이싱 (EzySlice 기반) |
|
|
| `SpawnEventInfo` | 스폰 파라미터 (hitDirection, colorSide, position, speed) |
|
|
| `AudioManager` | AudioSource + AudioMixer 래퍼 |
|
|
| `VR_BeatSettings` | ScriptableObject — 색상, 속도, 멀티플라이어 한도 등 |
|
|
|
|
### SpawnEventInfo 구조
|
|
```csharp
|
|
public class SpawnEventInfo {
|
|
public Direction hitDirection; // UpperLeft=0,Up=1,UpperRight=2,Left=3,Center=4,Right=5,LowerLeft=6,Down=7,LowerRight=8
|
|
public ColorSide colorSide; // Left, Right
|
|
public bool useSpark;
|
|
public Vector3 position; // -0.5~0.5 정규화 (PlayZone 기준)
|
|
public Vector3 rotation;
|
|
public float speed;
|
|
public int speedMultiplier;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 로컬 캐시 경로
|
|
|
|
```
|
|
Application.temporaryCachePath/beatsaber/{songId}/
|
|
├── {songId}.mp3
|
|
├── Map_{songId}_normal.json
|
|
├── Map_{songId}_hard.json
|
|
├── Map_{songId}_expert.json
|
|
└── Map_{songId}_expertplus.json
|
|
```
|
|
|
|
---
|
|
|
|
## 알려진 주의사항
|
|
|
|
1. **Game 씬 직접 Play 주의**: `GameSession.SelectedSong == null`이면 `SongController`가 오류를 로그로 남기고 진행하지 않는다. 곡 플레이는 `Menu.unity`의 SongSelect에서 선택/다운로드 후 진입해야 한다.
|
|
2. **NAS 업로드**: 수동 multipart body (UploadHandlerRaw) 사용. Unity 기본 multipart는 DSM에서 401 오류.
|
|
3. **AudioType.MPEG**: MP3 로딩 시 `UnityWebRequestMultimedia.GetAudioClip(uri, AudioType.MPEG)` 사용.
|
|
4. **Unity `??` 연산자**: Unity Object에 `??` 쓰면 fake-null을 못 잡음. 반드시 `if (x == null)` 또는 `TryGetComponent` 사용.
|
|
5. **Build Settings 현재 상태**: 현재 등록 순서는 `Menu`, `BoxingStyle`, `SongCreator`, `SaberStyle`, `Game`이다. 예전 목표 설계의 `Intro`, `SongSelect`, `MapEditorScene`은 현재 Build Settings에 없다.
|
|
6. **경고 0 상태 유지**: 패키지 내부까지 경고를 제거해 둔 상태라, 새 SDK/API를 추가할 때 `dotnet build VRBeatSaber.slnx --no-incremental`로 경고 재발 여부를 확인한다.
|
|
7. **VR 실기 테스트 필수 항목**: 게임오버 Back/Retry 클릭, SongCreator UI 클릭, 큐브 가로 간격, 큐브 도착 싱크, 세이버 각도는 Quest에서 직접 확인해야 한다.
|