ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Upgrade된 DNFTransform 컴포넌트
    게임 클라이언트 개발/MakeDNF 2024. 4. 16. 19:57

    이번 글에선 DNFTransform을 개선한 내용과 함께 제작한 툴에 대해 작성할 것이다.

     

    이전에 구현한 DNFTransform에 대해 작성한 글 링크를 남긴다.
    https://memmaeranger.tistory.com/33

     

    DNF Transform

    던전 앤 파이터는 2.5D 횡스크롤 게임이다. 던파를 구현하기 위해선 2.5D 좌표계와 쿼터뷰를 구현할 필요가 있다. 이는 카메라 회전을 통해 구현할 수도 있으나, 카메라를 회전시킬 경우 그래픽 리

    memmaeranger.tistory.com

     

    복잡한 Hierarchy의 구조를 가질 수밖에 없는 이유

    기존에 구현한 DNFTransform 컴포넌트는 2.5D 좌표계를 표현하기 위해서 하나의 오브젝트에 복잡한 Hierarchy 구조를 가지고 있다. XZ 평면을 표현하기 위한 "Root" 오브젝트 - Y 축을 표현하기 위한 "Y Pos Object" 오브젝트 - Scale 값을 표현하기 위한 "Scale Object" 오브젝트 - Sprite를 위한 "Sprite" 오브젝트로 하나의 객체를 표현하기 위해 최소한 4개의 오브젝트가 필요했다.

    복잡한 Hierarchy 구조를 가진 DNFTransform 컴포넌트

     

    이러한 구조를 갖게 된 이유를 설명하기 위해 먼저 DNFTransform의 position 값을 Transform의 position으로 치환하는 로직을 살펴보자. 2.5D 좌표계도 결국 3D 좌표계에 기반하여 계산하기 때문에 DNFTransform의 position은 Vector3 타입의 변수이다. 하지만 플레이어에게 보이는 모습은 2D 좌표계이기 때문에 3D 좌표계의 값 $(x, y, z)$를 2D 좌표계의 값 $(x, y)$로 치환해야 한다. 이를 위해 DNFTransform의 X 축의 값은 그대로 Transform의 X 값에 대입하고, DNFTransform의 Z 축의 값에 일정 비율을 곱하여 공중을 표현하기 위한 DNFTransform의 Y 축의 값과 더한 값을 Transform의 Y 값에 대입한다.

    public static Vector3 ConvertDNFPosToWorldPos(Vector3 dnfPosition)
    {
        return new Vector3(dnfPosition.x, dnfPosition.y + dnfPosition.z * GlobalDefine.CONV_RATE, 0f);
    }

     

    해당 몬스터의 DNFTransform의 Y 축의 값이 0일 경우, Transform의 position 값 $(x, y)$를 역으로 DNFTransform의 position 값 $(x, y, z)$로 치환할 수 있다. 하지만 DNFTransform의 Y 축의 값이 0이 아니라면, Transform의 position 값을 DNFTransform의 position 값으로 치환할 수 없었고, 에디터 상에서 오브젝트를 배치할 때, 공중에 떠있는 케이스를 처리할 수 없었다

    씬에 배치한 공중에 떠있는 몬스터, 하지만 Transform 컴포넌트의 값으론 공중에 얼마나 떠있는지 알 수 없다.

     

    이를 해결하기 위해, "Root" 오브젝트의 자식 오브젝트로 "Y Pos Object"를 추가하여 Y 축의 값을 설정할 수 있도록 하였고, 여러 몬스터 및 캐릭터 오브젝트를 스크립트가 아닌 에디터의 씬에서 배치할 수 있었다.

     

    엮이고 엮여서 계속 복잡해지는 구조

    하지만 이렇게 Hierarchy 구조가 복잡하게 되면서 Side Effect가 발생했다. 첫 번째로, 애니메이션 클립을 제작할 때마다 수많은 프로퍼티가 추가되었고, 클립 하나 만드는 데도 너무나도 복잡하여 실수하는 일이 잦았다. Sprite Renderer 컴포넌트가 있는 "Sprite" 오브젝트까지의 Hierarchy 경로가 굉장히 길어 자세히 보지 않으면 프로퍼티를 잘못 할당하여 애니메이션이 고장 났었다.

    오브젝트의 자식 오브젝트의 자식 오브젝트의 자식 오브젝트....

     

    두 번째로, "Y Pos Object"나 "Scale Object"가 없는 경우를 위한 예외처리가 필수적이었다. 예를 들어, 남귀검사 소울브링어의 '냉기의 사야' 스킬과 같은 장판류의 스킬의 경우, Y 축과 상관없이 해당 영역 안에 존재하는 몬스터에게 데미지를 입힌다. 또한 무조건 지상에 (Y 축의 값이 무조건 0) 설치되기 때문에 "Y Pos Object"가 필요가 없어 추가하지 않는다. 이렇게 자식 오브젝트가 없는 경우 DNFTransform 컴포넌트를 초기화할 때 Null 체크가 필수적으로 수행되어야 했다.

    // Y Pos Object가 존재하는 지를 반환하는 플래그 변수
    public bool HasYObj => yPosTransform != null;
    
    // DNF Transform 컴포넌트의 Position 값
    // 만약 Y Pos Object 가 없다면, Y 값은 0으로 고정됨.
    public Vector3 Position
    {
        set
        {
            X = value.x;
            Z = value.z;
            
            // Y Pos Object가 있는지 체크 후 값 대입
            Y = HasYObj ? value.y : 0f;
        }
        // Y Pos Object가 있는지 체크 후 값 반환
        get => new Vector3(X, HasYObj ? Y : 0f, Z);
    }

    냉기의 사야 스킬

     

    세 번째로, Hitbox Controller 컴포넌트가 필요 없는 오브젝트 역시 호환성을 위해 복잡한 Hierarchy 구조를 유지해야 했다. 캐릭터나 스킬 투사체 오브젝트와 달리 이펙트 오브젝트의 경우, 단순히 이펙트가 생성될 DNFTransform의 Position 값을 Transform의 Position 값으로 치환한 위치에 이펙트를 생성하면 된다고 생각했기 때문에, 굳이 DNFTransform 컴포넌트가 필요 없다고 생각했다.

     

    하지만 문제는 Sprite를 정렬하는 기능에서 발생했다. 스킬 이펙트 역시 Sprite로 위치에 따라 정렬하는 기능이 필요했다. 이를 위해 DNFTransform 컴포넌트가 필수적이었고, 이에 따라 스킬 이펙트마다 복잡한 Hierarchy 구조를 유지해야 했기 때문에 씬에 존재하는 오브젝트의 수가 너무 많아졌다.

    스킬 이펙트보다 앞에 있는 경우(왼쪽)와 뒤에 있는 경우(오른쪽)

    // DNFTransform 컴포넌트를 활용하여 Sprite의 Sorting Order를 계산하는 함수
    public int GetSortingOrder(DNFTransform obj)
    {
        float objDist = Mathf.Abs(maxZ - obj.Position.z);
    
        return (int)(Mathf.Lerp(System.Int16.MinValue, System.Int16.MaxValue, objDist / totalZ));
    }

     

    그냥 에디터를 만들어버리자!

    모든 문제점의 발생 원인은 유니티의 씬 에디터에선 복잡한 Hierarchy 구조를 유지해야만 씬에서 오브젝트의 위치를 손쉽게 배치할 수 있다는 점이었다. 그렇다면 에디터를 구현해서 복잡한 Hierarchy 구조가 없더라도 씬에서 오브젝트의 위치를 손쉽게 배치할 수 있게 한다면, DNFTransform 컴포넌트의 구조는 간단해질 것이다. 이를 위해 UnityEditor.Editor 클래스를 활용하여 DNFTransform Editor를 구현하였다. 

     

    우선 Inspector 창을 수정하였다. 각 프로퍼티를 통해 개발자가 직접 값을 대입할 수 있고, 씬에 여러 에디터가 활성화될 경우를 대비하여 씬 GUI를 켜고 끌 수 있는 버튼을 추가하였다. 또한 2D에서 작업하다 보니, Y 축과 Z 축의 방향이 겹쳐서 에디터 핸들러를 제대로 컨트롤할 수 없었다. 이를 위해 XY 평면을 수정할지, 혹은 XZ 평면을 수정할지 결정할 수 있는 버튼을 추가하였다.

    DNF Transform 컴포넌트의 Custom Editor

     

    Scene GUI에선 각 축의 값을 컨트롤하여 Position 값을 설정할 수 있는 Arrow Slider 헨들러와, 평면을 컨트롤하여 Position 값을 설정할 수 있는 Slider2D 헨들러를 추가하였다. 이를 활용하여 인스펙터 창에 직접 값을 대입하지 않아도 Scene GUI를 통해 오브젝트의 Position 값을 설정할 수 있게 구현했다.

    XZ 평면 수정(왼쪽) 및 XY 평면 수정(오른쪽)

     

    이렇게 에디터를 추가함에 따라 DNFTransform 컴포넌트를 사용하기 위한 Hierarchy 구조는 DNFTransform을 표현하기 위한 "Root" 오브젝트 - Sprite를 표현하기 위한 "Sprite" 오브젝트로 굉장히 간단해졌으며, 씬에 존재하는 오브젝트의 수를 현저히 줄일 수 있게 되었다.

    굉장히 간단해진 Hierarchy 구조

     

Designed by Tistory.