// T에 올 수 있는 타입을 class로 한정
public abstract class State<T> where T : class
{
// 해당 상태 시작 시 1회 호출
public abstract void Enter(T entity);
// 해당 상태 업데이트 시 매 프레임 호출
public abstract void Execute(T entity);
// 해당 상태 종료시 1회 호출
public abstract void Exit(T entity);
}
public enum StudentStates // 플레이어가 가질 수 있는 상태
{RestAndSleep, StudyHard, TakeAExam, PlayAGame, HitTheBottle}
public class Student
{
// Student의 속성들
private int knowledge; // 지식
private int stress; // 스트레스
private int fatigue; // 피로
private int totalScore; // 총 점수
private Locations currentLocation; // 현재 위치
// Student가 가지고 있는 모든 상태, 현재 상태
private State<Student>[] states;
// stateMachine 클래스에 현재 상태 관리 위임했으므로 해당 부분 삭제
// private State<Student> currentState;
// 대신에 stateMachine 변수 선언
private StateMachine<Student> stateMachine;
...
public void SetUp(string name)
{
// states에 State 클래스 형식의 객체가 5개 들어가는 배열 대입
states = new State[5];
// states의 각 인덱스에 각 클래스 메모리 할당
states[(int)StudentStates.RestAndSleep] = new StudentOwnedStates.RestAndSleep();
states[(int)StudentState.StudyHard] = new StudentOwendStates.StudyHard();
states[(int)StudentState.TakeAExam] = new StudentOwendStates.TakeAExam();
states[(int)StudentState.PlayAGame] = new StudentOwendStates.PlayAGame();
states[(int)StudentState.HitTheBottle] = new StudentOwendStates.HitTheBottle();
// stateMachine 클래스에 현재 상태 관리 위임했으므로 해당 부분 삭제
// 현재 상태를 RestAndSleep 상태로 설정
//ChangeState(StudentStates.RestAndSleep);
// 상태를 관리하는 StateMachine에 메모리 할당하고, 첫 상태 결정
stateMachine = new StateMachine<Student>();
// stateMachine.Setup()에서 ChangeState()를 호출하기 때문에 처음 상태를 매개변수로 전달
stateMachine.Setup(this, states[(int)StudentStates.RestAndSleep]);
knowledge = 0;
stress = 0;
fatigue = 0;
totalScore = 0;
currentLocation = Location.SweetHome;
}
public void Updated()
{
// currentState가 비어있지 않으면 현재 상태의 Execute 메서드 실행
//if(currentState != null)
// currentState.Execute(this); // this는 Student 클래스 객체
stateMachine.Execute(); // stateMachine에 정의된 Execute() 호출
}
public void ChangeState(StudentStates newState)
{
// 새로 바꾸려는 상태가 비어있으면 상태를 바꾸지 않고 return
// if(states[(int)newState] == null) return;
// 현재 상태를 종료하기 위해 Exit() 메소드 호출
// if(currentState != null)
// currentState.Exit(this);
// 새로운 상태로 변경하고, 새로 바뀐 상태의 Enter() 메서드 호출
// currentState = states[(int)newState]
// currentState.Enter(this);
stateMachine.ChangeState(states[(int)newState]) // stateMachine에 정의된 ChangeState() 호출
}
}
namespace StudentOwnedStates
{
public class RestAndSleep : State<Student>
{
public override void Enter(Student entity) {...}
public override void Execute(Student entity) {...}
public override void Exit(Student entity) {...}
}
public class StudyHard : State<Student>
{
public override void Enter(Student entity) {...}
public override void Execute(Student entity) {...}
public override void Exit(Student entity) {...}
}
...
}
public class StateMachine<T> where T : class
{
private T ownerEntity; // StateMachine의 소유주
private State<T> currentState; // 현재 상태
private State<T> globalState; // 전역 상태
private State<T> previousState; // 이전 상태
// StateMachine을 참조 클래스로 사용하는 에이전트에서 호출
public void SetUp(T owner, State<T> entryState)
{
ownerEntity = owner;
currentState = null;
globalState = null;
previousState = null;
// entryState 상태로 상태 변경
ChangeState(entryState);
}
public void Execute()
{
if(globalState != null)
{
globalState.Execute(ownerEntity);
}
if(currentState != null)
{
currentState.Execute(ownerEntity);
}
}
public void ChangeState(State<T> newState)
{
// 새로 바꾸려는 상태가 비어있으면 상태 변경 X
if(newState == null) return;
// 현재 재생 중인 상태가 있으면 Exit() 메서드 호출
if(currentState != null)
{
// 상태 변경되면 현재 상태가 이전 상태가 됨.
previousState = currentState;
currentState.Exit(ownerEntity)
}
// 새로운 상태로 변경하고, 새로 바뀐 상태의 Enter() 메서드 호출
currentState = newState;
currentState.Enter(ownerEntity);
}
public void SetGlobalState(State<T> newState)
{
globalState = newState;
}
// 이전 상태로 복귀
public void RevertToPreviousState()
{
ChangeState(previousState);
}
}
public enum UnemployedStates {RestAndSleep, PlayAGame, HitTheBottle, VisitBathroom, Global}
public class Unemployed
{
// Unemployed의 멤버 변수
private int bored;
private int stress;
private int fatigue;
private Locations currentLocation;
public UnemployedStates CurrentState {private set; get;} // 현재 상태
// Unemployed가 가지고 있는 모든 상태, 상태 관리자
private State<Unemployed>[] states;
private StateMachine<Unemployed> stateMachine;
// 각 멤버 변수에 대한 Property 정의
public int Bored
{
set => bored = Mathf.Max(0, value);
get => bored
}
public int Stress {...}
public int fatigue {...}
public Locations CurrentLocation {...}
public void Setup()
{
// Unemployed가 가질 수 있는 상태 개수만큼 메모리 할당, 각 상태에 클래스 메모리 할당
states = new State<Unemployed>[5];
states[(int)UnemployedStates.RestAndSleep] = new UnemployedOwnedStates.RestAndSleep();
states[(int)UnemployedStates.PlayAGame] = new UnemployedOwnedStates.PlayAGame();
states[(int)UnemployedStates.HitTheBottle] = new UnemployedOwnedStates.HitTheBottle();
states[(int)UnemployedStates.VisitBathroom] = new UnemployedOwnedStates.VisitBathroom();
states[(int)UnemployedStates.Global] = new UnemployedOwnedStates.Global();
// 상태를 관리하는 StateMachine에 메모리 할당 및 첫 상태 결정
stateMachine = new StateMachine<Unemployed>();
stateMachine.Setup(this, states[(int)UnemployedStates.RestAndSleep]);
stateMachine.SetGlobalState(states[(int)UnemployedStates.Global]);
bored = 0;
stress = 0;
fatigue = 0;
currentLocation = Location.SweetHome;
}
public void Updated()
{
stateMachine.Execute();
}
public void ChangeState(UnemployedStates newState)
{
CurrentState = newState;
stateMachine.ChangeState(states[(int)newState]);
}
public void RevertToPreviousState()
{
stateMachine.RevertToPreviousState();
}
}
namespace UnemployedOwnedStates
{
public class RestAndSleep : State<Unemployed>
{
public override void Enter(Unemployed entity) {...}
public override void Execute(Unemployed entity) {...}
public override void Exit(Unemployed entity) {...}
}
public class PlayAGame : State<Unemployed>
{
public override void Enter(Unemployed entity) {...}
public override void Execute(Unemployed entity) {...}
public override void Exit(Unemployed entity) {...}
}
public class VisitBathroom : State<Unemployed>
{
public override void Enter(Unemployed entity) {...}
public override void Execute(Unemployed entity)
{
// 바로 직전 상태로 돌아감.
entity.RevertToPreviousState();
}
public override void Exit(Unemployed entity) {...}
}
// 전역 상태. 현재 상태와 별개로 실행됨.
public class StateGlobal : State<Unemployed>
{
public override void Enter(Unemployed entity) {...}
public override void Execute(Unemployed entity)
{
if(entity.CurrentState == UnemployedStates.VisitBathroom)
{
return;
}
// 10% 확률로 "VisitBathroom" 상태로 변경
int bathroomState = Random.Range(0, 100);
if(bathroomState < 10)
{
entity.ChangeState(UnemployedStates.VisitBathroom);
}
}
public override void Exit(Unemployed entity) {...}
}
}
출처 : 고박사의 유니티 노트 - FSM3 (링크)