하나 만들고
하나 지우고
저장해봤는데
하나 만들었던 데이터가 그대로 남아있었음
저장용으로 관리하는 리스트가 뭔가 문제가 생긴듯
// 해당 블럭과 이후의 모든 블럭 삭제
WipeBlocksFromTheBlock(SelectedBlock);
Debug.Log("삭제 전 리스트 길이: " + noteBlockDataList.Count);
// 리스트에서 해당 블럭 이후의 모든 블럭 정보 삭제
Debug.Log("NoteAllocateIndex: " + NoteAllocateIndex);
noteBlockDataList.RemoveRange(NoteAllocateIndex - 1, noteBlockDataList.Count - NoteAllocateIndex);
Debug.Log("삭제 후 리스트 길이: " + noteBlockDataList.Count);
매개변수를 잘못 넣어줬었음
해결완료
// 해당 블럭과 이후의 모든 블럭 삭제
WipeBlocksFromTheBlock(SelectedBlock);
Debug.Log("삭제 전 리스트 길이: " + noteBlockDataList.Count);
// 리스트에서 해당 블럭 이후의 모든 블럭 정보 삭제
Debug.Log("NoteAllocateIndex: " + NoteAllocateIndex);
if (NoteAllocateIndex == 0) noteBlockDataList.RemoveAt(0);
else noteBlockDataList.RemoveRange(NoteAllocateIndex - 1, noteBlockDataList.Count - NoteAllocateIndex);
Debug.Log("삭제 후 리스트 길이: " + noteBlockDataList.Count);
0일 때 오류 뜨길래 if문 추가
이렇게 하면 0번째 하나만 지움
이후 블럭들 다 지워줘야 하는데 뭐한거
if (NoteAllocateIndex == 0) noteBlockDataList.RemoveRange(0, noteBlockDataList.Count);
고쳤다
아니었다
if (NoteAllocateIndex == 0) noteBlockDataList.RemoveRange(0, noteBlockDataList.Count);
else noteBlockDataList.RemoveRange(NoteAllocateIndex, noteBlockDataList.Count - NoteAllocateIndex);
진짜진짜 정상 작동
돌겠네 왜 점점 싱크가 이상해지는거지
메트로놈 틀고 음원 확인해보니까 음원 문제는 아님
회전하는 거에 뭐가 문제가 있는걸까

회전 안 시켜도 박자가 점점 틀어진다
Unity Version : 2022.3.49f1
위의 코드는 플레이어를 이동시키고, 플레이어가 노트 블럭에 들어왔을 때 해당하는 키를 누르면 판정을 하는 코드이다.
나는transform.Translate(goingDirection * speed * (float)dspTimeGap);를 통해
오브젝트가 오디오의 싱크와 동일하게 움직이게 하고 싶었다.
하지만, 처음엔 박자가 맞았지만 시간이 지날수록 점점 박자가 일치하지 않고 살짝 느려지기 시작했다.
메트로놈을 틀고 음원의 박자를 확인해보았지만, 음원은 12/8 박자를 잘 유지하고 있었다.
TurnWithNewPos()의 문제인건가 생각이 들어 일직선으로 가는 코스를 만들어 방향 전환이 이루어지지 않게도 해봤지만,
역시 박자가 점점 분리되는 현상을 피할 수 없었다.이때, 오브젝트의 움직임과 오디오와의 싱크가 분리되는 문제는 무엇이고 해결책은 무엇인가?
문제 요약
transform.Translate(goingDirection * speed * (float)dspTimeGap); 처럼 매 프레임마다 dspTimeGap(오디오 DSP 시계와의 차이)을 더해 가며 오브젝트를 이동시키고 있는데, 시간이 지날수록 오디오의 박자와 오브젝트 이동이 어긋나(드리프트) 버린다. 한마디로, 오디오와 오브젝트 이동 간의 싱크가 점점 떨어지는 문제가 발생한다.
Unity에서 오디오가 재생되는 “DSP 시계(*)”는 게임 로직이 돌아가는 “메인 스레드(또는 Update() 루프)”와 정확히 같지 않을 수 있습니다.
(*) AudioSettings.dspTime
프레임마다 누적 오차
Update()마다 “이전 프레임의 DSP 시간”과 “현재 DSP 시간”의 차(dspTimeGap)를 계산하여 이동하는 경우, 프레임 레이트가 들쑥날쑥할 때 부동소수점 연산에서의 미세한 오차가 누적될 수 있습니다.double(DSP 시간)에서 float(Unity 내 대부분의 연산)로 변환되는 과정에서도 정밀도 손실이 발생할 수 있습니다.AudioSource.Play()가 시작 지점을 약간씩 다를 수 있음
Play()를 호출하면, 실제로 오디오가 출발하는 시점이 AudioSettings.dspTime과 미세하게 어긋날 수 있습니다. 예를 들어 오디오 버퍼의 적재 타이밍, 플랫폼 레이턴시 등으로 인해 1~수 ms 정도 오차가 생길 수 있습니다.가장 권장되는 방법은, 오브젝트 이동을 ‘누적’ 방식이 아니라 ‘절대’ 방식으로 계산하는 것입니다. 즉, 현재 AudioSource(또는 DSP 시계)가 재생된 총 시간을 한 번에 가져와서, 그 절대값에 맞춰 오브젝트 위치를 결정하면 부동소수점 오차가 누적될 일이 거의 없어집니다.
// 예시 1) AudioSettings.dspTime을 시작 시점부터 절대적으로 이용
double audioStartTime;
bool isPlaying = false;
void StartMusic()
{
// 오디오 시작을 스케줄링(혹은 Play()) 후,
audioStartTime = AudioSettings.dspTime;
MainMusic.Play();
isPlaying = true;
}
// Update
void Update()
{
if (isPlaying)
{
double currentDSPTime = AudioSettings.dspTime;
double elapsed = currentDSPTime - audioStartTime;
// 경과된 '절대 시간' 사용
float distance = (float)(elapsed * speed);
transform.position = initPosition + goingDirection * distance;
}
}
// 예시 2) AudioSource.time 을 이용해 절대 시간으로 이동
void Update()
{
if (MainMusic.isPlaying)
{
float currentTime = MainMusic.time; // 재생 시간(초)
float distance = currentTime * speed;
transform.position = initPosition + goingDirection * distance;
}
}
이렇게 하면, 각 프레임 사이의 미세한 오차가 누적되지 않고 매 프레임마다 ‘이미 재생된 절대 시간’으로부터 좌표를 바로 구하기 때문에 박자가 어긋나는 현상이 크게 줄어듭니다.
PlayScheduled(double time) 메서드를 사용하면, 오디오 엔진의 DSP 시계를 기준으로 “정말로 샘플 단위”에 가깝게 원하는 정확한 시점에 플레이를 시작할 수 있습니다.audioStartTime = AudioSettings.dspTime + 0.2; 만큼 늦춰서 시작하도록 스케줄링할 수도 있죠. AudioSettings.dspTime - audioStartTime을 이용한 이동에서도 오차가 최소화됩니다.double scheduledTime = AudioSettings.dspTime + 0.2;
MainMusic.PlayScheduled(scheduledTime);
audioStartTime = scheduledTime;
double dspTime에서 float로 변환 시 발생하는 정밀도 손실을 줄이려면, double로 처리한 뒤에 마지막에 한 번만 float로 캐스팅해 주는 것이 좋습니다.float 계산으로 충분히 커버 가능하면 문제없지만, 장시간(수십 분~수 시간) 재생하는 경우엔 누적 오차가 좀 더 커질 가능성이 있습니다.Update() / LateUpdate() / FixedUpdate() 중 어디에서 수행되는지도 중요합니다.FixedUpdate() 보다는 Update() 쪽이 낫습니다.얘도 이거랑 비슷한 얘기하네
저번에 '이건 내 게임에 적용할 건 아닌 것 같은데?' 라고 생각하고 넘겼는데
다시 검토해봐야할듯
판정 로직 작동 안 하도록 첫번째 블럭 다음 블럭들은 전부 인덱스를 3으로 만들어버림
그랬더니 메트로놈에 맞게 작동함
즉, 싱크가 틀어지지 않음
TurnWithNewPos() 문제가 맞는 것 같음
추가로 실험을 해보았는데, TurnWithNewPos()을 아예 작동하지 않도록 했더니 오디오 싱크가 박자에 맞게 잘 유지된다.
TurnWithNewPos()에 무언가 싱크를 흐뜨러뜨리는 문제가 있는 것 같은데,
이 문제가 맞는지 생각해보고, 해결책을 생각해보아라.
TurnWithNewPos() 메서드를 무력화하자마자 싱크가 맞기 시작했다면, 싱크 문제의 “직접적인” 트리거(원인 제공자)는 TurnWithNewPos() 내의 위치/회전 조정 로직일 가능성이 큽니다.
즉, “오브젝트를 오디오 재생 시간에 맞춰 이동시키는 로직”과 “TurnWithNewPos()에서 임의로 강제 보정하는 로직”이 서로 간섭을 일으키고, 그 결과로 음원과 플레이어 이동의 박자가 어긋나게 되는 것이죠.
코드를 다시 살펴보면, TurnWithNewPos()에서 다음과 같은 작업을 수행합니다:
다음 방향 계산
transform.rotation을 회전.오브젝트 위치를 재조정
centerPos = missionBlockCollider.bounds.center(미션 블록의 중심) 기준으로 오브젝트가 어느 정도 떨어져 있는지(distanceToCenter)를 구한 뒤, transform.position을 다시 세팅.이 과정을 매 히트마다(또는 판정 직후마다) 수행하므로, 원래 ‘시간(오디오) 기반으로 누적 이동’시키려던 위치와 TurnWithNewPos()가 “보정해버린 위치” 사이에 미세한 어긋남이 발생할 수 있습니다.
transform.Translate(goingDirection * speed * (float)dspTimeGap)에서 이미 이동시켜 놓은 위치가, TurnWithNewPos() 직후에는 다른 좌표로 “한 번 더” 보정됩니다. 오디오 엔진(또는 AudioSource)의 “절대 시간”으로 이동 거리를 직접 산출하고, 회전이나 이동 보정이 필요할 때도 “현재까지 누적 이동 거리”만 조정해 주는 방식입니다.
예시:
- “현재까지 플레이된 시간” = t (오디오 재생 시간)
- “현재까지 누적 이동해야 할 총 거리” = speed * t
- “(누적 거리)가 어느 구간(노트 블록 구간)에 해당되는지” 판정
- 필요한 경우, 구간 이동 방향이 바뀐다면, 그 시점부터의 “경로(회전 각도)”를 재계산해서 최종 위치를 구함
즉, 프레임마다 [‘절대 시간’ → ‘누적 거리’ → ‘(맵/노트 정보 기반) 경로 상의 좌표’]를 한 번에 계산해내는 식입니다.
이렇게 하면 “프레임 단위로 ‘조금씩’ 보정”하지 않고, 각 프레임에서 오차가 누적되지도 않습니다.
TurnWithNewPos()가 “이미 움직인 좌표”를 더하거나 빼는 식의 상대 이동이 아니라,
이동 경로 혹은 회전 각도를 별도의 데이터(예: ‘현재 구간 인덱스’, ‘방향’, ‘누적 거리’)로 관리하고 그 값을 갱신 후, 최종적으로 transform의 position과 rotation을 절대적으로 세팅하는 접근입니다.
Update()(혹은 LateUpdate())에서는, “(현재 오디오 재생 시간) - (구간 시작 시간) = 구간 진행 시간”을 구해서, “초기 좌표 + 진행 시간 * 속도” 식으로 절대 좌표를 다시 세팅.이렇게 하면 TurnWithNewPos()에서 “지금 transform.position이 얼마였지?”를 기반으로 보정하지 않고, 구간의 시작점(절대 기준)과 시간만을 이용해서 좌표를 설정하게 되어, 부동소수점이나 중복 보정 문제가 줄어듭니다.
만약 지금처럼 “상대 이동 + transform.Translate + TurnWithNewPos()” 구조를 그대로 쓰고 싶다면, TurnWithNewPos()가 transform을 건드리는 시점을 달리 해서, 한 프레임에서 “중복 보정”이 일어나지 않게끔 조정할 수도 있습니다.
Update() → LateUpdate() 순으로 호출된다면, 오브젝트의 이동(Update())이 끝난 뒤에 TurnWithNewPos()가 “마지막에” 좌표 보정을 한다거나, 이러면 특정 시간대에 경로 상에서 어떤 좌표에 있어야 하는지에 대해 다 계산을 해놔야된다는 거 아닌가
그러면 로직에 대공사가 들어가야 한다는 건데
대공사 하고서도 똑같은 문제 발생하면 안되니까 원인이 뭔지를 찾고싶다.

스크립트 내에서의 dspTime 간극은 없는듯
테스트 여러 번 해보니까 싱크가 틀어질 때도 있고 맞을 때도 있음
판정 로직 작동 안 하게 했을 때도 틀어질 수가 있다는 이야기
FMOD를 쓴다고 해도 이동 로직 자체에 문제가 있으면 어차피 데이터 받아와서 내 게임 오브젝트 움직이게 하는게 틀어지는 거라 똑같이 문제 발생할 거임
아무리 생각해도 TurnWithPos()의 문제점을 못 찾겠음
어차피 프레임 단위로 이동하는거라 dspTimeGap을 건드리는 것도 아니고 위치만 상대적으로 바꿔주는 건데 틀릴 일이 없는데

이게 내 문제랑 비슷한 것 같긴 한데 아무리 생각해도 나는 박자를 판정할 필요가 없고 위치 증가만 dspTimeGap만큼 하면 되는거라 상관이 없는데
아니 어차피 dspTimeGap을 다 더하면 현재 음악 위치 되는거 아님?
dspTimeGap 다 더한거랑 dspTimeStart - AudioSettings.dspTimeGap이 일치하는지 확인해보자
디버깅 위해 자료형 변환하다가 생각난건데, 이거 float로 자르는 과정에서 부동소수점 연산이라 소수점 생각보다 많이 날려먹는거 아님? 처음 이 문제 생각했을 땐 문제될 일 없지 않나 생각했는데, 앞 자리수가 커지면 소수점을 못 담아서 정확도가 떨어지는 것 같음. 이게 문제 맞는 것 같다. 확실히 시간이 오래 지나야 점점 틀어지기 시작했던 것 같기도 하고. 심증도 있고 논증도 있는데 문제는 검증을 어떻게 하냐는 것.
아 아닌 것 같기도 하고. 어차피 dspTimeGap = AudioSettings.dspTime - lastdspTime; 이 부분에서 계산할 때 lastdapTime이 double이라 소수점이 날아가지는 않고, 그 상태에서 dspTimeGap이 1.xx~2.xx 정도로 나오면 그걸 기반으로 float로 형변환해서 사용한단 말임?

dspTimeGap 다 더한거랑 dspTimeStart - AudioSettings.dspTimeGap을 비교해봤는데, 조금씩 틀어지긴 함.
근데 심각할 정도로 달라지진 않는다.
이번에는 10번 이상 플레이도 해보고, 여러 번 게임 오버만 해봤는데도 멀쩡하게 잘 돌아감.

문제의 원인은 확실히 찾아낸 것 같다.
sumofdspTimeGap이 소수점 두번째자리부터 이상해지는 건 확인. 싱크도 안 맞음.
show_player가 아닌 진짜 플레이어 sprite renderer의 투명도를 0에서 높였더니 해당 현상 발생하긴 했는데,
그 이후에 에디터에서 플레이 끄고 다시 투명도 0인 상태로 해봐도 싱크가 안 맞음.
메모리 누수? 성능 문제?
아... 이 문제가 아닌건가...
싱크가 안 맞아도 sumofdspTimeGap이 같고,
싱크가 같아도 sumofdspTimeGap이 다른 현상이 발생함.
그냥 문제를 회피하면 안되는 건가
대공사 들어가야되나
아니면 블럭의 정보 기반으로 시간초를 구한 다음 hitsucess()할 때마다 보정하는 미봉책도 있음
dspTimeGap이 플레이어 위치가 이상해지는 것의 강력한 원인으로 보임.
사실 그거 아니면 딱히 이유가 없긴 하니까.
우회책을 찾아보려고 했지만 시간 낭비에 불과했고,
결국 translate가 아닌 dsptime에 1대1 대응되는 좌표로 위치를 업데이트 해주는게 근본적인 해결책이라고 생각됨.
lastBlockStartTime에 박자 길이를 더해주는 건 hitsuccess()에서 해주고
이번 점과 다음 점 사이의 경로에는 여유를 좀 더 추가해줘야하고
nowDirection을 이용한 일종의 1차 함수로 만들어야 하는데
nowDirection과 Vector3로 블럭의 중심과 조금 떨어진 시작점을 만든 뒤에 다음 블럭의 중심에서 또 조금 떨어진 도착점의 Vector3 1차 함수를 만든 후에
lastBlockStartTime와 현재 AudioSettings.dspTime의 차이 delta_dspTime를 x에 넣어주면 해결 완료
update할 때마다 1차 함수로 좌표값을 계산해주고
hitsuccess()할 때마다 경로 1차 함수를 바꿔주면 됨
함수가 바뀌어야 하니까 delegate 써도 될듯
근데 퍼포먼스 이슈가 걱정됨. 안전빵은 매개변수를 매번 가져와서 계산하는게 맞음. 코드도 더 간단할듯.
코드 갈아엎다가 기존 로직에서 쓰이던 dspTimeGap이 Update()와 HitSuccess() 두 곳에서 계산되고 있음을 발견. 정확히는 CheckMusic()을 호출하고, 거기서 dspTimeGap을 계산함. 이것 때문인건가? 생각이 들긴 하지만, 확실하게 가려면 결국 dspTime 기반으로 바꾸는게 맞음. 현혹되지 말자. Update()에 있던 건 필요 없어보이는데 일단 주석처리하고 나중에 문제 생기면 다시 확인.
→ 코드 다시 읽어보니까 Update에 필요한거 맞음. delta_dspTime 계산해야됨.
noteDuration라는 새로운 변수를 map editor에서 새로 기록하는 작업이 필요. 겸사겸사 isEndBlock도 해놓으면 좋을듯. GetComponent로 작동하는 end block 확인 작업을 최적화할 수 있다.
우선 변수 이름들을 다듬는 리팩토링을 먼저 하고 테스트. 기존 durationdropdown을 namedropdown으로 명확하게 바꿈. 해당 드랍다운 정보 기반으로 노트 프리팹 딕셔너리와 길이 값 딕셔너리에 접근하는 식으로. 정상 작동. 커밋.
isEndBlock 넣으려고 했는데 생각해보니까 끝나는 블럭 인덱스 정보를 맵 메타 정보에 넣고 그거 기반으로 하면 좋을 것 같긴 한데
지금은 맵을 파일에서 불러오는게 아니라 데모용으로 얼렁뚱땅 맵 에디터 씬에서 만든 오브젝트들 다른 씬에 복붙하는 식으로 작업하고 있어서 해당 데이터를 불러올 수 없음. 해당 사항은 보류.
리팩토링 하면서 noteDuration도 보이는대로 일단 기록하는거 저장해봤는데 잘 작동하는듯.
이제 어떤 note가 4분음표를 1이라고 했을 때 몇의 길이를 갖고 있는지에 대한 정보를 얻어낼 수 있다.
/// <summary>
/// 경로의 기준점을 노트 길이만큼 이동시킨다
/// </summary>
private void AddNoteDurationToDSP()
{
if (missionBlockScript != null)
{
double noteDuration = missionBlockScript.noteDuration * (60f / bpm);
lastBlockStartTime += noteDuration;
}
}
4분 음표는 60bpm일 때 1초니까 60을 곱해주고 bpm에 반비례하니까 bpm을 나눠준다
private void CheckInbox()
{
int hit1Index = hit1.collider?.GetComponentInParent<NoteBlock>()?.noteBlockIndex ?? -1;
int hit2Index = hit2.collider?.GetComponentInParent<NoteBlock>()?.noteBlockIndex ?? -1;
// Debug.Log("Hit1 : " + hit1Index + ", Hit2 : " + hit2Index);
// Debug.Log("missionBlockIndex : " + missionBlockIndex);
// 이번에 쳐야할 블럭에 진입
if (hit1Index == missionBlockIndex || hit2Index == missionBlockIndex)
{
if (!isInBox)
{
// Debug.Log("InBox");
if (hit1Index == missionBlockIndex) nowBlockCollider = hit1.collider;
else nowBlockCollider = hit2.collider;
nowBlockScript = nowBlockCollider.GetComponentInParent<NoteBlock>();
missionKeyType = nowBlockScript.requiredKeys;
현재 블럭과 다음 블럭의 정보가 필요해서
missionBlockCollider, nowBlockScript라는 것들을
전부 nowBlockCollider, nowBlockScript로 바꿨는데
바꾸고 보니까 왜 missionBlock이라는 이름을 썼는지 납득.
이건 다음 인덱스를 가진 블럭에 들어왔을 때 갱신되는 값임.
이걸 쳤는지 안 쳤는지는 아직 모름.
이 inbox에 들어온 블럭을 기준으로 위치를 계산하려면 direction을 기준으로 어떻게 잘 해보면 되지 않을까 생각했는데
nowblock을 쳤는지 안 쳤는지를 알 수 있어야 이전 블럭과 이어진 선의 좌표에 있을지
다음 블럭과 이어진 선의 좌표에 있을지를 알 수 있다.
차라리 hitsuccess했을 때의 블럭을 nowblock으로 할당하면 될 것 같음.
다행히 undo 기록 살아있어서 전부 다시 되돌렸다.
/// <summary>
/// 판정 성공했을 때 경로선의 기준들을 업데이트한다.
/// </summary>
private void UpdateLineBasis()
{
// 경로의 기준 블럭 위치들을 갱신한다
nowBlockPos = missionBlockCollider.transform.position;
nextBlockPos = missionBlockScript?.nextNoteBlock?.transform.position ?? nextBlockPos;
// 경로의 계산 기준점을 노트 길이만큼 이동시킨다
if (missionBlockScript != null)
{
double noteDuration = missionBlockScript.noteDuration * (60f / bpm);
lastBlockStartTime += noteDuration;
}
// 새로운 방향 벡터 계산
Vector2 missionDirection = Vector3.zero;
if ((missionKeyType & KeyType.Up) != 0) missionDirection += Vector2.up;
if ((missionKeyType & KeyType.Down) != 0) missionDirection += Vector2.down;
if ((missionKeyType & KeyType.Left) != 0) missionDirection += Vector2.left;
if ((missionKeyType & KeyType.Right) != 0) missionDirection += Vector2.right;
nowDirection = missionDirection.normalized;
}
HitSucces했을 때 불러오는 메서드.
nowBlock과 nextBlock 등 경로선에 필요한 변수들을 갱신해준다.
이렇게 되면 Update문에서 직접 호출하는 player_move()함수에서
TurnWithNewPos()의 역할을 대부분 가져가서
TurnWithNewPos()에 있는 방향 해석 부분만 UpdateLineBasis()에 넣어버리면 됐긴 한데
TurnWithNewPos()에 오프셋 측정 부분도 들어있어서 나중에 이 부분 HitSuccess()에 따로 구현해줘야함
일단은 TurnWithNewPos() 주석처리하고 없어진 걸로 생각하기
nowDirection이 필요한건지 의문. 시작점과 끝점만 있으면 굳이 방향벡터를 if문 떡칠해서 구할 필요 없을듯? 삭제함.
/// <summary>
/// 판정용 플레이어 이동
/// </summary>
private void player_move()
{
// nowBlockPos와 nextBlockPos를 포함하는 1차 함수를 만들어서, delta_dspTime에 따른 플레이어 위치를 계산
float newX = (float)(nowBlockPos.x + (nextBlockPos.x - nowBlockPos.x) * delta_dspTime / (missionBlockScript.noteDuration * (60f / bpm)));
float newY = (float)(nowBlockPos.y + (nextBlockPos.y - nowBlockPos.y) * delta_dspTime / (missionBlockScript.noteDuration * (60f / bpm)));
transform.position = new Vector3(newX, newY, transform.position.z);
}
함수 어떻게 짜야할지 그림 그린 다음에 머리 싸매고 있다가 일단 코드 적어봤더니 copilot이 알아서 완성해줬다. 내가 생각했던거 그대로 완성까지 해줘서 깜짝 놀람.
완성하고 당연히 안되겠지 하는 생각에 플레이 눌렀는데 버그는 있긴 한데 일단 이동은 해준다.
디버깅 좀만 더 하면 타이밍의 저주에서 벗어날 수 있다! (아마도)
두번째 블럭에서 바로 게임오버됨

뭔가 이상해서 여러가지 실험해봤는데,
해당 맵이 레거시 데이터라서 이상한게 아니라,
아래 → 오른쪽 조합의 블럭이면 진행이 제대로 안되는 거였음.
이동 로직에서 뭔가 문제가 생긴게 아닐까?

한 칸 띄워서 오른쪽 블럭 놓으면 또 잘만 된다. 뭐임??

일단 디버그 텍스트 넣어봤는데 게임 오버돼도 갱신이 안되는 문제 있음
이것 때문에 한 번 죽으면 다시 시작하기 전에 한 번 더 눌러야됨
게임 오버하면 delta_dspTime 초기화해서 그 문제는 해결했는데
두번째 블럭에서 바로 죽는건 해결 못함
if (hit1Index == missionBlockIndex || hit2Index == missionBlockIndex)
{
if (!isInBox)
{
// 생략
}
}
// hit 전에 블럭을 빠져나감
else
{
if (isInBox)
{
GameOver();
}
}
게임 오버의 조건을 살펴보면
raycast가 블럭 2개 때린게 missionBlockIndex도 아니었는데 아직 inbox에 있었다는 것

추가로 실험해봤는데, DottedQuater 이후부터 판정이 이상해짐. 방향은 관계 없다.
duration이 1 초과면 판정이 이상해진다는 건데
이러면
private void player_move()
{
// nowBlockPos와 nextBlockPos를 포함하는 1차 함수를 만들어서, delta_dspTime에 따른 플레이어 위치를 계산
float newX = (float)(nowBlockPos.x + (nextBlockPos.x - nowBlockPos.x) * delta_dspTime / (missionBlockScript.noteDuration * (60f / bpm)));
float newY = (float)(nowBlockPos.y + (nextBlockPos.y - nowBlockPos.y) * delta_dspTime / (missionBlockScript.noteDuration * (60f / bpm)));
DebugText.text = "newX : " + newX + ", newY : " + newY;
transform.position = new Vector3(newX, newY, transform.position.z);
}
// 경로의 계산 기준점을 노트 길이만큼 이동시킨다
if (missionBlockScript != null)
{
double noteDuration = missionBlockScript.noteDuration * (60f / bpm);
lastBlockStartTime += noteDuration;
}
판정 로직 스크립트에서 noteDuration이 쓰이는 건 이 둘밖에 없다.
여기에 문제가 숨어있을 확률이 높음.

맵 불러올 때 nowBlockPos가 missingRef 뜨는 문제.
이것도 문제와 연관된건진 모르겠지만 일단 보호 구문 넣어서 해결. 관련 없었다.
지금 제일 미치겠는게
길이나 방향에 따라 되는 조합도 있고 아닌 조합도 있고
계속 시도하다보면 어떤 때는 가끔씩 판정이 성공하기도 하고
무엇보다 원인을 어떻게 알아내야할지 막막하다

디버그 로그를 찍어보면 너무나도 정상적으로 나오기 때문.
확실하게 버그 재현은 가능하니까 디버깅 문구 계속 넣다보면 어떻게든 단서는 찾아낼 수 있을 것.
테스트 하던 중에, bpm이 낮으면 (= speed가 낮으면) show_player가 오히려 앞으로 가는 현상 발생하는 거 확인
그때 특정 값으로 고정해놓는게 아니라 역시 실험을 몇 번 더 해봤어야 됐음