내일배움캠프 Unity 35일차 TIL - Generic으로 선언한 enum unboxing

Wooooo·2023년 12월 15일
0

내일배움캠프Unity

목록 보기
37/94

오늘의 키워드

숙련 주차 팀 프로젝트가 시작됐다.
Monster 클래스에 FSM을 적용하기로 했는데, FSM을 자동화 하기 위해서 Generic으로 State 열거형을 받아와서 event Dictionary에 key로 등록해보려했다.


Generic으로 선언한 enum unboxing

where T : Enum으로 제네릭에 조건을 걸었는데도 명시적 캐스트가 안통한다...

as 연산자도 안통한다...

이것저것 막 바꿔보다가 IDE의 IntelliSense 자동완성에
var enums = Enum.GetValues(typeof(T)) as T[];
이게 나오길래 해봤더니, 잘 동작한다.

최고다 자동완성 ,,

해결을 하고 나서, 또 다른 해결법을 하나 더 찾았다.

두 번째로 틀렸던 방법에서 as연산자를 쓸게 아니라, 명시적 타입 캐스트를 해줘도 됐었다...


자동으로 callback Dictionary 만들기

위 기능을 이용해서 제네릭으로 enum type을 받아서, 자동으로 callback Dictionary를 만들어주는 상태 머신을 작성해봤다.

IStateMachine<T> interface

using System;

public interface IStateMachine<T> where T : Enum
{
    void OnStateEnter();
    void OnStateStay();
    void OnStateExit();
    void StateTransition(T state);
    void BindEvent(T state, StateEvent @event, Action action);
}

public enum StateEvent
{
    Enter,
    Stay,
    Exit,
}

StateMachine<T> class

using System.Collections.Generic;
using System;
using UnityEngine;

public class StateMachine<T> : IStateMachine<T> where T : Enum
{
	// 현재 상태
    private T _state;
    public T CurrentState => _state;
	
    // Generic으로 받아온 열거형을 이용해 각 상태에 따라 호출될 callback들을 저장할 Dictionary를 만듭니다.
    private Dictionary<StateEvent, Dictionary<T, Action>> _events = new();

	// 생성자에서 Dictionary를 초기화합니다.
    public StateMachine()
    {
        for (int i = 0; i < Enum.GetValues(typeof(StateEvent)).Length; i++)
        {
            _events[(StateEvent)i] = new Dictionary<T, Action>();
            var enums = Enum.GetValues(typeof(T)) as T[];
            for (int j = 0; j < enums.Length; j++)
                _events[(StateEvent)i].Add(enums[j], null);
        }
    }
	
    // 현재 상태에 알맞는 callback을 Dictionary에서 찾아 호출합니다.
    public void OnStateEnter() => _events[StateEvent.Enter][_state]?.Invoke();
    public void OnStateExit() => _events[StateEvent.Exit][_state]?.Invoke();
    public void OnStateStay() => _events[StateEvent.Stay][_state]?.Invoke();

	// 상태를 전이하는 메서드입니다.
    public void StateTransition(T state)
    {
    	// 바뀔 상태가 현재 상태와 같다면 실행하지 않습니다.
        if (state.Equals(_state))
            return;
            
        // 현재 상태의 Exit callback을 호출하고,
		// 상태를 바꾼 뒤 바뀐 상태의 Enter callback을 호출합니다.
        OnStateExit();
        _state = state;
        OnStateEnter();
    }

	// Dictionary에 callback을 등록합니다.
    public void BindEvent(T state, StateEvent @event, Action action)
    {
        _events[@event][state] += action;
    }
}

Monster class

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditorInternal;
using UnityEngine;

public class Monster : Creature
{
	// FSM에 사용할 상태들을 열거형으로 선언합니다.
    public enum MonsterState
    {
        Idle,
        Aggro,
        Attack,
        Dead,
    }

    private Player _player;
    private float _detectRange;
    private StateMachine<MonsterState> _fsm;	// 선언한 열거형으로 FSM 선언

    public MonsterState CurrentState => _fsm.CurrentState;
    public float DetectRange { get => _detectRange; set => _detectRange = value; }

    protected virtual void Start()
    {
        Initialize();
    }

    protected virtual void Update()
    {
    	// StateStay는 매 프레임 호출합니다.
        _fsm.OnStateStay();
    }

    public void Initialize()
    {
    	// FSM을 생성하고, 
		// 테스트용으로 Idle 상태일 때 매 프레임 로그를 찍는 callback을 등록했습니다.
        _fsm = new();
        _fsm.BindEvent(MonsterState.Idle, StateEvent.Stay, () => Debug.Log("Stay.."));
    }
}

Monster 클래스에서 MonsterState로 선언한 기본, 어그로, 공격, 죽음 상태로 바인딩 되는 callback들을 저장할 Dictionary가 자동으로 만들어진다!

나중에 Player클래스나, 더 많은 상태를 가지는 또 다른 몬스터 등을 만들 때, 그냥 상태를 정의한 열거형 타입만 넘겨주면 되는 구조로 짜봤다.

profile
game developer

0개의 댓글