作者:卡卡西0旗木
泰斗原文:http://www.taidous.com/forum.php?mod=viewthread&tid=32785&_dsign=78057cb0
有限状态机很多人都听过,但是真正理解的人却不多。很多人虽然都也写了FSMState,FSM这样的类。但还是自己处理着状态的跳转,执行等等。
这里要先罗嗦几句了,其实程序员,最重要的还是对基础和概念的理解,真正理解透了,做什么都是很容易的。
对状态机没怎么了解的,可以看看维基百科的解释:http://zh.wikipedia.org/wiki/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA
看了上面的介绍,简单来说,FSM就是一个图,这个图的每个结点是一个状态,图还声明了一个节点接收到怎样的输入就会跳转到什么结点。
好了。如果到这里你还不明白FSM,那么再反复好好先看看上面的介绍。然后再来看代码不急。
1、定义一个状态类
组成状态机的基本元素之一就是状态,这里我们先定义这样一个类
[Java] 纯文本查看 复制代码
/** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2014年12月2日 下午5:45:54 */ public class FiniteState<T> { private final int state; private IFiniteStateExecutor<T> stateExecutor; /** * @param state */ public FiniteState(int state) { this.state = state; } /** * * @param fightStateMachine * @param t * @param now * @param duration */ public void doState(FiniteStateMachine<T> fightStateMachine, T t, long now, int duration) { stateExecutor.execute(fightStateMachine, t, now, duration); } /** * @param stateExecutor * the stateExecutor to set */ public void setStateExecutor(IFiniteStateExecutor<T> stateExecutor) { this.stateExecutor = stateExecutor; } /** * @return the state */ public int getState() { return state; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "FightState [state=" + state + "]"; } /** * 当切换到状态的时候调用 */ public void onInit(FiniteStateMachine<T> stateMac, T t, long now, int duration) { stateExecutor.onInit(stateMac, t, now, duration); } }
这个类很简单,state属性在每个状态机中保持唯一即可,表明一个状态值。
stateExecutor属性则是表明在这个状态下要处理的行为,比如:在巡逻状态下,你要控制角色走动,那么stateExecutor就是走路的代码。
2、状态的行为
如果状态机只是状态的跳转,而状态没任何行为,那么它其实意义也不是很大。我们更多的时候是想要处理:在什么状态下,做什么事。
因此,我们这里把“做什么事”这个抽象出来一个接口IFiniteStateExecutor。
[Java] 纯文本查看 复制代码
/** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2014年12月2日 下午10:20:47 */ public interface IFiniteStateExecutor<T> { /** * * @param fightStateMachine * @param hoster * @param now * @param duration */ void execute(FiniteStateMachine<T> fightStateMachine, T hoster, long now, int duration); /** * */ void onInit(FiniteStateMachine<T> stateMac, T t, long now, int duration); }
这个接口很简单,就是定义了这个行为初始化和执行的方法。
3、状态机核心
说了这么多,还没讲道真正状态机的处理。但是如果细心的同学,可以发现上面的代码中都引用了一个FiniteStateMachine的类。没错,这个类正是状态机的核心。
我们先看看代码:
[Java] 纯文本查看 复制代码
/** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2014年12月2日 下午5:41:37 */ public class FiniteStateMachine<T> { private static final Logger LOGGER = LoggerFactory.getLogger(FiniteStateMachine.class); private final List<FiniteStateTransaction<T>> TRANSACTION_LIST_HOLDER = new ArrayList<FiniteStateTransaction<T>>(0); private FiniteState<T> lastState; private FiniteState<T> currentState; private Map<Integer, FiniteState<T>> allState = new HashMap<>(); private Map<FiniteState<T>, List<FiniteStateTransaction<T>>> transactions = new HashMap<>(); private Map<String, Integer> intParams = new HashMap<>(); private Map<String, Boolean> boolParams = new HashMap<>(); private Map<String, Long> longParams = new HashMap<>(); private IFiniteStatesProcesser<T> statesProcesser; private IOnFiniteStateChangeProcesser<T> onStateChangeProcesser; public FiniteStateMachine( IFiniteStatesProcesser<T> statesProcesser, IOnFiniteStateChangeProcesser<T> onStateChangeProcesser) { if (statesProcesser == null) { statesProcesser = new IFiniteStatesProcesser<T>() { @Override public void process(FiniteStateMachine<T> stateMachine, FiniteState<T> currentState, T t, long now, int duration) { } }; } this.statesProcesser = statesProcesser; if (onStateChangeProcesser == null) { onStateChangeProcesser = new IOnFiniteStateChangeProcesser<T>() { @Override public void onChange( FiniteStateMachine<T> stateMachine, T fightScene, FiniteState<T> oldState, FiniteState<T> newState) { } }; } this.onStateChangeProcesser = onStateChangeProcesser; } /** * 将一个状态设置为默认状态 * * @param state */ public void setDefaultState(int state) { FiniteState<T> fightState = allState.get(state); if (fightState == null) { throw new NullPointerException("Can not found such state " + state + " as default state."); } setDefaultState(fightState); } /** * 设置参数 * * @param key * @param value */ public void setInteger(String key, int value) { intParams.put(key, value); } public void setBoolean(String key, boolean value) { boolParams.put(key, value); } public void tick(T t, long now, int duration) { if (lastState != currentState) { currentState.onInit(this, t, now, duration); LOGGER.info("Change State -- old state=" + lastState + ", new state=" + currentState); onStateChangeProcesser.onChange(this, t, lastState, currentState); lastState = currentState; } currentState.doState(this, t, now, duration); processOnCurrentState(currentState, t, now, duration); checkCurrentState(); } /** * * @param currentState * @param t * @param now * @param duration */ public void processOnCurrentState(FiniteState<T> currentState, T t, long now, int duration) { statesProcesser.process(this, currentState, t, now, duration); } /** * 检查当前状态,并在需要的时候进行跳转。 */ private void checkCurrentState() { FiniteState<T> state = null; List<FiniteStateTransaction<T>> list = transactions.get(currentState); for (FiniteStateTransaction<T> t : list) { if (t.check(intParams, boolParams, longParams)) { state = t.getDstState(); break; } } if (state != null && state != currentState) { currentState = state; } } /** * 将一个状态设置为默认状态 * * @param fightState */ public void setDefaultState(FiniteState<T> fightState) { if (fightState == null) { throw new NullPointerException("Default state can not be null."); } currentState = fightState; } /** * 增加一个状态,如果已存在,则不添加。 * * @param state * @return 返回当前状态。 */ public FiniteState<T> addState(int state) { FiniteState<T> fightState = allState.get(state); if (fightState == null) { fightState = new FiniteState<T>(state); allState.put(state, fightState); if (transactions.get(fightState) == null) { transactions.put(fightState, TRANSACTION_LIST_HOLDER); } } return fightState; } /** * * @param state * @param executor * @return */ public FiniteState<T> addState(int state, IFiniteStateExecutor<T> executor) { FiniteState<T> addState = addState(state); addState.setStateExecutor(executor); return addState; } /** * 为两个状态之间添加关联。如果两个状态已经存在关联,则返回该关联。 * * @param src * @param dst * @return */ public FiniteStateTransaction<T> addTranscation(FiniteState<T> src, FiniteState<T> dst) { List<FiniteStateTransaction<T>> list = transactions.get(src); boolean checkContains = true; if (list == TRANSACTION_LIST_HOLDER) { list = new LinkedList<>(); transactions.put(src, list); checkContains = false; } FiniteStateTransaction<T> fightTransaction = null; if (checkContains) { for (FiniteStateTransaction<T> t : list) { if (t.getDstState() == dst) { fightTransaction = t; break; } } } if (fightTransaction == null) { fightTransaction = new FiniteStateTransaction<T>(dst); list.add(fightTransaction); } return fightTransaction; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "FightState<T>Machine [currentState=" + currentState + ", intParams=" + intParams + ", boolParams=" + boolParams + "]"; } /** * @return the currentState */ public FiniteState<T> getCurrentState() { return currentState; } }
tick方法即是状态机的触发,你只要在每帧中调用这个方法,状态机就会自己根据一些输入的情况进行状态跳转,并执行当前状态下应该执行的行为。
tick中调用了checkCurrentState,正是这个方法对当前状态进行检查和跳转。
我们详细看看这段代码:
[Java] 纯文本查看 复制代码
private void checkCurrentState() { FiniteState<T> state = null; List<FiniteStateTransaction<T>> list = transactions.get(currentState); for (FiniteStateTransaction<T> t : list) { if (t.check(intParams, boolParams, longParams)) { state = t.getDstState(); break; } } if (state != null && state != currentState) { currentState = state; } }
我们把当前状态跳转到其它状态这样的关系通过一个FiniteStateTransaction的类进行保存。这个类中又包含了跳转的跳转的条件ITransactionCondition。
4、状态跳转FiniteStateTransaction的定义
/** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2014年12月2日 下午5:50:58 */ public class FiniteStateTransaction<T> { private List<ITransactionCondition> list = new LinkedList<>(); private FiniteState<T> dst; /** * @param dst */ public FiniteStateTransaction(FiniteState<T> dst) { this.dst = dst; } public void addCondition(ITransactionCondition fightCondition) { list.add(fightCondition); } /** * * @param intParams * @param boolParams * @param longParams * @return */ public boolean check(Map<String, Integer> intParams, Map<String, Boolean> boolParams, Map<String, Long> longParams) { for (ITransactionCondition c : list) { if (!c.check(intParams, boolParams, longParams)) { return false; } } return true; } /** * @return */ public FiniteState<T> getDstState() { return dst; } }
可以看到这个类中包含了一个条件列表和一个目标状态。
他的check方法就是检查是否所有条件都满足。
5、条件的定义
public interface ITransactionCondition { /** * * @param intParams * @param boolParams * @param longParams * @return */ boolean check(Map<String, Integer> intParams, Map<String, Boolean> boolParams, Map<String, Long> longParams); }
6、条件的一些实现。
我分别实现了布尔值、int、long的条件
[Java] 纯文本查看 复制代码
/** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2014年12月2日 下午9:49:55 */ public class BoolCondition implements ITransactionCondition { private String key; private boolean expectValue; /** * * @param key * @param expectValue */ public BoolCondition(String key, boolean expectValue) { this.key = key; this.expectValue = expectValue; } @Override public boolean check(Map<String, Integer> intParams, Map<String, Boolean> boolParams, Map<String, Long> longParams) { Boolean currBool = boolParams.get(key); if (currBool == null) { return false; } return currBool == expectValue; } }
[Java] 纯文本查看 复制代码
/** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2014年12月2日 下午9:38:25 */ public class IntCondition implements ITransactionCondition { public static final byte EQUALS = 0; public static final byte SMALLER = 1; public static final byte LARGER = 2; public static final byte NOT_EQUALS = 3; private String key; private byte compareType; private int compareValue; /** * * @param key * @param compareType * @param compareValue */ public IntCondition(String key, byte compareType, int compareValue) { this.key = key; this.compareType = compareType; this.compareValue = compareValue; if (compareType != EQUALS && compareType != LARGER && compareType != SMALLER) { throw new IllegalArgumentException("compareType can noly be one of the IntCondition.EQUALS、IntCondition.LARGER、IntCondition.SMALLER"); } } @Override public boolean check(Map<String, Integer> intParams, Map<String, Boolean> boolParams, Map<String, Long> longParams) { Integer integer = intParams.get(key); if (integer == null) { return false; } switch (compareType) { case EQUALS: return integer == compareValue; case LARGER: return integer > compareValue; case SMALLER: return integer < compareValue; case NOT_EQUALS: return integer != compareValue; } return false; } }
[Java] 纯文本查看 复制代码
/** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2014年12月2日 下午9:38:25 */ public class LongCondition implements ITransactionCondition { public static final byte EQUALS = 0; public static final byte SMALLER = 1; public static final byte LARGER = 2; public static final byte NOT_EQUALS = 3; private String key; private byte compareType; private long compareValue; /** * * @param key * @param compareType * @param compareValue */ public LongCondition(String key, byte compareType, long compareValue) { this.key = key; this.compareType = compareType; this.compareValue = compareValue; if (compareType != EQUALS && compareType != LARGER && compareType != SMALLER) { throw new IllegalArgumentException("compareType can noly be one of the IntCondition.EQUALS、IntCondition.LARGER、IntCondition.SMALLER"); } } @Override public boolean check(Map<String, Integer> intParams, Map<String, Boolean> boolParams, Map<String, Long> longParams) { Long integer = longParams.get(key); if (integer == null) { return false; } switch (compareType) { case EQUALS: return integer == compareValue; case LARGER: return integer > compareValue; case SMALLER: return integer < compareValue; case NOT_EQUALS: return integer != compareValue; } return false; } }
7、其它接口
上面已经看完了整个FSM大致的类和接口的设计了。大家可以发现FiniteStateMachine的构造方法中还传入两个接口:
IFiniteStatesProcesser<T> statesProcesser,
IOnFiniteStateChangeProcesser<T> onStateChangeProcesser
先看看代码,这两个接口主要分别用于状态机的输入和当状态改变时的通知。
[Java] 纯文本查看 复制代码
public interface IFiniteStatesProcesser<T> { /** * * @param stateMachine * @param currentState * @param hoster * @param now * @param duration */ void process(FiniteStateMachine<T> stateMachine, FiniteState<T> currentState, T hoster, long now, int duration); }
[Java] 纯文本查看 复制代码
/** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2014年12月7日 下午5:02:03 */ public interface IOnFiniteStateChangeProcesser<T> { /** * * @param stateMachine * @param hoster * @param oldState * @param newState */ void onChange(FiniteStateMachine<T> stateMachine, T hoster, FiniteState<T> oldState, FiniteState<T> newState); }
8、怎么用
看了上面那么多代码,头晕了。怎么用这个状态机。
看看下面的测试代码:
[Java] 纯文本查看 复制代码
package com.duoyu001.framework.game.fsm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.duoyu001.framework.game.fsm.condition.impl.BoolCondition; import com.duoyu001.framework.game.fsm.condition.impl.IntCondition; /** * * @author cjunhong * @email [email]john.cha@qq.com[/email] * @date 2015年1月10日 下午9:58:12 */ public class TestFSM { private static final Logger LOGGER = LoggerFactory.getLogger(TestFSM.class); static class Monster { private boolean target; private int distance2Target = 800; private int targetHP = 3; } private static final int STATE_WAIT = 0; private static final int STATE_BATTLE = 1; private static final int STATE_CHARSE = 2; private static final String KEY_TARGET_EXIST = "tar"; private static final String KET_DISTANCE = "dis"; private static final String KEY_TARGET_DEAD = "tarDead"; private static final int ATTACK_RANGE = 300; /** * @param args */ public static void main(String[] args) { FiniteStateMachine<Monster> finiteStateMachine = new FiniteStateMachine<Monster>( createStatesProcesser(), createOnStateChangeProcesser()); FiniteState<Monster> wait = finiteStateMachine.addState(STATE_WAIT); wait.setStateExecutor(new IFiniteStateExecutor<TestFSM.Monster>() { @Override public void onInit(FiniteStateMachine<Monster> stateMac, Monster t, long now, int duration) { } @Override public void execute(FiniteStateMachine<Monster> fightStateMachine, Monster hoster, long now, int duration) { LOGGER.info("待机中"); hoster.target = true; } }); FiniteState<Monster> battle = finiteStateMachine.addState(STATE_BATTLE); battle.setStateExecutor(new IFiniteStateExecutor<TestFSM.Monster>() { @Override public void onInit(FiniteStateMachine<Monster> stateMac, Monster t, long now, int duration) { } @Override public void execute(FiniteStateMachine<Monster> fightStateMachine, Monster hoster, long now, int duration) { LOGGER.info("战斗中, 目标剩余血量=" + hoster.targetHP); hoster.targetHP--; if (hoster.targetHP < 0) { hoster.targetHP = 0; } } }); FiniteState<Monster> charse = finiteStateMachine.addState(STATE_CHARSE); charse.setStateExecutor(new IFiniteStateExecutor<TestFSM.Monster>() { @Override public void onInit(FiniteStateMachine<Monster> stateMac, Monster t, long now, int duration) { } @Override public void execute(FiniteStateMachine<Monster> fightStateMachine, Monster hoster, long now, int duration) { hoster.distance2Target -= 250; if (hoster.distance2Target < 0) { hoster.distance2Target = 0; } LOGGER.info("追击中,距离=" + hoster.distance2Target); } }); finiteStateMachine.setDefaultState(wait); // 待机>战斗 // 存在目标 FiniteStateTransaction<Monster> wait2Battle = finiteStateMachine.addTranscation(wait, battle); wait2Battle.addCondition(new BoolCondition(KEY_TARGET_EXIST, true)); // 追击>战斗 // 和目标的距离小于攻击距离 FiniteStateTransaction<Monster> move2Battle = finiteStateMachine.addTranscation(charse, battle); move2Battle.addCondition(new IntCondition(KET_DISTANCE, IntCondition.SMALLER, ATTACK_RANGE)); // 追击>待机 // 目标死亡 FiniteStateTransaction<Monster> move2Wait = finiteStateMachine.addTranscation(charse, wait); move2Wait.addCondition(new BoolCondition(KEY_TARGET_DEAD, true)); // 战斗>移动 // 和目标的距离大于攻击距离 FiniteStateTransaction<Monster> battle2Move = finiteStateMachine.addTranscation(battle, charse); battle2Move.addCondition(new IntCondition(KET_DISTANCE, IntCondition.LARGER, ATTACK_RANGE)); // 战斗>待机 // 目标死亡 FiniteStateTransaction<Monster> battle2Wait = finiteStateMachine.addTranscation(battle, wait); battle2Wait.addCondition(new BoolCondition(KEY_TARGET_DEAD, true)); Monster m = new Monster(); for (int i = 0; i < 10; i++) { finiteStateMachine.tick(m, 0, 0); } } /** * @return */ private static IOnFiniteStateChangeProcesser<Monster> createOnStateChangeProcesser() { // TODO Auto-generated method stub return null; } /** * @return */ private static IFiniteStatesProcesser<Monster> createStatesProcesser() { return new IFiniteStatesProcesser<TestFSM.Monster>() { @Override public void process(FiniteStateMachine<Monster> stateMachine, FiniteState<Monster> currentState, Monster hoster, long now, int duration) { stateMachine.setBoolean(KEY_TARGET_EXIST, hoster.target); stateMachine.setBoolean(KEY_TARGET_DEAD, hoster.targetHP == 0); stateMachine.setInteger(KET_DISTANCE, hoster.distance2Target); } }; } }
运行一下,打印下面内容:
23:52:23.682 [main] INFO c.d.f.game.fsm.FiniteStateMachine - Change State -- old state=null, new state=FightState [state=0]
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 待机中
23:52:23.685 [main] INFO c.d.f.game.fsm.FiniteStateMachine - Change State -- old state=FightState [state=0], new state=FightState [state=1]
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 战斗中, 目标剩余血量=3
23:52:23.685 [main] INFO c.d.f.game.fsm.FiniteStateMachine - Change State -- old state=FightState [state=1], new state=FightState [state=2]
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 追击中,距离=550
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 追击中,距离=300
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 追击中,距离=50
23:52:23.685 [main] INFO c.d.f.game.fsm.FiniteStateMachine - Change State -- old state=FightState [state=2], new state=FightState [state=1]
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 战斗中, 目标剩余血量=2
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 战斗中, 目标剩余血量=1
23:52:23.685 [main] INFO c.d.f.game.fsm.FiniteStateMachine - Change State -- old state=FightState [state=1], new state=FightState [state=0]
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 待机中
23:52:23.685 [main] INFO c.d.f.game.fsm.FiniteStateMachine - Change State -- old state=FightState [state=0], new state=FightState [state=1]
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 战斗中, 目标剩余血量=0
23:52:23.685 [main] INFO c.d.f.game.fsm.FiniteStateMachine - Change State -- old state=FightState [state=1], new state=FightState [state=0]
23:52:23.685 [main] INFO c.d.framework.game.fsm.TestFSM - 待机中
好了,上面测试例子只是一个简单的怪物AI。真实游戏中的AI会比这个复杂的多,大家可以利用状态机自行进行设计~。
对了,上面用的是Java。但是没事,原理是一样的~大家可以自行根据需要转成其它语言的~~~我就是同时写C#和Java的,语言只是工具嘛~~~