맵 만드는게 우선이긴 한데
화딱지가 나서 이거 해결 먼저 하고 가야할 것 같음

내 프로젝트에 깔려있는 URP 버전은 14.0.11이니
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@14.0/manual/integration-with-post-processing.html
해당되는 매뉴얼을 보면서 다시 한 번 따라해보기

카메라에 Post Processing 달려있는거 확인했고,
혹시 모르니 URP 애셋을 다시 기본으로 되돌린 후에 다시 새걸로 갈아껴보
아니 애셋을 삭제했는데 갑자기 됨

아니 왜 됨?
어제는 렌더링 파이프 애셋 뭘 어떻게 해도 아무것도 안됐잖아
그리고 뭘 적용한 것도 아니고 그냥 지웠는데 됐다고?

지금은 렌더 파이프라인 애셋이 아예 안 달려있는데????
아예 없어야 된다고???
심지어 원래 프로젝트에 적용하려고 봤더니
Quality에 있는 렌더 파이프 애셋까지 None으로 바꿔야 효과가 적용됨
(영상에서 1번째 Render Pipeline Asset은 기존에 있던 거, 2번째 Render Pipeline Asset은 새로 만든 거)
우연히 Render Pipeline Asset을 삭제했더니 포스트 프로세싱이 적용되기 시작함.
Project Settings를 확인했더니 당연히 Render Pipeline Asset은 None으로 설정돼있음.
다시 애셋을 적용시켜보니까 효과가 꺼짐.
이유 후보
인디 게임 개발 갤러리에 물어는 봤는데, 이유를 알아낼 수 있을까 잘 모르겠음.
핵심 요약
com.unity.postprocessing)와는 호환되지 않습니다. 
유니티 매뉴얼엔 제대로 Volume으로 하라고 돼있었음.
그냥 유니티 매뉴얼 보고 했으면 된건데
유튜브 튜토리얼 영상에 의존하는 나약한 버릇이 만들어낸 끔찍한 시간낭비였다.

해결 완료.
첫번째 곡 : Czerny Op. 599, No. 50 https://musescore.com/user/36486827/scores/6777481
두번째 곡 : Czerny Op.365 No.45 https://musescore.com/user/34770323/scores/6864848
마지막 곡 : Czerny Op.599 No. 60 - 75bpm https://musescore.com/user/36486827/scores/6784997
각 곡 다 하고 나면 어려움 난이도
보너스 곡 : Czerny Op.365 No.8 https://musescore.com/user/34770323/scores/6550225
박자가 살짝 어긋남.
반복 테스트해보니, 누르자마자 오디오가 실행되는게 아니라 살짝 딜레이가 발생함.
일단 저번에 gpt한테 물어봐놨던 오디오 딜레이 문제 해결 방법들 다 써봐야할듯
유니티에서 오디오 파일의 메모리 로드 상태는 Audio Clip의 Load Type 설정에 따라 다릅니다.
재생 딜레이를 줄이려면 Decompress on Load로 설정하십시오.
→ 원래 Decompress on Load로 돼있음
AudioSource.PlayOneShot을 사용하세요. 해당 메서드는 별도의 오디오 소스를 생성하지 않고 바로 사운드를 재생합니다.→ 이것도 안됨
유니티에서는 Preload Audio Data 옵션을 통해 오디오 데이터를 미리 로드할 수 있습니다.
1. 오디오 파일을 선택합니다.
2. Inspector 창에서 Preload Audio Data 옵션을 체크합니다.
3. 이 옵션이 활성화되어 있으면 게임이 시작될 때 해당 오디오 파일이 메모리에 로드됩니다.
→ 이것도 안됨
→ 이건 뭔 소린지 모르겠어서 일단 패스
AudioClip clip = Resources.Load<AudioClip>("Audio/MyAudioFile");
Resources.Load를 통해 특정 오디오 파일을 미리 로드해 둘 수도 있습니다.

→ 안됨
Streaming 옵션으로 설정된 파일은 디스크에서 데이터를 읽어오는 동안 딜레이가 발생할 수 있습니다. 이를 방지하려면 가능한 한 짧은 파일에 대해서는 Decompress on Load를 사용하세요.AudioSettings.dspTime을 사용해 사운드 카드의 지연 여부를 확인하거나, 품질이 낮은 오디오 장치를 사용하는 경우 발생할 수 있는 지연을 테스트하세요.
저번에 애셋 분석할 때도 이런 정보가 있긴 했다.


gpt가 설명하는거 들어보니까 이걸로 잡는게 맞긴 한듯
https://m.blog.naver.com/PostView.naver?blogId=theo5970&logNo=222041204275
얼불춤 개발자가 작성한 정답지를 찾아냈다
모든 것을 AudioSettings.dspTime을 기준으로 작동하게 만들면 된다고 한다.
말은 쉬운데, 내용 이해하는 것도 오래 걸렸고, 구현하려니까 막막하고 가슴이 답답하고 막
if (missionBlockScript != null) missionBlockScript.DisableCollider();
nullref 오류 때문에 스크립트가 멈춰버린 탓
보호 구문 추가로 해결
inputfield 만들고 인덱스 갱신만 해주면 될 줄 알았는데
맵을 불러오면 뜬금없이 게임이 시작돼버리는 버그 발생
아무것도 없을 때 missionKeyType도 아무것도 없어서 그런건가
치명적인거 아니니까 넘어가기
private List<NoteBlockData> noteBlockDataList; // 노트 블럭 정보 저장용 리스트
private Vector3 spawnPosition; // 블럭 생성 위치
private int NoteAllocateIndex; // noteBlockDataList 접근용 인덱스
private GameObject NewBlock; // 새로 생성한 블럭
private GameObject LastBlock; // 마지막으로 생성한 블럭
이 변수들 갱신해줘야 됨
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<NoteBlock>().noteBlockIndex = noteBlockData.order;
noteBlock.transform.SetParent(MapRoot.transform);
spawnPosition += GetDisplacement(noteBlockData);
NoteAllocateIndex++;
}
쓰레기 같은 코드 좀 내가 원래 썼던 코드 이용해서 줄여달라고 했더니
foreach (NoteBlockData noteBlockData in noteBlockDataList)
{
DurationDropdown.value = DurationDropdown.options.FindIndex(option => option.text == noteBlockData.noteLength);
DirectionDropdown.value = DirectionDropdown.options.FindIndex(option => option.text == noteBlockData.direction);
InstantiateBlock();
NewBlock.GetComponent<NoteBlock>().noteBlockIndex = noteBlockData.order;
NoteAllocateIndex++;
}
아름답게 바꿔줬다. dropdown을 바꿔버리는구나.
private void ConnectBlocksAndUpdateLastBlock()
{
// 이전 블럭과 쌍방 연결
if (NoteAllocateIndex != 0)
{
NewBlock.GetComponent<NoteBlock>().prevNoteBlock = LastBlock;
LastBlock.GetComponent<NoteBlock>().nextNoteBlock = NewBlock;
}
LastBlock = NewBlock;
NoteAllocateIndex++;
}
하는 김에 이것도 묶어서 재사용
foreach (NoteBlockData noteBlockData in noteBlockDataList)
{
DurationDropdown.value = DurationDropdown.options.FindIndex(option => option.text == noteBlockData.noteLength);
DirectionDropdown.value = DirectionDropdown.options.FindIndex(option => option.text == noteBlockData.direction);
InstantiateBlockAndStoreInformation();
ConnectBlocksAndUpdateLastBlock();
}
메서드 이름이 기니까 기분 나쁘긴 한데 직관성은 올라갔다.
?? 리팩토링 하고 나니까 급발진 게임 시작도 사라졌다.
맵 에디터가 제대로 기능하기 시작
카메라가 플레이어에 고정돼있어서 블럭 클릭하고 삭제를 못함
버튼 누르면 플레이어 고정 활성화/비활성화하는 스크립트 만들어야 함
public class MapCameraHandler : MonoBehaviour
{
[SerializeField] float moveSpeed;
[SerializeField] Cinemachine.CinemachineVirtualCamera virtualCamera;
[SerializeField] GameObject player;
bool isFollowingPlayer = true;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
isFollowingPlayer = !isFollowingPlayer;
}
if (isFollowingPlayer && virtualCamera.Follow == gameObject.transform)
{
virtualCamera.Follow = player.transform;
}
else if (!isFollowingPlayer && virtualCamera.Follow == player.transform)
{
gameObject.transform.position = player.transform.position;
virtualCamera.Follow = gameObject.transform;
}
if (!isFollowingPlayer)
{
MoveCamera();
}
}
void MoveCamera()
{
float x = Input.GetAxis("Horizontal");
float y = Input.GetAxis("Vertical");
Vector3 move = new Vector3(x, y, 0) * moveSpeed * Time.deltaTime;
transform.Translate(move);
}
}
만듦
아 만들고 나니까 카메라 움직이려는 키 입력인데 플레이어가 움직여버림
wasd로 할 수도 있겠지만
과감하게 스크립트 꺼버리자
GetAxis 편해서 쓰고 싶다
저번에 만들 필요 없지 않나? 했는데
첫 맵 만들려고 하자마자 바로 필요해지는 상황이 발생
블럭을 편집하면
새 정보를 가진 블럭을 만든 후에
원래 있던 블럭 정보를 복사해서 넣고
정보 리스트에 있는 해당 블럭 정보를 갱신한 후에
원래 있던 블럭과 이후 블럭들을 전부 지워버리고
새로 만들어진 블럭 뒤에 있는 블럭들을 전부 다시 생성한다
private void RegenerateBlocksFromTheIndex(int index)
{
if (index == 0) spawnPosition = Vector3.zero; // 첫 블럭이면 위치 초기화
NoteAllocateIndex = index; // 시작 인덱스 설정
for (int i = index; i < noteBlockDataList.Count; i++)
{
DurationDropdown.value = DurationDropdown.options.FindIndex(option => option.text == noteBlockDataList[i].noteLength);
DirectionDropdown.value = DirectionDropdown.options.FindIndex(option => option.text == noteBlockDataList[i].direction);
InstantiateBlockAndStoreInformation();
ConnectBlocksAndUpdateLastBlock();
}
}
LoadMap에서도 쓰는 로직을 재사용하기 위해 메소드 분리해주고
void ChangeBlock()
{
GameObject SelectedBlock = ObjectSelector.GetComponent<ObjectSelector>().selectedObject;
if (SelectedBlock == null)
{
Debug.Log("No selected block. Change failed.");
return;
}
InstantiateBlockAndStoreInformation(); // 새 블럭 생성
DuplicateBlockData(SelectedBlock); // 정보 복사
UpdateBlockDataInList(); // 리스트 정보 갱신
ObjectSelector.GetComponent<ObjectSelector>().selectedObject = NewBlock;
// 선택된 블럭과 이후 블럭 모두 삭제 후 새로 생성
WipeBlocksFromTheBlock(SelectedBlock);
spawnPosition += GetDisplacement(new NoteBlockData { noteLength = DurationDropdown.options[DurationDropdown.value].text, direction = DirectionDropdown.options[DirectionDropdown.value].text });
RegenerateBlocksFromTheIndex(1 + NewBlock.GetComponent<NoteBlock>().noteBlockIndex);
}
완성. spawnPosition 코드가 좀 더럽긴 한데 어쩔 수 없음.
맵이 한 번 이상해지고 나면 저장도 안된다;
"noteLength" : "Quarter",
"direction" : "Right",
"order" : 22
},{
"noteLength" : "DottedHalf",
"direction" : "Down",
"order" : 23
},{
"noteLength" : "Quarter",
"direction" : "Left",
"order" : 24
},{
"noteLength" : "Quarter",
"direction" : "Left",
"order" : 25
},{
"noteLength" : "Quarter",
"direction" : "Left",
"order" : 26
},{
"noteLength" : "DottedHalf",
"direction" : "Down",
"order" : 27
},{
"noteLength" : "DottedHalf",
"direction" : "Down",
"order" : 23
},{
"noteLength" : "Quarter",
"direction" : "Left",
"order" : 24
저기요 왜 23이 다시 시작되시나요
알 것 같다
삭제할 때 리스트에 있는 것들은 삭제를 안 해줘버림
// 이전 블럭과 다음 블럭 연결
var selectedBlockIndex = SelectedBlock.GetComponent<NoteBlock>();
if (selectedBlockIndex.prevNoteBlock != null)
{
selectedBlockIndex.prevNoteBlock.GetComponent<NoteBlock>().nextNoteBlock = selectedBlockIndex.nextNoteBlock;
}
블럭을 지우면 해당 블럭만 지워지는 로직을 위한 코드도 있길래 삭제해주고
private void WipeBlocksFromTheBlock(GameObject TheBlock)
{
LastBlock = TheBlock.GetComponent<NoteBlock>().prevNoteBlock;
NoteAllocateIndex = TheBlock.GetComponent<NoteBlock>().noteBlockIndex;
spawnPosition = TheBlock.transform.position;
while (TheBlock != null)
{
GameObject temp = TheBlock.GetComponent<NoteBlock>().nextNoteBlock;
Destroy(TheBlock);
TheBlock = temp;
noteBlockDataList.RemoveAt(noteBlockDataList.Count - 1);
}
}
noteBlockDataList.RemoveAt(noteBlockDataList.Count - 1); 이거 추가
지우는 블럭만큼 리스트에서도 똑같은 개수의 데이터가 사라짐
아 잠깐만 리스트를 아직 안 지우는 이유가 있었구나
WipeBlocksFromTheBlock()에서 데이터를 지워버리면 편집할 때 재생성을 못함
void DeleteBlock()
{
GameObject SelectedBlock = ObjectSelector.GetComponent<ObjectSelector>().selectedObject;
if (SelectedBlock == null)
{
Debug.Log("No selected block. Delete failed.");
return;
}
// 해당 블럭과 이후의 모든 블럭 삭제
WipeBlocksFromTheBlock(SelectedBlock);
// 리스트에서 해당 블럭 이후의 모든 블럭 정보 삭제
noteBlockDataList.RemoveRange(NoteAllocateIndex - 1, noteBlockDataList.Count - NoteAllocateIndex - 1);
}
블럭 정보 삭제는 DeleteBlock()에만 따로 넣어줬다
아 인덱스 분명 맞다고 생각했는데
"value" : [
{
"noteLength" : "Quarter",
"direction" : "Left",
"order" : 0
},{
"noteLength" : "Quarter",
"direction" : "Right",
"order" : 4
},{
"noteLength" : "Quarter",
"direction" : "Right",
"order" : 5
},{
"noteLength" : "Quarter",
"direction" : "Up",
"order" : 2
},{
"noteLength" : "Quarter",
"direction" : "Up",
"order" : 3
}
]
이렇게 들어가버리네
뭐가 잘못된거지
타이밍 맞추는거랑
폴리싱 작업 이후에
나머지 맵들 만들자
맵 편집 이슈는 나중에 재발하면 그때 고치고
맵 도착 블럭 디자인이랑 로직 만들어야됨
타이밍 맞추는게 좀 시간 걸릴듯