有限狀態機這名詞聽起來好像很高大上,其實本質上是對象(actor)在不同狀態下收到信息有不同的行爲(處理方式)和狀態轉換,有點類似設計模式中的狀態模式。
以一個簡單的遊戲場景爲案例,在rpg遊戲地圖中常常會出現一些怪物,怪物站在地圖裏的初始狀態是遊蕩狀態,如果玩家出現在他的實現範圍內,那麼他的狀態就會變成追擊狀態,離開怪物視野後又變爲遊蕩狀態,當人物打死怪物就會變成死亡,類似這種其狀態會因爲觸發事件而導致的狀態轉換就有限狀態機。
代碼如下所示
%%%-------------------------------------------------------------------
%%% @author zzh
%%% @copyright (C) 2019, <COMPANY>
%%% @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可讀性更好