비트 찍기 완료 및 클로드를 통한 api작업

This commit is contained in:
jongjae0305
2026-05-20 16:44:28 +09:00
commit 2cd1be88d4
1596 changed files with 444234 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
# Unity 씬 구성 가이드
> VRBeatSaber — 스크립트 연결 및 씬 세팅
---
## 0. 시작 전 — Build Settings
**File → Build Settings → Add Open Scenes**
순서대로 추가:
```
0. Scenes/Intro
1. Scenes/SongSelect ← 신규 생성
2. Scenes/SongCreator ← 신규 생성
3. Scenes/Game
4. Scenes/MapEditorScene
```
---
## 1. SongCard 프리팹 생성 (먼저 만들어야 함)
**Project 창 → Assets/Prefab → 우클릭 → Create Empty**
이름: `SongCard`
계층 구조:
```
SongCard (Button 컴포넌트 추가)
├── Background (Image)
├── TitleText (TextMeshPro - Text(UI))
├── ArtistText (TextMeshPro - Text(UI))
├── DurationText (TextMeshPro - Text(UI))
└── DownloadedBadge (Image — 초록 원 또는 체크 아이콘)
```
SongCard 루트에 **SongCard.cs** 컴포넌트 추가 후 Inspector 연결:
| 필드 | 연결 대상 |
|------|-----------|
| Title Text | TitleText |
| Artist Text | ArtistText |
| Duration Text | DurationText |
| Downloaded Badge | DownloadedBadge |
| Button | 루트의 Button 컴포넌트 |
완료 후 **Assets/Prefab/SongCard.prefab** 으로 저장
---
## 2. Intro 씬
기존 씬 재활용. 기존 UI 제거 후 아래 구성.
```
[Hierarchy]
├── Main Camera
├── XR Origin (기존 유지)
└── Canvas (World Space)
└── IntroPanel
├── TitleText "VR BEAT SABER"
├── PlayButton "게임하기"
└── CreateButton "노래만들기"
```
**Canvas 설정:**
- Render Mode: `World Space`
- Position: (0, 1.5, 2)
- Width/Height: 600 / 400
**빈 오브젝트 [IntroManager] 생성 → IntroManager.cs 추가:**
| 필드 | 값 |
|------|-----|
| Play Button | PlayButton |
| Create Button | CreateButton |
| Song Select Scene | `SongSelect` |
| Song Creator Scene | `SongCreator` |
---
## 3. SongSelect 씬 (신규)
**File → New Scene → 저장: Assets/Scenes/SongSelect**
### 계층 구조
```
[Hierarchy]
├── Main Camera
├── XR Origin
├── [GameManager] ← 빈 오브젝트
└── Canvas (World Space)
├── TabPanel
│ ├── TabAllButton "전체"
│ └── TabOwnedButton "보유중"
├── ListPanel
│ └── ScrollRect
│ └── Viewport
│ └── CardContainer ← Content 오브젝트
├── DetailPanel ← SongDetailPanel이 붙는 오브젝트
│ ├── TitleText
│ ├── ArtistText
│ ├── InfoText (BPM / 길이)
│ ├── DifficultyPanel
│ │ ├── EasyButton
│ │ ├── NormalButton
│ │ ├── HardButton
│ │ └── ExpertButton
│ ├── ActionPanel
│ │ ├── DownloadButton "다운로드"
│ │ ├── DeleteButton "삭제"
│ │ └── PlayButton "▶ 플레이"
│ └── ProgressGroup
│ ├── ProgressSlider
│ └── ProgressText
├── LoadingOverlay ← 전체 덮는 반투명 패널
│ └── LoadingText "불러오는 중..."
└── ErrorOverlay
└── ErrorText
```
### Canvas 설정
- Render Mode: `World Space`
- Position: (0, 1.5, 2)
- Width: 1200 / Height: 700
### [GameManager] 오브젝트에 컴포넌트 추가
1. **SongLibrary.cs**
2. **CacheManager.cs**
3. **DownloadManager.cs**
DownloadManager Inspector:
| 필드 | 값 |
|------|-----|
| Base Url | `http://whdwo798.synology.me:8180/beatsaber` |
### DetailPanel에 SongDetailPanel.cs 추가
| 필드 | 연결 대상 |
|------|-----------|
| Title Text | TitleText |
| Artist Text | ArtistText |
| Info Text | InfoText |
| Btn Easy | EasyButton |
| Btn Normal | NormalButton |
| Btn Hard | HardButton |
| Btn Expert | ExpertButton |
| Download Button | DownloadButton |
| Delete Button | DeleteButton |
| Play Button | PlayButton |
| Progress Group | ProgressGroup |
| Progress Slider | ProgressSlider |
| Progress Text | ProgressText |
| Game Scene Name | `Game` |
### Canvas 루트에 SongSelectManager.cs 추가
| 필드 | 연결 대상 |
|------|-----------|
| Tab All Btn | TabAllButton |
| Tab Owned Btn | TabOwnedButton |
| Card Container | CardContainer |
| Song Card Prefab | Assets/Prefab/SongCard |
| Detail Panel | DetailPanel |
| Download Manager | [GameManager]의 DownloadManager |
| Loading Overlay | LoadingOverlay |
| Error Overlay | ErrorOverlay |
| Error Text | ErrorText |
### ScrollRect 설정 (ListPanel)
- Scroll Rect → Content: CardContainer
- CardContainer에 **Vertical Layout Group** 추가
- Spacing: 10
- Child Force Expand Width: ✓
---
## 4. SongCreator 씬 (신규)
**File → New Scene → 저장: Assets/Scenes/SongCreator**
### 계층 구조
```
[Hierarchy]
├── Main Camera
├── XR Origin
├── [CreatorManager] ← 빈 오브젝트
└── Canvas (World Space)
├── TitleText "노래 만들기"
├── AudioPanel
│ ├── AudioDropdown (TMP_Dropdown)
│ ├── RefreshButton "새로고침"
│ └── InputPathHint (TMP_Text - 경로 안내)
├── MetaPanel
│ ├── TitleInput (TMP_InputField) Placeholder: "곡 제목"
│ ├── ArtistInput (TMP_InputField) Placeholder: "아티스트"
│ └── BpmInput (TMP_InputField) Placeholder: "BPM" / Content Type: Decimal Number
├── DifficultyPanel
│ ├── EasyToggle "Easy"
│ ├── NormalToggle "Normal"
│ ├── HardToggle "Hard" (기본값 ON)
│ └── ExpertToggle "Expert" (기본값 ON)
├── ActionPanel
│ ├── GenerateButton "AI 생성 시작"
│ └── ManualButton "직접 만들기 →" ← 작게
└── ProgressGroup
├── StatusText
└── ProgressSlider
```
### [CreatorManager] 오브젝트에 컴포넌트 추가
1. **BeatSageUploader.cs**
2. **NasPublisher.cs**
3. **SongCreatorManager.cs**
NasPublisher Inspector:
| 필드 | 값 |
|------|-----|
| Nas Base Url | `http://192.168.55.3:5000` |
| Nas Account | `admin` |
| Nas Password | _(DSM 비밀번호 입력)_ |
| Nas Root Path | `/volume1/web/beatsaber` |
| Static Base Url | `http://whdwo798.synology.me:8180/beatsaber` |
SongCreatorManager Inspector:
| 필드 | 연결 대상 |
|------|-----------|
| Audio Dropdown | AudioDropdown |
| Refresh Btn | RefreshButton |
| Input Path Hint | InputPathHint |
| Title Input | TitleInput |
| Artist Input | ArtistInput |
| Bpm Input | BpmInput |
| Toggle Easy | EasyToggle |
| Toggle Normal | NormalToggle |
| Toggle Hard | HardToggle |
| Toggle Expert | ExpertToggle |
| Generate Button | GenerateButton |
| Manual Editor Button | ManualButton |
| Progress Group | ProgressGroup |
| Status Text | StatusText |
| Progress Slider | ProgressSlider |
| Beat Sage Uploader | [CreatorManager]의 BeatSageUploader |
| Nas Publisher | [CreatorManager]의 NasPublisher |
| Map Editor Scene | `MapEditorScene` |
---
## 5. Game 씬 수정
기존 Spawner 오브젝트에서 `songName` 필드 제거됨.
아래만 확인하면 됨.
**Spawner 오브젝트 Inspector 확인:**
| 필드 | 연결 대상 |
|------|-----------|
| Audio Source | AudioSource 컴포넌트 |
| Cube Prefabs | [0] RED.prefab / [1] BLUE.prefab |
| Spawn Points | 기존 4개 SpawnPoint Transform |
| Note Speed | 2.0 |
| Distance To Hit | 10.0 |
| Song Select Scene Name | `SongSelect` |
> ⚠️ 기존에 AudioSource에 AudioClip이 연결되어 있으면 제거할 것.
> 이제 런타임에 로컬 파일에서 자동 로드됨.
---
## 6. 씬 이름 문자열 일치 확인
스크립트 내 씬 이름과 실제 파일명이 반드시 일치해야 함.
| 스크립트 | 필드 | 반드시 일치해야 할 씬 파일명 |
|----------|------|--------------------------|
| IntroManager | Song Select Scene | `SongSelect` |
| IntroManager | Song Creator Scene | `SongCreator` |
| SongDetailPanel | Game Scene Name | `Game` |
| SongCreatorManager | Map Editor Scene | `MapEditorScene` |
| Spawner | Song Select Scene Name | `SongSelect` |
---
## 7. Quest 음원 파일 넣는 방법 (SongCreator용)
Beat Sage에 보낼 MP3는 기기의 특정 폴더에 넣어야 함.
```bash
# ADB로 파일 복사
adb push life.mp3 /sdcard/Android/data/[패키지명]/files/input/
```
Unity Editor에서는 아래 경로에 넣으면 됨:
```
C:\Users\[유저명]\AppData\LocalLow\[Company]\[Product]\input\
```
---
## 8. 최종 동작 확인 순서
1. NAS 접속 확인: 브라우저에서 `http://whdwo798.synology.me:8180/beatsaber/songs.json` 열기
2. Unity Play → Intro 씬 실행
3. [게임하기] → SongSelect 씬 로드 → 곡 목록 표시 확인
4. 곡 다운로드 → 플레이 진입 확인
5. Intro → [노래만들기] → SongCreator 씬 → MP3 선택 → AI 생성 시도
+157
View File
@@ -0,0 +1,157 @@
# VRBeatSaber 기술 기획서
> 비트세이지 데이터 연동 온디맨드 스트리밍 및 에디터 통합 시스템
**작성일:** 2026년 5월 20일 | **문서 버전:** v1.0 | **프로젝트:** VRBeatSaber
---
## 1. 개요 (Introduction)
본 시스템은 오큘러스 퀘스트(독립형 VR) 하드웨어의 제한된 컴퓨팅 자원(CPU, RAM) 및 저장 공간 한계를 극복하기 위해 설계된 플랫폼 형태의 리듬 게임 인프라이다.
기기 내부에 대용량 음원을 상주시키는 대신, 필요 시 서버에서 동적으로 다운로드(On-Demand)하고, 외부의 무료 오토 매핑 서비스인 **비트세이지(Beat Sage)**의 연산 결과물(비트세이버 표준 포맷)을 흡수하여 구현한다.
이를 통해 자체적인 AI 모델 구축 비용(서버 유지비, GPU 연산 리소스)을 **0원으로 절감**하는 동시에 대량의 하이퀄리티 맵 데이터를 빠르게 수급할 수 있다. 개발자는 파이썬 머신러닝 레이어를 직접 다루지 않고, 오직 유니티 C# 환경 내에서 비트세이버 포맷을 자체 JSON 포맷으로 변환하는 데이터 컨버터 및 온디맨드 다운로드 시스템 구축에만 집중한다.
---
## 2. 전체 데이터 파이프라인 (Data Pipeline)
전체 워크플로우는 다음 4단계 구조를 가진다.
```
[외부 수급] → [서버 저장 및 관리] → [유니티 다운로드 및 포맷 변환] → [에디터 수정/인게임 플레이]
```
| 단계 | 구성요소 | 역할 |
|------|----------|------|
| 1 | **비트세이지 웹사이트** | 유저/개발자가 웹 인터페이스로 음원 입력 → 무료 AI 매핑 → 맵 데이터 패키지(`.zip`) 확보 |
| 2 | **자체 백엔드 서버 (Synology NAS)** | Web Station 정적 웹서버로 곡 메타데이터 리스트(`songs.json`), 음원 파일(`.mp3`), 비트세이지 원본 데이터 보관 |
| 3 | **유니티 클라이언트** | `UnityWebRequest`로 무선 네트워크 스트리밍, 오큘러스 퀘스트 임시 경로에 다운로드 후 내부 컨버터로 자체 포맷 변환 |
| 4 | **맵 에디터 통합** | 변환 완료된 데이터를 `MapEditor` 씬으로 로드, 기계 생성 초안(80%) 기반으로 개발자가 정교하게 디렉팅 및 수정 |
---
## 3. 데이터 구조 비교 및 변환 명세 (Data Mapping Spec)
### 3.1 비트세이지 결과물 포맷 (비트세이버 표준 v2 포맷)
비트세이지에서 생성된 압축파일 내 `Expert.dat` 등의 파일 구조:
```json
{
"_version": "2.0.0",
"_notes": [
{ "_time": 1.5, "_lineIndex": 0, "_lineLayer": 0, "_type": 0, "_cutDirection": 1 },
{ "_time": 2.3, "_lineIndex": 3, "_lineLayer": 0, "_type": 1, "_cutDirection": 0 }
]
}
```
### 3.2 현재 프로젝트 자체 포맷 (`Map_[곡이름].json`)
유니티 직렬화 및 기존 인게임 스폰 프레임워크가 요구하는 데이터 구조:
```json
{
"target": [
{ "time": 0.75, "position": 0, "colorType": 0 },
{ "time": 1.15, "position": 3, "colorType": 1 }
]
}
```
### 3.3 구조 변환 규칙 (Mapping Rules)
| 비트세이지 원본 필드 | 자체 프로젝트 매핑 필드 | 변환 연산 및 예외 처리 규칙 |
|---------------------|------------------------|--------------------------|
| `_time` (float) | `time` (float) | 비트 단위 → 초 단위 환산<br>**`Time(s) = (_time × 60) / BPM`** |
| `_lineIndex` (int) | `position` (int) | 가로 라인 0~3 범위 1:1 매칭 |
| `_type` (int) | `colorType` (int) | `0`: 빨간색 노트(왼손)<br>`1`: 파란색 노트(오른손)<br>폭탄(`value: 3`)은 `continue` 처리로 필터링 |
> ⚠️ **주의사항:** 비트세이지의 시간 축은 절대적인 초 단위가 아닌 BPM에 종속적인 비트 카운트이므로, 변환 연산 시 음원의 정확한 메타데이터 **BPM 값이 필수적으로 제공**되어야 동기화가 유지된다.
---
## 4. 유니티 클라이언트 코어 구현 (C#)
네트워크 비동기 수신처리와 수신 즉시 인메모리/로컬 캐시 직렬화 변환을 수행하기 위한 DTO 및 핵심 컨버터 C# 스크립트 설계 구조이다.
### 4.1 수신용 데이터 구조 클래스
```csharp
using System;
using System.Collections.Generic;
[Serializable]
public class BeatSageRoot
{
public string _version;
public List<BeatSageNote> _notes;
}
[Serializable]
public class BeatSageNote
{
public float _time; // 비트 단위 시간
public int _lineIndex; // 가로 위치 (0~3)
public int _lineLayer; // 세로 위치
public int _type; // 타입 (0: Red, 1: Blue)
public int _cutDirection; // 베는 방향
}
```
### 4.2 포맷 컨버터 코어 로직 (`BeatSageConverter.cs`)
```csharp
using UnityEngine;
using System.Collections.Generic;
public class BeatSageConverter : MonoBehaviour
{
/// <summary>
/// 비트세이지 .dat 텍스트 데이터를 우리 게임의 NoteData 리스트로 변환합니다.
/// </summary>
public List<NoteData> ConvertBeatSageToMyFormat(string rawJson, float songBPM)
{
BeatSageRoot sageData = JsonUtility.FromJson<BeatSageRoot>(rawJson);
List<NoteData> convertedNotes = new List<NoteData>();
if (sageData == null || sageData._notes == null) return convertedNotes;
foreach (var sageNote in sageData._notes)
{
// 일반 노트(0: 빨강, 1: 파랑)만 처리하고 나머지 오브젝트는 제외
if (sageNote._type != 0 && sageNote._type != 1) continue;
// BPM 공식을 대입하여 실제 게임 시간(초) 계산
float realTimeSeconds = (sageNote._time * 60f) / songBPM;
NoteData myNote = new NoteData
{
time = realTimeSeconds,
position = sageNote._lineIndex,
colorType = sageNote._type
};
convertedNotes.Add(myNote);
}
Debug.Log($"[컨버터] 비트세이지 포맷 변환 성공: {convertedNotes.Count}개 노트 생성됨.");
return convertedNotes;
}
}
```
---
## 5. 디바이스 스토리지 최적화 (Oculus Quest)
### 온디맨드 다운로드 패턴
유저가 인게임 UI 또는 맵 에디터에서 특정 곡을 선택하여 트리거하기 전까지는 음원(`.mp3`)과 데이터(`.json`) 파일을 다운로드하지 않아 앱 기본 설치 용량을 최소화한다.
### 임시 캐시 경로 활용
모든 스트리밍 다운로드 리소스는 `Application.temporaryCachePath`에 저장하여 오큘러스 내부 저장소의 파일 오염을 방지하며, OS의 메모리 부족 시 정리 가능한 대상 영역으로 권한을 위임한다.
### LRU (Least Recently Used) 삭제 메커니즘
게임 또는 툴 초기화 시 캐시 디렉터리 용량을 확인하여 **누적 용량이 1GB를 초과**할 경우, 파일 시스템의 '최근 접근 시간(Last Access Time)'이 가장 오래된 곡 폴더부터 순차적으로 `Directory.Delete`로 자동 소거한다.