feat: SongCreator 씬 완성 — Beat Sage URL 지원, info.dat 메타데이터 자동 추출

- BeatSageUploader: audio_url 지원(UploadFromUrl), PollAndDownload 공통화, ZIP 500 오류 3회 재시도
- BeatSageConverter: info.dat 파싱(SongMetadata), BPM 자동 감지 → 노트 타이밍 변환에 적용
- SongCreatorManager: title/BPM 필수 입력 제거, 난이도 4개 자동 선택, GenerateFlowFromUrl 버그 수정
- NasPublisher: audioPath null 허용(URL 흐름에서 로컬 파일 없는 경우 스킵)
- .gitignore/.gitattributes 초기 설정

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 23:37:34 +09:00
commit 4dad9e5d5b
1068 changed files with 175146 additions and 0 deletions
@@ -0,0 +1,36 @@
using UnityEngine;
namespace VRBeats
{
public enum Direction
{
UpperLeft = 0,
Up,
UpperRight,
Left,
Center,
Right,
LowerLeft,
Down,
LowerRight
}
public enum ColorSide
{
Left,
Right
}
[System.Serializable]
public class SpawnEventInfo
{
public Direction hitDirection = Direction.Up;
public ColorSide colorSide = ColorSide.Right;
public bool useSpark = true;
public Vector3 position = Vector3.zero;
public Vector3 rotation = Vector3.zero;
public float speed = 2.0f;
public int speedMultiplier = 1;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0306981eca736184985aa56ff8f27554
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 168243
packageName: VR Beats Kit
packageVersion: 2.0
assetPath: Assets/VRBeatsKit/Scripts/Spawneable/SpawnEventInfo.cs
uploadId: 546658
@@ -0,0 +1,115 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VRBeats
{
public class Spawneable : MonoBehaviour
{
[SerializeField] private float speed = 2.0f;
[SerializeField] private Vector3 rotation = Vector3.zero;
public float Speed { get { return speed; } }
private bool updatePositionX = false;
private bool updatePositionY = false;
private bool updatePositionZ = false;
private bool updateSpeed = false;
private bool updateRotation = false;
public System.Action onSpawnComplete;
#if UNITY_EDITOR
public virtual void CustomInspector(SpawnEventInfo info, Object[] targets)
{
EditorGUI.BeginChangeCheck();
Vector3 lastPosition = info.position;
info.position = EditorGUILayout.Vector3Field("Position", info.position);
if (EditorGUI.EndChangeCheck())
{
if (lastPosition.x != info.position.x)
updatePositionX = true;
else if (lastPosition.y != info.position.y)
updatePositionY = true;
else if (lastPosition.z != info.position.z)
updatePositionZ = true;
}
EditorGUI.BeginChangeCheck();
info.speed = EditorGUILayout.FloatField("Speed", info.speed);
if (EditorGUI.EndChangeCheck())
{
updateSpeed = true;
}
EditorGUI.BeginChangeCheck();
info.rotation = EditorGUILayout.Vector3Field("Rotation" , info.rotation);
if (EditorGUI.EndChangeCheck())
{
updateRotation = true;
}
if (info.speed < 0.0001f)
{
info.speed = 0.0001f;
}
foreach (Object obj in targets)
{
if (obj is VR_BeatSpawnMarker spawnMarker)
{
if (updateSpeed)
{
spawnMarker.spawInfo.speed = info.speed;
}
if (updatePositionX)
spawnMarker.spawInfo.position.x = info.position.x;
if (updatePositionY)
spawnMarker.spawInfo.position.y = info.position.y;
if (updatePositionZ)
spawnMarker.spawInfo.position.z = info.position.z;
if (updateRotation)
spawnMarker.spawInfo.rotation = info.rotation;
}
}
updatePositionX = false;
updatePositionY = false;
updatePositionZ = false;
updateRotation = false;
updateSpeed = false;
}
#endif
public virtual Quaternion GetSpawnRotation()
{
return Quaternion.Euler(rotation);
}
public virtual void Construct(SpawnEventInfo info)
{
speed = info.speed * info.speedMultiplier;
}
public virtual void OnSpawn()
{
if (onSpawnComplete != null)
onSpawnComplete.Invoke();
}
public void SetSpeedDirection(int dir)
{
speed = Mathf.Abs(speed) * dir;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 6e04755e95693e745b8825af30c3ce94
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 168243
packageName: VR Beats Kit
packageVersion: 2.0
assetPath: Assets/VRBeatsKit/Scripts/Spawneable/Spawneable.cs
uploadId: 546658
@@ -0,0 +1,184 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace VRBeats
{
public class VR_BeatCubeSpawneable : Spawneable
{
[SerializeField] private GameObject arrow = null;
[SerializeField] private GameObject dot = null;
[SerializeField] private Spark sparkPrefab = null;
[SerializeField] private ColorSide colorSide = ColorSide.Left;
[SerializeField] private bool useSpark = false;
public ColorSide ColorSide { get { return colorSide; } }
public Direction HitDirection { get { return hitDirection; } }
private Direction hitDirection = Direction.Center;
public override void OnSpawn()
{
base.OnSpawn();
if (useSpark)
{
if (sparkPrefab == null) return;
Color desireColor = VR_BeatManager.instance.GetColorFromColorSide(colorSide);
Spark spark = Instantiate(sparkPrefab, transform.position, Quaternion.identity);
spark.transform.parent = transform;
spark.Construct(desireColor);
}
}
public override void Construct(SpawnEventInfo info)
{
base.Construct(info);
transform.rotation = CalculateRotationFromDirection(info.hitDirection);
colorSide = info.colorSide;
useSpark = info.useSpark;
hitDirection = info.hitDirection;
//use the arrow of the center
arrow.SetActive( info.hitDirection != Direction.Center );
dot.SetActive( info.hitDirection == Direction.Center );
}
private Quaternion CalculateRotationFromDirection(Direction dir)
{
Vector3 rot = Vector3.zero;
if (dir == Direction.Up)
{
rot = new Vector3(0.0f, 0.0f, 0.0f);
}
else if (dir == Direction.UpperRight)
{
rot = new Vector3(0.0f, 0.0f, -45.0f);
}
else if (dir == Direction.Right)
{
rot = new Vector3(0.0f, 0.0f, -90.0f);
}
else if (dir == Direction.LowerRight)
{
rot = new Vector3(0.0f, 0.0f, -135.0f);
}
else if (dir == Direction.Down)
{
rot = new Vector3(0.0f, 0.0f, -180.0f);
}
else if (dir == Direction.LowerLeft)
{
rot = new Vector3(0.0f, 0.0f, -225.0f);
}
else if (dir == Direction.Left)
{
rot = new Vector3(0.0f, 0.0f, -270.0f);
}
else if (dir == Direction.UpperLeft)
{
rot = new Vector3(0.0f, 0.0f, -315.0f);
}
return Quaternion.Euler(rot);
}
#if UNITY_EDITOR
string[] textureNameArray = { "upperLeft", "up", "upperRight", "left", "center", "right", "lowerLeft", "down", "lowerRight" };
Texture[] texArray = null;
private bool updateHitDir = false;
private bool updateUseSpark = false;
private bool updateColorSide = false;
private void LoadArrowTextures()
{
texArray = new Texture[textureNameArray.Length];
for (int n = 0; n < texArray.Length; n++)
{
texArray[n] = Resources.Load("Editor/Arrow/" + textureNameArray[n]) as Texture;
}
}
public override void CustomInspector(SpawnEventInfo info , Object[] targets)
{
EditorGUI.BeginChangeCheck();
LoadArrowTextures();
EditorGUI.BeginChangeCheck();
info.hitDirection = DrawArrowGridInspector("" , info.hitDirection);
if (EditorGUI.EndChangeCheck())
{
updateHitDir = true;
}
base.CustomInspector(info , targets);
EditorGUI.BeginChangeCheck();
info.useSpark = EditorGUILayout.Toggle("Use Spark" , info.useSpark);
if (EditorGUI.EndChangeCheck())
{
updateUseSpark = true;
}
EditorGUI.BeginChangeCheck();
info.colorSide = (ColorSide) EditorGUILayout.EnumPopup("Color Side" , info.colorSide);
if (EditorGUI.EndChangeCheck())
{
updateColorSide = true;
}
/*manual assignment here, remember to check that the selected objects
are in fact of the appropriate type.*/
foreach (Object obj in targets)
{
if (obj is VR_BeatSpawnMarker spawnMarker)
{
if (updateHitDir)
spawnMarker.spawInfo.hitDirection = info.hitDirection;
else if (updateUseSpark)
spawnMarker.spawInfo.useSpark = info.useSpark;
else if (updateColorSide)
spawnMarker.spawInfo.colorSide = info.colorSide;
}
}
updateHitDir = false;
updateColorSide = false;
updateUseSpark = false;
}
private Direction DrawArrowGridInspector(string label, Direction dir)
{
EditorGUILayout.LabelField(label, EditorStyles.boldLabel);
GUILayout.BeginVertical("Box");
dir = (Direction)GUILayout.SelectionGrid((int)dir, texArray, 3, GUILayout.ExpandWidth(false), GUILayout.MaxHeight(150f), GUILayout.MaxWidth(150f));
GUILayout.EndVertical();
return dir;
}
#endif
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8112f3076d75f4648939288b5636aa7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 168243
packageName: VR Beats Kit
packageVersion: 2.0
assetPath: Assets/VRBeatsKit/Scripts/Spawneable/VR_BeatCubeSpawneable.cs
uploadId: 546658