Post List

[실용적 예제로 본 게임 인공지능 프로그램하기] 2-1. 유한상태기계(FSM)

2-1. 유한상태기계(FSM)

유한상태기계(finite state machine)은 초기 인공지능 개발부터 사용되어져 왔다고 합니다.
하지만 저자는 앞으로도 FSM의 사용은 빈번해질 것이라고 예측하였는데요, 그 이유는 다음
과 같습니다.
  1. 빠르고 코딩하기가 쉽다.
  2. 오류수정이 용이하다.
  3. 계산부담이 없다.
  4. 직관적이다.
  5. 유연성이 있다.
즉, FSM은 코딩하기가 쉬우며, 각 State별로 나뉘어져 있으므로 오류 수정이 용이합니다. 그리고 유한상태기계는 본질적으로 코드화된 규칙을 따르기 때문에 프로세서의 귀중한 시간을 전혀 빼앗지 않습니다. 그저 IF - THIS - THEN - THAT과 같은 논리만 사용하며, 더 높은수준의 '생각하기'는 포함되지 않습니다. 직관적이라는 말은, 다음과 같다고 생각합니다. 

배가 고프다 => 밥먹기 상태로 돌입한다 => 음식이 있는지 확인한다 => 음식을 먹는다 => 배가 부르다 =>  밥먹기 상태에서 탈출한다.

배가 고프면 밥을 먹어야 겠지요. 따라서 밥먹기 상태로 돌입했습니다. 직관적입니다.
또한 새로운 규칙과 상태를 추가함으로써 에이전트의 행동범위를 간단하게 확장시킬 수 있으므로 유연성이 있습니다.


상태 전환표

상태전환에 영향을 미치고 상태들을 조직하는 더 좋은 방법은, 상태전환표(state transition table)을 이용하는 것 입니다. 개략적인 도표는 다음과 같습니다.

 현재 상태
 조건
상태전환 
도망가기 
안전하다
순찰하기 
공격하기 
적보다 약하다 
도망가기 
순찰하기
위협받고_적보다 강하다 
공격하기 

이러한 형식의 구조는 매우 유연하여 새로운 '상태'들을 추가함으로써 에이전트의 실행종목을 쉽게 확장할 수 있게 해줍니다.

프로그래머 입장에서 에이전트가 유연해지도록 도와주려면 어떻게 해야 할까요?
바로 State라는 class를 공통으로 공유, 즉 상속 하는 것 입니다.


상태 디자인 패턴

비록 상태 디자인 패턴은 수학적 FSM 형식화로부터의 이탈이지만 직관적이고 코딩하기 간단하며 확장이 용이해집니다. 구조는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;
public class State<Entity_Type> : MonoBehaviour
{
    public virtual void Enter(Entity_Type entity)
    {
        // Do something..
    }
    public virtual void Execute(Entity_Type entity)
    {
        // Do something..
    }
    public virtual void Exit(Entity_Type entity)
    {
        // Do something..
    }
}
cs


템플릿으로 작성된 State 클래스를 에이전트의 어떠한 State의 Entity type으로도 받을 수 있습니다. 이 디자인 패턴은 유한상태기계 프로그래밍을 훨씬 쉽게 해주며 유연성을 부여합니다.

이제 각각의 에이전트는, 현재 자신의 상태를 가리키는 포인터를 사용하고 각 상태의 매니저가 상태 전이시 Enter, 그후 Execute를 Update 주기에 맞춰서 반복한 후 상태 전이시 Exit 함수를 호출한 후 상태를 전이시키게 합니다.
 상태의 매니저, StateMachine class의 구조는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class StateMachine<Entity_Type> 
{
    // The pointer that indicate an agent who own this instance
    Entity_Type m_pOwner;
    State<Entity_Type> m_pCurrentState;
    // Trace of last state that this agent constituted.
    State<Entity_Type> m_pPreviousState;
    // This state logic will be called whenever FSM is updated.
    State<Entity_Type> m_pGlobalState;
    public StateMachine(Entity_Type owner)
    {
        m_pOwner = owner;
        m_pCurrentState = null;
        m_pPreviousState = null;
        m_pGlobalState = null;
    }
    // Use those method to initiate FSM.
    public void SetCurrentState(State<Entity_Type> s) { m_pCurrentState = s; }
    public void SetGlobalState(State<Entity_Type> s) { m_pGlobalState = s; }
    public void SetPreviousState(State<Entity_Type> s) { m_pPreviousState = s; }
    // Use this method to uptate FSM
    public void Updating()
    {
       
        // If static state is existed, call execute method
        if (m_pGlobalState)
            m_pGlobalState.Execute(m_pOwner);
        // Just same as now state
        if (m_pCurrentState)
            m_pCurrentState.Execute(m_pOwner);
    }
    // Chage to New state
    public void ChangeState(State<Entity_Type> pNewState)
    {
        if (pNewState == null)
        {
            Debug.Log("<StateMachine::ChangeState>: trying to change to a null state");
        }
        // Maintain previous state
        m_pPreviousState = m_pCurrentState;
        // Call Exit method of Current state
        m_pCurrentState.Exit(m_pOwner);
        // Change to new state
        m_pCurrentState = pNewState;
        // Call Enter method of New state
        m_pCurrentState.Enter(m_pOwner);
    }
    // Revert the state to Previous State
    public void RevertToPreviousState()
    {
        ChangeState(m_pPreviousState);
    }
    // accessor
    State<Entity_Type> CurrentState()
    {
        return m_pCurrentState;
    }
    State<Entity_Type> GlobalState()
    {
        return m_pGlobalState;
    }
    State<Entity_Type> PreviousState()
    {
        return m_pPreviousState;
    }
    // Return true when argument is same to Now state
    public bool IsInstate(State<Entity_Type> st){
        if (st == CurrentState()) return true;
        else return false;
    }
}
cs


Line 32 의 Updating() 메소드에서 현 상태의 Execute를 반복하게 됩니다.
Line 44 의 ChangeState() 메소드에서 Exit - Enter - Execute 구조를 볼 수 있습니다.

이상으로 FSM에 대한 포스팅을 마치겠습니다.



댓글