Files

215 lines
7.6 KiB
C#
Raw Permalink Normal View History

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; }
}