feat: Game scene — SongController bridges custom map to VRBeatsKit

- SongController: loads MP3 + Beat Saber JSON map, runs countdown (3→2→1→GO),
  spawns cubes via VR_BeatManager.Spawn() synced to audioSource.time
- NoteData → SpawnEventInfo mapping: position/lineLayer → x/y, colorType → ColorSide,
  cutDirection → Direction enum
- travelTimeOverride on SpawnEventInfo: each cube's travel time is back-calculated
  from remaining time at spawn moment, so simultaneous notes arrive at hit zone together
  regardless of frame-level spawn delay
- AudioManager: add PlayClip(AudioClip) and CurrentTime property
- VR_BeatManager: respect travelTimeOverride when non-zero
- Settings.asset: targetTravelTime 0.5 → 1.8 for natural Beat Saber approach feel
- SceneBuilder ④: auto-builds Game.unity from SaberStyle, wires SongController refs,
  registers in Build Settings
- LiberationSans SDF fallback updated with NanumGothic for Korean text support
- Remove unused SampleScene

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 14:32:25 +09:00
parent 64ef3d64ec
commit 2f6aff7691
13 changed files with 5813 additions and 471 deletions
+87
View File
@@ -3,11 +3,98 @@ using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using VRBeats;
using VRBeats.ScriptableEvents;
public static class VRBeatSaberSceneBuilder
{
private const string MenuScene = "Assets/VRBeatsKit/Scenes/Menu.unity";
// ─────────────────────────────────────────────
// ④ Build Game Scene
// SaberStyle 복제 → Game.unity 생성
// PlayableManager 제거, SongController + 카운트다운 캔버스 추가
// ─────────────────────────────────────────────
[MenuItem("Tools/VRBeatSaber/④ Build Game Scene")]
public static void BuildGameScene()
{
const string saberStylePath = "Assets/VRBeatsKit/Scenes/SaberStyle.unity";
const string gamePath = "Assets/Scenes/Game.unity";
// SaberStyle → Game.unity 복제 (이미 있으면 그냥 열기)
if (!AssetDatabase.LoadAssetAtPath<Object>(gamePath))
{
if (!AssetDatabase.CopyAsset(saberStylePath, gamePath))
{
Debug.LogError("[SceneBuilder] SaberStyle.unity 복제 실패");
return;
}
AssetDatabase.Refresh();
}
var scene = EditorSceneManager.OpenScene(gamePath, OpenSceneMode.Single);
// PlayableManager 제거 (PlayableDirector는 유지)
var pm = Object.FindObjectOfType<PlayableManager>();
if (pm != null)
Object.DestroyImmediate(pm);
// SongController GO 생성
var scGO = new GameObject("SongController");
var songController = scGO.AddComponent<SongController>();
var cubePrefab = AssetDatabase.LoadAssetAtPath<Spawneable>(
"Assets/VRBeatsKit/Prefabs/Spawneable/VR_BeatCube.prefab");
var onLevelComplete = AssetDatabase.LoadAssetAtPath<GameEvent>(
"Assets/VRBeatsKit/GameEvents/OnLevelComplete.asset");
// 카운트다운 캔버스 생성
var canvasGO = new GameObject("CountdownCanvas");
var canvas = canvasGO.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 100;
canvasGO.AddComponent<CanvasScaler>();
canvasGO.AddComponent<GraphicRaycaster>();
var countdownGO = new GameObject("CountdownText");
countdownGO.transform.SetParent(canvasGO.transform, false);
var cRect = countdownGO.AddComponent<RectTransform>();
cRect.anchorMin = new Vector2(0.5f, 0.5f);
cRect.anchorMax = new Vector2(0.5f, 0.5f);
cRect.pivot = new Vector2(0.5f, 0.5f);
cRect.anchoredPosition = Vector2.zero;
cRect.sizeDelta = new Vector2(400f, 200f);
var cTmp = countdownGO.AddComponent<TextMeshProUGUI>();
cTmp.text = "";
cTmp.fontSize = 120f;
cTmp.color = Color.white;
cTmp.alignment = TextAlignmentOptions.Center;
cTmp.fontStyle = FontStyles.Bold;
countdownGO.SetActive(false);
// SongController 필드 연결
var scSO = new SerializedObject(songController);
scSO.FindProperty("cubePrefab") .objectReferenceValue = cubePrefab;
scSO.FindProperty("onLevelComplete") .objectReferenceValue = onLevelComplete;
scSO.FindProperty("countdownText") .objectReferenceValue = cTmp;
scSO.ApplyModifiedPropertiesWithoutUndo();
// Build Settings 에 Game.unity 추가
var scenes = EditorBuildSettings.scenes;
bool exists = System.Array.Exists(scenes, s => s.path == gamePath);
if (!exists)
{
var newList = new EditorBuildSettingsScene[scenes.Length + 1];
System.Array.Copy(scenes, newList, scenes.Length);
newList[scenes.Length] = new EditorBuildSettingsScene(gamePath, true);
EditorBuildSettings.scenes = newList;
}
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
Debug.Log("[SceneBuilder] ✓ Game.unity 생성 완료");
}
// ─────────────────────────────────────────────
// ③ Menu — Rebuild SongSelect Panel
//