템플릿 애셋같은 것들 봐도 파일 많이 안 쪼개고 하나에 길게 적었던 이유가
Monobehaviour의 저주 때문에 그런게 아닐까
나도 그냥 region으로 구분해야 할듯
저장 불러오기 메서드 분리하지 말고 같은 클래스에 쓰자

아니 이건 또 왜 이러는데 같은 메서드잖아
아 매개변수랑 return값 있어서 그랬던거

드롭다운 변경할 때 상태 전환을 어떻게 할지 애매해서 얼불춤 확인해봤더니
여긴 탐색기를 사용하네
또 레이어 하나 더 들어가야 됨
판정 → 맵 편집 → 맵 저장 → 탐색기
https://github.com/gkngkc/UnityStandaloneFileBrowser
private void LoadMapData()
{
// TODO: 탐색기를 이용해 easy save로 저장된 데이터를 불러와서 noteBlockDataList에 저장
var paths = StandaloneFileBrowser.OpenFilePanel("Select a Map", Application.persistentDataPath, "etude", false);
if (paths.Length > 0 && !string.IsNullOrEmpty(paths[0]))
{
string selectedFilePath = paths[0];
Debug.Log("Selected File: " + selectedFilePath);
// Easy Save로 선택한 파일 로드
if (ES3.FileExists(selectedFilePath))
{
noteBlockDataList = ES3.Load<List<NoteBlockData>>("NoteBlocks", selectedFilePath);
Debug.Log("Data loaded successfully.");
}
else
{
Debug.LogWarning("File not found or incompatible with Easy Save.");
}
}
else
{
Debug.LogWarning("No file selected.");
}
}

탐색기로 불러오기 구현 완료
는 아니고 불러올 맵을 만들어야 한다
불러올 맵을 만들려면 일단 저장을 구현해야 하고
저장을 하려면 저장할 데이터를 만들어야 함
실제 화면에 있는 오브젝트들과
데이터로 관리하고 있는 리스트가 다름
오브젝트들은 MapRoot라는 공통 부모를 갖게 될 것
// noteBlockDataList에 있는 데이터로 MapRoot 아래에 노트 블럭들 생성
spawnPosition = Vector3.zero;
foreach (NoteBlockData noteBlockData in noteBlockDataList)
{
GameObject noteBlock = Instantiate(notePrefabs[noteBlockData.noteLength], spawnPosition, Quaternion.identity);
noteBlock.transform.rotation = Quaternion.Euler(0, 0, noteDirections[noteBlockData.direction]);
noteBlock.GetComponent<NoteBlockData>().noteLength = noteBlockData.noteLength;
noteBlock.GetComponent<NoteBlockData>().direction = noteBlockData.direction;
noteBlock.GetComponent<NoteBlockData>().order = noteBlockData.order;
noteBlock.transform.SetParent(MapRoot.transform);
spawnPosition += GetDisplacement(noteBlockData);
}
로드할 때 MapRoot 아래에 오브젝트 생성하는 코드
아 y축 좌표 순서에 맞게 곱해주는거 까먹음 나중에 하자
private void AddMap()
{
// TODO: fileName은 별도 UI로 입력받아야 함
string fileName = "NewMap.etude";
string filePath = Path.Combine(Application.persistentDataPath, fileName);
if (!File.Exists(filePath)) // 같은 파일 있는지 확인하고
{
File.Create(filePath).Close(); // 빈 파일 생성 후 스트림 닫기
selectedFilePath = filePath;
Debug.Log($"File created: {fileName}");
}
else
{
Debug.LogWarning($"File already exists: {filePath}");
}
}
경로는 임시로 대충 초기화해놓음
selectedFilePath는 전역 변수로 관리해서 savemapdata()에서 쓸 수 있게
private void SaveMapData()
{
// noteBlockDataList에 있는 데이터를 easy save로 저장
if (selectedFilePath != null)
{
ES3.Save<List<NoteBlockData>>("Map", noteBlockDataList, selectedFilePath);
Debug.Log("Data saved successfully.");
}
else
{
Debug.LogWarning("No file selected to save.");
}
}
save 구현은 간단
하지 않았다 오류 남

아오 분명 그냥 저장해도 된데매
using UnityEngine;
public class NoteBlockData : MonoBehaviour
{
public string noteLength;
public string direction;
public int order;
// 데이터를 저장용 DTO로 변환
public NoteBlockDataDTO ToDTO()
{
return new NoteBlockDataDTO(noteLength, direction, order);
}
// DTO를 다시 NoteBlockData로 변환
public void FromDTO(NoteBlockDataDTO dto)
{
noteLength = dto.noteLength;
direction = dto.direction;
order = dto.order;
}
}
각 노트 블럭에 붙이려면 MonoBehaviour는 어쩔 수 없이 붙어야 되니까
DTO(Data Transfer Object) 방식을 사용하라고 함. 그니까 클래스를 또 파라는거. 돌아버리겠네.
MonoBehaviour를 포기할 순 없음
그래야 플레이할 때 중간에 있는 오브젝트 선택해서 편집하는게 가능함

그저께 내가 생각해냈던 방법이 이거랑 비슷하면서 더 간결. 노트 블럭 리스트랑 저장용 리스트 2개의 일관성을 맞춰야해서 귀찮긴 한데 다른 방법이 안 보인다. gpt가 말한 것처럼 메서드 쓰는 건 두 정보 사이를 이을 수가 없음. 나는 같은 인덱스로 관리해서 해결할거.
public class NoteBlockIndex : MonoBehaviour
{
public int noteBlockIndex;
}
근데 이래도 안됨


어우 이게 문제였네
빈 파일 만들면 안됐던거
easy save 믿고 경로만 설정한 후에 신뢰의 도약 했어야됨

데이터도 잘 들어가는 거 확인
이제 클래스에서 Serializeable 떼도 잘 들어간다
근데 데이터들을 어디에 어떻게 관리해야할지 아직도 모르겠고 빙빙 헤매게 돼서 한 번 정리해보면
.etude 파일에 들어있던 NoteBlockData들을 읽어서 GameObject들을 만든다.NoteBlock.cs의 NoteBlockIndex가 함께 기록된다. NoteBlockIndex는 해당 블록의 List<NoteBlockData>에서의 인덱스를 나타낸다. 또한, List<NoteBlockData>에도 NoteBlockData를 추가한다.NoteBlockIndex를 기반으로 편집이 이루어진다.NoteBlockIndex와 List<NoteBlockData>에 있는 정보들이 변할 수 있다.List<NoteBlockData>를 저장한다.자료형이랑 로직 좀 바꾸니까 코드를 처음부터 끝까지 다 수정하고 검토해줘야했다;
와캬퍄
힘들었지만 완성
private void InstantiateBlock()
{
string duration = DurationDropdown.options[DurationDropdown.value].text;
string direction = DirectionDropdown.options[DirectionDropdown.value].text;
NewBlock = Instantiate(notePrefabs[duration], spawnPosition, Quaternion.identity);
NewBlock.transform.rotation = Quaternion.Euler(0, 0, noteDirections[direction]);
NewBlock.transform.SetParent(MapRoot.transform);
spawnPosition += GetDisplacement(new NoteBlockData { noteLength = duration, direction = direction });
}
새로운 블럭을 생성할 때마다 해당 블럭의 길이와 방향에 맞게 다음 spawnposition을 지정해준다.
private Vector3 GetDisplacement(NoteBlockData noteBlockData)
{
Dictionary<string, float> noteLengths = new Dictionary<string, float>
{
{ "DottedWhole", 6.0f },
{ "Whole", 4.0f },
{ "DottedHalf", 3.0f },
{ "Half", 2.0f },
{ "DottedQuarter", 1.5f },
{ "Quarter", 1.0f },
{ "DottedEighth", 0.75f },
{ "Eighth", 0.5f },
{ "DottedSixteenth", 0.375f },
{ "Sixteenth", 0.25f }
};
Dictionary<string, Vector3> directions = new Dictionary<string, Vector3>
{
{ "Up", new Vector3(0, 1, 0) },
{ "Down", new Vector3(0, -1, 0) },
{ "Right", new Vector3(1, 0, 0) },
{ "Left", new Vector3(-1, 0, 0) },
{ "UpRight", new Vector3(Mathf.Sqrt(2) / 2, Mathf.Sqrt(2) / 2, 0) },
{ "UpLeft", new Vector3(-Mathf.Sqrt(2) / 2, Mathf.Sqrt(2) / 2, 0) },
{ "DownRight", new Vector3(Mathf.Sqrt(2) / 2, -Mathf.Sqrt(2) / 2, 0) },
{ "DownLeft", new Vector3(-Mathf.Sqrt(2) / 2, -Mathf.Sqrt(2) / 2, 0) }
};
float displacement = noteLengths[noteBlockData.noteLength];
Vector3 directionVector = directions[noteBlockData.direction];
return directionVector * displacement;
}
딕셔너리에서 길이와 방향에 맞는 변위를 구하여 반환

이제 노트 길이만큼 떨어져서 생성된다
한 가지 실수했던게 있다.
블럭의 정보를 저장할 때 List<GameObject>가 아닌 List<NoteBlock>을 사용해서,
각 오브젝트에 있는 인덱스만으로는 다음 블럭에 접근을 하지 못한다.
각 노트 블럭에 연결 리스트로 이후 블럭을 연결해놓으면 최소한의 변경으로 문제 해결 가능하긴 할텐데
나중에 위치 변경시 블러 효과 넣을지 말지 결정되는건 해결 불가
근데 그건 어차피 List<GameObject>로 갖고 있어도
최종 저장할 때 전부 순회하거나 raycast 한 번씩 쏴서 이벤트 지정해줘야하는 거일듯
일단 먼 얘기니까 생각하지 말자
public class NoteBlockIndex : MonoBehaviour
{
public int noteBlockIndex;
public GameObject prevNoteBlock;
public GameObject nextNoteBlock;
}
NoteBlockIndex에 이전과 이후 레퍼런스를 달아주고
void CreateNewBlock()
{
InstantiateBlock();
AddNewBlockDataToList(); // 새 블럭 정보 저장용 리스트에 추가
NewBlock.GetComponent<NoteBlockIndex>().noteBlockIndex = NoteAllocateIndex++; // 접근용 인덱스 할당
// 이전 블럭과 쌍방 연결
if (LastBlock != null)
{
NewBlock.GetComponent<NoteBlockIndex>().prevNoteBlock = LastBlock;
LastBlock.GetComponent<NoteBlockIndex>().nextNoteBlock = NewBlock;
LastBlock = NewBlock;
}
}
CreateNewBlock(), ChangeBlock(), DeleteBlock()에 정보 갱신 관리 로직 추가하여
연결 리스트 생성 완료
/// <summary>
/// 해당 블럭과 이후의 모든 블럭을 삭제
/// </summary>
private void WipeBlocksFromTheBlock(GameObject TheBlock)
{
while (TheBlock != null)
{
GameObject temp = TheBlock.GetComponent<NoteBlockIndex>().nextNoteBlock;
Destroy(TheBlock);
TheBlock = temp;
}
}
원래는 변경이나 삭제하면 이후 블럭들 재배치도 하려고 했는데 좀 복잡해지기도 하고
맵 편집할 때 의미있는 기능이 될지 의문이 들어서 일단 전부 삭제로 구현
이렇게되면 종류 변경이 살짝 의미 없어지긴 하는데
혹시 모르니까 굳이 지우진 말고 이대로 두기
이제 맵 편집기는 거의 다 완성했다

저장 후 불러오면 경고 뜨면서 안 불러와지긴 하는데 이거만 내일 고치면 됨.