멈추지 않고 끈질기게

[Unity][포트폴리오] 태그 관리용 클래스 / WaitForSeconds 관리용 클래스 본문

포트폴리오

[Unity][포트폴리오] 태그 관리용 클래스 / WaitForSeconds 관리용 클래스

sam0308 2023. 5. 31. 09:50

※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.

※ 해당 포스팅은 Unity 2021.3.15f1 버전을 기준으로 작성되었습니다.

 개발 중인 유니티 3D 포트폴리오의 소스 코드를 포함하고 있습니다.

 

 

 

1. Tags(태그 관리용 클래스)

// 태그를 잘못 입력하는 예시
void OnCollisionEnter(Collision coll)
{
    // Player를 player로 잘못 입력 
    if(coll.gameObject.CompareTag("player"))
    {
        StartCoroutine(Fall());
        return;
    }
}

 유니티에서 충돌 판정을 체크할 때 CompareTag() 함수를 통해 태그를 확인하는 경우가 많습니다. 문제는 해당 함수의 매개변수가 string 타입이라, 사용자 입력에 오타가 있을 경우 예상한대로 동작하지 않습니다. 예를 들어 충돌 대상이 플레이어인지 확인하기 위해 CompareTag()를 사용하면서 태그명은 Player인데 CompareTag("player")로 입력했다면, 대상이 플레이어인지 제대로 구분하지 못합니다. 무엇보다, 기본적으로 string 매개변수를 " "로 직접 입력해서 집어넣는 것은 지양하는 것이 좋습니다.

 

 위와 같은 상황을 방지하고자 public static 클래스를 하나 만들었습니다.

// 태그 관리용 전역 클래스
// 태그 추가시 갱신 요망
public static class Tags
{
    public readonly static string Player = "Player";
    public readonly static string Platform = "Platform";
    public readonly static string MovingPlatform = "MovingPlatform";
    public readonly static string RotatingPlatform = "RotatingPlatform";
    public readonly static string Fall = "Fall";
    public readonly static string SavePoint = "SavePoint";
    public readonly static string Goal = "Goal";

 Tags라는 이름의 클래스를 public static으로 선언하고, 내부에 읽기전용 전역 문자열 변수들을 선언하여 외부에서 Tags.Player와 같은 식으로 사용할 수 있도록 작성했습니다. 태그를 추가할 때마다 해당 클래스에도 추가해주어야 하는 불편함이 있긴 하지만, 태그 이름을 해당 클래스를 통해서만 사용하면 문제(오타)가 있을 경우에도 해당 클래스만 수정하면 되므로 관리가 편리해지는 장점이 있습니다. 

// Tags 클래스 사용 예시
void OnCollisionEnter(Collision coll)
{
    // 태그 명은 Tags 클래스를 통해서만 입력 
    if(coll.gameObject.CompareTag(Tags.Player))
    {
        StartCoroutine(Fall());
        return;
    }
}

 

    // 레이어 관리용 열거형
    public enum Layers
    { 
        Default = 0,
        TransparentFx = 1,
        IgnoreLaycast = 2,
        Water = 4,
        UI = 5
    }
}

 추가로 떨어지는 발판을 구현하면서 레이어를 변경할 필요가 있어 Tags 내부에 열거형을 선언해두었습니다. 오브젝트의 레이어는 기본적으로 int형 변수이므로, 열거형으로 관리하는 쪽이 편리했습니다. 다음은 FallingPlatform 클래스에서 레이어 관리용 열거형을 사용했던 코드입니다.

IEnumerator Restore()
{
    // 레이어 변경(Tags.Layers 사용)
    gameObject.layer = (int)Tags.Layers.IgnoreLaycast;

    myCol.enabled = false;
    rigid.isKinematic = true;
    transform.position = initialPos;
    yield return WfsManager.Instance.GetWaitForSeconds(restoreDelay);

    myCol.enabled = true;
    // 레이어 변경(Tags.Layers 사용)
    gameObject.layer = (int)Tags.Layers.Default;
}

 

 

 

2. WfsManager(WaitForSeconds 관리용 클래스)

 WaitForSeconds 객체를 관리하기 위한 클래스로, 하기 블로그 포스팅을 참조하여 작성한 것입니다.

https://moondongjun.tistory.com/5

 

유니티, 코루틴 최적화(Coroutine Yield Instruction)

매번 생성된는 반복기 타이밍 코루틴의 경우 잦은 new() 생성, 메모리 할당으로 인해 불필요한 메모리를 차지합니다. private IEnumerator CountDown(int startTime) { while (true) { timerText.text = (startTime--).ToString(

moondongjun.tistory.com

// WaitForSeconds 관리용 클래스
// 해당 클래스를 통해 이미 생성한 WaitForSeconds 객체 반환
// 아직 존재하지 않는 시간의 WaitForSeconds만 객체 새로 생성
public class WfsManager
{
    Dictionary<float, WaitForSeconds> secondsDic;

    #region 싱글톤 구현
    private WfsManager()
    {
        secondsDic = new Dictionary<float, WaitForSeconds>();
    }

    private static WfsManager instance;
    public static WfsManager Instance
    {
        get
        {
            if (instance == null)
                instance = new WfsManager();

            return instance;
        }
    }
    #endregion

    public WaitForSeconds GetWaitForSeconds(float time)
    {
        WaitForSeconds value;
        // 해당 시간의 WaitForSeconds 객체가 없을 경우 추가
        if (secondsDic.TryGetValue(time, out value) == false)
            secondsDic.Add(time, value = new WaitForSeconds(time));

        return value;
    }
}

  다른 점이라면 전역 클래스 대신 싱글톤으로 구현하였습니다. Tags 클래스의 경우 짧은 문자열 변수만 몇개 사용할 예정이라 크기가 높아질 염려는 없다고 생각되어 전역 클래스로 구현하였지만, 해당 클래스는 Dictionary가 경우에 따라 크기가 좀 커질수도 있다고 생각되어 싱글톤으로 작성했습니다. 

 

 기능 자체는 WaitForEndOfFrame, WaitForFixedUpdate를 생략한 부분을 제외하면 동일합니다. WaitForSeconds 객체가 필요할 때 new로 새로 객체를 생성하는 대신 GetWaitForSeconds() 함수를 호출하여 반환받는 방식입니다. 매개변수로 넘긴 시간의 WaitForSeconds 객체가 존재한다면 바로 해당 객체를 반환, 없다면 Dictionary에 추가한 후 반환합니다. 해당 클래스를 통해 반복적으로 WaitForSeconds 객체를 생성하는 것을 방지합니다. 다음은 FallingPlatform의 코루틴에서 WfsManager를 사용하는 코드입니다.

IEnumerator Restore()
{
    gameObject.layer = (int)Tags.Layers.IgnoreLaycast;

    myCol.enabled = false;
    rigid.isKinematic = true;
    transform.position = initialPos;
    // WfsManager 사용 예시
    yield return WfsManager.Instance.GetWaitForSeconds(restoreDelay);

    myCol.enabled = true;
    gameObject.layer = (int)Tags.Layers.Default;
}

 

 

 기존의 포트폴리오에서는 반복문 안에서 WaitForSeconds 객체를 계속 생성하는 것이 부적절해 보여 코루틴, 혹은 클래스마다 WaitForSeconds 객체를 캐싱해두는 식으로 사용했습니다. 후에 더 총괄적으로 관리하는 방법이 없을까 찾아보다가, 상기 포스팅의 내용이 적절해보여 참고하여 사용하게 되었습니다.