TIL(Today I Learned)

Unity 팀 프로젝트 : Invoke의 사용법, BPM 계산 - TIL#26

Najdorf 2024. 1. 29. 21:47
728x90

어려웠던 점 & 문제 상황

 

1. BPM 계산이 조금씩 계속 어긋나는 현상

 

 

문제 상황

BPM(Beats Per Minute, 분당 비트 수)에 맞춰 플레이어 hp UI가 작아졌다 커지도록 하려고 했지만

무슨 이유에서 인지 계산 식은 정확하나 계속 애니메이션과 박자가 맞질 않는 것이었다.

 

 

원인 추정

박자 하나가 가지는 길이는 60초 / BPM 이다. 가령 BPM이 120이라면 4분박 하나 당 0.5초의 길이를 가진다.

그래서 내가 작성했던 코드는 일종의 반복문을 만들고 박자 하나 만큼의 길이를 넘는 조건을 만족하면

시간이 0으로 초기화 되는 방식이었다.

private void CheckBPM(int bpm)
{
    chunk = 60f / bpm;
    timer += Time.deltaTime;

    if (timer >= chunk)
    {
        MakeScaleMin();
        Invoke("MakeScaleOrigin", chunk / 3);
        timer = 0
    }
}

그러나 내가 생각 못했던 것은, Unity도 결국은 작은 단위의 프레임으로 Time.deltaTime을 계산하기 때문에,

정확히 박자 하나 만큼의 길이랑 일치하지 않는 경우가 많다는 것이다. 즉, 예상한 것보다 살짝 더 긴 오차가 생긴다.

위의 코드에서 알 수 있는 오류는, 조건을 만족하면 조건에 쓰이는 변수를 다시 초기화 한다는 데 있었다.

점점 작은 오차들이 쌓여 큰 오차를 만드는 식인 것이다.

 

 

문제 해결

이 같은 문제를 해결하기 위해, 조건에 쓰이는 변수는 건드리지 않고, 새로운 조건만 추가해 주는 방식을 구상했다.

count라는 반복 횟수를 나타내는 int형 변수를 할당해 조건에 그 만큼을 곱해주는 것이다.

private void CheckBPM(int bpm)
{
    chunk = 60f / bpm;
    timer += Time.deltaTime;

    if (timer >= chunk * count)
    {
        MakeScaleMin();
        Invoke("MakeScaleOrigin", chunk / 3);
        count++;
    }
}

이렇게 되면 조건식을 검사하는데 있어서 약간의 소소한 딜레이가 있을 수는 있어도 전체적으로 보면 작은 오차가 쌓여 큰 오차를 만들 염려는 없게 된다.

 

 

 

2. Unity에서의 지연 기능 구현, Invoke와 작동 순서

 

 

문제 상황

카메라의 특정 동작(좌우로 휙 돌기, 줌 인/아웃)을 구현하기 위해 함수를 지연시켜서 실행시키고 싶었는데, 마침 검색하니 Unity엔 Invoke라는 메서드가 있다고 한다. 그런데, 내가 원하는 메서드의 실행 지연은 호출했을 때 그 메서드가 모든 동작이 끝날 때까지 이후 메서드도 대기하고 있는 상태였는데, 그건 아니고 단지 동시 실행인데 해당 메서드만 지연 호출하는 것이었다. 

 

또한, 매개 변수가 있는 메서드의 경우엔 Invoke 메서드를 사용해 매개변수를 지정할 수 없다는 치명적인 단점이 있었다.

카메라가 특정 동작을 마치고 나면 원래 값을 받아 다시 처음 상태로 돌려놔야 하는데, 이걸 어떻게 구현을 해야할까 고민이 많이 되었다.

 

 

문제 해결

우선, 차례차례 메서드를 의도한 순서에 맞게 실행하는 경우는 Invoke를 여러 개 써서 하는 방법도 있지만, 나 같은 경우엔 꼬리 물기 식으로 필요한 메서드를 Invoke로 계속 호출하는 방식을 사용했다. 메서드가 많아져서 코드가 난잡해지지는 않을까 걱정이 됐으나, 의외로 각 기능들을 메서드화 시켜놓으니 메서드의 이름 자체가 주석과도 같은 기능을 해서 읽기 더 편해진 감이 있었다.

 

매개 변수가 필요한 메서드의 지연 실행의 경우는, Getter Setter로 임시 변수 'Temp'를 필드에서 정의한 뒤, 이를 필요할 때 매개 변수처럼 쓰는 식으로 했다. 이 경우는 여러 매개 변수가 필요한 경우 난잡해질 수 있지만, 내가 사용할 메서드는 원본 값 하나만 필요하기 때문에 별 문제 없이 해결했다.

 


 

알게 된 사실

 

1. Invoke를 사용한다고 해당 메서드가 실행한 이후에 나머지 코드가 실행되는 것은 아니다.

2. Invoke말고도 coroutine을 사용해서 지연 실행 기능을 사용할 수 있다. 다만 해당 기능은 내가 잘 알지 못하기 때문에 시간을 들여서 나중에 알아봐야 할 것 같다.

3. Mathf.Lerp(a, b, t)를 이용해 부드러운 움직임을 구현할 수 있다. 해당 메서드는 a부터 b까지 값 중 t인 지점을 선택한다는 것이다. 따라서, t를 시간에 따라 부드럽게 값을 변화시켜 앞서 말한 기능을 구현해 볼 수 있다.

 

 

728x90