有限状态机这名词听起来好像很高大上,其实本质上是对象(actor)在不同状态下收到信息有不同的行为(处理方式)和状态转换,有点类似设计模式中的状态模式。
以一个简单的游戏场景为案例,在rpg游戏地图中常常会出现一些怪物,怪物站在地图里的初始状态是游荡状态,如果玩家出现在他的实现范围内,那么他的状态就会变成追击状态,离开怪物视野后又变为游荡状态,当人物打死怪物就会变成死亡,类似这种其状态会因为触发事件而导致的状态转换就有限状态机。
代码如下所示
%%%-------------------------------------------------------------------
%%% @author zzh
%%% @copyright (C) 2019,
%%% @doc
%%%
%%% @end
%%% Created : 21. 十一月 2019 20:38
%%%-------------------------------------------------------------------
-module(monster_fsm).
-author("zzh").
-behaviour(gen_fsm).
%% gen_fsm callbacks
-export([init/1,handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4,
wander/2,follow/2]).
%% API
-export([create/1,player_join/1,player_leave/1,defeat_mon/1]).
-record(state, {
monster_id = 0,
status = 0 %状态 0 游荡 1追着玩家锤 2死亡
}).
%%%===================================================================
%%% API
create(MonsterId) -> %启动怪物进程
PidName = monster_process_name(MonsterId),
gen_fsm:start_link({local, PidName}, ?MODULE, [MonsterId], []),
PidName.
%% 玩家进入视野
player_join(PidName) ->
gen_fsm:send_event(PidName ,player_join).
%% 玩家离开视野
player_leave(PidName) ->
gen_fsm:send_event(PidName, player_leave).
%玩家打败怪物
defeat_mon(PidName)->
gen_fsm:send_event(PidName,die).
%%%===================================================================
%%%===================================================================
%%% CallBack function
init([MonsterId])->
io:format("monster is created,Id:~p ~n",[MonsterId]),
State = #state{monster_id = MonsterId},
{ok,wander,State}. %初始化后为游荡状态
%0游荡状态
wander(Event, State) ->
case Event of
player_join -> %% 玩家进入视野进入视野
fuck_player(), %% 捶玩家
NewState = State#state{status = 1},
{next_state, follow, NewState}; %进入下一个状态
_ -> %如果这个状态下还有其他事件 do something
{next_state, wander, State}
end.
%1追击状态
follow(Event,State) ->
case Event of
player_leave -> %% 玩家离开视野
do_wander(), %% 游荡mou
NewState = State#state{status = 0},
{next_state, wander, NewState}; %进入下一个状态
die -> %被锤死
die(),
NewState = State#state{status = 2},
{stop,normal,NewState}
end.
handle_event(_Event, StateName, State) ->
{next_state, StateName, State}.
handle_sync_event(_Event, _From, StateName, State) ->
Reply = ok,
{reply, Reply, StateName, State}.
handle_info(_Info, StateName, State) ->
{next_state, StateName, State}.
terminate(Reason, StateName, State) ->
io:format("process stop,Reason:~p ,StateName: ~p,State ~w ~n",[Reason,StateName,State]),
ok.
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
%%%===================================================================
%% 捶玩家
fuck_player()->
io:format("Face the wind!hasaki! ~n").
do_wander() ->
io:format("monster stop moving, join wander...~n").
die()->
io:format("why kill me W_W...~n").
%%返回怪物进程名称 MonsterId为1时返回mod_monster_1
monster_process_name(MonsterId) ->
list_to_atom(lists:concat([mod_monster_, MonsterId])).
运行截图
看完你大概会觉得这用gen_server实现也没啥问题啊,是的,用gen_server实现也是没问题的,当你点击gen_fsm看源码时你会发现2者有很多相似,你可以看成gen_fsm是由gen_server封装(虽然erlang没有封装这种说话),state作为抽象代码中模式匹配的回调。。。2者相比,在一些典型的状态转换场景下用gen_fsm可读性更好