C# Reflection의 Activator.CreateInstance 참고 자료
| 졸업작품 프로젝트 FSM에 활용한 리플렉션
저의 교내 졸업작품 프로젝트 펭덤에서 사용하는 FSM 코드를 가져와봤습니다.
먼저 State와 StateMachine 구조에 대해서 살펴보겠습니다.
State 스크립트
public class State
{
protected PenguinStateMachine _stateMachine;
protected Penguin _penguin;
protected NavMeshAgent _navAgent;
protected int _animBoolHash;
protected bool _triggerCalled = true;
public State(Penguin penguin, PenguinStateMachine stateMachine, string animationBoolName)
{
_penguin = penguin;
_stateMachine = stateMachine;
_animBoolHash = Animator.StringToHash(animationBoolName);
_navAgent = _penguin.NavAgent;
}
public virtual void EnterState()
{
_penguin.AnimatorCompo.SetBool(_animBoolHash, true); //들어오면 내 애니메이션을 활성화 해주는 것
_navAgent = _penguin.NavAgent;
}
public virtual void UpdateState()
{
}
public virtual void ExitState()
{
_penguin.AnimatorCompo.SetBool(_animBoolHash, false); //나갈땐 꺼줌
}
public virtual void AnimationTrigger()
{
_triggerCalled = true;
}
}
기타 기능을 수행하는 함수들도 여럿 있지만, 코드 구조 설명의 가독성을 위해 중요한 것만 남겼습니다.
일단 펭귄이 작동할 때 꼭 필요한 것들 (Penguin 스크립트, StateMachine)을 멤버 변수로 선언해주고, State 생성자에서 초기화 시켜줍니다.
여기서 animationBoolName을 할당받는 멤버 변수 _animBoolHash의 사용처에 대해서는 아래에서 설명하겠습니다.
그리고 State에 필수적인 EnterState, UpdateState, ExitState 3종을 virtual 함수로 만들었습니다.
StateMachine 스크립트와 StateEnum
public class PenguinStateMachine
{
public State CurrentState { get; private set; }
public State PrevState { get; private set; }
public Dictionary<PenguinStateType, State> StateDictionary { get; private set; }
public PenguinStateMachine()
{
StateDictionary = new Dictionary<PenguinStateType, State>();
}
public void Init(PenguinStateType state)
{
PrevState = CurrentState;
//이전 State를 현재 State로 설정해주고
CurrentState = StateDictionary[state];
//현재 State에 매개변수로 들어온 Enum값을 Dictionary에 조회하여 Value를 받아와 할당해준다
CurrentState.EnterState();
//현재 State의 EnterState 메소드 실행
}
public void ChangeState(PenguinStateType newState)
{
PrevState = CurrentState;
CurrentState.ExitState();
//현재 State는 나가주고
CurrentState = StateDictionary[newState];
//위에서 말한 그대로 현재 State를 바꿔준다.
CurrentState.EnterState();
//현재 State의 EnterState 메소드 실행
}
public void AddState(PenguinStateType stateType, State playerState)
{
StateDictionary.Add(stateType, playerState);
}
}
public enum PenguinStateType
{
Idle,
Chase,
Move,
MustMove,
Attack,
Dash
}
StateMachine에는 현재 State를 담는 CurrentState와 이전 State를 담는 PrevState를 선언해주었습니다.
(PrevState는 아직 쓸 상황이 없어서 사용하지는 않는 중)
그리고 State들을 관리하는 StateDictionary를 만들었습니다. Key값을 PenguinStateType Enum으로 설정하여 어디서든 Enum값으로 불러올 수 있도록 하였습니다.
맨 처음 State를 설정해주는 Init 함수, 현재 State를 바꿔주는 ChangeState 함수, 그리고 Dictionary에 새로운 State를 등록해주는 AddState함수가 있습니다. 모두 PenguinStateType을 매개변수로 받아 로직을 실행합니다. (자세한건 주석참고)
Pengin 스크립트
public class KatanaGeneralPenguin : General
{
public PenguinStateMachine StateMachine { get; private set; }
protected override void Awake()
{
base.Awake();
StateMachine = new PenguinStateMachine();
foreach (PenguinStateType state in Enum.GetValues(typeof(PenguinStateType)))
{
string typeName = state.ToString();
Type t = Type.GetType($"Penguin{typeName}State");
State newState = Activator.CreateInstance(t, this, StateMachine, typeName) as State;
if (newState == null)
{
Debug.LogError($"There is no script : {state}");
return;
}
StateMachine.AddState(state, newState);
}
}
protected override void Start()
{
StateInit();
}
protected override void Update()
{
base.Update();
StateMachine.CurrentState.UpdateState();
}
public override void StateInit()
{
StateMachine.Init(PenguinStateType.Idle);
}
public override void AnimationTrigger() => StateMachine.CurrentState.AnimationTrigger();
}
펭귄에 직접적으로 붙힐 Penguin 스크립트입니다. State 스크립트들을 Inspector에 드래그 앤 드롭하여 사용하지 않고
스크립트 자체를 끌어와서 State들을 사용할 수 있게 하는것이 목적입니다. (설명 많음 주의)
1. StateMachine을 선언하고 생성해줍니다.
2. foreach 루프를 사용하여 PenguinStateType Enum의 각 멤버에 대해 반복하고 Enum.GetValue를 사용하여 PenguinStateType Enum의 모든 값들을 가져옵니다.
3. state.ToString()으로 각 State Enum들의 이름을 가져오고 string형 변수 typeName에 할당합니다.
4. Type.GetType($"Penguin{typeName}State")을 사용하여 해당 상태를 나타내는 클래스의 Type을 가져옵니다. 예를 들어 PenguinStateType.Idle의 경우 PenguinIdleState 클래스의 Type을 가져옵니다.
5. Activator.CreateInstance 메소드를 사용하여 해당 클래스의 인스턴스를 생성합니다. 이때 State의 매개변수대로 인자를 전달합니다.
(Penguin penguin -> this, PenguinStateMachine stateMachine -> StateMachine, string animationBoolName -> typeName)
6. State 변수 newState에 할당합니다.
State newState = Activator.CreateInstance(t, this, StateMachine, typeName) as State;
7. newState가 null인지 검사를 한번 해주고, null이 아니라면 StateMachine.AddState를 통해 State를 등록합니다.
위와 같은 과정들을 거치고 모든 State들을 등록에 성공했다면, InitState 함수로 첫번째 State를 설정해주고 Update문에서
현재 State를 계속 Update해주면 됩니다.
여기서 다시 State의 멤버 변수였던 _animBoolHash의 사용처를 알아보면,
현재 State의 이름을 가져와 Animator.StringToHash(animationBoolName) 으로 _animBoolHash에 저장하고,
State가 시작될 때 Animator.SetBool을 통해 현재 State와 알맞는 애니메이션을 즉각적으로 실행시켜주는 것입니다.
예를 들어 Penguin"Idle"State가 현재 State로 설정되어 EnterState가 된다면, 애니메이터에서는 "Idle"이라는 Bool이 실행되어 Idle 애니메이션이 실행되는것입니다.
| 유니티에서 확인
애니메이터를 확인해보면 이런식으로 구성되어있습니다.
Entry에서 이어지는 화살표에는 true를, Exit로 이어지는 화살표에는 false를 설정해두어 현재 AttackState라면 Attack Bool이 true가 되고, 어떤 조건이 만족되어 다른 State로 이동할 때는 false가 되어 애니메이션도 같이 바뀝니다.
현재 제가 제작하고 있는 카타나 펭귄의 작동과 애니메이션 작동방식을 보여드리며 마무리하겠습니다.
'Unity > 졸업작품' 카테고리의 다른 글
[Unity] 졸업작품<펭덤>의 전투 시스템 (1) | 2024.06.05 |
---|---|
[Unity] 졸업작품<펭덤>의 UI 구조 (UIManager) (0) | 2024.05.07 |