2026-05-21 23:37:34 +09:00
# VR Beat Saber 프로젝트 인수인계 문서
## 개요
Meta Quest용 VR Beat Saber 클론. Beat Sage API로 노래를 자동 채보하고, Synology NAS에 업로드/다운로드한다.
이 문서는 **기존 프로젝트(구버전)를 VRBeatsKit 기반 새 프로젝트로 이전**하기 위한 인수인계 자료다.
---
2026-05-28 19:01:20 +09:00
## 현재 상태 (2026-05-28)
2026-05-26 17:18:02 +09:00
2026-05-28 19:01:20 +09:00
현재 저장소는 VRBeatsKit 기반 프로젝트 위에 커스텀 곡 선택/생성/NAS 연동, 360도 영상 배경, 점수/콤보 개편, 싱크 보정 화면, VR UI 포인터 보정, 세이버/큐브 트레일 효과를 붙인 상태다.
2026-05-26 17:18:02 +09:00
- Unity 버전: `6000.3.12f1`
2026-05-28 19:01:20 +09:00
- 현재 브랜치: `master`
2026-05-26 17:18:02 +09:00
- 원격 저장소: `origin` = `https://whdwo798.synology.me/whdwo798/BeatSaber.git`
2026-05-26 18:54:56 +09:00
- `dotnet build VRBeatSaber.slnx --no-incremental` 결과: 오류 0개, 경고 0개
2026-05-28 19:01:20 +09:00
- NAS 비밀번호/세션 파일은 로컬 전용이다. `env` , `cookies.txt` , `Assets/StreamingAssets/nas_config.json` , Unity `_Recovery` 는 커밋하지 않는다.
- Unity Codex Bridge는 에디터 내부 HTTP 브리지와 MCP 서버를 통해 씬/로그/캡처를 조회하는 용도다. 포트 충돌 시 Unity 재시작 또는 기존 포트 점유 프로세스 정리가 필요하다.
2026-05-26 17:18:02 +09:00
### 실제 씬 구성
현재 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 19:01:20 +09:00
### 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 이전 반영된 변경
2026-05-26 17:18:02 +09:00
2026-05-26 18:54:56 +09:00
- `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개.
2026-05-26 17:18:02 +09:00
- `Assets/Script/VRPointerController.cs` , `VRPointerSetup.cs` 추가
- VR 컨트롤러 레이로 Unity UI 버튼을 직접 hover/click 처리한다.
- `Game` 씬에서는 게임오버 전까지 비활성화하고, 메뉴 계열 씬에서는 활성화한다.
2026-05-26 18:21:58 +09:00
- `Assets/Script/VRPointerSetup.cs`
- `DontDestroyOnLoad` 싱글턴으로 변경되어 `Menu -> SongCreator -> Game` 같은 씬 전환 후에도 포인터를 다시 주입한다.
- `SceneManager.sceneLoaded` 마다 현재 씬 컨트롤러를 검사한다.
2026-05-26 17:18:02 +09:00
- `Assets/VRBeatsKit/Scripts/Core/VR_InteractorController.cs`
- XR Ray Interactor enable/disable 시 `VRPointerController` 도 함께 제어한다.
2026-05-26 18:21:58 +09:00
- 컨트롤러 구조 차이를 고려해 현재 오브젝트, 부모, 자식, 루트 하위에서 `VRPointerController` 를 찾는다.
- `Assets/VRBeatsKit/Scripts/Core/AudioManager.cs`
- `AudioSource.Play()` 대신 `PlayScheduled()` 를 사용하고, `AudioSettings.dspTime` 기준으로 `CurrentTime` 을 계산한다.
- MP3 재생 시작 시점과 노트 스폰 기준 시간이 프레임 상태에 따라 흔들리는 문제를 줄이기 위한 변경이다.
2026-05-26 17:18:02 +09:00
- `Assets/VRBeatsKit/Scripts/Core/VR_BeatCube.cs`
- `IsCutIntentValid()` 를 public으로 변경하고 `maxCutAngle` 을 추가했다.
- `Assets/VRBeatsKit/Scripts/Core/Cuttable.cs`
- 색상/방향/속도가 틀린 큐브는 절단 시각 효과도 발생하지 않도록 막았다.
- `Assets/Scenes/Game.unity`
- `SongController` 가 큐브 프리팹, `OnLevelComplete` , 카운트다운 텍스트와 연결되어 있다.
2026-05-26 18:21:58 +09:00
- `GameOverPopup` 의 Back 버튼에 깨진 스크립트 참조가 있어 `LoadSceneButton` 으로 복구했다.
- 현재 사용 중인 좌/우 세이버 루트 회전을 `X 45도` 로 보정해 컨트롤러에서 너무 수직으로 서는 문제를 줄였다.
2026-05-26 17:18:02 +09:00
- `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` 추가.
### 현재 주의사항
2026-05-28 19:03:34 +09:00
1. `Assets/StreamingAssets/nas_config.json` 과 루트 `env` 는 로컬 전용이다. NAS 업로드 테스트 전 `Assets/StreamingAssets/nas_config.example.json` 을 `nas_config.json` 으로 복사한 뒤 계정/비밀번호를 직접 넣되 절대 커밋하지 않는다.
2026-05-28 19:01:20 +09:00
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 착용 테스트 후 수치를 조정하는 것이 좋다.
2026-05-26 17:18:02 +09:00
---
2026-05-21 23:37:34 +09:00
## 기존 프로젝트 소스 코드
**기존 프로젝트 전체 파일은 아래 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` | 곡 상세 / 다운로드 / 플레이 버튼 |
2026-05-26 17:18:02 +09:00
| `SongCreatorManager.cs` | 크리에이터 UI, 파일 선택, URL 다운로드. title/BPM 수동 입력 불필요 — info.dat에서 자동 추출. 난이도는 현재 항상 4개 전부 생성. |
| `SongController.cs` | Game 씬 실행부. 캐시된 mp3/map json을 로드하고 VRBeatsKit `VR_BeatManager.Spawn()` 으로 노트를 스폰. |
2026-05-21 23:37:34 +09:00
| `DesktopUIMode.cs` | 에디터에서 XR 없이 테스트할 때 UI Raycaster 교체 |
2026-05-26 17:18:02 +09:00
| `VRPointerController.cs` | VR 컨트롤러 레이로 UI hover/click 처리. 디버그 로그 포함. |
| `VRPointerSetup.cs` | 씬 로드 후 손/컨트롤러 오브젝트에 `VRPointerController` 자동 주입. |
| `XRSimulatorLoader.cs` | 에디터/PC 테스트용 XR Interaction Simulator 프리팹 주입. |
2026-05-21 23:37:34 +09:00
2026-05-26 17:18:02 +09:00
### 현재 미이식/미확인 스크립트
2026-05-21 23:37:34 +09:00
| 파일 | 내용 |
|---|---|
2026-05-26 17:18:02 +09:00
| `IntroManager.cs` | 현재 저장소에 없음. 인트로 씬 흐름을 살릴 경우 작성/이식 필요. |
| `ScoreManager.cs` , `ScoreHUD.cs` , `ResultsPanel.cs` | 전역 네임스페이스 커스텀 점수 UI는 현재 저장소에 없음. 현재는 VRBeatsKit `VRBeats.ScoreManager` 와 이벤트 자산을 사용. |
| `SaberGlow.cs` , `SaberSkinSelector.cs` , `CacheManager.cs` | 현재 저장소에 없음. 필요 시 기존 프로젝트에서 이식. |
2026-05-21 23:37:34 +09:00
---
2026-05-26 17:18:02 +09:00
## Game 실행부 현재 구현
2026-05-21 23:37:34 +09:00
2026-05-26 17:18:02 +09:00
기존 인수인계 문서에는 `Spawner.cs` 를 새로 작성하라고 되어 있었지만, 현재 저장소에서는 별도 `Spawner.cs` 대신 `Assets/Script/SongController.cs` 가 그 역할을 수행한다.
2026-05-21 23:37:34 +09:00
2026-05-26 17:18:02 +09:00
### `SongController.cs` 핵심
2026-05-21 23:37:34 +09:00
``` csharp
2026-05-26 17:18:02 +09:00
private IEnumerator LoadAndPlay ( )
2026-05-21 23:37:34 +09:00
{
2026-05-26 17:18:02 +09:00
SongInfo song = GameSession . SelectedSong ;
string diff = GameSession . SelectedDifficulty ;
// mp3와 map json을 Application.temporaryCachePath/beatsaber/{songId}/ 에서 로드
// 카운트다운 후 VRBeats.AudioManager.PlayClip(clip)
// SpawnRoutine(map.target) 실행
2026-05-21 23:37:34 +09:00
}
2026-05-26 17:18:02 +09:00
private void SpawnNote ( NoteData note )
2026-05-21 23:37:34 +09:00
{
2026-05-26 18:54:56 +09:00
float x = MapLaneX ( note . position ) ;
float y = MapLayerY ( note . lineLayer ) ;
2026-05-26 17:18:02 +09:00
var info = new SpawnEventInfo
2026-05-21 23:37:34 +09:00
{
2026-05-26 17:18:02 +09:00
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 ,
2026-05-21 23:37:34 +09:00
} ;
2026-05-26 17:18:02 +09:00
VR_BeatManager . instance . Spawn ( cubePrefab , info ) ;
2026-05-21 23:37:34 +09:00
}
```
2026-05-26 17:18:02 +09:00
`travelTimeOverride` 는 동시 노트가 프레임 차이로 스폰되어도 같은 타이밍에 도착하도록 `VR_BeatManager` 에 추가된 값이다.
2026-05-21 23:37:34 +09:00
2026-05-26 18:54:56 +09:00
현재 라인 매핑은 `LaneSpacing = 0.42f` , `LayerSpacing = 0.38f` 를 사용한다. 이는 VRBeatsKit 큐브 콜라이더의 실제 폭이 기존 라인 간격보다 커서 인접 라인이 겹치던 문제를 피하기 위한 값이다.
2026-05-21 23:37:34 +09:00
---
## ScoreManager 충돌 없음
2026-05-26 17:18:02 +09:00
- 예전 설계: 전역 네임스페이스 커스텀 `ScoreManager.cs`
- 현재 저장소: `Assets/VRBeatsKit/Scripts/UI/ScoreManager.cs` 의 `VRBeats.ScoreManager` 사용
- 전역 커스텀 `ScoreManager.cs` 를 다시 이식하면 네임스페이스가 달라 공존은 가능하지만, 이벤트 연결과 UI 패널을 별도로 구성해야 한다.
2026-05-21 23:37:34 +09:00
---
## 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
```
---
## 알려진 주의사항
2026-05-26 17:18:02 +09:00
1. **Game 씬 직접 Play 주의 ** : `GameSession.SelectedSong == null` 이면 `SongController` 가 오류를 로그로 남기고 진행하지 않는다. 곡 플레이는 `Menu.unity` 의 SongSelect에서 선택/다운로드 후 진입해야 한다.
2026-05-21 23:37:34 +09:00
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` 사용.
2026-05-26 17:18:02 +09:00
5. **Build Settings 현재 상태 ** : 현재 등록 순서는 `Menu` , `BoxingStyle` , `SongCreator` , `SaberStyle` , `Game` 이다. 예전 목표 설계의 `Intro` , `SongSelect` , `MapEditorScene` 은 현재 Build Settings에 없다.
2026-05-26 18:54:56 +09:00
6. **경고 0 상태 유지 ** : 패키지 내부까지 경고를 제거해 둔 상태라, 새 SDK/API를 추가할 때 `dotnet build VRBeatSaber.slnx --no-incremental` 로 경고 재발 여부를 확인한다.
7. **VR 실기 테스트 필수 항목 ** : 게임오버 Back/Retry 클릭, SongCreator UI 클릭, 큐브 가로 간격, 큐브 도착 싱크, 세이버 각도는 Quest에서 직접 확인해야 한다.