docs: update project handoff status
This commit is contained in:
+116
-76
@@ -7,6 +7,83 @@ Meta Quest용 VR Beat Saber 클론. Beat Sage API로 노래를 자동 채보하
|
||||
|
||||
---
|
||||
|
||||
## 현재 상태 (2026-05-26)
|
||||
|
||||
현재 저장소는 VRBeatsKit 기반 프로젝트 위에 커스텀 곡 선택/생성/NAS 연동 흐름을 붙인 상태다.
|
||||
|
||||
- Unity 버전: `6000.3.12f1`
|
||||
- 현재 브랜치: `main`
|
||||
- 원격 저장소: `origin` = `https://whdwo798.synology.me/whdwo798/BeatSaber.git`
|
||||
- 최근 푸시 커밋: `10e9eba feat: improve VR menu pointer and BeatSaber flow`
|
||||
- `dotnet build VRBeatSaber.slnx` 결과: 오류 0개, 경고 60개
|
||||
|
||||
### 실제 씬 구성
|
||||
|
||||
현재 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에 업로드
|
||||
```
|
||||
|
||||
### 최근 반영된 변경
|
||||
|
||||
- `Assets/Script/VRPointerController.cs`, `VRPointerSetup.cs` 추가
|
||||
- VR 컨트롤러 레이로 Unity UI 버튼을 직접 hover/click 처리한다.
|
||||
- `Game` 씬에서는 게임오버 전까지 비활성화하고, 메뉴 계열 씬에서는 활성화한다.
|
||||
- `Assets/VRBeatsKit/Scripts/Core/VR_InteractorController.cs`
|
||||
- XR Ray Interactor enable/disable 시 `VRPointerController`도 함께 제어한다.
|
||||
- `Assets/VRBeatsKit/Scripts/Core/VR_BeatCube.cs`
|
||||
- `IsCutIntentValid()`를 public으로 변경하고 `maxCutAngle`을 추가했다.
|
||||
- `Assets/VRBeatsKit/Scripts/Core/Cuttable.cs`
|
||||
- 색상/방향/속도가 틀린 큐브는 절단 시각 효과도 발생하지 않도록 막았다.
|
||||
- `Assets/Scenes/Game.unity`
|
||||
- `SongController`가 큐브 프리팹, `OnLevelComplete`, 카운트다운 텍스트와 연결되어 있다.
|
||||
- `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`은 현재 저장소에 없다. NAS 업로드를 테스트하려면 로컬에 직접 만들어야 하며, 절대 커밋하지 않는다.
|
||||
2. `SongCreator.unity`의 직렬화된 `nasBaseUrl` 값에 끝 공백이 들어가 있다: `http://whdwo798.synology.me:5000 `. 런타임에서 `nas_config.json`으로 덮어쓰지 않으면 로그인 URL 문제가 날 수 있다.
|
||||
3. `SongCreatorManager`는 난이도 토글 필드를 갖고 있지만 현재 로직은 4개 난이도(`normal`, `hard`, `expert`, `expertplus`)를 항상 전부 생성한다.
|
||||
4. `manualEditorButton`은 씬에서 미연결이고 코드에서도 사용하지 않는다.
|
||||
5. `Assets/img/360.mp4`는 약 192MB다. 현재 일반 Git으로 푸시되어 있으므로, 원격 정책이 바뀌면 Git LFS 전환을 검토해야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 기존 프로젝트 소스 코드
|
||||
|
||||
**기존 프로젝트 전체 파일은 아래 git 저장소에서 가져온다.**
|
||||
@@ -168,101 +245,64 @@ SongSelect → SongCreator → (NAS 업로드) → SongSelect
|
||||
| `SongLibrary.cs` | 다운로드 상태 추적 (persistentDataPath) |
|
||||
| `SongSelectManager.cs` | 곡 목록 UI |
|
||||
| `SongDetailPanel.cs` | 곡 상세 / 다운로드 / 플레이 버튼 |
|
||||
| `SongCreatorManager.cs` | 크리에이터 UI, 파일 선택, URL 다운로드. title/BPM 수동 입력 불필요 — info.dat에서 자동 추출. 난이도는 항상 4개 전부 생성. |
|
||||
| `IntroManager.cs` | 인트로 → SongSelect 자동 전환 |
|
||||
| `SongCreatorManager.cs` | 크리에이터 UI, 파일 선택, URL 다운로드. title/BPM 수동 입력 불필요 — info.dat에서 자동 추출. 난이도는 현재 항상 4개 전부 생성. |
|
||||
| `SongController.cs` | Game 씬 실행부. 캐시된 mp3/map json을 로드하고 VRBeatsKit `VR_BeatManager.Spawn()`으로 노트를 스폰. |
|
||||
| `DesktopUIMode.cs` | 에디터에서 XR 없이 테스트할 때 UI Raycaster 교체 |
|
||||
| `ScoreManager.cs` | 싱글턴 — Score/Combo/MaxCombo/Multiplier/HP |
|
||||
| `ScoreHUD.cs` | 점수/콤보 TMP + HP 슬라이더 |
|
||||
| `ResultsPanel.cs` | 결과 화면 (CLEAR/FAILED, 랭크 S~D) |
|
||||
| `SaberGlow.cs` | 검 끝 포인트 라이트 제어 |
|
||||
| `SaberSkinSelector.cs` | 검 외형 선택 (PlayerPrefs 저장) |
|
||||
| `CacheManager.cs` | 캐시 정리 유틸 |
|
||||
| `VRPointerController.cs` | VR 컨트롤러 레이로 UI hover/click 처리. 디버그 로그 포함. |
|
||||
| `VRPointerSetup.cs` | 씬 로드 후 손/컨트롤러 오브젝트에 `VRPointerController` 자동 주입. |
|
||||
| `XRSimulatorLoader.cs` | 에디터/PC 테스트용 XR Interaction Simulator 프리팹 주입. |
|
||||
|
||||
### 새로 작성이 필요한 스크립트
|
||||
### 현재 미이식/미확인 스크립트
|
||||
|
||||
| 파일 | 내용 |
|
||||
|---|---|
|
||||
| `Spawner.cs` | **VRBeatsKit 통합 버전** — 아래 상세 참고 |
|
||||
| `IntroManager.cs` | 현재 저장소에 없음. 인트로 씬 흐름을 살릴 경우 작성/이식 필요. |
|
||||
| `ScoreManager.cs`, `ScoreHUD.cs`, `ResultsPanel.cs` | 전역 네임스페이스 커스텀 점수 UI는 현재 저장소에 없음. 현재는 VRBeatsKit `VRBeats.ScoreManager`와 이벤트 자산을 사용. |
|
||||
| `SaberGlow.cs`, `SaberSkinSelector.cs`, `CacheManager.cs` | 현재 저장소에 없음. 필요 시 기존 프로젝트에서 이식. |
|
||||
|
||||
---
|
||||
|
||||
## Spawner.cs 새 버전 작성 방법 (핵심)
|
||||
## Game 실행부 현재 구현
|
||||
|
||||
기존 Spawner는 `cubePrefabs[]`(GameObject)를 Instantiate했다.
|
||||
새 버전은 `VR_BeatManager.instance.Spawn(Spawneable, SpawnEventInfo)`를 호출한다.
|
||||
기존 인수인계 문서에는 `Spawner.cs`를 새로 작성하라고 되어 있었지만, 현재 저장소에서는 별도 `Spawner.cs` 대신 `Assets/Script/SongController.cs`가 그 역할을 수행한다.
|
||||
|
||||
### 변경 포인트
|
||||
### `SongController.cs` 핵심
|
||||
|
||||
**1. AudioSource 획득 방식 변경**
|
||||
```csharp
|
||||
// 기존: [SerializeField] AudioSource audioSource;
|
||||
// 신규: VRBeatsKit AudioManager에서 가져옴
|
||||
void Awake()
|
||||
private IEnumerator LoadAndPlay()
|
||||
{
|
||||
var am = FindObjectOfType<VRBeats.AudioManager>();
|
||||
if (am != null) audioSource = am.GetComponent<AudioSource>();
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
```
|
||||
|
||||
**2. 큐브 프리팹 타입 변경**
|
||||
```csharp
|
||||
// 기존: public GameObject[] cubePrefabs; (RED=0, BLUE=1)
|
||||
// 신규:
|
||||
public VRBeats.Spawneable redCubePrefab; // colorType == 0
|
||||
public VRBeats.Spawneable blueCubePrefab; // colorType == 1
|
||||
```
|
||||
|
||||
**3. SpawnNote() 전면 교체**
|
||||
```csharp
|
||||
// Beat Saber cutDirection(0~8) → VRBeats Direction 매핑
|
||||
private static readonly VRBeats.Direction[] DirMap =
|
||||
{
|
||||
VRBeats.Direction.Up, // 0
|
||||
VRBeats.Direction.Down, // 1
|
||||
VRBeats.Direction.Left, // 2
|
||||
VRBeats.Direction.Right, // 3
|
||||
VRBeats.Direction.UpperLeft, // 4
|
||||
VRBeats.Direction.UpperRight, // 5
|
||||
VRBeats.Direction.LowerLeft, // 6
|
||||
VRBeats.Direction.LowerRight, // 7
|
||||
VRBeats.Direction.Center // 8 (dot)
|
||||
};
|
||||
|
||||
private void SpawnNote(NoteData data)
|
||||
{
|
||||
if (VRBeats.VR_BeatManager.instance == null) return;
|
||||
|
||||
VRBeats.Spawneable prefab = data.colorType == 0 ? redCubePrefab : blueCubePrefab;
|
||||
if (prefab == null) return;
|
||||
|
||||
var info = new VRBeats.SpawnEventInfo
|
||||
{
|
||||
colorSide = data.colorType == 0 ? VRBeats.ColorSide.Right : VRBeats.ColorSide.Left,
|
||||
hitDirection = DirMap[Mathf.Clamp(data.cutDirection, 0, 8)],
|
||||
// position: 0~3열 → -0.5~0.5, 0~2행 → -0.5~0.5
|
||||
position = new Vector3(
|
||||
(data.position / 3.0f) - 0.5f,
|
||||
(data.lineLayer / 2.0f) - 0.5f,
|
||||
0f),
|
||||
speed = noteSpeed,
|
||||
useSpark = true
|
||||
};
|
||||
|
||||
VRBeats.VR_BeatManager.instance.Spawn(prefab, info);
|
||||
}
|
||||
```
|
||||
|
||||
**4. spawnPoints[] 제거** — VR_BeatManager가 PlayZone 기반으로 위치 계산하므로 불필요.
|
||||
|
||||
**5. 나머지 로직은 기존과 동일** — InitGame(), CountDown(), LoadAudioClip(), ShowResults() 등
|
||||
`travelTimeOverride`는 동시 노트가 프레임 차이로 스폰되어도 같은 타이밍에 도착하도록 `VR_BeatManager`에 추가된 값이다.
|
||||
|
||||
---
|
||||
|
||||
## ScoreManager 충돌 없음
|
||||
|
||||
- 우리 `ScoreManager.cs` → 전역 네임스페이스
|
||||
- VRBeatsKit `Scripts/UI/ScoreManager.cs` → `namespace VRBeats`
|
||||
- 두 파일이 공존 가능. 우리 ScoreManager를 그대로 사용한다.
|
||||
- 예전 설계: 전역 네임스페이스 커스텀 `ScoreManager.cs`
|
||||
- 현재 저장소: `Assets/VRBeatsKit/Scripts/UI/ScoreManager.cs`의 `VRBeats.ScoreManager` 사용
|
||||
- 전역 커스텀 `ScoreManager.cs`를 다시 이식하면 네임스페이스가 달라 공존은 가능하지만, 이벤트 연결과 UI 패널을 별도로 구성해야 한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -427,8 +467,8 @@ Application.temporaryCachePath/beatsaber/{songId}/
|
||||
|
||||
## 알려진 주의사항
|
||||
|
||||
1. **Game 씬 직접 Play 금지**: `GameSession.SelectedSong == null` → SongSelect로 튕김. 반드시 SongSelect 씬부터 시작.
|
||||
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 등록 필수**: Intro(0), SongSelect(1), Game(2), SongCreator(3), MapEditorScene(4)
|
||||
5. **Build Settings 현재 상태**: 현재 등록 순서는 `Menu`, `BoxingStyle`, `SongCreator`, `SaberStyle`, `Game`이다. 예전 목표 설계의 `Intro`, `SongSelect`, `MapEditorScene`은 현재 Build Settings에 없다.
|
||||
|
||||
Reference in New Issue
Block a user