遊戲裏經常有各種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框架