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,86 @@
using UnityEngine;
using UnityEngine.Serialization;
using VRSDK;
namespace DamageSystem
{
public class DamageablePart : Damageable
{
[FormerlySerializedAs("damageMultiplier")]
[SerializeField] private float m_damageMultiplier = 1.0f;
[FormerlySerializedAs("rb")]
[SerializeField] private Rigidbody m_rb = null;
private DamageableManager m_owner = null;
public Rigidbody RB => m_rb;
public DamageableManager Owner => m_owner;
public float DamageMultiplier => m_damageMultiplier;
private void Awake()
{
if(m_rb == null) m_rb = GetComponent<Rigidbody>();
}
public void ExternalSetup(float damageMultiplier, Rigidbody rb)
{
m_damageMultiplier = damageMultiplier;
m_rb = rb;
}
public void SetOwner(DamageableManager owner)
{
this.m_owner = owner;
}
public override void DoDamage(DamageInfo info)
{
if (m_owner == null)
{
return;
}
info.dmg *= m_damageMultiplier;
m_owner.DoDamage( info, this );
}
private void ProcessHit(Rigidbody rb , GameObject sender)
{
if (m_owner == null)
{
return;
}
DamageInfo info = new DamageInfo();
info.damageType = DamageType.Physical;
info.hitDir = rb.linearVelocity.normalized;
info.dmg = rb.linearVelocity.magnitude * m_damageMultiplier;
info.hitForce = rb.linearVelocity.magnitude;
info.sender = sender;
DoDamage( info );
}
private void OnCollisionEnter(Collision other)
{
//in this way we can respond to hits from objects and apply damage,
//like the player throwing a box to a enemy
if (other.rigidbody != null)
{
VR_Grabbable grabbable = VR_Manager.instance.GetGrabbableFromCollider(other.collider);
if (grabbable != null && grabbable.ObjectWasThrow && grabbable.LastInteractController != null)
{
GameObject sender = grabbable.LastInteractController.transform.root.gameObject;
ProcessHit( other.rigidbody, sender );
}
else
{
ProcessHit( other.rigidbody, other.gameObject );
}
}
}
}
}