유한상태기계(finite state machine, FSM)는 인정받은 설계 기법입니다. 유한상태기계는 상태(state)들의 유한한 집합이며, 어떤 때에 오직 하나의 상태만 활성화되고, 그 상태에 대한 코드가 실행됩니다. 어떤 상태에서 다른 상태로 전환하는 방법은 두 가지가 있는데요. 첫 번째는 어떤 상태에 대한 코드가 다른 상태로 바꿔야 하는 상황을 검사하여 그 상태로 전환하는 것입니다. 이 때는 상태에 따른 행동, 상태 전환 코드가 그 객체에만 들어있게 됩니다. 두 번째 방법에서는 상태 관련 코드의 밖에 있는 코드에서 상황들을 점검하고 상태를 전환합니다. 이 때는 상태에 따른 행동은 그 객체에 들어있고 상태 전환 코드는 다른 객체(주로 관리자 객체)에 있거나, 아니면 둘 다 관리자 객체에 있습니다.
일인칭 슈팅 게임에서 주인공을 죽이는 것이 목적인 인공지능 적이 있다고 가정합시다. 적에게는 Patrol, Attack이라는 두 상태가 있습니다. Patrol 상태에서는 주인공을 탐색하는 코드만 실행하는데, 주인공을 발견하면 Attack 상태로 전환합니다. 이를 코드로 표현하면 다음과 같습니다. (그나마 제대로 할 줄 아는 언어가 액션스크립트밖에 없어서 액션스크립트로 씁니다)
///////////////////////////////////////////////
// 초기화 코드에서
const PATROL:String = "patrol";
const ATTACK:String = "attack";
var state:String = PATROL;
///////////////////////////////////////////////
// enterFrame 수신자 또는 타이머 함수 안에서
switch(state){
case PATROL :
// 정찰을 수행한다
if(주인공을 발견했다면){
state = ATTACK;
}
break;
case ATTACK :
// 주인공을 공격한다
break;
}
///////////////////////////////////////////////
또는 행동 코드와 전환 코드를 분리하여 이렇게 쓸 수 있습니다.
///////////////////////////////////////////////
// 초기화 코드에서
const PATROL:String = "patrol";
const ATTACK:String = "attack";
var state:String = PATROL;
///////////////////////////////////////////////
// enterFrame 수신자 또는 타이머 함수 안에서
switch(state){
case PATROL :
// 정찰을 수행한다
break;
case ATTACK :
// 주인공을 공격한다
break;
}
///////////////////////////////////////////////
// 다른 함수 안에서
if(state == PATROL && 적이 주인공을 발견했다면){
state = ATTACK;
}
///////////////////////////////////////////////
사실 각 상태에 따른 행동은 매우 복잡해질 수 있고 그에 따라 코드의 양도 늘어날 수 있으므로 상태 수행 코드를 어떤 함수에 따로 작성하고 switch문 안에서는 그 함수를 호출만 하는 식으로 코드를 작성할 수도 있습니다. (윈도우 API에서 메시지를 이렇게 처리하는 것을 메시지 크랙킹이라고 하던가요?)
///////////////////////////////////////////////
// 초기화 코드에서
const PATROL:String = "patrol";
const ATTACK:String = "attack";
var state:String = PATROL;
///////////////////////////////////////////////
// enterFrame 수신자 또는 타이머 함수 안에서
switch(state){
case PATROL :
patrol();
break;
case ATTACK :
attack();
break;
}
///////////////////////////////////////////////
// 함수 정의
function patrol():void {
// 정찰을 수행한다 (코드 매우 많음)
if(주인공을 발견했다면){
state = ATTACK;
}
}
function attack():void {
// 주인공을 공격한다 (코드 매우 많음)
}
///////////////////////////////////////////////
기왕이면 함수 참조를 써서 state 변수랑 switch문을 없애버립시다.
///////////////////////////////////////////////
// 초기화 코드에서
const PATROL:String = "patrol";
const ATTACK:String = "attack";
var action:Function = patrol;
///////////////////////////////////////////////
// enterFrame 수신자 또는 타이머 함수 안에서
action();
///////////////////////////////////////////////
// 함수 정의
function patrol():void {
// 정찰을 수행한다 (코드 매우 많음)
if(주인공을 발견했다면){
action = attack;
}
}
function attack():void {
// 주인공을 공격한다 (코드 매우 많음)
}
///////////////////////////////////////////////
유한상태기계는 여러 방법으로 응용할 수 있습니다. 두 가지만 예를 들어보자면 유한상태기계에 디자인 패턴을 적용하는 것과 스택 기반 유한상태기계를 사용하는 것입니다. 관련 자료를 참고하시기 바랍니다.
유한상태기계의 단점은 애초에 의도한 것 이상을 구현하기 어렵다는 점입니다. 확장이 어렵지요. 그래서 인공지능 쪽에서는 유한상태기계를 넘어서는 새로운 방법이 많이 연구되었습니다.
출처 : http://liverwort.tistory.com/458
일인칭 슈팅 게임에서 주인공을 죽이는 것이 목적인 인공지능 적이 있다고 가정합시다. 적에게는 Patrol, Attack이라는 두 상태가 있습니다. Patrol 상태에서는 주인공을 탐색하는 코드만 실행하는데, 주인공을 발견하면 Attack 상태로 전환합니다. 이를 코드로 표현하면 다음과 같습니다. (그나마 제대로 할 줄 아는 언어가 액션스크립트밖에 없어서 액션스크립트로 씁니다)
///////////////////////////////////////////////
// 초기화 코드에서
const PATROL:String = "patrol";
const ATTACK:String = "attack";
var state:String = PATROL;
///////////////////////////////////////////////
// enterFrame 수신자 또는 타이머 함수 안에서
switch(state){
case PATROL :
// 정찰을 수행한다
if(주인공을 발견했다면){
state = ATTACK;
}
break;
case ATTACK :
// 주인공을 공격한다
break;
}
///////////////////////////////////////////////
또는 행동 코드와 전환 코드를 분리하여 이렇게 쓸 수 있습니다.
///////////////////////////////////////////////
// 초기화 코드에서
const PATROL:String = "patrol";
const ATTACK:String = "attack";
var state:String = PATROL;
///////////////////////////////////////////////
// enterFrame 수신자 또는 타이머 함수 안에서
switch(state){
case PATROL :
// 정찰을 수행한다
break;
case ATTACK :
// 주인공을 공격한다
break;
}
///////////////////////////////////////////////
// 다른 함수 안에서
if(state == PATROL && 적이 주인공을 발견했다면){
state = ATTACK;
}
///////////////////////////////////////////////
사실 각 상태에 따른 행동은 매우 복잡해질 수 있고 그에 따라 코드의 양도 늘어날 수 있으므로 상태 수행 코드를 어떤 함수에 따로 작성하고 switch문 안에서는 그 함수를 호출만 하는 식으로 코드를 작성할 수도 있습니다. (윈도우 API에서 메시지를 이렇게 처리하는 것을 메시지 크랙킹이라고 하던가요?)
///////////////////////////////////////////////
// 초기화 코드에서
const PATROL:String = "patrol";
const ATTACK:String = "attack";
var state:String = PATROL;
///////////////////////////////////////////////
// enterFrame 수신자 또는 타이머 함수 안에서
switch(state){
case PATROL :
patrol();
break;
case ATTACK :
attack();
break;
}
///////////////////////////////////////////////
// 함수 정의
function patrol():void {
// 정찰을 수행한다 (코드 매우 많음)
if(주인공을 발견했다면){
state = ATTACK;
}
}
function attack():void {
// 주인공을 공격한다 (코드 매우 많음)
}
///////////////////////////////////////////////
기왕이면 함수 참조를 써서 state 변수랑 switch문을 없애버립시다.
///////////////////////////////////////////////
// 초기화 코드에서
const PATROL:String = "patrol";
const ATTACK:String = "attack";
var action:Function = patrol;
///////////////////////////////////////////////
// enterFrame 수신자 또는 타이머 함수 안에서
action();
///////////////////////////////////////////////
// 함수 정의
function patrol():void {
// 정찰을 수행한다 (코드 매우 많음)
if(주인공을 발견했다면){
action = attack;
}
}
function attack():void {
// 주인공을 공격한다 (코드 매우 많음)
}
///////////////////////////////////////////////
유한상태기계는 여러 방법으로 응용할 수 있습니다. 두 가지만 예를 들어보자면 유한상태기계에 디자인 패턴을 적용하는 것과 스택 기반 유한상태기계를 사용하는 것입니다. 관련 자료를 참고하시기 바랍니다.
유한상태기계의 단점은 애초에 의도한 것 이상을 구현하기 어렵다는 점입니다. 확장이 어렵지요. 그래서 인공지능 쪽에서는 유한상태기계를 넘어서는 새로운 방법이 많이 연구되었습니다.
출처 : http://liverwort.tistory.com/458