實現簡易而強大的游戲AI——FSM,有限狀態機


http://blog.friskit.me/2012/05/introduction-of-fsm/

在很久很久以前,受限於計算機性能和圖形效果,游戲往往是以玩家為唯一主動對象的,玩家發出動作,游戲響應結果。除此之外,不需要系統在玩家沒有發出動作時產生響應。可以說,玩家的動作與游戲是“同步”的。

隨着計算機的處理能力的發展,更絢麗的游戲逐漸產生。玩家就不能只滿足盯着屏幕上靜態的一張張圖片進行游戲。也就是說,游戲應該有自己的方式能夠與玩家主動溝通。這樣才能使游戲更加生動,虛擬的環境顯得更加真實。游戲上非玩家角色(NPC)應該有着自己獨立的動作。

像星際爭霸這類RTS游戲推動了游戲AI產業的發展。游戲AI的編寫方法也逐漸規范。

最早,游戲AI往往是這樣寫的:

switch(自己){
	case "血量充足":
		打怪();
		break;
	case "快死了":
		補血();
		break;
	case "死了":
		游戲全局->Gameover();
		break;
}

血量充足就要去打怪,快死了就補血,徹底死了游戲就結束。
把這個代碼放到一個無限死循環里頭,好了,這個游戲AI就算做成了。把這東西放到代碼里頭,提交,然后等老板發工資。

但是老板突然說:“你這個AI寫的太簡單了”。然后又balabala提了一大堆需求:
老板又讓你加上這些功能:血量80的時候用魔法補一補就行了,血量60的時候吃個小血瓶,血量40的時候吃大血瓶,血量20的時候趕快逃跑。

然后你又要找到上面這個switch,然后修改里頭的case。想象一下,萬一你碰到了一個Dota高手當老板,心中有着各種很NB的殺敵策略,你需要雖是根據環境判斷利用那種策略。策略越來越多,很快,一個帶有上萬行代碼的函數就橫空出世了!如果這個時候遇到bug了,先別說怎么改了,光把這個幾M的源代碼打開都夠費勁的吧?然后,然后你就沒有然后了。。。

上面的方法在遇到大量的狀態(State)的時候會讓代碼崩潰,好在有無數前輩前仆后繼用各種切身體會幫我們提出了一種又一種精簡代碼的手段。

不知大家有多少人學過數字電路。學過里面的“狀態圖”?
在時序電路里頭,一個系統往往由一堆狀態組成,從狀態A,通過輸入,跳轉到狀態B。這就是狀態圖。例如下面的圖片:

上面這種方法就被稱作“有限狀態機”(FSM,Finite State Machine)。那么究竟什么是FSM?

·FSM是一種數據結構,它由以下幾個部分組成:

1,內在的所有狀態(必須是有限個)

2,輸入條件

3,狀態之間起到連接性作用的轉換函數

·為什么要用FSM?

因為它編程快速簡單,易於調試,性能高,與人類思維相似從而便於梳理,靈活且容易修改

·FSM的描述性定義:

一個有限狀態機是一個設備,或是一個模型,具有有限數量的狀態。它可以在任何給定時間根據輸入進行操作,使得系統從一個狀態轉換到另一個狀態,或者是使一個輸出或者一種行為的發生,一個有限狀態機在任何瞬間只能處於一種狀態。

·挖掘狀態

例如我們有這樣一個場景。玩家控制一個Hero打怪練級。這個英雄等級不夠沒啥經驗帶上典型的阿Q精神,所以基本上只有這三個動作:
1,平時的狀態是巡邏,就是漫無目的的走。。。
2,如果遇到敵人之后大量一下敵人。
3,如果敵人比自己弱小,那就打。
4,如果敵人比自己強大,那就跑。

根據上面的需求我們能畫出這樣一張狀態圖:

我對FSM的基本解釋:一個智能體,在有規則的時間間隔內詢問現在所掌握的環境數據,使得它能夠基於從游戲環境中接受到的刺激進行必要的狀態轉換。每一個狀態可以模型化為一個分離的對象,或者存在於智能體外部的函數。

這樣,FSM提供給了我們一個清楚靈活的結構。

·FSM一般骨架代碼

一般FSM中需要有以下幾個類作為一種數據框架:
FSMState類:抽象類,表示基本狀態,所有狀態都應該繼承自這個類
FSMMachine類:一台有限狀態機
FSMAIControl類:存放有限狀態機,通常就是游戲AI的主循環。並且能存放環境感知數據等內容。

下面是Java偽代碼:

/**
*FSMState:
*/
public abstract class FSMState{
	public FSMAIControl m_parent;
	public int m_type;
 
	public abstract void Enter();		//狀態進入時執行動作
	public abstract void Exit();		//狀態退出時執行動作
	public abstract void Update();		//游戲主循環中狀態的內部執行機制
	public abstract void Init();		//狀態的初始化
	public FSMState CheckTransition();	//狀態轉移判斷
}
 
/**
*FSMMachine
*/
public class FSMMachine{
	private ArrayList<FSMState> m_states;
	private FSMState m_currentState;
	private FSMState m_defaultState;
	private FSMState m_goalState;
	private int m_goalID;
 
	public void UpdateMachine();			//更新狀態機狀態
	public void AddState(FSMState state);		//給狀態機添加狀態
	public void SetDefaultState(FSMState state);	//設置默認狀態
	public void SetGoalID(int goal);		//設置目標狀態
	public void TransitionState(int goal);		//狀態轉移
	public void Reset();				//狀態重置
}
 
/**
*FSMAIControl
*/
public class FSMAIControl{
	public …	//游戲感知數據
	private FSMMachine m_machine;
 
	public void Update();
	public void UpdatePerceptions();
	public void Init();
}

然后就是要實現狀態

public class StateA extends FSMState{
	public void Update(){};
	…
 
	FSMState CheckTransitions(){
		if(parent.xxx=xxx)	//判斷感知數據
			return StateB;
	}
	…
}

從上面的介紹中我們很容易歸納出一套FSM比較通用的一般步驟:
1,確定狀態
2,列舉感知數據
3,分別編寫狀態Update邏輯
4,確定轉移狀態的條件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM