ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Hitbox Controller Refactoring 및 Hitbox Debug Tool
    게임 클라이언트 개발/MakeDNF 2024. 4. 17. 00:48

    이번 글에선 Hitbox Controller 클래스와 Hitbox 클래스를 리펙토링 한 내용과 플레이 모드 진입 시 설정된 Hitbox가 정상적으로 작동하는지 Scene에서 확인할 수 있는 Debugging 툴에 대해 작성하고자 한다.

     

    너의 역할은?

    히트 박스의 범위 및 충돌 로직이 작성되어 있는 Hitbox 클래스와 이를 관리하는 HitboxController 클래스를 통해 2.5D 좌표계에서의 충돌 시스템을 구현했었다. Hitbox 클래스에서 HitboxType, Size, Offset, Pivot 멤버 변수를 통해 히트 박스의 범위를 minHitboxPos, maxHitboxPos로 계산하고, AABB 충돌 알고리즘을 구현한 멤버 함수를 통해 히트 박스의 충돌 검사 로직을 수행했다. HitboxController 클래스에선 오브젝트에 할당된 Hitbox 배열을 멤버 변수로 갖고, activeHitbox 멤버 변수에 현재 활성화된 Hitbox 클래스를 할당/제거하여 히트 박스를 활성화/비활성화시켰다.

    Hitbox Controller 클래스와 Hitbox 클래스의 Class UML

     

    그런데 Hitbox 클래스의 역할에 대해 의구심이 들기 시작했다. Hitbox 클래스는 HitboxController 클래에 의해 관리되는데, Hitbox 클래스에서 충돌 처리 로직이 다 들어가 있는 게 맞나? Hitbox 클래스는 오브젝트의 히트 박스를 설정하기 위한 클래스 아닌가? HitboxController끼리 현재 활성화되어 있는 히트 박스로 충돌 처리 로직을 수행해야 하는 것 아닌가?

     

    그래서 Hitbox 클래스는 오브젝트의 히트 박스 범위나 모양을 설정하는 클래스로, HitboxController 클래스는 히트 박스의 정보를 멤버 변수로 갖고, 이를 활용하여 다른 HitboxController 클래스와 충돌 로직을 수행하도록 두 클래스의 멤버들을 수정했다. 또한 현재 활성화된 히트 박스를 가리키는 멤버 변수로 Hitbox 클래스 타입이 아닌 int형 타입의 index로 수정하여 참조 비교를 최대한 지양하였다.

    새로운 Class UML

     

    기존 Hitbox Editor의 문제점

    기존 Hitbox Editor는 UnityEditor.Editor를 상속받는 클래스로 Hierarchy에서 HitboxController 컴포넌트가 할당된 오브젝트를 선택했을 때 활성화된다. Edit Mode에서 히트 박스의 값을 설정할 수 있고, Play Mode에서 모든 히트 박스 오브젝트의 범위를 Scene GUI로 표현해 줬다. 

    Edit Mode(왼쪽)와 Play Mode(오른쪽)

     

    Edit Mode에서 히트 박스를 수정하고 싶은 오브젝트를 클릭하고, 값을 수정하는 것까진 의도한 바였다. 하지만 Play Mode에서 히트 박스가 어떻게 설정되어 있는지 확인하기 위해선 Hierarchy에서 HitboxController 컴포넌트가 할당된 오브젝트를 다시 찾아서 클릭해야 했다. 만약 다른 로직을 확인하기 위해서 다른 오브젝트를 클릭하여 포커싱이 변경될 경우, 해당 오브젝트에 HitboxController 컴포넌트가 없다면, 히트 박스 범위를 보여주는 Scene GUI들이 바로 꺼졌다. 또한, 모든 히트 박스가 Scene GUI에 출력되어 오브젝트가 너무 많을 경우 어떤 오브젝트의 히트 박스인지 확인할 수 없었다

    히트 박스가 너무 많이 겹쳐서, 확인을 제대로 할 수 없다.

     

    또 다른 문제점으로, Hitbox 클래스의 멤버 변수들(Size, Offset, Pivot 등)은 Editor 클래스를 제외한 외부 클래스에선 수정할 수 없도록 제한하고 싶었다. 또한 각 멤버 변수를 반환할 일도 없다고 생각하여 private 접근 지정자로 Set, Get 로직을 모두 제한했다. 하지만, 스크립트에선 에디터로 히트 박스를 설정하기 위해 HitboxEditor 클래스에서 Hitbox 클래스로의 접근이 필요했다. 그래서 #if 전처리기 지시문을 통해 Editor에서만 Hitbox 클래스의 멤버 변수들에 접근할 수 있도록 코드를 작성했다.

    public class Hitbox
    {
        #region Variables
        
        [SerializeField] private Vector3 size = Vector3.zero;
            
        // .. Other variables
        
        #endregion Variables
        
        // Unity 에디터에서만 프로퍼티가 작성되도록 작성. 빌드 시 UNITY_EDITOR 사이의 코드는 삭제됨.
    #if UNITY_EDITOR 
        #region Properties
    
        public Vector3 Size
        {
            set
            {
                Vector3 input = value;
                
                if (input.x < 0f) input.x = 0f;
                if (input.y < 0f) input.y = 0f;
                if (input.z < 0f) input.z = 0f;
    
                size = input;
            }
            get => size;
        }
        
        // .. Other properties
        
        #endregion Properties
    #endif
    }

     

    하지만, 개발을 하다 보면 아무리 주석을 작성하더라도 확인하지 못하는 경우가 존재하기도 하고, 변수와 프로퍼티가 맨 앞 철자의 대소문자만 다르기 때문에, 사용하지 말아야 하는 프로퍼티를 사용하는 경우가 발생했다. 이런 경우, 게임을 빌드했을 때, UNITY_EDITOR 전처리기 지시자 사이의 코드는 사라지기 때문에, hitboxType 변수가 아닌 HitboxType 프로퍼티를 사용한 코드에서 에러를 발생하게 된다. (에디터 스크립트 상에선 에러가 없지만, 빌드한 파일에서 에러가 발생하면 "뭐지?"싶을 거다.)

    public class Hitbox
    {
        // .. Other Members
        
        public void CalculateHitbox()
        {
            // Error : 빌드 시 Size 프로퍼티는 코드에서 제거되기 때문에 에러 발생
            Vector3 minHitboxPos = new Vector3(Size.x, Size.y, Size.z);
        }
        
        // .. Other Members
    }
    
    public class AnotherClass
    {
        public void SomeMethod()
        {
            Hitbox hitbox = new Hitbox();
            
            // Error : 빌드 시 Size 프로퍼티는 코드에서 제거되기 때문에 에러 발생
            Vector3 size = hitbox.Size;
        }
    }

     

    혼자 개발하는 중임에도 불구하고, 해당 프로퍼티를 사용한 여러 코드를 발견할 수 있었으며, 팀 프로젝트라면, 해당 에러를 찾기 위해 수많은 시간을 소비했을 것이다.

     

    Hitbox Debug Tool

    우선, 첫 번째 문제점을 해결하기 위해서, Hitbox Editor의 Play Mode에서 설정된 히트 박스를 디버깅하는 기능을 따로 분리하여 UnityEditor.Editor 대신 UnityEditor.EditorWindow를 사용하는 툴로 추가했다. EditorWindow의 경우, 유니티의 메뉴 창에 옵션으로 추가할 수 있어, 개발자가 원할 때에 창으로 띄워놓을 수 있다.

    메뉴 창에 추가된 Hitbox Debug Tool

     

    Hitbox Debug Tool은 Play Mode에서만 작동하도록 구현했다. 툴이 켜져 있을 경우, 맨 위의 Deactivate Tool 버튼을 통해 툴을 끌 수 있다. 또한 런타임으로 새로운 오브젝트가 생성될 경우, Refresh hitbox controller 버튼을 통해, Hitbox Controller 컴포넌트를 포함한 씬에 존재하는 모든 게임 오브젝트의 이름을 아래의 Hitbox Controller List에 추가할 수 있다.

    Hitbox Debug Tool 모습

     

    Hitbox Controller List에는 Hitbox Controller 컴포넌트를 갖고 있는 모든 게임 오브젝트의 이름이 출력되며, 해당 게임 오브젝트가 비활성화되어 있거나, 현재 활성화된 히트 박스가 없는 경우, 버튼이 비활성화된다. 각 버튼을 클릭하면, 해당하는 오브젝트의 히트 박스만 Scene GUI에 출력되고, 카메라가 해당 히트 박스의 위치로 이동하게 된다. Reset selection 버튼을 클릭하여 다시 모든 히트 박스를 Scene GUI에 출력할 수 있다. 이를 통해, Hierarchy에서 특정 오브젝트를 선택하지 않아도, 히트 박스가 어떻게 설정되어 있는지 확인할 수 있었다.

    List에 활성화된 버튼을 클릭하면, 해당 오브젝트의 히트 박스만 Scene GUI에 활성화되고 카메라도 이동한다.

     

    두 번째 문제점은 직렬화를 활용했다. Hitbox Editor로 히트 박스의 정보를 씬 파일에 저장하기 위해 각 멤버 변수들은 [SerializedField] 속성을 추가하여 직렬화하였다. 직렬화란 데이터 구조 또는 오브젝트의 상태를 동일하거나 다른 컴퓨터 환경에 저장하고 나중에 재구성할 수 있는 포멧으로 변환하는 과정이다. 즉, 여러 타입의 변수들을 바이트 스트림으로 변환하는 과정인데, 이를 통해 유니티에선 에디터에서 씬 파일에 값을 저장할 수 있다. 직렬화된 클래스나 변수들은 SerializedObject, 또는 SerializedProperty 클래스를 활용하여 private 멤버 변수라도 Set / Get 로직이 가능해진다. 

    // HitboxDebugTool.cs
    // 매 Scene Update Frame마다 호출되는 이벤트 함수
    private void OnSceneGUI(SceneView sceneView)
    {
        for (int index = 0; index < hitboxControllerList.Count; index++)
        {
            if (!hitboxControllerList[index].IsHitboxActivated) continue;
            
            // SerializedObject는 MonoBehaviour 클래스를 상속받는 클래스를 직렬화한 오브젝트
            SerializedObject serializedObject = new SerializedObject(hitboxControllerList[index]);
    
            // Setter / Getter 없이 private 멤버 변수에 접근할 수 있음
            EHitboxType hitboxType = (EHitboxType)serializedObject.FindProperty("hitboxType").intValue;
            Vector3 minHitboxPos = serializedObject.FindProperty("minHitboxPos").vector3Value;
            Vector3 maxHitboxPos = serializedObject.FindProperty("maxHitboxPos").vector3Value;
    
            DrawHitbox(hitboxType, minHitboxPos, maxHitboxPos);
        }
    }

     

    이를 활용하여 불필요한 프로퍼티를 선언하는 것을 피함과 동시에, Hitbox Debug Tool 클래스에서 HitboxController의 private 멤버 변수에 접근하여 활성화된 히트 박스를 Scene GUI에 표현할 수 있었다.

Designed by Tistory.