diff --git a/README.md b/README.md index 3179c92..6820bf3 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,9 @@ Build Usage ---- - {TitleMaps, TreeMaps, TreeNodeMaps} = behavior_tree:load_tree_file("example.json"), - {ok, RootID} = behavior_tree:init_btree_by_title(<<"example_ai"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), - {_BTStatus, _BTState1} = behavior_tree:execute(RootID, BTState = #{}). + {TitleMaps, TreeMaps, TreeNodeMaps} = eBhv3:load_tree_file("example.json"), + {ok, RootID} = eBhv3:init_btree_by_title(<<"example_ai"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), + {_BTStatus, _BTState1} = eBhv3:execute(RootID, BTState = #{}). More ---- diff --git a/examples/action/action_skill_attack.erl b/examples/action/action_skill_attack.erl index f2f9d2a..2e9112f 100644 --- a/examples/action/action_skill_attack.erl +++ b/examples/action/action_skill_attack.erl @@ -18,7 +18,7 @@ tick(#{properties := Prop} = _BTree, #{uid := UID, rival_list := [RivalUID | _]} = State) -> BTreeID = game_dict:get_passivity_btree_id(RivalUID), RivalState = get_be_attacker_rival_state(Prop, UID, RivalUID), - {_, RivalState1} = behavior_tree:execute_sub_tree(BTreeID, RivalState), + {_, RivalState1} = eBhv3:execute_sub_tree(BTreeID, RivalState), game_dict:put_role_state(RivalUID, RivalState1), {?BT_SUCCESS, State}. diff --git a/examples/game_process.erl b/examples/game_process.erl index fdd3d9f..9536305 100644 --- a/examples/game_process.erl +++ b/examples/game_process.erl @@ -24,7 +24,7 @@ start_link() -> gen_server:start_link(?MODULE, [], []). init([]) -> - {TitleMaps, TreeMaps, TreeNodeMaps} = behavior_tree:load_tree_file("examples/example.json"), + {TitleMaps, TreeMaps, TreeNodeMaps} = eBhv3:load_tree_file("examples/example.json"), ZombieList = [ %% <<"HUNTER"/utf8>>, @@ -97,8 +97,8 @@ new_state(UID, Type, CurGrid) -> State. init_zombie([UID | T], TitleMaps, TreeMaps, TreeNodeMaps) -> - {ok, InitiativeBTreeID} = behavior_tree:init_btree_by_title(<<"丧尸主动AI"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), - {ok, PassivityBTreeID} = behavior_tree:init_btree_by_title(<<"丧尸被动AI"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), + {ok, InitiativeBTreeID} = eBhv3:init_btree_by_title(<<"丧尸主动AI"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), + {ok, PassivityBTreeID} = eBhv3:init_btree_by_title(<<"丧尸被动AI"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), game_dict:put_initiative_btree_id(UID, InitiativeBTreeID), game_dict:put_passivity_btree_id(UID, PassivityBTreeID), Grid = {?RAND(0, ?MAX_X), ?RAND(0, ?MAX_Y)}, @@ -108,8 +108,8 @@ init_zombie([], _TitleMaps, _TreeMaps, _TreeNodeMaps) -> ok. init_patrol(UID, TitleMaps, TreeMaps, TreeNodeMaps) -> - {ok, InitiativeBTreeID} = behavior_tree:init_btree_by_title(<<"巡逻兵主动AI"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), - {ok, PassivityBTreeID} = behavior_tree:init_btree_by_title(<<"巡逻兵被动AI"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), + {ok, InitiativeBTreeID} = eBhv3:init_btree_by_title(<<"巡逻兵主动AI"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), + {ok, PassivityBTreeID} = eBhv3:init_btree_by_title(<<"巡逻兵被动AI"/utf8>>, TitleMaps, TreeMaps, TreeNodeMaps), game_dict:put_initiative_btree_id(UID, InitiativeBTreeID), game_dict:put_passivity_btree_id(UID, PassivityBTreeID), Grid = {?RAND(0, ?MAX_X), ?RAND(0, ?MAX_Y)}, @@ -122,6 +122,6 @@ run(UID) -> true -> ok; false -> - {_, State1} = behavior_tree:execute(BTreeID, State), + {_, State1} = eBhv3:execute(BTreeID, State), game_dict:put_role_state(UID, State1) end. \ No newline at end of file diff --git a/rebar.config b/rebar.config index 804ac9f..348c66b 100644 --- a/rebar.config +++ b/rebar.config @@ -1,9 +1,9 @@ -{erl_opts, [debug_info, {i, "include"} +{erl_opts, [no_debug_info, {i, "include"} %% {d, show_bt_log}, %% log_switch ]}. {deps, [ - {jsx, {git, "https://github.com/happyshiyu/jsx.git"}} + {jiffy, ".*", {git, "http://192.168.0.88:53000/SisMaker/jiffy.git", {branch, "master"}}} ]}. {xref_checks, [ diff --git a/src/core/base_node.erl b/src/core/base_node.erl index 5b5237e..6835c6a 100644 --- a/src/core/base_node.erl +++ b/src/core/base_node.erl @@ -44,9 +44,9 @@ execute(NodeID, BTState) -> %% 初始化行为树 -spec do_init(bt_uid()|undefined, bt_node_id(), #{bt_node_id() => btree()}, tree_nodes()) -> {ok, bt_uid()}|{error, term()}. do_init(ParentNodeID, BTNodeID, TreeMaps, TreeNodeMaps) -> - case behavior_tree:get_btree_node(BTNodeID, TreeNodeMaps) of + case eBhv3:get_btree_node(BTNodeID, TreeNodeMaps) of #{name := Name, category := tree} -> - #{root := Root} = behavior_tree:get_btree(Name, TreeMaps), + #{root := Root} = eBhv3:get_btree(Name, TreeMaps), do_init(ParentNodeID, Root, TreeMaps, TreeNodeMaps); #{name := Name, children := Children} = BTNode -> case code:ensure_loaded(Name) of diff --git a/src/core/behavior_tree.erl b/src/core/behavior_tree.erl deleted file mode 100644 index b0c856b..0000000 --- a/src/core/behavior_tree.erl +++ /dev/null @@ -1,200 +0,0 @@ --module(behavior_tree). - --include("eBhv3.hrl"). - --export([ - load_tree_file/1, - init_btree_by_title/4, init_btree/3, - execute/2, execute_child/2, close_btree_node/2, unload_btree/1 -]). - --export([get_btree_id_by_title/2, get_btree/2, get_btree_node/2]). - -%% @doc -%% 载入行为树文件 --spec load_tree_file(file:name_all()) -> {#{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()}|{error, term()}. -load_tree_file(JsonConfig) -> - case file:read_file(JsonConfig) of - {ok, Json} -> - JsonTerm = jsx:decode(Json, [return_maps]), - parse_btree(JsonTerm); - {error, Reason} -> - ?BT_LOG("Error:~w Reason:~w JsonConfig:~w", [error, Reason, JsonConfig]), - {error, Reason} - end. - -%% @doc -%% 获取行为树根节点id --spec get_btree_id_by_title(string(), #{string() => bt_node_id()}) -> bt_node_id()|undefined. -get_btree_id_by_title(Title, TitleMaps) -> - case TitleMaps of - #{Title := ID} -> - ID; - #{} -> - undefined - end. - -%% @doc -%% 获取行为树根节点 --spec get_btree(bt_node_id(), #{bt_node_id() => btree()}) -> btree()|undefined. -get_btree(BTNodeID, TreeMaps) -> - case TreeMaps of - #{BTNodeID := BTree} -> - BTree; - #{} -> - undefined - end. - -%% @doc -%% 获取行为树子节点 --spec get_btree_node(bt_node_id(), tree_nodes()) -> uninit_bt_node()|undefined. -get_btree_node(BTNodeID, TreeNodeMaps) -> - case TreeNodeMaps of - #{BTNodeID := BTNode} -> - BTNode; - #{} -> - undefined - end. - -%% @doc -%% 根据给定节点名初始化整颗行为树 --spec init_btree_by_title(string(), #{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()) -> {ok, bt_uid()}|{error, term()}. -init_btree_by_title(Title, TitleMaps, TreeMaps, TreeNodeMaps) -> - BTNodeID = get_btree_id_by_title(Title, TitleMaps), - init_btree(BTNodeID, TreeMaps, TreeNodeMaps). - -%% @doc -%% 根据给定节点初始化整颗行为树 --spec init_btree(bt_node_id(), #{bt_node_id() => btree()}, tree_nodes()) -> {ok, bt_uid()}|{error, term()}. -init_btree(BTNodeID, TreeMaps, TreeNodeMaps) -> - #{root := Root} = behavior_tree:get_btree(BTNodeID, TreeMaps), - #{id := NodeID} = behavior_tree:get_btree_node(Root, TreeNodeMaps), - base_node:do_init(undefined, NodeID, TreeMaps, TreeNodeMaps). - -%% @doc -%% 执行行为树节点 --spec execute(bt_uid(), bt_state()) -> {bt_status(), bt_state()}. -execute(NodeID, BTState) -> - BTState1 = blackboard:init_maps_keys(BTState), - case base_node:execute(NodeID, BTState1) of - {?BT_RUNNING, BTState2} -> - {?BT_RUNNING, BTState2}; - {BTStatus, BTState2} -> - BTState3 = blackboard:erase_btree(BTState2), - {BTStatus, BTState3} - end. - -%% @doc -%% 动态执行子节点 --spec execute_child(bt_uid(), bt_state()) -> {bt_status(), bt_state()}. -execute_child(NodeID, BTState) -> - base_node:execute(NodeID, BTState). - -%% @doc -%% 强制关闭指定节点下的所有子节点 --spec close_btree_node(bt_uid(), bt_state()) -> bt_state(). -close_btree_node(NodeID, BTState) -> - BTState1 = do_close_btree_node(NodeID, BTState), - blackboard:erase_btree(BTState1). - -%% @doc -%% 卸载指定节点下的所有子节点 --spec unload_btree(bt_uid()) -> ok. -unload_btree(NodeID) -> - do_unload_btree_node(NodeID). - -%% 解析行为树 --spec parse_btree(map()) -> {#{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()}. -parse_btree(#{<<"trees">> := Trees}) -> - parse_btree_1(Trees, #{}, #{}, #{}). - -parse_btree_1([Node | T], TitleMaps, TreeMaps, TreeNodeMaps) -> - #{<<"id">> := ID, <<"title">> := Title, <<"root">> := Root, <<"nodes">> := Nodes} = Node, - Iterator = maps:iterator(Nodes), - {TreeNodes, TreeNodeMaps1} = parse_btree_node(maps:next(Iterator), #{}, TreeNodeMaps), - BTree = #{ - id => ID, - root => Root, - properties => parse_properties(Node), - tree_nodes => TreeNodes - }, - parse_btree_1(T, TitleMaps#{Title => ID}, TreeMaps#{ID => BTree}, TreeNodeMaps1); -parse_btree_1([], TitleMaps, TreeMaps, TreeNodeMaps) -> - {TitleMaps, TreeMaps, TreeNodeMaps}. - -%% 解析树节点 --spec parse_btree_node({bt_node_id(), map(), maps:iterator()}|none, tree_nodes(), tree_nodes()) -> {tree_nodes(), tree_nodes()}. -parse_btree_node({ID, Node, NextIterator}, TreeNodes, TreeNodeMaps) -> - #{<<"name">> := Name, <<"category">> := Category} = Node, - case Category of - <<"tree">> -> - Name1 = Name; - _ -> - Name1 = name_convert(Name) - end, - TreeNode = #{ - id => ID, - name => Name1, - category => binary_to_atom(Category, utf8), - properties => parse_properties(Node), - children => parse_children(Node) - }, - parse_btree_node(maps:next(NextIterator), TreeNodes#{ID => TreeNode}, TreeNodeMaps#{ID => TreeNode}); -parse_btree_node(none, TreeNodes, TreeNodeMaps) -> - {TreeNodes, TreeNodeMaps}. - -%% 解析子节点属性数据, -%% key会转为下划线分割小写atom,value保持不变 --spec parse_properties(map()) -> properties(). -parse_properties(#{<<"properties">> := Properties} = _Node) -> - Fun = fun(K, V, Map) -> Map#{name_convert(K) => V} end, - maps:fold(Fun, #{}, Properties). - -%% 解析子节点 --spec parse_children(map()) -> [bt_node_id()]. -parse_children(#{<<"child">> := Child} = _Node) -> - [Child]; -parse_children(#{<<"children">> := Children} = _Node) -> - Children; -parse_children(_Node) -> - []. - -%% 模块名转换 -%% 例:PlayerID -> player_id Name -> name --spec name_convert(unicode:chardata()) -> atom(). -name_convert(Name) -> - case name_convert_1(Name) of - <<"_"/utf8, NewName/binary>> -> - binary_to_atom(NewName, utf8); - NewName -> - binary_to_atom(NewName, utf8) - end. - -name_convert_1(<>) when $A =< C, C =< $Z -> - Other1 = name_convert_1(Other), - <<"_"/utf8, (C + 32)/utf8, Other1/binary>>; -name_convert_1(<>) -> - Other1 = name_convert_1(Other), - <>; -name_convert_1(<<>>) -> - <<>>. - -%% 执行关闭行为树节点 --spec do_close_btree_node(bt_uid(), bt_state()) -> bt_state(). -do_close_btree_node(NodeID, BTState) -> - BTNode = blackboard:get_btree_node(NodeID), - base_node:do_close(BTNode, BTState). - -%% 执行卸载行为树节点 --spec do_unload_btree_node(bt_uid()) -> ok. -do_unload_btree_node(NodeID) -> - #{children := Children} = blackboard:get_btree_node(NodeID), - do_unload_btree_node_1(Children), - blackboard:erase_btree_node(NodeID), - ok. - -do_unload_btree_node_1([NodeID | T]) -> - do_unload_btree_node(NodeID), - do_unload_btree_node_1(T); -do_unload_btree_node_1([]) -> - ok. diff --git a/src/eBhv3.erl b/src/eBhv3.erl index f6e4544..731899c 100644 --- a/src/eBhv3.erl +++ b/src/eBhv3.erl @@ -1,3 +1,200 @@ -module(eBhv3). --export([]). +-include("eBhv3.hrl"). + +-export([ + load_tree_file/1, + init_btree_by_title/4, init_btree/3, + execute/2, execute_child/2, close_btree_node/2, unload_btree/1 +]). + +-export([get_btree_id_by_title/2, get_btree/2, get_btree_node/2]). + +%% @doc +%% 载入行为树文件 +-spec load_tree_file(file:name_all()) -> {#{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()}|{error, term()}. +load_tree_file(JsonConfig) -> + case file:read_file(JsonConfig) of + {ok, Json} -> + JsonTerm = jiffy:decode(Json, [return_maps]), + parse_btree(JsonTerm); + {error, Reason} -> + ?BT_LOG("Error:~w Reason:~w JsonConfig:~w", [error, Reason, JsonConfig]), + {error, Reason} + end. + +%% @doc +%% 获取行为树根节点id +-spec get_btree_id_by_title(string(), #{string() => bt_node_id()}) -> bt_node_id()|undefined. +get_btree_id_by_title(Title, TitleMaps) -> + case TitleMaps of + #{Title := ID} -> + ID; + #{} -> + undefined + end. + +%% @doc +%% 获取行为树根节点 +-spec get_btree(bt_node_id(), #{bt_node_id() => btree()}) -> btree()|undefined. +get_btree(BTNodeID, TreeMaps) -> + case TreeMaps of + #{BTNodeID := BTree} -> + BTree; + #{} -> + undefined + end. + +%% @doc +%% 获取行为树子节点 +-spec get_btree_node(bt_node_id(), tree_nodes()) -> uninit_bt_node()|undefined. +get_btree_node(BTNodeID, TreeNodeMaps) -> + case TreeNodeMaps of + #{BTNodeID := BTNode} -> + BTNode; + #{} -> + undefined + end. + +%% @doc +%% 根据给定节点名初始化整颗行为树 +-spec init_btree_by_title(string(), #{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()) -> {ok, bt_uid()}|{error, term()}. +init_btree_by_title(Title, TitleMaps, TreeMaps, TreeNodeMaps) -> + BTNodeID = get_btree_id_by_title(Title, TitleMaps), + init_btree(BTNodeID, TreeMaps, TreeNodeMaps). + +%% @doc +%% 根据给定节点初始化整颗行为树 +-spec init_btree(bt_node_id(), #{bt_node_id() => btree()}, tree_nodes()) -> {ok, bt_uid()}|{error, term()}. +init_btree(BTNodeID, TreeMaps, TreeNodeMaps) -> + #{root := Root} = eBhv3:get_btree(BTNodeID, TreeMaps), + #{id := NodeID} = eBhv3:get_btree_node(Root, TreeNodeMaps), + base_node:do_init(undefined, NodeID, TreeMaps, TreeNodeMaps). + +%% @doc +%% 执行行为树节点 +-spec execute(bt_uid(), bt_state()) -> {bt_status(), bt_state()}. +execute(NodeID, BTState) -> + BTState1 = blackboard:init_maps_keys(BTState), + case base_node:execute(NodeID, BTState1) of + {?BT_RUNNING, BTState2} -> + {?BT_RUNNING, BTState2}; + {BTStatus, BTState2} -> + BTState3 = blackboard:erase_btree(BTState2), + {BTStatus, BTState3} + end. + +%% @doc +%% 动态执行子节点 +-spec execute_child(bt_uid(), bt_state()) -> {bt_status(), bt_state()}. +execute_child(NodeID, BTState) -> + base_node:execute(NodeID, BTState). + +%% @doc +%% 强制关闭指定节点下的所有子节点 +-spec close_btree_node(bt_uid(), bt_state()) -> bt_state(). +close_btree_node(NodeID, BTState) -> + BTState1 = do_close_btree_node(NodeID, BTState), + blackboard:erase_btree(BTState1). + +%% @doc +%% 卸载指定节点下的所有子节点 +-spec unload_btree(bt_uid()) -> ok. +unload_btree(NodeID) -> + do_unload_btree_node(NodeID). + +%% 解析行为树 +-spec parse_btree(map()) -> {#{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()}. +parse_btree(#{<<"trees">> := Trees}) -> + parse_btree_1(Trees, #{}, #{}, #{}). + +parse_btree_1([Node | T], TitleMaps, TreeMaps, TreeNodeMaps) -> + #{<<"id">> := ID, <<"title">> := Title, <<"root">> := Root, <<"nodes">> := Nodes} = Node, + Iterator = maps:iterator(Nodes), + {TreeNodes, TreeNodeMaps1} = parse_btree_node(maps:next(Iterator), #{}, TreeNodeMaps), + BTree = #{ + id => ID, + root => Root, + properties => parse_properties(Node), + tree_nodes => TreeNodes + }, + parse_btree_1(T, TitleMaps#{Title => ID}, TreeMaps#{ID => BTree}, TreeNodeMaps1); +parse_btree_1([], TitleMaps, TreeMaps, TreeNodeMaps) -> + {TitleMaps, TreeMaps, TreeNodeMaps}. + +%% 解析树节点 +-spec parse_btree_node({bt_node_id(), map(), maps:iterator()}|none, tree_nodes(), tree_nodes()) -> {tree_nodes(), tree_nodes()}. +parse_btree_node({ID, Node, NextIterator}, TreeNodes, TreeNodeMaps) -> + #{<<"name">> := Name, <<"category">> := Category} = Node, + case Category of + <<"tree">> -> + Name1 = Name; + _ -> + Name1 = name_convert(Name) + end, + TreeNode = #{ + id => ID, + name => Name1, + category => binary_to_atom(Category, utf8), + properties => parse_properties(Node), + children => parse_children(Node) + }, + parse_btree_node(maps:next(NextIterator), TreeNodes#{ID => TreeNode}, TreeNodeMaps#{ID => TreeNode}); +parse_btree_node(none, TreeNodes, TreeNodeMaps) -> + {TreeNodes, TreeNodeMaps}. + +%% 解析子节点属性数据, +%% key会转为下划线分割小写atom,value保持不变 +-spec parse_properties(map()) -> properties(). +parse_properties(#{<<"properties">> := Properties} = _Node) -> + Fun = fun(K, V, Map) -> Map#{name_convert(K) => V} end, + maps:fold(Fun, #{}, Properties). + +%% 解析子节点 +-spec parse_children(map()) -> [bt_node_id()]. +parse_children(#{<<"child">> := Child} = _Node) -> + [Child]; +parse_children(#{<<"children">> := Children} = _Node) -> + Children; +parse_children(_Node) -> + []. + +%% 模块名转换 +%% 例:PlayerID -> player_id Name -> name +-spec name_convert(unicode:chardata()) -> atom(). +name_convert(Name) -> + case name_convert_1(Name) of + <<"_"/utf8, NewName/binary>> -> + binary_to_atom(NewName, utf8); + NewName -> + binary_to_atom(NewName, utf8) + end. + +name_convert_1(<>) when $A =< C, C =< $Z -> + Other1 = name_convert_1(Other), + <<"_"/utf8, (C + 32)/utf8, Other1/binary>>; +name_convert_1(<>) -> + Other1 = name_convert_1(Other), + <>; +name_convert_1(<<>>) -> + <<>>. + +%% 执行关闭行为树节点 +-spec do_close_btree_node(bt_uid(), bt_state()) -> bt_state(). +do_close_btree_node(NodeID, BTState) -> + BTNode = blackboard:get_btree_node(NodeID), + base_node:do_close(BTNode, BTState). + +%% 执行卸载行为树节点 +-spec do_unload_btree_node(bt_uid()) -> ok. +do_unload_btree_node(NodeID) -> + #{children := Children} = blackboard:get_btree_node(NodeID), + do_unload_btree_node_1(Children), + blackboard:erase_btree_node(NodeID), + ok. + +do_unload_btree_node_1([NodeID | T]) -> + do_unload_btree_node(NodeID), + do_unload_btree_node_1(T); +do_unload_btree_node_1([]) -> + ok.