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,69 @@
using System;
using UnityEngine;
using UnityEngine.Events;
namespace VRBeats.ScriptableEvents
{
public abstract class BaseEventListener<T1 , T2 ,T3 , T4> : MonoBehaviour ,
IExposeInvoke<T2>
where T1 :
IExposeAddListener<T3> ,
IExposeRemoveListener<T3>
where T4: UnityEvent<T2>
{
[SerializeField] private T1 gameEvent;
[SerializeField] private T4 response;
public T4 Response { get { return response; } }
public T1 GameEvent { get { return gameEvent; } }
private void Awake()
{
if (gameEvent != null)
{
gameEvent.AddListener( (T3) ((System.Object)this) );
}
}
private void OnDisable()
{
if (gameEvent != null)
{
gameEvent.RemoveListener((T3)((System.Object)this));
}
}
private void OnDestroy()
{
if (gameEvent != null)
{
gameEvent.RemoveListener( (T3)((System.Object)this) );
}
}
public void Invoke(T2 value)
{
if(response != null)
response.Invoke(value);
}
public void AddEventListener(UnityAction<T2> unityAction)
{
response.AddListener( unityAction );
}
public void SetEventListener(T1 gameEvent)
{
this.gameEvent = gameEvent;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: df5ed0832a6aa6743ad1f64bb4b80d2f
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/ScritableEvents/Core/Base/BaseEventListener.cs
uploadId: 546658
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using UnityEngine;
namespace VRBeats.ScriptableEvents
{
public class BaseGameEvent<T1 , T2> : ScriptableObject , IExposeAddListener<T1>, IExposeRemoveListener<T1> , IExposeInvoke<T2> where T1 : IExposeInvoke<T2>
{
[SerializeField] private List<T1> eventListenerList = null;
[HideInInspector] public T2 invokeValue;
public List<T1> EventListenerList { get { return eventListenerList; } }
public void AddListener(T1 listener)
{
eventListenerList.Add(listener);
}
public void RemoveListener(T1 listener)
{
eventListenerList.Remove(listener);
}
public void Invoke(T2 value)
{
for (int n = eventListenerList.Count - 1; n >= 0; n--)
{
eventListenerList[n].Invoke(value);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 77195594139989a4fb2f82dae5f1c77e
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/ScritableEvents/Core/Base/BaseGameEvent.cs
uploadId: 546658
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e5e389c231710d54bbef17cd54347f38
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,71 @@
using UnityEngine;
using UnityEngine.Events;
using UnityEditor;
namespace VRBeats.ScriptableEvents
{
//T1 IntGameEvent
//T2 int
//T3 IntEventListener
//T4 OnIntValueChange
public class BaseGameEventInspector<T1 , T2 , T3, T4> : Editor
where T1 : BaseGameEvent<T3 , T2>
where T3 : BaseEventListener<T1 , T2 , T3 , T4>
where T4 : UnityEvent<T2>
{
private T1 targetEvent = null;
private SerializedProperty invokeValue;
private void OnEnable()
{
targetEvent = (T1) target;
invokeValue = serializedObject.FindProperty("invokeValue");
}
public override void OnInspectorGUI()
{
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("Sritable events can only be use in running time", MessageType.Info);
return;
}
EditorGUILayout.LabelField("Listeners");
for (int n = 0; n < targetEvent.EventListenerList.Count; n++)
{
if (GUILayout.Button(targetEvent.EventListenerList[n].gameObject.name))
{
EditorGUIUtility.PingObject(targetEvent.EventListenerList[n].gameObject);
}
DrawEventNames(targetEvent.EventListenerList[n].Response);
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(invokeValue);
if (GUILayout.Button("Invoke"))
{
targetEvent.Invoke( targetEvent.invokeValue);
}
serializedObject.ApplyModifiedProperties();
}
private void DrawEventNames(UnityEvent<T2> unityEvent)
{
for (int n = 0; n < unityEvent.GetPersistentEventCount(); n++)
{
EditorGUILayout.LabelField(unityEvent.GetPersistentTarget(n).GetType().Name + "." + unityEvent.GetPersistentMethodName(n));
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1e91efe1ddac6ff4e9414e95247c6dd8
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/ScritableEvents/Core/Base/Editor/BaseGameEventInspector.cs
uploadId: 546658
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea5f4da0f37512a44a68b18784194cba
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
namespace VRBeats.ScriptableEvents
{
public interface IExposeAddListener<T>
{
void AddListener(T value);
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 9411d134adbe0b24abba7d7a9e751c29
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/ScritableEvents/Core/Base/Interfaces/IExposeAddListener.cs
uploadId: 546658
@@ -0,0 +1,8 @@
namespace VRBeats.ScriptableEvents
{
public interface IExposeInvoke<T>
{
void Invoke(T value);
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3d43bca55bcdfe342b3dba605f8c4fcd
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/ScritableEvents/Core/Base/Interfaces/IExposeInvoke.cs
uploadId: 546658
@@ -0,0 +1,9 @@
namespace VRBeats.ScriptableEvents
{
public interface IExposeRemoveListener<T>
{
void RemoveListener(T value);
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1d5af0a99701e1649b8f6834c1974cc7
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/ScritableEvents/Core/Base/Interfaces/IExposeRemoveListener.cs
uploadId: 546658