241216

lililllilillll·2024년 12월 16일

개발 일지

목록 보기
22/350

✅ 오늘 한 일


  • Project Etude


📝 배운 것들


🏷️ Unity : 파일 저장 위치

저장 기능 구현할 때 쓸 수 있는 파일 저장 위치

(1) Application.persistentDataPath

  • 플랫폼별로 사용 가능한 데이터 저장소 경로:
    • Windows: %AppData%/LocalLow/CompanyName/GameName
    • Android: /data/data/com.CompanyName.GameName/files/
    • iOS: App Sandbox 내 Documents 디렉토리.
  • 게임 저장 데이터에 적합.

(2) Application.dataPath

  • Unity 프로젝트의 Assets 폴더 경로.
  • 빌드된 게임에서는 사용하지 않는 것이 좋음(읽기 전용).

(3) PlayerPrefs

  • Unity에서 간단한 키-값 데이터를 저장할 때 사용.
  • 주로 게임 설정(그래픽, 볼륨 등)을 저장하는 데 적합.


🎮 Project Etude


smoothness는 나중에 폴리싱 단계에서 해도 될듯

오늘 해야 할 거

  • 박자선에서 raycast 쏘면 처음 맞는 콜라이더의 최상위 부모로 가서 노트 블럭 순서가 일치한지 확인한다. 아니라면 틀림. 맞다면 순서값을 1 올린다.
    콜라이더의 정보를 확인해서 굿, 나이스, 퍼펙트를 구분한다.

  • 블럭을 놓을 때 콜라이더가 겹치는 부분이 있다면 겹치는 블럭들 중에 가장 마지막에 사라지는 블럭을 저장해두고, 그 블럭이 사라질 때 블러 효과가 풀린다.

  • 박자선이 회전할 때 중심과의 거리를 재서 해당 각도에서의 상태 위치로 옮긴다.

박자선 판정

raycast 쏘면 왼쪽 오른쪽 비는 부분이 생겨서 collider로 해야할듯

근데 collider는 필요한 콜라이더만 가져오는게 아니라 다 가져온 다음 필요한게 있는지 없는지 체크해야 할거라
나중에 성능 문제 생길 수 있을 것 같아서 raycast로 다시 돌아가려고 했는데

2d raycast는 z축 판정이 안된다니 그게 무슨 말이니

그래서 3D 콜라이더 사용해봤더니 이것도 감지를 안 해줌

https://www.youtube.com/watch?v=RkJyyi6lmQ8

이거 보고 씬뷰나 게임뷰에서 어떻게 그려지는지 좀 보려고 했더니 그려지지도 않음
-> 이건 rayLength를 초기화 안 해줘서 그랬던 것

콜라이더 안에 raycast가 묻힌 문제였음. 크기 줄이니까 감지 성공.

z축 좌표를 맵에서의 순서랑 곱해서
먼저 연주돼야 할 노트가 나중 노트에 가려지지 않도록 배치
C# float는 부동소수점 7자리까지 정확 (실제로 해보니 0 6개까지 판정됨)
good, nice, perfect 3개의 순서도 다름

raycast를 앞과 뒤 두 번 쏴서 맞춘 콜라이더에 따라 판정이 달라지는데
그거 생각하니까 어떻게 정하는게 좋을지 모르겠어서 일단 맞았냐/안 맞았냐 이 둘만 판정할 수 있도록 raycast 중앙에서 하나만 쏴서, collider도 하나만 놓고 구현

맵 저장

이제 박자 순서를 지정해줘야 하는데, 그걸 위해선 맵 에디터의 가장 중요한 부분을 지금 고민해야 한다.
(생각해보니까 지금 당장 안 해도 되긴 함. 그냥 플레이 모드에서 대충 몇 개 만들어보면 되긴 한데 거의 막바지라 순서 의미 없)
그것은 바로 저장.

  • EditorSceneManager.SaveScene
    : 저번에 gpt한테 물어봤을 때 이걸로 가능은 하다고 했는데, Play mode에 한정된 맵 에디터를 만들고 싶진 않다. 빌드에서도 편집 가능하게 하고 싶음.

  • Play Mode Save 애셋
    : 굳이 사야할지 의문이고, 이것도 Play mode에서 편집하는 걸 도와주는 거.

  • 프리팹을 이용한 방법
    : 프리팹으로 저장해도 play mode 벗어나도 저장됨. Map이라는 부모 오브젝트를 프리팹으로 만들고 그 안에 전부 집어넣으면 되지 않을까? gpt가 빌드에서는 이 방법 안된다고 함. 크로스체크 위해 스택오버플로우 보니까 프리팹은 에디터에만 있는 개념이라고 함. (명확한 근거 문서는 없긴 한데 일단 넘어가기)

  • Easy Save 애셋
    : 사놨던 거. 지원되는 자료형이나 저장 형식이 좀 더 다양한 PlayerPrefs 느낌인듯.

  • JSON으로 저장
    : 제일 일반적인 방법인듯. 일단 이걸로 하다가 한계 느끼거나 너무 불편하거나 하면 Easy Save 시도해보자.

JSON으로 맵 정보 저장

  • 노트 transform
  • 노트 순서
  • 노트 종류

이 3가지를 저장하면 된다

NewBlock.GetComponent<NoteBlock>().order = recentNoteIndex++;

순서는 임시로 시작할 때 0으로 초기화해놓고

transform을 건들려고 했는데
그전에 생성될 때마다 이전 블럭에서 길이만큼 진행된 위치에 놓는 걸 먼저 했어야 됐음

이거 다 필요없다

드롭다운에서 인덱스값을 주니까 어쩔 수 없이 enum으로 했던 건데,
그냥 인덱스로 다시 드롭다운 텍스트 접근한 값 가져오고
그 값으로 딕셔너리 쓰면 됐던 거잖아

public class NoteBlock : MonoBehaviour
{
    public string noteLength;
    public int direction;
    public int order;
    
    public GameObject prevNote;
    public GameObject nextNote;
}

노트 블럭에는 이 다섯 개를 넣고

    void InitNoteVariables()
    {
        notePrefabs = new Dictionary<string, GameObject>
        {
            { "DottedWholeNote", DottedWholeNote },
            { "WholeNote", WholeNote },
            { "DottedHalfNote", DottedHalfNote },
            { "HalfNote", HalfNote },
            { "DottedQuarterNote", DottedQuarterNote },
            { "QuarterNote", QuarterNote },
            { "DottedEighthNote", DottedEighthNote },
            { "EighthNote", EighthNote },
            { "DottedSixteenthNote", DottedSixteenthNote },
            { "SixteenthNote", SixteenthNote }
        };

        noteDirections = new Dictionary<string, int>
        {
            { "Up", 90 },
            { "Down", -90 },
            { "Right", 0 },
            { "Left", 180 },
            { "UpRight", 45 },
            { "UpLeft", 135 },
            { "DownRight", -45 },
            { "DownLeft", -135 }
        };
    }

맵 에디터 스크립트에서 이걸 지정해놓고
블럭 새로 만들거나 맵 불러올 때 사용하면 된다.

    private void SetBlockTypeAndInstantiate()
    {
        string duration = DurationDropdown.options[DurationDropdown.value].text;

        NewBlock = Instantiate(notePrefabs[duration], Vector3.zero, Quaternion.identity);
        NewBlock.GetComponent<NoteBlock>().noteLength = duration;
    }

    private void SetBlockDirection()
    {
        string direction = DirectionDropdown.options[DirectionDropdown.value].text;

        NewBlock.transform.rotation = Quaternion.Euler(0, 0, noteDirections[direction]);
        NewBlock.GetComponent<NoteBlock>().direction = direction;
    }

그렇게 일단 여기까지 수정

    public GameObject prevNote;
    public GameObject nextNote;

이것들은 어떻게 할까 생각하다가,
애초에 필요없는 정보라는 걸 알게됨.

JSON 파일에 1번째 블럭, 2번째 블럭 ... 이렇게 모든 블럭에 대한 정보가 있을텐데
굳이 연결 리스트로 해야 할 이유가?

최적화하려면 transform이 추가로 필요할듯.
맵 불러올때마다 배치하는 건 비효율적이니까 한 번만 transform 계산하면 되도록.
굳이 담을 필요는 없음. 어차피 해당 GameObject의 transform 가져오면 되는거니까.
JSON 파일에는 저장해야 함.

새로 생성할 땐 마지막 블럭 위치에서 마지막 블럭 길이만큼 마지막 블럭 방향대로 간 다음 배치하고 transform 저장.
맵 불러올 땐 인덱스 0부터 하나씩 transform을 불러와서 갖다 붙이기. 점선은 어떡하지? 나중에 다 구현한 다음에 점선 로직만 껴넣으면 될듯.
중간에 있는 블럭 편집할 땐 해당 블럭과 나머지 블럭 전부 destroy한 후에 해당 블럭은 종류랑 길이 갱신해서 배치, 나머지 블럭들은 transform들만 갱신해주기.

[System.Serializable]
public class test
{
    public int testvar;
}

public class JSONtest : MonoBehaviour
{
    private test testdata;
    private string filePath;

    // Start is called before the first frame update
    void Start()
    {
        filePath = Path.Combine(Application.persistentDataPath, "playerData.json"); // 경로랑 파일 이름 지정하고
        testdata = new test { testvar = 10 }; // 직렬화된 클래스 데이터 만들고
        string json = JsonUtility.ToJson(testdata); // JSON으로 만들고
        File.WriteAllText(filePath, json); // 파일에 쓰고
        Debug.Log(json);
    }
}

오 됐다
생각보다 간단할지도
easy save 안 써도 되겠다

저장돼있는 JSON 목록 드롭다운에 불러오는 것도 해야됨

missing the class attribute 'ExtensionOfNativeClass'!

[System.Serializable]
public class NoteBlock
{
    public string noteLength;
    public string direction;
    public int order;
    public Transform transform;
}

noteblock.cs에 이렇게 써놓고 플레이했더니
'NoteBlock' is missing the class attribute 'ExtensionOfNativeClass'!라는 오류가 떴다.

gpt한테 물어봤더니

지금 NoteBlock 클래스는 Unity에서 직렬화 가능한 데이터 클래스로 설계된 것처럼 보입니다.
하지만 Unity 오브젝트(MonoBehaviour나 ScriptableObject)처럼 사용하려고 시도했기 때문에 오류가 발생한 것입니다.

라고 함. 그래서 어쩌냐고 물어봤더니

직렬화 가능한 데이터 클래스와 MonoBehaviour 클래스를 분리하여 설계합니다.
MonoBehaviour는 GameObject에 붙이고, 데이터를 담는 클래스는 JSON으로 저장합니다.

public class NoteBlock : MonoBehaviour
{
    public string noteLength;
    public string direction;
    public int order;
}

이건 noteblock.cs에 넣고

[System.Serializable]
public class NoteBlockData
{
    public string noteLength;
    public string direction;
    public int order;
    public Transform transform;
}

이건 mapeditor.cs에 넣었다.

각 오브젝트에 붙어있는건 NoteBlock class,
그 데이터들 모아서 JSON으로 파싱할 때 쓸 건 NoteBlockData class.

해결 완료.

[System.Serializable]
public class MapData
{
    public List<NoteBlockData> noteBlockDataList;
}

아 아니다 딱 알았다 이렇게 하면 됨

아아아 진짜 알았다

유니티 내장 쓰지 말고 easy save 써야겠다
사람들이 많이 쓰는 이유가 있었는듯
개불편하네

맵 하나 불러오려는데 이렇게나 많은 기능이 추가로 필요함



🎮 애셋 정리


그동안 무료로 받음 / 메가 번들 / 충동 구매 등으로 쌓인 유니티 애셋들을 기능과 함께 한 곳에 모아 정리했다.

profile
너 정말 **핵심**을 찔렀어

0개의 댓글