비트 찍기 완료 및 클로드를 통한 api작업
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using TMPro;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
public class MapEditor : MonoBehaviour
|
||||
{
|
||||
[Header("Components")]
|
||||
public AudioSource audioSource;
|
||||
public Slider timelineSlider;
|
||||
public TMP_Text timeText;
|
||||
|
||||
[Header("Song Settings")]
|
||||
[Tooltip("확장자와 Map_을 제외한 저장할 노래 제목을 적으세요 (예: Life, Virus)")]
|
||||
public string songName = "Life";
|
||||
|
||||
[Header("Note Guide (Large Background Numbers)")]
|
||||
public TMP_Text[] guideTexts; // 4, 5, 1, 2 순서로 배치된 큰 텍스트들
|
||||
|
||||
[Header("Timeline (On Slider)")]
|
||||
public RectTransform[] timelineRows; // Slider 내부 NoteContainer 안의 4개 줄
|
||||
|
||||
[Header("Prefabs")]
|
||||
public GameObject noteUIPrefab; // 텍스트가 없는 10x10 크기의 작은 사각형 프리팹
|
||||
|
||||
private List<NoteData> recordedNotes = new List<NoteData>();
|
||||
private Dictionary<NoteData, GameObject> visualNoteMap = new Dictionary<NoteData, GameObject>();
|
||||
|
||||
void Start()
|
||||
{
|
||||
// 슬라이더 클릭 시 해당 시간대로 이동하는 리스너
|
||||
timelineSlider.onValueChanged.AddListener(OnSliderValueChanged);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
var kb = Keyboard.current;
|
||||
if (kb == null) return;
|
||||
|
||||
// 1. 재생/정지 제어 (Space)
|
||||
if (kb.spaceKey.wasPressedThisFrame)
|
||||
{
|
||||
if (audioSource.isPlaying) audioSource.Pause();
|
||||
else audioSource.Play();
|
||||
}
|
||||
|
||||
// 2. 10초 되감기 (Left Arrow)
|
||||
if (kb.leftArrowKey.wasPressedThisFrame)
|
||||
{
|
||||
audioSource.time = Mathf.Max(0, audioSource.time - 10f);
|
||||
}
|
||||
|
||||
// 3. 시간 및 UI 업데이트
|
||||
if (audioSource.isPlaying)
|
||||
{
|
||||
UpdateTimelineUI();
|
||||
}
|
||||
|
||||
// 4. 입력 처리 (가이드 색상 변경 + 노트 기록)
|
||||
HandleEditorInput(kb);
|
||||
}
|
||||
|
||||
void UpdateTimelineUI()
|
||||
{
|
||||
float currentTime = audioSource.time;
|
||||
float totalTime = audioSource.clip.length;
|
||||
timelineSlider.value = currentTime / totalTime;
|
||||
timeText.text = string.Format("{0:00}:{1:00} / {2:00}:{3:00}",
|
||||
(int)currentTime / 60, (int)currentTime % 60,
|
||||
(int)totalTime / 60, (int)totalTime % 60);
|
||||
}
|
||||
|
||||
void HandleEditorInput(Keyboard kb)
|
||||
{
|
||||
// 1. 단일 노트 컬러 선택 (Q: 빨강, W: 파랑)
|
||||
int soloColor = kb.qKey.isPressed ? 0 : (kb.wKey.isPressed ? 1 : -1);
|
||||
|
||||
// 가이드 비주얼 업데이트
|
||||
UpdateGuideVisuals(kb, soloColor);
|
||||
|
||||
if (!audioSource.isPlaying) return;
|
||||
|
||||
// 2. E 키를 누르고 있을 때 (더블 노트 모드: 1,4 빨강 / 2,5 파랑)
|
||||
if (kb.eKey.isPressed)
|
||||
{
|
||||
// E를 누른 상태에서 숫자패드를 누르는 순간 기록
|
||||
if (kb.numpad4Key.wasPressedThisFrame) ProcessNote(0, 0); // 4번 위치 빨강
|
||||
if (kb.numpad5Key.wasPressedThisFrame) ProcessNote(1, 1); // 5번 위치 파랑
|
||||
if (kb.numpad1Key.wasPressedThisFrame) ProcessNote(2, 0); // 1번 위치 빨강
|
||||
if (kb.numpad2Key.wasPressedThisFrame) ProcessNote(3, 1); // 2번 위치 파랑
|
||||
}
|
||||
// 3. E 키를 누르하지 않았을 때 (기존 Q/W 단일 노트 모드)
|
||||
else if (soloColor != -1)
|
||||
{
|
||||
if (kb.numpad4Key.wasPressedThisFrame) ProcessNote(0, soloColor);
|
||||
if (kb.numpad5Key.wasPressedThisFrame) ProcessNote(1, soloColor);
|
||||
if (kb.numpad1Key.wasPressedThisFrame) ProcessNote(2, soloColor);
|
||||
if (kb.numpad2Key.wasPressedThisFrame) ProcessNote(3, soloColor);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateGuideVisuals(Keyboard kb, int color)
|
||||
{
|
||||
// 기본 흰색으로 초기화
|
||||
foreach (var txt in guideTexts) txt.color = Color.white;
|
||||
|
||||
if (color == -1) return;
|
||||
|
||||
Color activeColor = (color == 0) ? Color.red : Color.blue;
|
||||
|
||||
// 키패드 누르고 있을 때 가이드 텍스트 색 변경
|
||||
if (kb.numpad4Key.isPressed) guideTexts[0].color = activeColor;
|
||||
if (kb.numpad5Key.isPressed) guideTexts[1].color = activeColor;
|
||||
if (kb.numpad1Key.isPressed) guideTexts[2].color = activeColor;
|
||||
if (kb.numpad2Key.isPressed) guideTexts[3].color = activeColor;
|
||||
}
|
||||
|
||||
void ProcessNote(int pos, int color)
|
||||
{
|
||||
float currentTime = audioSource.time;
|
||||
|
||||
// 수정 로직: 현재 시간 0.1초 내에 같은 줄에 이미 노트가 있다면 제거 (덮어쓰기 준비)
|
||||
NoteData existingNote = recordedNotes.Find(n => n.position == pos && Mathf.Abs(n.time - currentTime) < 0.1f);
|
||||
if (existingNote != null) RemoveNote(existingNote);
|
||||
|
||||
RecordNote(pos, color);
|
||||
}
|
||||
|
||||
void RecordNote(int pos, int color)
|
||||
{
|
||||
NoteData newNote = new NoteData { time = audioSource.time, position = pos, colorType = color };
|
||||
recordedNotes.Add(newNote);
|
||||
|
||||
// 1. 생성 및 부모 설정
|
||||
GameObject obj = Instantiate(noteUIPrefab, timelineRows[pos]);
|
||||
RectTransform rt = obj.GetComponent<RectTransform>();
|
||||
|
||||
// 2. 크기 및 앵커 강제 고정 (길어짐 방지)
|
||||
rt.anchorMin = new Vector2(0, 0.5f);
|
||||
rt.anchorMax = new Vector2(0, 0.5f);
|
||||
rt.pivot = new Vector2(0, 0.5f);
|
||||
rt.sizeDelta = new Vector2(1, 15);
|
||||
rt.localScale = Vector3.one; // 스케일 1로 초기화
|
||||
|
||||
// 3. 색상 강제 적용 (색상 사라짐 방지)
|
||||
Image noteImage = obj.GetComponent<Image>();
|
||||
if (noteImage != null)
|
||||
{
|
||||
// 0이면 빨강, 1이면 파랑 적용
|
||||
noteImage.color = (color == 0) ? Color.red : Color.blue;
|
||||
}
|
||||
|
||||
// 4. 위치 계산
|
||||
float rowWidth = timelineRows[pos].rect.width;
|
||||
float xPos = (newNote.time / audioSource.clip.length) * rowWidth;
|
||||
rt.anchoredPosition = new Vector2(xPos, 0);
|
||||
|
||||
// 5. 관리 리스트에 추가
|
||||
visualNoteMap.Add(newNote, obj);
|
||||
obj.GetComponent<Button>().onClick.AddListener(() => RemoveNote(newNote));
|
||||
}
|
||||
|
||||
void RemoveNote(NoteData note)
|
||||
{
|
||||
if (visualNoteMap.ContainsKey(note))
|
||||
{
|
||||
Destroy(visualNoteMap[note]);
|
||||
visualNoteMap.Remove(note);
|
||||
recordedNotes.Remove(note);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSliderValueChanged(float value)
|
||||
{
|
||||
// 정지 상태에서 슬라이더 조작 시 음악 시간 이동
|
||||
if (!audioSource.isPlaying)
|
||||
{
|
||||
audioSource.time = value * audioSource.clip.length;
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveBeatMap()
|
||||
{
|
||||
// 오디오 소스나 클립이 없을 때 예외 처리
|
||||
if (audioSource == null || audioSource.clip == null)
|
||||
{
|
||||
Debug.LogError("[맵에디터] AudioSource에 음악 파일(Clip)이 등록되어 있지 않습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 😎 인스펙터의 Song Name을 무시하고, 실제 오디오 클립의 이름(예: Bethoven_Virus__Piano_)을 가져옵니다.
|
||||
string actualSongName = audioSource.clip.name;
|
||||
string fileName = "Map_" + actualSongName + ".json";
|
||||
string filePath = Path.Combine(Application.streamingAssetsPath, fileName).Replace("\\", "/");
|
||||
|
||||
// 데이터 직렬화 및 저장
|
||||
string json = JsonUtility.ToJson(new Serialization<NoteData>(recordedNotes));
|
||||
File.WriteAllText(filePath, json);
|
||||
|
||||
Debug.Log($"[맵에디터] 오디오 이름으로 저장 완료! 파일명: {fileName} | 경로: {filePath}");
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.AssetDatabase.Refresh();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class Serialization<T>
|
||||
{
|
||||
public List<T> target;
|
||||
public Serialization(List<T> target) { this.target = target; }
|
||||
}
|
||||
Reference in New Issue
Block a user