有限狀態機在遊戲中的應用

遊戲裏經常有各種AI行爲,例如怪物砍人,玩家自動掛機等等。假設有這樣的簡易場景

場景裏的一隻怪物平時就在洞口巡邏。當遇到敵人的時候,如果比對方強大,就揍敵人;如果比敵人弱,就繞道逃跑。

用switch或if語句,很簡易實現上面的需求

enum State {
    Patrol,
	RunAway,
	Attack
};

void onUpdate(State currState) {
	
	switch(currStage) {
		case Patrol: {
			if (seeEnemy()) {
				if (StrongerThanEnemy) {
					changeState(Attack);
				} else {
					changeState(RunAway);
				}
			}
			break;
		}
		case Attack: {
			// 快死啦
			if (willDieSoon()) {
				changeState(RunAway);
			} else {
				if (enemyDie()) {
					changeState(Patrol);
				}
			}
			break;
		}
		case RunAway: {
			if (safeNow()) {
				changeState(Patrol);
			}
			break;
		}
	}

}
乍看一下,這樣實現的合理的。但在實際應用場合中,如果碰到狀態複雜的情況,這樣的代碼就變成了惡夢。

有兩種比較簡單的方法來實現遊戲ai。一種是行爲樹,也叫決策樹。另外一種,就是今天要講的方法,即有限狀態機。

一個有限狀態機,是一種具有有限數量狀態的智能體。在給定的狀態下接受某些事件,就能從一個狀態切換到另外一個狀態。一個有限狀態機,在任何時刻都只能處於一種狀態。

最簡單的狀態機,可以通過燈的開關來理解。燈有兩種狀態,開或關。在開的狀態接受點擊事件,就會切換成關閉狀態;在關的狀態下接受點擊事件,就會切換成打開狀態。


狀態變換表

一個用於組織狀態和影響狀態切換的方法就是制定一個狀態變換表。


在設計AI的時候,一定要清楚狀態的變換流程,不然很容易使智能體陷入某一個狀態而走不出來。


下面我們使用設計模式裏的“狀態模式”來實現有限狀態機的代碼。

對於每一個狀態,我們至少需要三個方法。

public interface State {
	
	/**
	 * 切換至新狀態
	 * @param creature
	 */
	void onEnter(Creature creature);
	
	/**
	 * 離開當前狀態
	 * @param creature
	 */
	void onExit(Creature creature);
	
	/**
	 * 每一個tick跑的業務
	 * @param creature
	 */
	void execute(Creature creature);

}
給出一個攻擊的狀態實現,其他狀態代碼類似

public class AttackState implements State {

	@Override
	public void onEnter(Creature creature) {
		// 進入攻擊狀態
		
	}

	@Override
	public void onExit(Creature creature) {
		// 離開攻擊狀態
	}

	@Override
	public void execute(Creature creature) {
		Player player = (Player)creature;
		Scene scene = player.getScene();
		Monster monster = scene.getMonster();
		player.changeHp(-monster.getAttack());
		monster.changeHp(-player.getAttack());
		System.err.println("邂逅敵人,快使用雙截棍,哼哼哈兮。"
				+ "我方血量["+ player.getHp() + "]"
				+ "敵方血量["+ monster.getHp() + "]");

	}

}
狀態切換規則(Transition抽象類),需要綁定開始狀態和結束狀態,以及抽象方法用於判斷智能體在當前的狀態下能否發生轉換。

public abstract class Transition {

	/** 開始狀態 */
	private State from;
	/** 結束狀態 */
	private State to;
	
	public Transition(State from, State to) {
		this.from = from;
		this.to = to;
	}
	
	/**
	 * 條件判定
	 * @param creature
	 * @return
	 */
	public abstract boolean meetCondition(Creature creature);

	public State fromState() {
		return this.from;
	}
	
	public State toState() {
		return this.to;
	}
	
}
給出一種狀態的實現,其他代碼類似

public class Attack2RunTransition extends Transition {

	public Attack2RunTransition(State from, State to) {
		super(from, to);
	}

	@Override
	public boolean meetCondition(Creature creature) {
		// 如果當前在攻擊狀態,且攻擊力比怪物低,那就趕緊逃命吧
		Player player = (Player)creature;
		Scene scene = player.getScene();
		return  player.getHp() < 50 	// 快死啦
				|| player.getAttack() > scene.getMonster().getAttack()
				|| Math.random() < 0.4	; //有概率逃跑,增大隨機事件
	}

}
有限狀態機(智能體業務執行者)

public class FiniteStateMachine {
	
	private State initState;
	
	private State currState;
	/** 各種狀態以及對應的轉換規則 */
	private Map<State, List<Transition>> state2Transtions = new HashMap<>();
	/** 爲了支持ai暫停 */
	private volatile boolean running = true;
	/** 恢復ai超時時間 */
	private long freezeTimeOut;

	public void addTransition(Transition transition) {
		List<Transition> transitions = state2Transtions.get(transition.fromState());
		if (transitions == null) {
			transitions = new ArrayList<>();
			state2Transtions.put(transition.fromState(), transitions);
		}
		transitions.add(transition);
	}
	
	public State getInitState() {
		return initState;
	}

	public void setInitState(State initState) {
		this.initState = initState;
	}

	public void enterFrame(Creature creature) {

		if (this.currState == null) {
			this.currState = this.initState;
			this.currState.onEnter(creature);
		}

		Set<String> passed = new HashSet<>();
		String clazzName = this.currState.getClass().getName();

		for (; ;) {

			if (!running) {
				if (freezeTimeOut > 0 && System.currentTimeMillis() > freezeTimeOut) {
					running = true;
				} else {
					break;
				}

			} 

			this.currState.execute(creature);
			if (passed.contains(clazzName)) {
				break;
			}
			passed.add(clazzName);

			List<Transition> transitions = state2Transtions.get(this.currState);
			for (Transition transition:transitions) {
				if (transition.meetCondition(creature)) {
					this.currState.onExit(creature);
					this.currState = transition.toState();
					this.currState.onEnter(creature);
				}
			}
		}

	}
	
	/**
	 * 暫停ai
	 * @param timeout
	 */
	public void freeze(long timeout) {
		this.freezeTimeOut = System.currentTimeMillis() + timeout;
	}

}
示例代碼

public class AiTest {
	
	public static void main(String[] args) throws Exception {
		Player player = new Player(100, 15);
		Monster monster = new Monster(120, 10);
		
		Scene scene = new Scene();
		scene.setPlayer(player);
		scene.setMonster(monster);
		
		player.setScene(scene);
		monster.setScene(scene);
		
		State patrolState = new PatrolState();
		State attackState = new AttackState();
		State runState = new RunAwayState();
		
		Transition transition1 = new Patrol2AttackTransition(patrolState, attackState);
		Transition transition2 = new Attack2RunTransition(attackState, runState);
		Transition transition3 = new Atttack2PatrolTransition(attackState, patrolState);
		Transition transition4 = new Run2PatrolTransition(runState, patrolState);
		
		FiniteStateMachine fsm = new FiniteStateMachine();
		fsm.setInitState(patrolState);
		
		fsm.addTransition(transition1);
		fsm.addTransition(transition2);
		fsm.addTransition(transition3);
		fsm.addTransition(transition4);
		
		while (true) {
			fsm.enterFrame(player);
			Thread.sleep(500);
		}
			
		
	}

}
代碼運行結果(限於篇幅,部分截圖不可見)


其他輔助代碼

生物類(Creature),玩家與怪物的父類

public abstract class Creature {
	
	protected long hp;
	
	protected int attack;
	
	private Scene scene;

	public Creature(long hp, int attack) {
		this.hp = hp;
		this.attack = attack;
	}

	public long getHp() {
		return hp;
	}

	public void setHp(long hp) {
		this.hp = hp;
	}
	
	public void changeHp(long changeHp) {
		this.hp += changeHp;
	}

	public int getAttack() {
		return attack;
	}

	public void setAttack(int attack) {
		this.attack = attack;
	}
	
	public Scene getScene() {
		return scene;
	}

	public void setScene(Scene scene) {
		this.scene = scene;
	}
	
	public boolean isDie() {
		return this.hp <= 0;
	}

}
玩家類。怪物類與它相似,換個名字即可
public class Player extends Creature {
	

	public Player(long hp, int attack) {
		super(hp, attack);
	}

	@Override
	public String toString() {
		return "Player [hp=" + hp + ", attack=" + attack + "]";
	}
	
}
場景類

public class Scene {
	
	private Player player;
	
	private Monster monster;

	public Player getPlayer() {
		return player;
	}

	public void setPlayer(Player player) {
		this.player = player;
	}

	public Monster getMonster() {
		return monster;
	}

	public void setMonster(Monster monster) {
		this.monster = monster;
	}

}

 分層有限狀態機

如果某個狀態本身又是由一系統小狀態組成的,那麼爲了方便管理,我們可以使用分層次的有限狀態機(HierarchicalFiniteStateMachine)。例如攻擊狀態,可細分爲跟蹤敵人->選擇技能->戰鬥。這裏就不展開了。



工程git路徑 --> mmorpg框架









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章