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,231 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Platinio.SDK.EditorTools
{
public static class PlatinioEditorGUILayout
{
/// <summary>
/// Create a property field from a SerializedProperty
/// </summary>
public static void PropertyField(SerializedProperty property, string label = null, bool showChildren = false)
{
EditorGUILayout.PropertyField(property, new GUIContent( label == null ? property.displayName : label ), showChildren);
}
/// <summary>
/// Create a property field from a SerializedProperty
/// </summary>
public static void PropertyField(SerializedProperty property, bool showChildren)
{
PropertyField(property, null, showChildren);
}
/// <summary>
/// Create a float slider in the inspector
/// </summary>
/// <param name="min">Min slider value</param>
/// <param name="max">Max slider value</param>
/// <param name="minLimit">Min slider limit</param>
/// <param name="maxLimit">Max slider limit</param>
public static void MinMaxFloatSlider(SerializedProperty min, SerializedProperty max, float minLimit, float maxLimit)
{
PropertyField(min);
PropertyField(max);
float minValue = min.floatValue;
float maxValue = max.floatValue;
EditorGUILayout.MinMaxSlider(ref minValue, ref maxValue, minLimit, maxLimit);
min.floatValue = minValue;
max.floatValue = maxValue;
}
/// <summary>
/// Creates a foldout style label with indent value
/// </summary>
/// <returns>Current foldout value</returns>
public static bool Foldout(bool foldout, GUIContent content, int indent)
{
GUIStyle style = new GUIStyle(EditorStyles.foldout);
style.fontStyle = FontStyle.Bold;
style.fontSize = 12;
style.active.textColor = Color.black;
style.focused.textColor = Color.black;
style.onHover.textColor = Color.black;
style.normal.textColor = Color.black;
style.onNormal.textColor = Color.black;
style.onActive.textColor = Color.black;
style.onFocused.textColor = Color.black;
Rect rect = GUILayoutUtility.GetRect(40f, 40f, 16f, 16f);
rect.x += indent * 20;
return EditorGUI.Foldout(rect, foldout, content , style);
}
/// <summary>
/// Creates a foldout style label with indent value
/// </summary>
/// <param name="title">Title</param>
/// <param name="foldout">Foldout value</param>
/// <param name="indent">Indent level</param>
/// <param name="drawCallback">Callback called when it is foldout</param>
public static void Foldout(GUIContent title, ref bool foldout, int indent , Action drawCallback)
{
foldout = Foldout(foldout, title , indent);
if (foldout)
{
drawCallback();
}
}
/// <summary>
/// Creates a foldout style label with indent value
/// </summary>
/// <param name="title">Title</param>
/// <param name="foldout">Foldout value</param>
/// <param name="drawCallback">Callback called when it is foldout</param>
public static void Foldout(GUIContent title, ref bool foldout, Action drawCallback)
{
Foldout(title, ref foldout, 0, drawCallback);
}
/// <summary>
/// Add space in the inspector
/// </summary>
public static void Space(int height)
{
for (int n = 0; n < height; n++) EditorGUILayout.Space();
}
/// <summary>
/// Draws a title label
/// </summary>
public static void DrawTittle(string text)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField( text, EditorStyles.boldLabel );
EditorGUILayout.Space();
}
/// <summary>
/// Draws a grid of buttons in the inspector
/// </summary>
/// <returns>Current selected button index</returns>
public static int DrawGridButtons(int selection, int xSize, params GUIContent[] labels)
{
EditorGUIUtility.SetIconSize( new Vector2(20.0f , 20.0f));
GUIStyle SelectionGridStyle = new GUIStyle(EditorStyles.miniButton);
SelectionGridStyle.fixedHeight = 35;
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
int newSelection = GUILayout.SelectionGrid(selection, labels, xSize, SelectionGridStyle, GUILayout.Height(68), GUILayout.Width(85 * Screen.width / Screen.dpi));
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
return newSelection;
}
/// <summary>
/// Draws a tooltip box in the inspector
/// </summary>
public static void DrawTooltipBox(Texture icon , string title , string text)
{
EditorGUILayout.BeginVertical("Box");
var style = new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter };
EditorGUILayout.LabelField(new GUIContent(icon), style, GUILayout.ExpandWidth(true), GUILayout.Height(32));
EditorGUILayout.LabelField(title, style, GUILayout.ExpandWidth(true));
style = EditorStyles.helpBox;
style.richText = true;
style.fontSize = 11;
EditorGUILayout.LabelField(text , style );
EditorGUILayout.EndVertical();
GUILayout.FlexibleSpace();
}
public static void DrawTooltipBox(MessageType messageType, string title, string text)
{
DrawTooltipBox(LoadIcon(messageType.ToString()), title, text);
}
/// <summary>
/// Loads editor icons from "Editor/Icons/"
/// </summary>
public static Texture LoadIcon(string iconName)
{
return Resources.Load("Editor/Icons/" + iconName) as Texture;
}
/// <summary>
/// Draws a console in the inspector
/// </summary>
/// <param name="consoleMessageList">messages for the user</param>
public static void DrawConsole(List<ConsoleMessage> consoleMessageList)
{
for (int n = 0; n < consoleMessageList.Count; n++)
{
EditorGUILayout.HelpBox( consoleMessageList[n].text , consoleMessageList[n].messageType );
}
}
/// <summary>
/// Sometimes is better to use a enum dropdown Yes/No instead of a checkbox
/// </summary>
public static bool DrawBoolEnum(GUIContent content, bool value)
{
return ((BoolEnum)EditorGUILayout.EnumPopup(content, value? BoolEnum.Yes : BoolEnum.No)) == BoolEnum.Yes;
}
/// <summary>
/// Sometimes is better to use a enum dropdown Yes/No instead of a checkbox
/// </summary>
public static bool DrawBoolEnum(GUIContent content, Rect rect , bool value)
{
return ((BoolEnum)EditorGUI.EnumPopup(rect , content, value ? BoolEnum.Yes : BoolEnum.No )) == BoolEnum.Yes;
}
public static void FoldoutInspector(GUIContent title, ref bool foldout, int indent , Action drawCallback)
{
foldout = Foldout(foldout, title , indent);
if (foldout)
{
drawCallback();
}
}
public static void FoldoutInspector(GUIContent title, ref bool foldout, Action drawCallback)
{
FoldoutInspector(title, ref foldout, 0, drawCallback);
}
}
public class ConsoleMessage
{
public string text;
public MessageType messageType;
public ConsoleMessage(string text , MessageType messageType)
{
this.text = text;
this.messageType = messageType;
}
}
public enum BoolEnum
{
Yes,
No
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 75a81db9430ee15498ae2cb154238a5e
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/Modules/VRSDK/EditorTools/Editor/PlatinioEditorGUILayout.cs
uploadId: 546658