Erlang 行为模式gen_fsm状态机 解析和案例

有限状态机这名词听起来好像很高大上,其实本质上是对象(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可读性更好

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