20240207_유니티 상속 관련

2024. 2. 7. 22:56IT/TIL

오늘의 TIL은 유니티에서 사용하는 상속과 관련된 내용이다.

 

이전에 객체지향 프로그래밍에 대해 공부하면서 상속에 대해서도 어느정도 공부했었는데,

 

실제로 프로젝트를 진행하면서 상속에 대해 자세히 알지 못했었던 개념이 있어서

 

예시와 함께 정리하려고 한다.

 

1. 추상화 클래스

상속을 하고 받는데 있어서 추상화(abstract) 함수를 만드려는 경우에는 클래스에서도 추상화를 선언해야 한다.

 

오늘 작업했던 표지판의 기본이 되는 스크립트를 예를 들면 아래와 같은데,

public abstract class SignBase : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            OpenPopupSign();
        }
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            ClosePopupSign();
        }
    }

    protected abstract void OpenPopupSign();

    protected abstract void ClosePopupSign();
}

 

간단하게 플레이어가 접근하면 PopupSign을 보여주고, 멀어지면 사라지는 코드를 작성했는데,

 

표지판의 종류에 따라서 플레이어가 접근했을 때 다른 방식으로 반응하게 하려고 이렇게 기본 코드를 작성했다.

 

OpenPopupSign() 함수와 ClosePopupSign() 함수를 작성하는데 있어서 하위에 있는 클래스의 함수가 매우 다르기 때문에

 

이를 추상화하여 작성하였는데, 그 과정에서 기본적인 virtual이 아닌 abstract를 사용하게 되었다.

 

이 abstract를 사용하기 위해서는 클래스에서도 abstract를 사용해야되는 것을 경험했고,

 

이 SignBase를 상속받는 모든 클래스들은 추상화된 함수를 필수로 구현해야된다는 것을 경험했다.

 

public class SignWorld : SignBase
{
    private Canvas _canvas;

    private void Awake()
    {
        _canvas = GetComponentInChildren<Canvas>(true);
        _canvas.gameObject.SetActive(false);
    }
    protected override void OpenPopupSign()
    {
        _canvas.gameObject.SetActive(true);
    }

    protected override void ClosePopupSign()
    {
        _canvas.gameObject.SetActive(false);
    }
}


public class SignOverlay : SignBase
{
    protected TextMeshProUGUI _signText;
    [SerializeField]
    protected string _signID;

    private string _signMessage;

    protected override void OpenPopupSign()
    {
        _signMessage = SignDataManager.Instance.GetMessage(_signID);
        _signText = UIManager.Instance.GetUI(PopupType.ToolTip).GetComponentInChildren<TextMeshProUGUI>();
        _signText.text = _signMessage;
        UIManager.Instance.OpenPopupUI(PopupType.ToolTip);
    }

    protected override void ClosePopupSign()
    {
        UIManager.Instance.ClosePopupUI(PopupType.ToolTip);
    }
}

 

조금 더 적어보면,

 

PopupUI로 표지판의 정보를 표시하는 표지판도 있지만,

 

월드맵 상에 이미지로 정보를 표시하는 표지판도 있기 때문에

 

이를 나눠서 구현하는 과정에서 상속을 받는 방식으로 구현했다.

 

이 때, 상속받은 두 클래스의 표시 방식이 다르고, 구조도 다르기 때문에 추상화(abstract)로 구현했는데,

 

같은 기능인 표지판을 보여주는 기능을 갖고 있지만,

 

하나는 자신의 하위에 있는 오브젝트를 보여주는 경우와 UI Manager를 통해서 PopupUI를 보여주는 경우로

 

표시할 오브젝트를 가져오는 방식과 표시하는 방식이 매우 달랐기 때문에 추상화를 이용하여 구현하였다.

 

 

2. GetComponentInChildren

위의 코드 중에서 SignWorld 클래스를 작성하는 과정에서 겪었던 오류와 그에대한 해결 방법인데

public class SignWorld : SignBase
{
    private Canvas _canvas;

    private void Awake()
    {
        _canvas = GetComponentInChildren<Canvas>(true);
        _canvas.gameObject.SetActive(false);
    }
    protected override void OpenPopupSign()
    {
        _canvas.gameObject.SetActive(true);
    }

    protected override void ClosePopupSign()
    {
        _canvas.gameObject.SetActive(false);
    }
}

 

위의 코드에서 Awake 부분에서 _canvas를 선언하는 과정에서 GetComponent를 사용하는데,

 

이때 GetComponentInChildren에서 기본적으로 사용하지 않는 괄호 안에 true를 넣는 방식으로 사용하였다.

 

이 괄호안의 true를 넣는 것과 비워두는 것의 차이가 있는데,

 

true를 넣어주면 비활성화된 오브젝트들도 검사하지만 비워두면 활성화된 오브젝트들만 검사한다.

 

따라서 true를 넣어주면 모든 오브젝트들을 검사하게 되므로 성능에 영향을 줄 수도 있으니 주의해서 사용해야된다.

 

그렇기 때문에 기본적으로 비워두면 false를 갖게하는 기본 값을 가지고 있다.

 

또, GetComponent의 종류가 매우 많은데 몇 가지를 살펴보면

 

1) GetComponent

기본적인 기능으로 해당 스크립트를 갖고 있는 오브젝트에서만 확인한다

이 때, 괄호안에 true가 들어갈 수 없는데 자기 자신만을 확인하기 때문에 활성도와 무관하게 확인할 수 있기 때문이다.

 

2) GetComponentInChildren

자신의 하위에 있는 오브젝트들에서 확인한다. 단, 처음으로 나오는 컴포넌트를 반환한다.

 

3) GetComponentsInChildren

component에 s가 붙은 형태로 자신의 하위에 있는 오브젝트들에서 확인한다.

이 경우에는 모든 컴포넌트들을 반환한다.

 

4) GetComponentInParent

자신의 상위에 있는 오브젝트들에서 확인한다. 단, 처음으로 나오는 컴포넌트를 반환한다.

 

5) GetComponentsInParent

component에 s가 붙은 형태로 자신의 상위에 있는 오브젝트들에서 확인한다.

이 경우에는 모든 컴포넌트들을 반환한다.

 

이렇게 GetComponent의 종류가 많으니 필요한 상황에 맞게 사용할 수 있도록 공부하였다.

 

 

3. 직렬화(Serialization)

작업을 진행하면서 직렬화와 역직렬화에 대한 개념이 정확하지 않고 모호해지는 느낌이 있어서 이를 정리하고자 한다.

 

https://learn.microsoft.com/ko-kr/dotnet/standard/serialization/

 

serialization - .NET

이 문서에서는 이진 serialization, XML 및 SOAP serialization, JSON serialization을 비롯한 .NET serialization 기술에 관한 정보를 제공합니다.

learn.microsoft.com

더보기

직렬화란 지속시키거나 전송할 수 있는 형태로 개체 상태를 변환하는 프로세스입니다.

직렬화와 짝을 이루는 것은 스트림을 개체로 변환하는 역직렬화입니다.

이 프로세스를 함께 사용하여 데이터를 쉽게 저장하고 전송할 수 있습니다.

 

좀 더 자세히 알아보면,

 

직렬화(serialize)란 개체를 저장하거나, 메모리, 데이터베이스 또는 파일로 전송하기 위해

 

개체를 바이트 스트림(stream of bytes) 형태로 연속적인(serial) 데이터로 변환하는 포맷 변환 기술을 말한다.

 

데이터를 저장 혹은 전송하는데 데이터 자체로 작업하는 것이 아니라

 

포장하여 저장 혹은 전송하는 방식을 택하게 되는데 이 포장하는 과정을 직렬화라고 할 수 있다.

 

직렬화를 하는 이유는 데이터를 저장하는 방식이 값 타입과 참조 타입이 있는데,

 

값 타입의 경우에는 저장이나 통신을 하는데 문제가 없지만,

 

참조 타입의 경우에는 저장이나 통신을 하는 과정에서 정보가 주소만 있기 때문에

 

임시로 저장되었던 메모리는 휘발성이며, 통신을 하는 각 PC마다 값을 저장하는 주소가 다르기 때문이다.

 

즉, 참조 타입의 데이터를 값 타입으로 변환시켜서 저장과 통신에 용이하게 하는 작업을 직렬화라고 할 수 있다.

 

 

오늘은 작업을 하는 과정에서 상속을 이용하여 스크립트를 구현하려고 하는데

 

경험이 부족하여 문제가 생겼던 부분들을 해결하면서 배운 것들을 정리하였다.

'IT > TIL' 카테고리의 다른 글

20240209_유니티 초기화 순서 관련  (0) 2024.02.10
20240208_기술면접 회고  (1) 2024.02.09
20240207_삼각 달팽이(프로그래머스)  (0) 2024.02.07
20240206_멀쩡한 사각형(프로그래머스)  (0) 2024.02.06
20240205_SOLID원칙  (0) 2024.02.05