부트캠프/본캠프

[내일배움캠프_2025SEP18] 쿨다운 타이머, Stun과 Hit, 잘못된 기즈모 헬퍼...

Young_A 2025. 9. 18. 23:37

목차

    쿨다운 타이머, Stun과 Hit, 잘못된 기즈모 헬퍼...


    잘못된 기즈모 헬퍼는 오히려 독이 된다..

    어제 좌우반전을 구현하고 맞닥뜨린 모습...

    콜라이더가 좌우반전이 되지 않는 줄 알고 콜라이더를.. 또 자식 오브젝트에 넣어서 해야하나?

    근데 그렇게 하면 이제껏 제작했던 애니메이션들 싹 다 뜯어 고쳐야하는데...

     

    진짜 너무 스트레스 받았다.

    전부 재 작업을 해야한다면, 팀원들 모두가 고생했던 애니메이션 작업물을 다시 해야하는 것이기 때문에...

     

    선택해서 애니메이션을 자꾸 재생해보고, 자식 오브젝트에서 만들어보고 하다가, 그제서야 초록색 콜라이더박스가 다시 보였다.

    내가 그려준 빨간 박스는 진짜 콜라이더 위치가 아니었다.

    내가 콜라이더 위치를 가늠해서 그쳐준 기즈모일 뿐이었고 초록색 박스가 진짜 콜라이더의 위치였다.

    콜라이더를 갖고 있는 객체의 transform이 좌우 반전이 되면서, 기즈모 또한 좌우 반전으로 그려줘야 했던 것이다.

    float sign = transform.localScale.x >= 0 ? 1f : -1f;
    
    // 공격 범위 표시
    Gizmos.color = Color.red;
    if (hitbox != null && hitbox.enabled)
    {
        Vector3 offset = hitbox.offset;
        offset.x *= sign;
        Gizmos.DrawWireCube(transform.position + offset, hitbox.size);
    }

    기즈모 또한 현재 transform 기준으로 +1 혹은 -1을 곱해주는 것으로 변경하였다.

     

    저녁 마무리 스크럼 때도 몬스터의 크기가 작은 것 같아서 scale을 키우자는 말이 나왔는데,

    이때 또한 위와 같이 보이는 문제가 발생했다.

    오전에 알아차렸던 문제라서 콜라이더는 정상이지만 기즈모가 잘못 출력되고 있다는 걸 바로 캐치했지만,

    팀원 분 중 한분은 어? 콜라이더만 안 늘어나고 있는 것 같은데요? 라고 반응 하셨다.

     

    객체의 transform을 늘리고, 줄이고, 좌우반전해도 해당 객체의 혹은 자식 객체의 콜라이더는 의도대로 구현되고 있었다.

    다만 내가 출력한 기즈모가 이 변화를 따라가지 못해서 의도대로 구현되지 않는다고 착각을 하게 된 것이다.

    이 때문에 괜히 복잡한 방법으로 가거나 괜찮은 옵션을 선택하지 못할 뻔 했던 상황이었다.

     

    자체로 헬퍼를 제작하면서 QA를 충분히 하지 않고 바로 사용하기 때문이었다.

    이를 감안하고 언제든 문제가 있을 수 있다는 점을 항상 유의하고 있어야겠다.

     

    참고로 사이즈 키우는 것도 금방 수정했다!

    float sign = transform.localScale.x >= 0 ? 1f : -1f;
    float offsetSizeX = transform.localScale.x;
    float offsetSizeY = transform.localScale.y;
    
    // 공격 범위 표시
    Gizmos.color = Color.red;
    if (hitbox != null && hitbox.enabled)
    {
        Vector3 offset = hitbox.offset;
        offset.x *= offsetSizeX;
        offset.y *= offsetSizeY;
        Vector3 scaledSize = new Vector3(hitbox.size.x * Mathf.Abs(offsetSizeX) * sign, hitbox.size.y * Mathf.Abs(offsetSizeY), 1f);
        Gizmos.DrawWireCube(transform.position + offset, scaledSize);
    }

    팀원들 코드로부터 알게된 것들

    [field: SerializeField] 

    프로퍼티를 인스펙터에서 필드처럼 볼 수 있게 해준다!

    캡슐화 때문에 프로퍼티를 쓰는 걸 선호하는 편인데, 인스펙터에서 확인할 수 없어서 안쓰고 있었던지라 정말정말 가뭄 속 단비 같은 정보였다.

    이제까지 구현한 것들을 다시 리팩토링 하기에는 시간 낭비일 것 같고, 다음부터 그렇게 쓰려고 한다.

    rigidbody.MovePosition

    rigidbody 기반으로 원하는 지점까지 이동시켜주는 함수이다.


    쿨다운 타이머 (Time.time과 Time.deltaTime)

    상황: Time.deltaTime의 한계

    행동트리에서 Sequence 노드는 자식 노드가 Running 상태를 반환하면 그 노드에서 실행을 멈춘다.

    만약 2번 노드가 Running을 반환하는 동안 1번 노드는 아예 실행되지 않는다.

    이 때문에 1번 노드 내부의 cooldowntimer가 Time.deltaTime으로 시간을 더하는 방식이라면,

    2번 노드가 실행되는 동안 1번 노드의 타이머는 멈춰있게 된다.

    쿨다운은 다른 스킬들이 실행되는 동안에도 같이 돌고 있어야하기 때문에 논리적인 오류를 발생시키게 된다.

    해결: Time.time의 활용

    Time.time은 게임 시작 이훌부터 현재까지 흘러간 절대 시간을 나타낸다.

    따라서 Time.deltaTime처럼 매 프레임마다 누적시키는 방식 되신,

    특정 시점의 Time.time을 기록해두고, 현재 시간과 비교하는 방식으로 쿨다운을 구현하고자 했다.

     

    각 노드에 lastUsedTime 변수를 추가한 뒤, 다음과 같은 조건으로 스킬 노드를 실행시켰다.

    if (Time.time - lastUsedTime >= cooldownDuration) {
        // 쿨다운이 끝났으므로 노드 실행
        // 실행 후 lastUsedTime = Time.time; 로 갱신
    }

    결론: Time.time vs Time.deltaTime 

    Time.time

    스톱워치처럼 생각하면 된다. 게임이 시작된 이후부터 현재까지 흘러간 총 경과 시간을 의미한다.

     

    쿨다운 타이머, 스킬 대기 시간, 특정 시간이 지나면 발생하는 이벤트, 등

    이벤트 시작 시간을 기록하는 데 적합하다.

     

    Time.deltaTime

    자동차 속도계 같은 거라고 생각하면 된다.

    이전 프레임에서 현재 프레임까지 걸린 프레임 간 시간을 의미한다.

     

    오브젝트 이동, 회전이나 프레임 속도에 관계 없이 일정한 속도를 유지해야할 때와 같이

    시간의 변화량에 따른 계산이 필요할 때 적합하다.


    ICountable 구현! Stun? Hit? 두 상황을 모두 표현해야 하는 상황.

    상황

    플레이어가 몬스터의 공격을 받아칠 수 있어야한다.

    몬스터의 AttackController에 ICountable 인터페이스를 생성하고 CounterAttacked()가 호출되면 MonsterCondition에서 Stun()이 호출되도록 했다.

    이로써 Damage를 받는 hit와 Stun을 받는 counterattack을 구분할 수 있도록 하였다.

     

    다만 문제는, hit 애니메이션과 stun 애니메이션을 동시에 재생할 수 없다는 것이다.

    해결

    다행히도 기획 의도 상 hit 애니메이션은 따로 없고 컬러값만 바뀌도록 하면 되었다.

     

    이전 프로젝트에서는 애니메이션에서 spriteRendere의 color값을 직접 조정했었고, 이로 인해 다른 애니메이션 혹은 효과와 충돌하는 경험이 있었다.

     

    그래서 이번에는 아예 SpriteRenderer의 color값을 조절하는 건 코루틴으로만 실행하였다.

    팀원들과도 애니메이션에서 SpriteRenderer의 color 값을 조정하는 경우는 없도록 하자고 합의하였다.

    private IEnumerator HitAnimation(float duration)
    {
        Color hitColor = Color.red;
        float flashDuration = 0.1f;
        float elapsedTime = 0f;
        while (elapsedTime < duration)
        {
            spriteRenderer.color = spriteRenderer.color == Color.red ? originalColor : hitColor;
            yield return new WaitForSeconds(flashDuration);
            elapsedTime += flashDuration;
        }
    
        spriteRenderer.color = originalColor;
    }

     

    따라서 플레이어가 몬스터에게

    Stun을 주어야할 경우에는 ICountable을,

    Damage를 주어야할 경우에는 IDamagable을,

    Stun과 Damage를 모두 주어야할 경우는 둘 다 호출하면 된다!


    느낀점

    조금 바보 같은 점들을 오늘 많이 깨달은 것 같다.

    chase노드에서 dash와 chase(run)으로 나뉘어서 선택했어야했는데

    알고보니 dash는 스킬로 구현이 되는 부분이었고 추적에 포함되지 않는 것이었다.

    결론적으로 작업량이 줄어들긴 했는데, 그럼 지금 BT에서 ChaseSelector는 쓸모없는 부분이 된다. ...

     

    기즈모 부분도 그렇고, 타이머 부분도 그렇고... 애매하게 알아서 고생한 느낌


    내일 학습 할 것은 무엇인지

    일정상 21일까지가 1차 프로토타입 빌드가 예정되어있지만 주말은 원칙적으로 제외해야하는 법이라고 생각한다.

    따라서 내일까지 한 부분이 1차 프로토타입이 된다.

     

    일단 스킬을 제작하기 위해 나누었던 브랜치들을 정리해야한다!

    누락된게 없는지 마지막으로 체크하고 없애버리기로.

     

    좌우반전시 -1을 곱하는게 아니라 -1을 하고 있다.

    이것도 빠르게 픽스해야함. -> 자러 가기 전에 끝내버렸다.

     

    그리고.. 또 뭐하지?