From 658adce1a9ac6af352545e9f4c34d9829a54470c Mon Sep 17 00:00:00 2001 From: SisMaker <1713699517@qq.com> Date: Mon, 19 Apr 2021 12:50:02 +0800 Subject: [PATCH] =?UTF-8?q?ft:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 29 + LICENSE | 21 + README.md | 78 + examples/action/action_add_rage.erl | 21 + examples/action/action_become_attacking.erl | 20 + examples/action/action_become_idle.erl | 20 + examples/action/action_become_patrolling.erl | 20 + examples/action/action_become_recovering.erl | 20 + examples/action/action_collect_dest_path.erl | 60 + examples/action/action_collect_path.erl | 45 + examples/action/action_collect_patrol.erl | 26 + examples/action/action_cost_power.erl | 22 + examples/action/action_cost_rage.erl | 21 + examples/action/action_del_dead_target.erl | 20 + examples/action/action_died.erl | 21 + examples/action/action_finish.erl | 21 + examples/action/action_move_grid.erl | 21 + examples/action/action_recover_power.erl | 22 + examples/action/action_skill_attack.erl | 44 + examples/condition/cond_is_attacking.erl | 24 + examples/condition/cond_is_dest.erl | 23 + examples/condition/cond_is_died.erl | 23 + examples/condition/cond_is_idle.erl | 24 + examples/condition/cond_is_patrolling.erl | 24 + examples/condition/cond_is_power_full.erl | 24 + examples/condition/cond_is_rage_full.erl | 24 + examples/condition/cond_is_recovering.erl | 24 + examples/condition/cond_is_target_died.erl | 28 + examples/condition/cond_power_lt.erl | 24 + examples/example.hrl | 32 + examples/example.json | 1786 ++++++++++++++++++ examples/game_dict.erl | 59 + examples/game_process.erl | 127 ++ include/behavior3.hrl | 64 + rebar.config | 15 + src/action/error.erl | 22 + src/action/failer.erl | 22 + src/action/runner.erl | 22 + src/action/succeeder.erl | 22 + src/action/wait.erl | 36 + src/composite/mem_priority.erl | 43 + src/composite/mem_sequence.erl | 43 + src/composite/priority.erl | 31 + src/composite/sequence.erl | 31 + src/core/base_node.erl | 137 ++ src/core/behavior_tree.erl | 215 +++ src/core/blackboard.erl | 157 ++ src/decorator/inverter.erl | 31 + src/decorator/limiter.erl | 43 + src/decorator/max_time.erl | 39 + src/decorator/repeat_until_failure.erl | 44 + src/decorator/repeat_until_success.erl | 45 + src/decorator/repeater.erl | 44 + src/eBhv3.app.src | 10 + src/eBhv3.erl | 3 + 55 files changed, 3917 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 examples/action/action_add_rage.erl create mode 100644 examples/action/action_become_attacking.erl create mode 100644 examples/action/action_become_idle.erl create mode 100644 examples/action/action_become_patrolling.erl create mode 100644 examples/action/action_become_recovering.erl create mode 100644 examples/action/action_collect_dest_path.erl create mode 100644 examples/action/action_collect_path.erl create mode 100644 examples/action/action_collect_patrol.erl create mode 100644 examples/action/action_cost_power.erl create mode 100644 examples/action/action_cost_rage.erl create mode 100644 examples/action/action_del_dead_target.erl create mode 100644 examples/action/action_died.erl create mode 100644 examples/action/action_finish.erl create mode 100644 examples/action/action_move_grid.erl create mode 100644 examples/action/action_recover_power.erl create mode 100644 examples/action/action_skill_attack.erl create mode 100644 examples/condition/cond_is_attacking.erl create mode 100644 examples/condition/cond_is_dest.erl create mode 100644 examples/condition/cond_is_died.erl create mode 100644 examples/condition/cond_is_idle.erl create mode 100644 examples/condition/cond_is_patrolling.erl create mode 100644 examples/condition/cond_is_power_full.erl create mode 100644 examples/condition/cond_is_rage_full.erl create mode 100644 examples/condition/cond_is_recovering.erl create mode 100644 examples/condition/cond_is_target_died.erl create mode 100644 examples/condition/cond_power_lt.erl create mode 100644 examples/example.hrl create mode 100644 examples/example.json create mode 100644 examples/game_dict.erl create mode 100644 examples/game_process.erl create mode 100644 include/behavior3.hrl create mode 100644 rebar.config create mode 100644 src/action/error.erl create mode 100644 src/action/failer.erl create mode 100644 src/action/runner.erl create mode 100644 src/action/succeeder.erl create mode 100644 src/action/wait.erl create mode 100644 src/composite/mem_priority.erl create mode 100644 src/composite/mem_sequence.erl create mode 100644 src/composite/priority.erl create mode 100644 src/composite/sequence.erl create mode 100644 src/core/base_node.erl create mode 100644 src/core/behavior_tree.erl create mode 100644 src/core/blackboard.erl create mode 100644 src/decorator/inverter.erl create mode 100644 src/decorator/limiter.erl create mode 100644 src/decorator/max_time.erl create mode 100644 src/decorator/repeat_until_failure.erl create mode 100644 src/decorator/repeat_until_success.erl create mode 100644 src/decorator/repeater.erl create mode 100644 src/eBhv3.app.src create mode 100644 src/eBhv3.erl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ad44f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +.eunit +*.o +*.beam +*.plt +erl_crash.dump +.concrete/DEV_MODE + +# rebar 2.x +.rebar +rel/example_project +ebin/* +deps + +# rebar 3 +.rebar3 +_build/ +_checkouts/ +rebar.lock + +# idea +.idea +*.iml +cmake-build* +CMakeLists.txt + +# nif compile temp file +*.pdb +*.d +compile_commands.json \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9b24fa3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2019-2020 alisdair sullivan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4111f3f --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +中文 +===== +Behavior3的erlang支持库 + +快速开始 +---- +添加如下内容到**rebar.config** + + {deps, [ + ... + {behavior3erl, "1.0.0"} + ]}. + +编译 +---- + $ rebar3 compile + +使用 +---- + {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 = #{}). + +更多 +---- +[behavior3editor](https://github.com/behavior3/behavior3editor) + +[behavior3go](https://github.com/magicsea/behavior3go) + +#### Behavior3使用系列文章: + +[(一)行为树应用之行为树简介](http://note.youdao.com/s/77bGugj9) + +[(二)行为树应用之组合节点](http://note.youdao.com/s/XiKlHPIr) + +[(三)行为树应用之装饰节点](http://note.youdao.com/s/9Z6zI3YE) + +[(四)行为树应用之自定义节点](http://note.youdao.com/s/AcRrY8ig) + +[(五)行为树应用之加载行为树](http://note.youdao.com/s/DiqLf0ES) + +[(六)行为树应用之节点执行](http://note.youdao.com/s/PI3Wic5D) + +[(七)行为树应用之设计巡逻兵AI](http://note.youdao.com/s/HTCGTgAm) + +[(八)行为树应用之设计丧尸AI](http://note.youdao.com/s/3wKFxcTw) + +English +===== + +Behavior3 by erlang library + +Quickstart +---- +add to **rebar.config** + + {deps, [ + ... + {behavior3erl, "1.0.0"} + ]}. + +Build +---- + + $ rebar3 compile + +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 = #{}). + +More +---- + +[behavior3editor](https://github.com/behavior3/behavior3editor) + +[behavior3go](https://github.com/magicsea/behavior3go) diff --git a/examples/action/action_add_rage.erl b/examples/action/action_add_rage.erl new file mode 100644 index 0000000..5991875 --- /dev/null +++ b/examples/action/action_add_rage.erl @@ -0,0 +1,21 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 增加怒气 +%%% 根据add_rage增加相应怒气,结果小于等于100 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_add_rage). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(#{properties := #{add_rage := AddRage}} = _BTree, #{cur_rage := CurRage} = State) -> + State1 = State#{cur_rage := min(CurRage + AddRage, ?MAX_RAGE)}, + ?INFO("~ts:怒气值增加~w点 总怒气~w点", [maps:get(uid, State1), AddRage, maps:get(cur_rage, State1)]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_become_attacking.erl b/examples/action/action_become_attacking.erl new file mode 100644 index 0000000..5947fa7 --- /dev/null +++ b/examples/action/action_become_attacking.erl @@ -0,0 +1,20 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 变为攻击状态 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_become_attacking). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, State) -> + State1 = State#{cur_state := ?SET_STATE(?STATE_TYPE_ATTACKING)}, + ?INFO("~ts:变为攻击状态", [maps:get(uid, State1)]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_become_idle.erl b/examples/action/action_become_idle.erl new file mode 100644 index 0000000..4e4c1eb --- /dev/null +++ b/examples/action/action_become_idle.erl @@ -0,0 +1,20 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 变为空闲状态 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_become_idle). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, State) -> + State1 = State#{cur_state := ?SET_STATE(?STATE_TYPE_IDLE)}, + ?INFO("~ts:变为空闲状态", [maps:get(uid, State1)]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_become_patrolling.erl b/examples/action/action_become_patrolling.erl new file mode 100644 index 0000000..1dd8a87 --- /dev/null +++ b/examples/action/action_become_patrolling.erl @@ -0,0 +1,20 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 变为巡逻状态 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_become_patrolling). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, State) -> + State1 = State#{cur_state := ?SET_STATE(?STATE_TYPE_PATROLLING)}, + ?INFO("~ts:变为巡逻状态", [maps:get(uid, State1)]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_become_recovering.erl b/examples/action/action_become_recovering.erl new file mode 100644 index 0000000..e1e56f1 --- /dev/null +++ b/examples/action/action_become_recovering.erl @@ -0,0 +1,20 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 变为恢复状态 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_become_recovering). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, State) -> + State1 = State#{cur_state := ?SET_STATE(?STATE_TYPE_RECOVERING)}, + ?INFO("~ts:变为恢复状态", [maps:get(uid, State1)]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_collect_dest_path.erl b/examples/action/action_collect_dest_path.erl new file mode 100644 index 0000000..72968aa --- /dev/null +++ b/examples/action/action_collect_dest_path.erl @@ -0,0 +1,60 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 收集终点路径 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_collect_dest_path). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_grid := CurGrid, grid_list := GirdList} = State) -> + case GirdList of + [] -> + EndGrid = get_end_grid(CurGrid), + State1 = State#{grid_list := get_grid_path(CurGrid, EndGrid)}, + ?INFO("~ts:找到穿传说中的“大蒜”的位置,要尽快到达那里!", [maps:get(uid, State1)]), + {?BT_SUCCESS, State1}; + _ -> + ?INFO("~ts:天要黑了,要尽快到达那里!", [maps:get(uid, State)]), + {?BT_SUCCESS, State} + end. + +get_end_grid(CurGrid) -> + EndGridList = [{0, 0}, {0, ?MAX_X}, {?MAX_Y, ?MAX_X}, {?MAX_Y, 0}], + {_, EndGrid} = hd(lists:sort([{calc_distance(CurGrid, Grid), Grid} || Grid <- EndGridList])), + EndGrid. + +calc_distance({X1, Y1}, {X2, Y2}) -> + math:sqrt(math:pow(X1 - X2, 2) + math:pow(Y1 - Y2, 2)). + +get_grid_path(CurGrid, EndGrid) -> + case EndGrid of + {0, 0} -> + get_grid_path(CurGrid, [EndGrid], 1, 1); + {0, ?MAX_X} -> + get_grid_path(CurGrid, [EndGrid], 1, -1); + {?MAX_Y, ?MAX_X} -> + get_grid_path(CurGrid, [EndGrid], -1, -1); + {?MAX_Y, 0} -> + get_grid_path(CurGrid, [EndGrid], -1, 1) + end. + +get_grid_path(CurGrid, GridPath, XInc, YInc) -> + case GridPath of + [CurGrid | _] -> + GridPath; + [{X, Y} | _] -> + case Y + YInc of + Y1 when Y1 == 0 orelse Y1 == ?MAX_X -> + get_grid_path(CurGrid, [{X + XInc, Y1}, {X, Y1} | GridPath], XInc, bnot YInc + 1); + Y1 -> + get_grid_path(CurGrid, [{X, Y1} | GridPath], XInc, YInc) + end + end. \ No newline at end of file diff --git a/examples/action/action_collect_path.erl b/examples/action/action_collect_path.erl new file mode 100644 index 0000000..c138266 --- /dev/null +++ b/examples/action/action_collect_path.erl @@ -0,0 +1,45 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 收集巡逻路径 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_collect_path). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2, get_grid_path/1]). + + +tick(_BTree, #{cur_grid := CurGrid} = State) -> + State1 = State#{grid_list := get_grid_path(CurGrid)}, + {?BT_SUCCESS, State1}. + +get_grid_path(CurGrid) -> + List = [{1, 0}, {-1, 0}, {0, 1}, {0, -1}], + Max = length(List), + List1 = [E || {_, E} <- lists:sort([{?RAND(1, Max), E} || E <- List])], + case get_grid_path_1(List1, CurGrid, []) of + [] -> + get_grid_path(CurGrid); + Result -> + Result + end. + +get_grid_path_1([{X, Y} | T], {CurX, CurY}, Result) -> + X1 = CurX + X, + Y1 = CurY + Y, + Grid = {X1, Y1}, + case X1 >= 0 andalso X1 =< ?MAX_X andalso Y1 >= 0 andalso Y1 =< ?MAX_Y of + true -> + get_grid_path_1(T, Grid, [Grid | Result]); + false -> + [] + end; +get_grid_path_1([], _, Result) -> + Result. + diff --git a/examples/action/action_collect_patrol.erl b/examples/action/action_collect_patrol.erl new file mode 100644 index 0000000..c1c5f0f --- /dev/null +++ b/examples/action/action_collect_patrol.erl @@ -0,0 +1,26 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 寻找巡逻兵 +%%% 找到:SUCCESS +%%% 未找到:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_collect_patrol). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_grid := CurGrid, rival_list := RivalList} = State) -> + Fun = fun(UID) -> maps:get(type, game_dict:get_role_state(UID)) == ?HUMAN end, + case lists:filter(Fun, game_dict:get_map_data(CurGrid)) of + UIDList when UIDList /= [] -> + ?INFO("~ts:嗅到了人类的味道,将要对[~ts]发起攻击", [maps:get(uid, State), lists:join(",", UIDList)]), + {?BT_SUCCESS, State#{rival_list := UIDList ++ RivalList}}; + [] -> + {?BT_FAILURE, State} + end. \ No newline at end of file diff --git a/examples/action/action_cost_power.erl b/examples/action/action_cost_power.erl new file mode 100644 index 0000000..67aa5e7 --- /dev/null +++ b/examples/action/action_cost_power.erl @@ -0,0 +1,22 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 扣除体力 +%%% 获取Misc结构中的atk_dmg字段的值 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_cost_power). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, State) -> + #{cur_power := CurPower, misc := #{atk_dmg := {AtkType, Dmg}} = Misc} = State, + State1 = State#{cur_power := max(CurPower - Dmg, 0), misc := maps:remove(atk_dmg, Misc)}, + ?INFO("~ts:受到~ts攻击,体力值减少~w点 剩余体力~w点", [maps:get(uid, State1), AtkType, Dmg,maps:get(cur_power, State1)]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_cost_rage.erl b/examples/action/action_cost_rage.erl new file mode 100644 index 0000000..c604624 --- /dev/null +++ b/examples/action/action_cost_rage.erl @@ -0,0 +1,21 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 扣除怒气 +%%% 根据cost_rage扣除相应怒气,结果大于等于0 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_cost_rage). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(#{properties := #{cost_rage := CostRage}} = _BTree, #{cur_rage := CurRage} = State) -> + State1 = State#{cur_rage := max(CurRage - CostRage, 0)}, + ?INFO("~ts:怒气值减少~w点", [maps:get(uid, State1), CurRage - maps:get(cur_rage, State1)]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_del_dead_target.erl b/examples/action/action_del_dead_target.erl new file mode 100644 index 0000000..87b6a42 --- /dev/null +++ b/examples/action/action_del_dead_target.erl @@ -0,0 +1,20 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 删除死亡目标 +%%% 删除rival_list中第一个目标 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_del_dead_target). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{rival_list := [_ | T]} = State) -> + State1 = State#{rival_list := T}, + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_died.erl b/examples/action/action_died.erl new file mode 100644 index 0000000..7325188 --- /dev/null +++ b/examples/action/action_died.erl @@ -0,0 +1,21 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 自我死亡 +%%% 销毁数据 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_died). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{uid := UID} = State) -> + self() ! {died, UID}, + ?INFO("~ts 被消灭!", [UID]), + {?BT_SUCCESS, State#{cur_state := ?SET_STATE(?STATE_TYPE_DEAD)}}. \ No newline at end of file diff --git a/examples/action/action_finish.erl b/examples/action/action_finish.erl new file mode 100644 index 0000000..ff7151f --- /dev/null +++ b/examples/action/action_finish.erl @@ -0,0 +1,21 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 游戏结束,根据is_win属性打印结果 +%%% 打印结果,结束行为树运行 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_finish). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(#{properties := #{is_win := IsWin}} = _BTree, #{uid := UID} = State) -> + self() ! finish, + ?INFO("游戏结束,~ts~ts!", [UID, IsWin]), + {?BT_SUCCESS, State}. \ No newline at end of file diff --git a/examples/action/action_move_grid.erl b/examples/action/action_move_grid.erl new file mode 100644 index 0000000..a43cab2 --- /dev/null +++ b/examples/action/action_move_grid.erl @@ -0,0 +1,21 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 移动一格 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_move_grid). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{uid := UID, cur_grid := OldGrid, grid_list := [Grid | T]} = State) -> + game_dict:update_map_date(OldGrid, Grid, UID), + State1 = State#{grid_list := T, cur_grid := Grid}, + ?INFO("~ts:移动到了~w格子", [UID, Grid]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_recover_power.erl b/examples/action/action_recover_power.erl new file mode 100644 index 0000000..87c257a --- /dev/null +++ b/examples/action/action_recover_power.erl @@ -0,0 +1,22 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 恢复体力 +%%% 根据Max - Min之间随机恢复几点体力 +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_recover_power). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(#{properties := #{max_power := MaxPower, min_power := MinPower}} = _BTree, #{cur_power := CurPower} = State) -> + RecoverPower = ?RAND(MinPower, MaxPower), + State1 = State#{cur_power := min(CurPower + RecoverPower, ?MAX_POWER)}, + ?INFO("~ts:体力值增加了~w点 当前体力:~w点", [maps:get(uid, State1), RecoverPower, maps:get(cur_power, State1)]), + {?BT_SUCCESS, State1}. \ No newline at end of file diff --git a/examples/action/action_skill_attack.erl b/examples/action/action_skill_attack.erl new file mode 100644 index 0000000..ddf4d51 --- /dev/null +++ b/examples/action/action_skill_attack.erl @@ -0,0 +1,44 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 技能攻击 +%%% 随机伤害值 RAND(min_power ,max_power) * RAND(min_rate, max_rate) +%%% 返回:SUCCESS +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(action_skill_attack). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +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), + game_dict:put_role_state(RivalUID, RivalState1), + {?BT_SUCCESS, State}. + +get_be_attacker_rival_state(Prop, UID, RivalUID) -> + #{ + skill_type := SkillType, + min_power := MinPower, max_power := MaxPower, + min_rate := MinRate, max_rate := MaxRate + } = Prop, + #{rival_list := RivalList} = RivalState = game_dict:get_role_state(RivalUID), + case lists:member(UID, RivalList) of + true -> + RivalList1 = RivalList; + false -> + RivalList1 = RivalList ++ [UID] + end, + RivalState1 = RivalState#{ + rival_list := RivalList1, + misc := #{atk_dmg => {SkillType, ?RAND(MinPower, MaxPower) * ?RAND(MinRate, MaxRate)}} + }, + game_dict:put_role_state(RivalUID, RivalState1), + ?INFO("~ts:发动了~ts攻击", [UID, SkillType]), + RivalState1. \ No newline at end of file diff --git a/examples/condition/cond_is_attacking.erl b/examples/condition/cond_is_attacking.erl new file mode 100644 index 0000000..492d6f1 --- /dev/null +++ b/examples/condition/cond_is_attacking.erl @@ -0,0 +1,24 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断是否处于攻击状态 +%%% 是:SUCCESS +%%% 否:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(cond_is_attacking). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_state := CurState} = State) -> + case ?IS_STATE(?STATE_TYPE_ATTACKING, CurState) of + true -> + {?BT_SUCCESS, State}; + false -> + {?BT_FAILURE, State} + end. \ No newline at end of file diff --git a/examples/condition/cond_is_dest.erl b/examples/condition/cond_is_dest.erl new file mode 100644 index 0000000..363906f --- /dev/null +++ b/examples/condition/cond_is_dest.erl @@ -0,0 +1,23 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断是否到达终点 +%%% 是:SUCCESS +%%% 否:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:18 +%%%------------------------------------------------------------------- +-module(cond_is_dest). +-include("behavior3.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, State) -> + case State of + #{grid_list := []} -> + {?BT_SUCCESS, State}; + #{} -> + {?BT_FAILURE, State} + end. \ No newline at end of file diff --git a/examples/condition/cond_is_died.erl b/examples/condition/cond_is_died.erl new file mode 100644 index 0000000..c623caf --- /dev/null +++ b/examples/condition/cond_is_died.erl @@ -0,0 +1,23 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断自己是否死亡 +%%% 死亡:SUCCESS +%%% 存活:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:26 +%%%------------------------------------------------------------------- +-module(cond_is_died). +-include("behavior3.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_power := CurPower} = State) -> + case CurPower =< 0 of + true -> + {?BT_SUCCESS, State}; + false -> + {?BT_FAILURE, State} + end. \ No newline at end of file diff --git a/examples/condition/cond_is_idle.erl b/examples/condition/cond_is_idle.erl new file mode 100644 index 0000000..fb86afa --- /dev/null +++ b/examples/condition/cond_is_idle.erl @@ -0,0 +1,24 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断是否处于空闲状态 +%%% 是:SUCCESS +%%% 否:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(cond_is_idle). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_state := CurState} = State) -> + case ?IS_STATE(?STATE_TYPE_IDLE, CurState) of + true -> + {?BT_SUCCESS, State}; + false -> + {?BT_FAILURE, State} + end. diff --git a/examples/condition/cond_is_patrolling.erl b/examples/condition/cond_is_patrolling.erl new file mode 100644 index 0000000..ea33e1c --- /dev/null +++ b/examples/condition/cond_is_patrolling.erl @@ -0,0 +1,24 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断是否处于巡逻状态 +%%% 是:SUCCESS +%%% 否:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(cond_is_patrolling). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_state := CurState} = State) -> + case ?IS_STATE(?STATE_TYPE_PATROLLING, CurState) of + true -> + {?BT_SUCCESS, State}; + false -> + {?BT_FAILURE, State} + end. \ No newline at end of file diff --git a/examples/condition/cond_is_power_full.erl b/examples/condition/cond_is_power_full.erl new file mode 100644 index 0000000..2d5fa92 --- /dev/null +++ b/examples/condition/cond_is_power_full.erl @@ -0,0 +1,24 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断体力是否已满 +%%% 已满:SUCCESS +%%% 未满:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(cond_is_power_full). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_power := CurPower} = State) -> + case CurPower >= ?MAX_POWER of + true -> + {?BT_SUCCESS, State}; + false -> + {?BT_FAILURE, State} + end. \ No newline at end of file diff --git a/examples/condition/cond_is_rage_full.erl b/examples/condition/cond_is_rage_full.erl new file mode 100644 index 0000000..038707f --- /dev/null +++ b/examples/condition/cond_is_rage_full.erl @@ -0,0 +1,24 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断怒气值是否已满 +%%% 已满:SUCCESS +%%% 未满:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:18 +%%%------------------------------------------------------------------- +-module(cond_is_rage_full). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_rage := CurRage} = State) -> + case CurRage >= ?MAX_RAGE of + true -> + {?BT_SUCCESS, State}; + false -> + {?BT_FAILURE, State} + end. \ No newline at end of file diff --git a/examples/condition/cond_is_recovering.erl b/examples/condition/cond_is_recovering.erl new file mode 100644 index 0000000..216b2f6 --- /dev/null +++ b/examples/condition/cond_is_recovering.erl @@ -0,0 +1,24 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断是否处于恢复状态 +%%% 是:SUCCESS +%%% 否:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:13 +%%%------------------------------------------------------------------- +-module(cond_is_recovering). +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([tick/2]). + +tick(_BTree, #{cur_state := CurState} = State) -> + case ?IS_STATE(?STATE_TYPE_RECOVERING, CurState) of + true -> + {?BT_SUCCESS, State}; + false -> + {?BT_FAILURE, State} + end. \ No newline at end of file diff --git a/examples/condition/cond_is_target_died.erl b/examples/condition/cond_is_target_died.erl new file mode 100644 index 0000000..2bb8731 --- /dev/null +++ b/examples/condition/cond_is_target_died.erl @@ -0,0 +1,28 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断目标是否死亡 +%%% 死亡:SUCCESS +%%% 未死亡:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:20 +%%%------------------------------------------------------------------- +-module(cond_is_target_died). +-include("behavior3.hrl"). +-include("example.hrl"). +%% API +-export([tick/2]). + +tick(_BTree, State) -> + case State of + #{rival_list := [RivalUID | _]} -> + case game_dict:get_role_state(RivalUID) of + #{cur_state := CurState} when ?IS_STATE(?STATE_TYPE_DEAD, CurState) -> + {?BT_SUCCESS, State}; + #{} -> + {?BT_FAILURE, State} + end; + #{} -> + {?BT_FAILURE, State} + end. diff --git a/examples/condition/cond_power_lt.erl b/examples/condition/cond_power_lt.erl new file mode 100644 index 0000000..cf413f5 --- /dev/null +++ b/examples/condition/cond_power_lt.erl @@ -0,0 +1,24 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 判断体力是否小于某值 +%%% 是:SUCCESS +%%% 否:FAILURE +%%% @end +%%% Created : 07. 10月 2020 15:20 +%%%------------------------------------------------------------------- +-module(cond_power_lt). +-include("behavior3.hrl"). +-include("example.hrl"). +%% API +-export([tick/2]). + +tick(#{properties := #{power := LTPower}} = _BTree, #{cur_power := CurPower} = State) -> + case CurPower < LTPower of + true -> + ?INFO("~ts:当前体力低于~w点,必须要尽快休息恢复体力!", [maps:get(uid, State), LTPower]), + {?BT_SUCCESS, State}; + false -> + {?BT_FAILURE, State} + end. diff --git a/examples/example.hrl b/examples/example.hrl new file mode 100644 index 0000000..16c11ef --- /dev/null +++ b/examples/example.hrl @@ -0,0 +1,32 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 定义 +%%% @end +%%% Created : 07. 10月 2020 15:16 +%%%------------------------------------------------------------------- +-type uid() :: binary(). +-type grid() :: {X :: non_neg_integer(), Y :: non_neg_integer()}. + +-define(INFO(Format, Args), io:format(Format ++ "~n", Args)). + +-define(MAX_X, 5). +-define(MAX_Y, 5). + +-define(HUMAN, 1). +-define(ZOMBIE, 2). + +-define(RAND(Min, Max), (rand:uniform(Max - (Min - 1)) + (Min - 1))). + +-define(STATE_TYPE_IDLE, 1). +-define(STATE_TYPE_ATTACKING, 2). +-define(STATE_TYPE_PATROLLING, 3). +-define(STATE_TYPE_RECOVERING, 4). +-define(STATE_TYPE_DEAD, 5). + +-define(IS_STATE(StateType, CurState), (CurState band (1 bsl (StateType - 1)) > 0)). +-define(SET_STATE(StateType), (1 bsl (StateType - 1))). + +-define(MAX_POWER, 100). +-define(MAX_RAGE, 100). diff --git a/examples/example.json b/examples/example.json new file mode 100644 index 0000000..f6399ba --- /dev/null +++ b/examples/example.json @@ -0,0 +1,1786 @@ +{ + "version": "0.3.0", + "scope": "project", + "selectedTree": "1bbac5e1-753c-4f30-8fef-6db07a54746a", + "trees": [ + { + "version": "0.3.0", + "scope": "tree", + "id": "9da29574-2f80-4d31-98ba-eafd8a29b6bc", + "title": "巡逻兵巡逻逻辑", + "description": "", + "root": "8bf79ff6-a1e8-4f45-8342-82c4ecccc68e", + "properties": {}, + "nodes": { + "8bf79ff6-a1e8-4f45-8342-82c4ecccc68e": { + "id": "8bf79ff6-a1e8-4f45-8342-82c4ecccc68e", + "name": "MemSequence", + "category": "composite", + "title": "MemSequence", + "description": "", + "properties": {}, + "display": { + "x": 48, + "y": -132 + }, + "children": [ + "11f48f39-e5d0-4697-9f06-457eff8cea3e", + "ea4f98b0-8be8-4cda-b9b3-182a3b8c8ec3" + ] + }, + "ea4f98b0-8be8-4cda-b9b3-182a3b8c8ec3": { + "id": "ea4f98b0-8be8-4cda-b9b3-182a3b8c8ec3", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 252, + "y": 60 + }, + "children": [ + "3ea60740-694d-4486-b692-fc0b4d4d6057", + "2a291d2b-91e3-4f5b-886b-7205861cb480", + "0d2b4c05-f854-40f3-864b-990bffe2b50b" + ] + }, + "3ea60740-694d-4486-b692-fc0b4d4d6057": { + "id": "3ea60740-694d-4486-b692-fc0b4d4d6057", + "name": "Inverter", + "category": "decorator", + "title": "Inverter", + "description": "", + "properties": {}, + "display": { + "x": 468, + "y": -60 + }, + "child": "c46a8f97-4d9b-4491-9ae5-61876186d2e0" + }, + "2a291d2b-91e3-4f5b-886b-7205861cb480": { + "id": "2a291d2b-91e3-4f5b-886b-7205861cb480", + "name": "action_move_grid", + "category": "action", + "title": "移动一格", + "description": "action_move_grid\n移动一格\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 468, + "y": 72 + } + }, + "c46a8f97-4d9b-4491-9ae5-61876186d2e0": { + "id": "c46a8f97-4d9b-4491-9ae5-61876186d2e0", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 672, + "y": -60 + }, + "children": [ + "4bd05aea-40b4-4b6f-a8c4-c3c29020f21a", + "8c3497d7-0b53-43d7-9215-3f13ff44d223" + ] + }, + "4bd05aea-40b4-4b6f-a8c4-c3c29020f21a": { + "id": "4bd05aea-40b4-4b6f-a8c4-c3c29020f21a", + "name": "cond_is_dest", + "category": "condition", + "title": "到达终点", + "description": "cond_is_dest\n判断是否到达终点\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 876, + "y": -108 + } + }, + "0d2b4c05-f854-40f3-864b-990bffe2b50b": { + "id": "0d2b4c05-f854-40f3-864b-990bffe2b50b", + "name": "Runner", + "category": "action", + "title": "Runner", + "description": "", + "properties": {}, + "display": { + "x": 468, + "y": 156 + } + }, + "8c3497d7-0b53-43d7-9215-3f13ff44d223": { + "id": "8c3497d7-0b53-43d7-9215-3f13ff44d223", + "name": "action_finish", + "category": "action", + "title": "游戏", + "description": "action_finish\n游戏结束,根据is_win属性打印结果\n打印结果,结束行为树运行\n返回:SUCCESS", + "properties": { + "is_win": "胜利" + }, + "display": { + "x": 876, + "y": -12 + } + }, + "11f48f39-e5d0-4697-9f06-457eff8cea3e": { + "id": "11f48f39-e5d0-4697-9f06-457eff8cea3e", + "name": "action_collect_dest_path", + "category": "action", + "title": "收集终点路径", + "description": "action_collect_dest_path\n收集终点路径\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 252, + "y": -192 + } + } + }, + "display": { + "camera_x": 550, + "camera_y": 562.5, + "camera_z": 1, + "x": -156, + "y": -132 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "c084afd6-c72b-40fc-88a8-9c4445771fc6", + "title": "巡逻兵主动AI", + "description": "", + "root": "2b5aaee3-9b28-4925-8a8b-b21236788844", + "properties": {}, + "nodes": { + "2b5aaee3-9b28-4925-8a8b-b21236788844": { + "id": "2b5aaee3-9b28-4925-8a8b-b21236788844", + "name": "Priority", + "category": "composite", + "title": "Priority", + "description": "", + "properties": {}, + "display": { + "x": 204, + "y": 0 + }, + "children": [ + "03643393-9519-41c5-a5fc-5d2e68ad13c7", + "af215e32-e4d5-45a9-8511-cd4d0bcea761", + "253accf9-14bb-450a-b562-02e9a528b20b", + "7673cc91-2cbe-4c17-a57a-2c7d614ffb37" + ] + }, + "5c18cf59-642f-4e99-9847-242df72579d6": { + "id": "5c18cf59-642f-4e99-9847-242df72579d6", + "name": "b7eea887-72c9-4462-8d74-83c13ac82fba", + "category": "tree", + "title": "巡逻兵攻击逻辑", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": 132 + } + }, + "060b2c7a-aebb-46e1-8f56-288264ba61e4": { + "id": "060b2c7a-aebb-46e1-8f56-288264ba61e4", + "name": "cond_is_idle", + "category": "condition", + "title": "是空闲状态", + "description": "cond_is_idle\n判断是否处于空闲状态\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 624, + "y": -312 + } + }, + "03643393-9519-41c5-a5fc-5d2e68ad13c7": { + "id": "03643393-9519-41c5-a5fc-5d2e68ad13c7", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": -264 + }, + "children": [ + "060b2c7a-aebb-46e1-8f56-288264ba61e4", + "4a111c90-18b7-4af8-88b9-13caf316d72a" + ] + }, + "7631d5cb-0820-42e6-a3fa-c7a368f71312": { + "id": "7631d5cb-0820-42e6-a3fa-c7a368f71312", + "name": "action_become_patrolling", + "category": "action", + "title": "变为巡逻状态", + "description": "action_become_patrolling\n变为巡逻状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 828, + "y": -228 + } + }, + "df3825c5-5d6f-4a0d-88b7-d54a277c4a52": { + "id": "df3825c5-5d6f-4a0d-88b7-d54a277c4a52", + "name": "9da29574-2f80-4d31-98ba-eafd8a29b6bc", + "category": "tree", + "title": "巡逻兵巡逻逻辑", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": -48 + } + }, + "4a111c90-18b7-4af8-88b9-13caf316d72a": { + "id": "4a111c90-18b7-4af8-88b9-13caf316d72a", + "name": "Inverter", + "category": "decorator", + "title": "Inverter", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": -228 + }, + "child": "7631d5cb-0820-42e6-a3fa-c7a368f71312" + }, + "b671173e-6347-44ee-8253-5f41d65f48a3": { + "id": "b671173e-6347-44ee-8253-5f41d65f48a3", + "name": "cond_is_patrolling", + "category": "condition", + "title": "是巡逻状态", + "description": "cond_is_patrolling\n判断是否处于巡逻状态\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 624, + "y": -132 + } + }, + "af215e32-e4d5-45a9-8511-cd4d0bcea761": { + "id": "af215e32-e4d5-45a9-8511-cd4d0bcea761", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": -96 + }, + "children": [ + "b671173e-6347-44ee-8253-5f41d65f48a3", + "df3825c5-5d6f-4a0d-88b7-d54a277c4a52" + ] + }, + "253accf9-14bb-450a-b562-02e9a528b20b": { + "id": "253accf9-14bb-450a-b562-02e9a528b20b", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": 84 + }, + "children": [ + "36527522-a9df-44e6-8179-0503e731bdc4", + "5c18cf59-642f-4e99-9847-242df72579d6" + ] + }, + "36527522-a9df-44e6-8179-0503e731bdc4": { + "id": "36527522-a9df-44e6-8179-0503e731bdc4", + "name": "cond_is_attacking", + "category": "condition", + "title": "是攻击状态", + "description": "cond_is_attacking\n判断是否处于攻击状态\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 624, + "y": 36 + } + }, + "730ae916-ec3f-4bfe-b577-0c9545792e72": { + "id": "730ae916-ec3f-4bfe-b577-0c9545792e72", + "name": "a62217c8-655c-4266-9405-4ffcdbb4360d", + "category": "tree", + "title": "巡逻兵恢复逻辑", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": 300 + } + }, + "7673cc91-2cbe-4c17-a57a-2c7d614ffb37": { + "id": "7673cc91-2cbe-4c17-a57a-2c7d614ffb37", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": 264 + }, + "children": [ + "0304627c-6944-435e-9c8d-4e4cb2062435", + "730ae916-ec3f-4bfe-b577-0c9545792e72" + ] + }, + "0304627c-6944-435e-9c8d-4e4cb2062435": { + "id": "0304627c-6944-435e-9c8d-4e4cb2062435", + "name": "cond_is_recovering", + "category": "condition", + "title": "是恢复状态", + "description": "cond_is_recovering\n判断是否处于恢复状态\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 624, + "y": 216 + } + } + }, + "display": { + "camera_x": 485, + "camera_y": 519.5, + "camera_z": 1, + "x": 0, + "y": 0 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "b7eea887-72c9-4462-8d74-83c13ac82fba", + "title": "巡逻兵攻击逻辑", + "description": "", + "root": "cf2ef19d-fbd1-4c7c-80b6-0b3a5c355598", + "properties": {}, + "nodes": { + "cf2ef19d-fbd1-4c7c-80b6-0b3a5c355598": { + "id": "cf2ef19d-fbd1-4c7c-80b6-0b3a5c355598", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 204, + "y": 0 + }, + "children": [ + "8f21ebb2-ccd8-4938-8d50-753b2251e60c", + "9f376dff-8225-4718-8895-193b4bb3d613" + ] + }, + "8f21ebb2-ccd8-4938-8d50-753b2251e60c": { + "id": "8f21ebb2-ccd8-4938-8d50-753b2251e60c", + "name": "Inverter", + "category": "decorator", + "title": "Inverter", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": -264 + }, + "child": "5588cee3-28b6-4e00-8b57-67f7ea261b35" + }, + "5588cee3-28b6-4e00-8b57-67f7ea261b35": { + "id": "5588cee3-28b6-4e00-8b57-67f7ea261b35", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": -264 + }, + "children": [ + "08a08a9c-2867-4824-805a-d7ad1c2a4425", + "9136ce4c-e2f2-41de-89a7-234543ebf7d8", + "79eb3e34-df1e-45f4-8f9a-6ad572e693cf" + ] + }, + "9f376dff-8225-4718-8895-193b4bb3d613": { + "id": "9f376dff-8225-4718-8895-193b4bb3d613", + "name": "Priority", + "category": "composite", + "title": "Priority", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": 252 + }, + "children": [ + "f8f3b041-7d51-49a3-aeaa-884f1fcf96e9", + "f6618c6b-236a-48ee-8c51-0b537d408c3b" + ] + }, + "f8f3b041-7d51-49a3-aeaa-884f1fcf96e9": { + "id": "f8f3b041-7d51-49a3-aeaa-884f1fcf96e9", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": 144 + }, + "children": [ + "45ef4736-d23e-4572-b774-f7376813781c", + "8fe84e26-7b19-4e75-82dc-3a9c7de38a88", + "44f08768-4f25-448d-b19c-1fb41c326c68" + ] + }, + "f6618c6b-236a-48ee-8c51-0b537d408c3b": { + "id": "f6618c6b-236a-48ee-8c51-0b537d408c3b", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": 360 + }, + "children": [ + "9af19bbe-1c42-4553-9db6-cb4be8901a98", + "604d7780-7012-4175-88b1-7b5c110132ea" + ] + }, + "5c75bd99-3249-448d-8a09-2127d91a1c12": { + "id": "5c75bd99-3249-448d-8a09-2127d91a1c12", + "name": "action_become_idle", + "category": "action", + "title": "变为空闲状态", + "description": "action_become_idle\n变为空闲状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 1032, + "y": -36 + } + }, + "45ef4736-d23e-4572-b774-f7376813781c": { + "id": "45ef4736-d23e-4572-b774-f7376813781c", + "name": "cond_is_rage_full", + "category": "condition", + "title": "怒气值已满", + "description": "cond_is_rage_full\n判断怒气值是否已满\n已满:SUCCESS\n未满:FAILURE", + "properties": {}, + "display": { + "x": 828, + "y": 48 + } + }, + "44f08768-4f25-448d-b19c-1fb41c326c68": { + "id": "44f08768-4f25-448d-b19c-1fb41c326c68", + "name": "action_cost_rage", + "category": "action", + "title": "扣除怒气", + "description": "action_cost_rage\n扣除怒气\n根据cost_rage扣除相应怒气,结果大于等于0\n返回:SUCCESS", + "properties": { + "cost_rage": 100 + }, + "display": { + "x": 828, + "y": 228 + } + }, + "08a08a9c-2867-4824-805a-d7ad1c2a4425": { + "id": "08a08a9c-2867-4824-805a-d7ad1c2a4425", + "name": "cond_is_target_died", + "category": "condition", + "title": "目标死亡", + "description": "cond_is_target_died\n判断目标是否死亡\n死亡:SUCCESS\n未死亡:FAILURE", + "properties": {}, + "display": { + "x": 828, + "y": -384 + } + }, + "604d7780-7012-4175-88b1-7b5c110132ea": { + "id": "604d7780-7012-4175-88b1-7b5c110132ea", + "name": "action_add_rage", + "category": "action", + "title": "增加怒气", + "description": "action_add_rage\n增加怒气\n根据add_rage增加相应怒气,结果小于等于100\n返回:SUCCESS", + "properties": { + "add_rage": 10 + }, + "display": { + "x": 828, + "y": 408 + } + }, + "d7bf8a85-e2a4-48d0-807b-c8b4ff0eb85e": { + "id": "d7bf8a85-e2a4-48d0-807b-c8b4ff0eb85e", + "name": "cond_power_lt", + "category": "condition", + "title": "体力小于", + "description": "cond_power_lt\n判断体力是否小于某值\n是:SUCCESS\n否:FAILURE", + "properties": { + "power": 50 + }, + "display": { + "x": 1248, + "y": -216 + } + }, + "f80a69a0-3eca-43a7-8a0f-8bf62e459d9e": { + "id": "f80a69a0-3eca-43a7-8a0f-8bf62e459d9e", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 1032, + "y": -168 + }, + "children": [ + "d7bf8a85-e2a4-48d0-807b-c8b4ff0eb85e", + "835e8a59-65c5-4dcf-a9c0-69cb36cb5b82" + ] + }, + "835e8a59-65c5-4dcf-a9c0-69cb36cb5b82": { + "id": "835e8a59-65c5-4dcf-a9c0-69cb36cb5b82", + "name": "action_become_recovering", + "category": "action", + "title": "变为恢复状态", + "description": "action_become_recovering\n变为恢复状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 1248, + "y": -120 + } + }, + "79eb3e34-df1e-45f4-8f9a-6ad572e693cf": { + "id": "79eb3e34-df1e-45f4-8f9a-6ad572e693cf", + "name": "Priority", + "category": "composite", + "title": "Priority", + "description": "", + "properties": {}, + "display": { + "x": 828, + "y": -96 + }, + "children": [ + "f80a69a0-3eca-43a7-8a0f-8bf62e459d9e", + "5c75bd99-3249-448d-8a09-2127d91a1c12" + ] + }, + "8fe84e26-7b19-4e75-82dc-3a9c7de38a88": { + "id": "8fe84e26-7b19-4e75-82dc-3a9c7de38a88", + "name": "action_skill_attack", + "category": "action", + "title": "技能攻击", + "description": "action_skill_attack\n技能攻击\nskill_type : 普通攻击,技能攻击\n 随机伤害值 RAND(min_power ,max_power) * RAND(min_rate, max_rate)\n返回:SUCCESS", + "properties": { + "min_power": 5, + "max_power": 10, + "min_rate": 3, + "max_rate": 4, + "skill_type": "爆头" + }, + "display": { + "x": 828, + "y": 144 + } + }, + "9af19bbe-1c42-4553-9db6-cb4be8901a98": { + "id": "9af19bbe-1c42-4553-9db6-cb4be8901a98", + "name": "action_skill_attack", + "category": "action", + "title": "技能攻击", + "description": "action_skill_attack\n技能攻击\nskill_type : 普通攻击,技能攻击\n 随机伤害值 RAND(min_power ,max_power) * RAND(min_rate, max_rate)\n返回:SUCCESS", + "properties": { + "min_power": 5, + "max_power": 10, + "min_rate": 1, + "max_rate": 2, + "skill_type": "平砍" + }, + "display": { + "x": 828, + "y": 312 + } + }, + "9136ce4c-e2f2-41de-89a7-234543ebf7d8": { + "id": "9136ce4c-e2f2-41de-89a7-234543ebf7d8", + "name": "action_del_dead_target", + "category": "action", + "title": "删除死亡目标", + "description": "action_del_dead_target\n删除死亡目标\n删除rival_list中第一个目标\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 828, + "y": -300 + } + } + }, + "display": { + "camera_x": 316, + "camera_y": 534.5, + "camera_z": 1, + "x": 0, + "y": 0 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "a62217c8-655c-4266-9405-4ffcdbb4360d", + "title": "巡逻兵恢复逻辑", + "description": "", + "root": "1af2edc5-7d01-4975-8439-e391d2f7c719", + "properties": {}, + "nodes": { + "1af2edc5-7d01-4975-8439-e391d2f7c719": { + "id": "1af2edc5-7d01-4975-8439-e391d2f7c719", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 204, + "y": 0 + }, + "children": [ + "068d057d-a43d-4936-8d25-b1ee86f77ea6", + "0dda2c45-3ff1-4a26-88bb-ee516beaad0d", + "a04072a5-31cc-4125-89a0-9187ffa17fe9" + ] + }, + "0dda2c45-3ff1-4a26-88bb-ee516beaad0d": { + "id": "0dda2c45-3ff1-4a26-88bb-ee516beaad0d", + "name": "Inverter", + "category": "decorator", + "title": "Inverter", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": 0 + }, + "child": "ede1fa6f-2295-409b-8641-2c32970c069f" + }, + "ede1fa6f-2295-409b-8641-2c32970c069f": { + "id": "ede1fa6f-2295-409b-8641-2c32970c069f", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": 0 + }, + "children": [ + "1c629e69-7006-4146-8c2a-cd525736f1a2", + "68c837c4-2e31-45bf-80bf-488c8699c824" + ] + }, + "1c629e69-7006-4146-8c2a-cd525736f1a2": { + "id": "1c629e69-7006-4146-8c2a-cd525736f1a2", + "name": "cond_is_power_full", + "category": "condition", + "title": "体力已满", + "description": "cond_is_power_full\n判断体力是否已满\n已满:SUCCESS\n未满:FAILURE", + "properties": {}, + "display": { + "x": 828, + "y": -48 + } + }, + "68c837c4-2e31-45bf-80bf-488c8699c824": { + "id": "68c837c4-2e31-45bf-80bf-488c8699c824", + "name": "action_become_idle", + "category": "action", + "title": "变为空闲状态", + "description": "action_become_idle\n变为空闲状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 828, + "y": 36 + } + }, + "068d057d-a43d-4936-8d25-b1ee86f77ea6": { + "id": "068d057d-a43d-4936-8d25-b1ee86f77ea6", + "name": "action_recover_power", + "category": "action", + "title": "恢复 - 体力", + "description": "action_recover_power\n恢复体力\n根据Max - Min之间随机恢复几点体力\n返回:SUCCESS", + "properties": { + "max_power": 10, + "min_power": 5 + }, + "display": { + "x": 408, + "y": -132 + } + }, + "a04072a5-31cc-4125-89a0-9187ffa17fe9": { + "id": "a04072a5-31cc-4125-89a0-9187ffa17fe9", + "name": "Runner", + "category": "action", + "title": "Runner", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": 132 + } + } + }, + "display": { + "camera_x": 359, + "camera_y": 491, + "camera_z": 1, + "x": 0, + "y": 0 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "0cbbfbf6-c591-4373-80e4-6cef94fdb961", + "title": "巡逻兵被动AI", + "description": "", + "root": "e4227377-3bbb-48ca-9d86-80796498c818", + "properties": {}, + "nodes": { + "3b209dd0-e890-493b-835e-0fdb3c8d2fd6": { + "id": "3b209dd0-e890-493b-835e-0fdb3c8d2fd6", + "name": "ac40be47-ca6f-4969-9ee0-881bc7f6f831", + "category": "tree", + "title": "巡逻兵受击逻辑", + "description": "", + "properties": {}, + "display": { + "x": 156, + "y": 60 + } + }, + "e4227377-3bbb-48ca-9d86-80796498c818": { + "id": "e4227377-3bbb-48ca-9d86-80796498c818", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": -48, + "y": 0 + }, + "children": [ + "28a16fe8-0397-429a-a1e4-9886e9034468", + "3b209dd0-e890-493b-835e-0fdb3c8d2fd6" + ] + }, + "28a16fe8-0397-429a-a1e4-9886e9034468": { + "id": "28a16fe8-0397-429a-a1e4-9886e9034468", + "name": "Priority", + "category": "composite", + "title": "Priority", + "description": "", + "properties": {}, + "display": { + "x": 156, + "y": -72 + }, + "children": [ + "1c4eb87e-bf0b-466e-b8b5-1e7252f79a8e", + "4ef71f58-8159-49eb-8d8d-4adb0c2646a5" + ] + }, + "1c4eb87e-bf0b-466e-b8b5-1e7252f79a8e": { + "id": "1c4eb87e-bf0b-466e-b8b5-1e7252f79a8e", + "name": "cond_is_attacking", + "category": "condition", + "title": "是攻击状态", + "description": "cond_is_attacking\n判断是否处于攻击状态\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 372, + "y": -120 + } + }, + "4ef71f58-8159-49eb-8d8d-4adb0c2646a5": { + "id": "4ef71f58-8159-49eb-8d8d-4adb0c2646a5", + "name": "action_become_attacking", + "category": "action", + "title": "变为攻击状态", + "description": "action_become_attacking\n变为攻击状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 372, + "y": -24 + } + } + }, + "display": { + "camera_x": 804, + "camera_y": 537.5, + "camera_z": 1, + "x": -252, + "y": 0 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "ac40be47-ca6f-4969-9ee0-881bc7f6f831", + "title": "巡逻兵受击逻辑", + "description": "", + "root": "d4fa320c-bbc3-4258-813c-cfb13f5df1f4", + "properties": {}, + "nodes": { + "d4fa320c-bbc3-4258-813c-cfb13f5df1f4": { + "id": "d4fa320c-bbc3-4258-813c-cfb13f5df1f4", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 24, + "y": 24 + }, + "children": [ + "04bdb774-4d05-497d-894d-9c3e8974a3ad", + "daeadd62-d74e-4dfd-8a1c-cd7bb34a4810", + "0df0056e-0e92-4136-970d-706194b56556" + ] + }, + "daeadd62-d74e-4dfd-8a1c-cd7bb34a4810": { + "id": "daeadd62-d74e-4dfd-8a1c-cd7bb34a4810", + "name": "action_add_rage", + "category": "action", + "title": "增加怒气", + "description": "action_add_rage\n增加怒气\n根据add_rage增加相应怒气,结果小于等于100\n返回:SUCCESS", + "properties": { + "add_rage": 5 + }, + "display": { + "x": 228, + "y": -12 + } + }, + "e0c2363e-bc0c-4b1a-a11f-def279017890": { + "id": "e0c2363e-bc0c-4b1a-a11f-def279017890", + "name": "cond_is_died", + "category": "condition", + "title": "已经死亡", + "description": "cond_is_died\n判断自己是否死亡\n死亡:SUCCESS\n存活:FAILURE", + "properties": {}, + "display": { + "x": 444, + "y": 72 + } + }, + "0df0056e-0e92-4136-970d-706194b56556": { + "id": "0df0056e-0e92-4136-970d-706194b56556", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 228, + "y": 168 + }, + "children": [ + "e0c2363e-bc0c-4b1a-a11f-def279017890", + "9d4a7cc5-176b-47e8-8b89-df51f2737af8", + "8da8ccf3-70ff-4fbe-8150-de4a0d6be8d1" + ] + }, + "8da8ccf3-70ff-4fbe-8150-de4a0d6be8d1": { + "id": "8da8ccf3-70ff-4fbe-8150-de4a0d6be8d1", + "name": "action_finish", + "category": "action", + "title": "游戏", + "description": "action_finish\n游戏结束,根据is_win属性打印结果\n打印结果,结束行为树运行\n返回:SUCCESS", + "properties": { + "is_win": "失败" + }, + "display": { + "x": 444, + "y": 252 + } + }, + "04bdb774-4d05-497d-894d-9c3e8974a3ad": { + "id": "04bdb774-4d05-497d-894d-9c3e8974a3ad", + "name": "action_cost_power", + "category": "action", + "title": "扣除体力", + "description": "action_cost_power\n扣除体力\n获取Misc结构中的atk_dmg字段的值\n返回:SUCCESS", + "properties": { + "max_power": "", + "min_power": "" + }, + "display": { + "x": 228, + "y": -96 + } + }, + "9d4a7cc5-176b-47e8-8b89-df51f2737af8": { + "id": "9d4a7cc5-176b-47e8-8b89-df51f2737af8", + "name": "action_died", + "category": "action", + "title": "自我死亡", + "description": "action_died\n自我死亡\n销毁对象数据\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 444, + "y": 168 + } + } + }, + "display": { + "camera_x": 723, + "camera_y": 516.5, + "camera_z": 1, + "x": -180, + "y": 24 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "1bbac5e1-753c-4f30-8fef-6db07a54746a", + "title": "丧尸主动AI", + "description": "", + "root": "eb8e9463-53ff-4f24-b52f-f8a96eb5b850", + "properties": {}, + "nodes": { + "9413445a-1417-4b4d-9ad1-d2166629f6e6": { + "id": "9413445a-1417-4b4d-9ad1-d2166629f6e6", + "name": "cond_is_idle", + "category": "condition", + "title": "是空闲状态", + "description": "cond_is_idle\n判断是否处于空闲状态\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 276, + "y": -228 + } + }, + "cef0c3dd-76bc-468a-8cb8-04a334577975": { + "id": "cef0c3dd-76bc-468a-8cb8-04a334577975", + "name": "action_become_patrolling", + "category": "action", + "title": "变为巡逻状态", + "description": "action_become_patrolling\n变为巡逻状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 480, + "y": -132 + } + }, + "eb8e9463-53ff-4f24-b52f-f8a96eb5b850": { + "id": "eb8e9463-53ff-4f24-b52f-f8a96eb5b850", + "name": "Priority", + "category": "composite", + "title": "Priority", + "description": "", + "properties": {}, + "display": { + "x": -144, + "y": 0 + }, + "children": [ + "a595ab10-3b5e-48e6-89cb-6d04b61b8464", + "5ace3342-0d7a-40f1-b4ab-e2045bfc9bcf", + "a90441be-cfff-45e7-8366-c38dbf37a6da" + ] + }, + "a595ab10-3b5e-48e6-89cb-6d04b61b8464": { + "id": "a595ab10-3b5e-48e6-89cb-6d04b61b8464", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 60, + "y": -180 + }, + "children": [ + "9413445a-1417-4b4d-9ad1-d2166629f6e6", + "25bfbded-ea50-40fd-ba5d-a433e4fce2f9" + ] + }, + "698392c2-5651-4eee-8c08-aac7a7cad452": { + "id": "698392c2-5651-4eee-8c08-aac7a7cad452", + "name": "2fcfa9bf-f759-4eb6-9168-8ef89d1c46da", + "category": "tree", + "title": "丧尸攻击逻辑", + "description": "", + "properties": {}, + "display": { + "x": 276, + "y": 216 + } + }, + "03082add-ab19-4c27-9926-086ee0bb3262": { + "id": "03082add-ab19-4c27-9926-086ee0bb3262", + "name": "5cb48d65-0fa7-4b75-82e8-a222a0deffdd", + "category": "tree", + "title": "丧尸巡逻逻辑", + "description": "", + "properties": {}, + "display": { + "x": 276, + "y": 36 + } + }, + "e3619b0d-64b0-40f2-8246-a90ca01b23a9": { + "id": "e3619b0d-64b0-40f2-8246-a90ca01b23a9", + "name": "cond_is_patrolling", + "category": "condition", + "title": "是巡逻状态", + "description": "cond_is_patrolling\n判断是否处于巡逻状态\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 276, + "y": -48 + } + }, + "5ace3342-0d7a-40f1-b4ab-e2045bfc9bcf": { + "id": "5ace3342-0d7a-40f1-b4ab-e2045bfc9bcf", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 60, + "y": 0 + }, + "children": [ + "e3619b0d-64b0-40f2-8246-a90ca01b23a9", + "03082add-ab19-4c27-9926-086ee0bb3262" + ] + }, + "a90441be-cfff-45e7-8366-c38dbf37a6da": { + "id": "a90441be-cfff-45e7-8366-c38dbf37a6da", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 60, + "y": 168 + }, + "children": [ + "8cbe368e-65ce-4cf2-8563-ca83ffc0f002", + "698392c2-5651-4eee-8c08-aac7a7cad452" + ] + }, + "8cbe368e-65ce-4cf2-8563-ca83ffc0f002": { + "id": "8cbe368e-65ce-4cf2-8563-ca83ffc0f002", + "name": "cond_is_attacking", + "category": "condition", + "title": "是攻击状态", + "description": "cond_is_attacking\n判断是否处于攻击状态\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 276, + "y": 132 + } + }, + "25bfbded-ea50-40fd-ba5d-a433e4fce2f9": { + "id": "25bfbded-ea50-40fd-ba5d-a433e4fce2f9", + "name": "Inverter", + "category": "decorator", + "title": "Inverter", + "description": "", + "properties": {}, + "display": { + "x": 276, + "y": -132 + }, + "child": "cef0c3dd-76bc-468a-8cb8-04a334577975" + } + }, + "display": { + "camera_x": 960, + "camera_y": 508.5, + "camera_z": 1, + "x": -348, + "y": 0 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "5cb48d65-0fa7-4b75-82e8-a222a0deffdd", + "title": "丧尸巡逻逻辑", + "description": "", + "root": "0dd7d80e-734c-4832-9b1a-c8b85df4a580", + "properties": {}, + "nodes": { + "0dd7d80e-734c-4832-9b1a-c8b85df4a580": { + "id": "0dd7d80e-734c-4832-9b1a-c8b85df4a580", + "name": "MemSequence", + "category": "composite", + "title": "MemSequence", + "description": "", + "properties": {}, + "display": { + "x": -72, + "y": 36 + }, + "children": [ + "5de1553f-7936-4349-8960-34d2ab175def", + "9d77ee7a-f5c3-4bdf-bc67-24d28cdce98b" + ] + }, + "9d77ee7a-f5c3-4bdf-bc67-24d28cdce98b": { + "id": "9d77ee7a-f5c3-4bdf-bc67-24d28cdce98b", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 132, + "y": 192 + }, + "children": [ + "8320ac47-9d8c-432e-a0f3-3583e4e592e5", + "fe54994c-99a0-4086-a827-ffbfb9144653", + "4afa6247-b396-4e15-b1e4-2b5ec4659789", + "5dc62c15-2709-4cba-90cb-01f8e06e8f6c" + ] + }, + "8320ac47-9d8c-432e-a0f3-3583e4e592e5": { + "id": "8320ac47-9d8c-432e-a0f3-3583e4e592e5", + "name": "Inverter", + "category": "decorator", + "title": "Inverter", + "description": "", + "properties": {}, + "display": { + "x": 348, + "y": 0 + }, + "child": "a7ef4ace-028e-4d8e-9054-082acad9f568" + }, + "fe54994c-99a0-4086-a827-ffbfb9144653": { + "id": "fe54994c-99a0-4086-a827-ffbfb9144653", + "name": "action_move_grid", + "category": "action", + "title": "移动一格", + "description": "action_move_grid\n移动一格\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 348, + "y": 132 + } + }, + "a7ef4ace-028e-4d8e-9054-082acad9f568": { + "id": "a7ef4ace-028e-4d8e-9054-082acad9f568", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 552, + "y": 0 + }, + "children": [ + "7f5cc5fa-39d2-4939-8a9c-158e75ca927a", + "1650138e-bccc-41dc-868f-d48485ac164e" + ] + }, + "7f5cc5fa-39d2-4939-8a9c-158e75ca927a": { + "id": "7f5cc5fa-39d2-4939-8a9c-158e75ca927a", + "name": "cond_is_dest", + "category": "condition", + "title": "到达终点", + "description": "cond_is_dest\n判断是否到达终点\n是:SUCCESS\n否:FAILURE", + "properties": {}, + "display": { + "x": 756, + "y": -48 + } + }, + "5dc62c15-2709-4cba-90cb-01f8e06e8f6c": { + "id": "5dc62c15-2709-4cba-90cb-01f8e06e8f6c", + "name": "Runner", + "category": "action", + "title": "Runner", + "description": "", + "properties": {}, + "display": { + "x": 348, + "y": 396 + } + }, + "5de1553f-7936-4349-8960-34d2ab175def": { + "id": "5de1553f-7936-4349-8960-34d2ab175def", + "name": "action_collect_path", + "category": "action", + "title": "收集巡逻路径", + "description": "action_collect_path\n收集巡逻路径\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 132, + "y": -132 + } + }, + "1650138e-bccc-41dc-868f-d48485ac164e": { + "id": "1650138e-bccc-41dc-868f-d48485ac164e", + "name": "action_become_idle", + "category": "action", + "title": "变为空闲状态", + "description": "action_become_idle\n变为空闲状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 756, + "y": 36 + } + }, + "5f6c51dc-f579-4855-8fb7-342e93b9c255": { + "id": "5f6c51dc-f579-4855-8fb7-342e93b9c255", + "name": "action_collect_patrol", + "category": "action", + "title": "寻找巡逻兵", + "description": "action_collect_patrol\n寻找巡逻兵\n找到:SUCCESS\n未找到:FAILURE", + "properties": {}, + "display": { + "x": 756, + "y": 216 + } + }, + "78d85af4-83d9-4a46-856b-ae4d5c710941": { + "id": "78d85af4-83d9-4a46-856b-ae4d5c710941", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 552, + "y": 264 + }, + "children": [ + "5f6c51dc-f579-4855-8fb7-342e93b9c255", + "bb283abe-9129-43a5-8fa4-2a9876042b41" + ] + }, + "bb283abe-9129-43a5-8fa4-2a9876042b41": { + "id": "bb283abe-9129-43a5-8fa4-2a9876042b41", + "name": "action_become_attacking", + "category": "action", + "title": "变为攻击状态", + "description": "action_become_attacking\n变为攻击状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 756, + "y": 300 + } + }, + "4afa6247-b396-4e15-b1e4-2b5ec4659789": { + "id": "4afa6247-b396-4e15-b1e4-2b5ec4659789", + "name": "Inverter", + "category": "decorator", + "title": "Inverter", + "description": "", + "properties": {}, + "display": { + "x": 348, + "y": 264 + }, + "child": "78d85af4-83d9-4a46-856b-ae4d5c710941" + } + }, + "display": { + "camera_x": 648, + "camera_y": 397.5, + "camera_z": 1, + "x": -276, + "y": 36 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "2fcfa9bf-f759-4eb6-9168-8ef89d1c46da", + "title": "丧尸攻击逻辑", + "description": "", + "root": "95440e68-5b72-4f57-a643-0b5ad4cf16c3", + "properties": {}, + "nodes": { + "95440e68-5b72-4f57-a643-0b5ad4cf16c3": { + "id": "95440e68-5b72-4f57-a643-0b5ad4cf16c3", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 204, + "y": 0 + }, + "children": [ + "64d0d38a-d813-4350-8864-9f49d4cc2cd1", + "795036b5-a153-4872-b42f-a583b09c517e" + ] + }, + "64d0d38a-d813-4350-8864-9f49d4cc2cd1": { + "id": "64d0d38a-d813-4350-8864-9f49d4cc2cd1", + "name": "Inverter", + "category": "decorator", + "title": "Inverter", + "description": "", + "properties": {}, + "display": { + "x": 408, + "y": -96 + }, + "child": "b1dcd56f-2af7-4771-b280-30d0d6215d34" + }, + "b1dcd56f-2af7-4771-b280-30d0d6215d34": { + "id": "b1dcd56f-2af7-4771-b280-30d0d6215d34", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 624, + "y": -96 + }, + "children": [ + "c0f543a4-ab0c-48d8-8ea4-a714762e9f0e", + "9fcaa262-144f-4340-bc30-1469ed6ac7f0", + "5bbba631-c4da-452a-9e65-f22d12d98ba2" + ] + }, + "5bbba631-c4da-452a-9e65-f22d12d98ba2": { + "id": "5bbba631-c4da-452a-9e65-f22d12d98ba2", + "name": "action_become_idle", + "category": "action", + "title": "变为空闲状态", + "description": "action_become_idle\n变为空闲状态\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 828, + "y": 0 + } + }, + "c0f543a4-ab0c-48d8-8ea4-a714762e9f0e": { + "id": "c0f543a4-ab0c-48d8-8ea4-a714762e9f0e", + "name": "cond_is_target_died", + "category": "condition", + "title": "目标死亡", + "description": "cond_is_target_died\n判断目标是否死亡\n死亡:SUCCESS\n未死亡:FAILURE", + "properties": {}, + "display": { + "x": 828, + "y": -180 + } + }, + "795036b5-a153-4872-b42f-a583b09c517e": { + "id": "795036b5-a153-4872-b42f-a583b09c517e", + "name": "action_skill_attack", + "category": "action", + "title": "技能攻击", + "description": "action_skill_attack\n技能攻击\nskill_type : 普通攻击,技能攻击\n 随机伤害值 RAND(min_power ,max_power) * RAND(min_rate, max_rate)\n返回:SUCCESS", + "properties": { + "min_power": 1, + "max_power": 10, + "min_rate": 1, + "max_rate": 3, + "skill_type": "撕咬" + }, + "display": { + "x": 408, + "y": 84 + } + }, + "9fcaa262-144f-4340-bc30-1469ed6ac7f0": { + "id": "9fcaa262-144f-4340-bc30-1469ed6ac7f0", + "name": "action_del_dead_target", + "category": "action", + "title": "删除死亡目标", + "description": "action_del_dead_target\n删除死亡目标\n删除rival_list中第一个目标\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 828, + "y": -96 + } + } + }, + "display": { + "camera_x": 446, + "camera_y": 521.5, + "camera_z": 1, + "x": 0, + "y": 0 + } + }, + { + "version": "0.3.0", + "scope": "tree", + "id": "2e441ec0-fdeb-4ab4-8738-f3bba3d7cc93", + "title": "丧尸被动AI", + "description": "", + "root": "35e25c83-58a9-4b80-9876-0d3eb8a0039e", + "properties": {}, + "nodes": { + "35e25c83-58a9-4b80-9876-0d3eb8a0039e": { + "id": "35e25c83-58a9-4b80-9876-0d3eb8a0039e", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": -36, + "y": 0 + }, + "children": [ + "1dd4c3f5-3e36-4327-8612-6603529a4fe8", + "d0f019f0-b156-4a47-856f-710eabb8c1d5" + ] + }, + "a44f21a3-743a-4ea0-8350-5981428ce497": { + "id": "a44f21a3-743a-4ea0-8350-5981428ce497", + "name": "cond_is_died", + "category": "condition", + "title": "已经死亡", + "description": "cond_is_died\n判断自己是否死亡\n死亡:SUCCESS\n存活:FAILURE", + "properties": {}, + "display": { + "x": 384, + "y": 12 + } + }, + "d0f019f0-b156-4a47-856f-710eabb8c1d5": { + "id": "d0f019f0-b156-4a47-856f-710eabb8c1d5", + "name": "Sequence", + "category": "composite", + "title": "Sequence", + "description": "", + "properties": {}, + "display": { + "x": 168, + "y": 60 + }, + "children": [ + "a44f21a3-743a-4ea0-8350-5981428ce497", + "468dc36b-d1e5-4d77-a8a2-ac5079628799" + ] + }, + "1dd4c3f5-3e36-4327-8612-6603529a4fe8": { + "id": "1dd4c3f5-3e36-4327-8612-6603529a4fe8", + "name": "action_cost_power", + "category": "action", + "title": "扣除体力", + "description": "action_cost_power\n扣除体力\n获取Misc结构中的atk_dmg字段的值\n返回:SUCCESS", + "properties": { + "max_power": "", + "min_power": "" + }, + "display": { + "x": 168, + "y": -72 + } + }, + "468dc36b-d1e5-4d77-a8a2-ac5079628799": { + "id": "468dc36b-d1e5-4d77-a8a2-ac5079628799", + "name": "action_died", + "category": "action", + "title": "自我死亡", + "description": "action_died\n自我死亡\n销毁对象数据\n返回:SUCCESS", + "properties": {}, + "display": { + "x": 384, + "y": 108 + } + } + }, + "display": { + "camera_x": 817, + "camera_y": 507.5, + "camera_z": 1, + "x": -240, + "y": 0 + } + } + ], + "custom_nodes": [ + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_patrolling", + "category": "condition", + "title": "是巡逻状态", + "description": "cond_is_patrolling\n判断是否处于巡逻状态\n是:SUCCESS\n否:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_dest", + "category": "condition", + "title": "到达终点", + "description": "cond_is_dest\n判断是否到达终点\n是:SUCCESS\n否:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_move_grid", + "category": "action", + "title": "移动一格", + "description": "action_move_grid\n移动一格\n返回:SUCCESS", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_become_idle", + "category": "action", + "title": "变为空闲状态", + "description": "action_become_idle\n变为空闲状态\n返回:SUCCESS", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_attacking", + "category": "condition", + "title": "是攻击状态", + "description": "cond_is_attacking\n判断是否处于攻击状态\n是:SUCCESS\n否:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_rage_full", + "category": "condition", + "title": "怒气值已满", + "description": "cond_is_rage_full\n判断怒气值是否已满\n已满:SUCCESS\n未满:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_power_full", + "category": "condition", + "title": "体力已满", + "description": "cond_is_power_full\n判断体力是否已满\n已满:SUCCESS\n未满:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_died", + "category": "condition", + "title": "已经死亡", + "description": "cond_is_died\n判断自己是否死亡\n死亡:SUCCESS\n存活:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_target_died", + "category": "condition", + "title": "目标死亡", + "description": "cond_is_target_died\n判断目标是否死亡\n死亡:SUCCESS\n未死亡:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_idle", + "category": "condition", + "title": "是空闲状态", + "description": "cond_is_idle\n判断是否处于空闲状态\n是:SUCCESS\n否:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_become_patrolling", + "category": "action", + "title": "变为巡逻状态", + "description": "action_become_patrolling\n变为巡逻状态\n返回:SUCCESS", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_finish", + "category": "action", + "title": "游戏", + "description": "action_finish\n游戏结束,根据is_win属性打印结果\n打印结果,结束行为树运行\n返回:SUCCESS", + "properties": { + "is_win": "" + } + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_add_rage", + "category": "action", + "title": "增加怒气", + "description": "action_add_rage\n增加怒气\n根据add_rage增加相应怒气,结果小于等于100\n返回:SUCCESS", + "properties": { + "add_rage": "" + } + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_recover_power", + "category": "action", + "title": "恢复 - 体力", + "description": "action_recover_power\n恢复体力\n根据Max - Min之间随机恢复几点体力\n返回:SUCCESS", + "properties": { + "max_power": "", + "min_power": "" + } + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_cost_rage", + "category": "action", + "title": "扣除怒气", + "description": "action_cost_rage\n扣除怒气\n根据cost_rage扣除相应怒气,结果大于等于0\n返回:SUCCESS", + "properties": { + "cost_rage": "" + } + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_is_recovering", + "category": "condition", + "title": "是恢复状态", + "description": "cond_is_recovering\n判断是否处于恢复状态\n是:SUCCESS\n否:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_become_recovering", + "category": "action", + "title": "变为恢复状态", + "description": "action_become_recovering\n变为恢复状态\n返回:SUCCESS", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "cond_power_lt", + "category": "condition", + "title": "体力小于", + "description": "cond_power_lt\n判断体力是否小于某值\n是:SUCCESS\n否:FAILURE", + "properties": { + "power": 50 + } + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_become_attacking", + "category": "action", + "title": "变为攻击状态", + "description": "action_become_attacking\n变为攻击状态\n返回:SUCCESS", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_collect_dest_path", + "category": "action", + "title": "收集终点路径", + "description": "action_collect_dest_path\n收集终点路径\n返回:SUCCESS", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_collect_path", + "category": "action", + "title": "收集巡逻路径", + "description": "action_collect_path\n收集巡逻路径\n返回:SUCCESS", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_collect_patrol", + "category": "action", + "title": "寻找巡逻兵", + "description": "action_collect_patrol\n寻找巡逻兵\n找到:SUCCESS\n未找到:FAILURE", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_cost_power", + "category": "action", + "title": "扣除体力", + "description": "action_cost_power\n扣除体力\n获取Misc结构中的atk_dmg字段的值\n返回:SUCCESS", + "properties": { + "max_power": "", + "min_power": "" + } + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_skill_attack", + "category": "action", + "title": "技能攻击", + "description": "action_skill_attack\n技能攻击\nskill_type : 普通攻击,技能攻击\n 随机伤害值 RAND(min_power ,max_power) * RAND(min_rate, max_rate)\n返回:SUCCESS", + "properties": { + "min_power": 0, + "max_power": 0, + "min_rate": 0, + "max_rate": 0, + "skill_type": "" + } + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_del_dead_target", + "category": "action", + "title": "删除死亡目标", + "description": "action_del_dead_target\n删除死亡目标\n删除rival_list中第一个目标\n返回:SUCCESS", + "properties": {} + }, + { + "version": "0.3.0", + "scope": "node", + "name": "action_died", + "category": "action", + "title": "自我死亡", + "description": "action_died\n自我死亡\n销毁对象数据\n返回:SUCCESS", + "properties": {} + } + ] +} \ No newline at end of file diff --git a/examples/game_dict.erl b/examples/game_dict.erl new file mode 100644 index 0000000..451807a --- /dev/null +++ b/examples/game_dict.erl @@ -0,0 +1,59 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 数据字典 +%%% @end +%%% Created : 18. 10月 2020 20:53 +%%%------------------------------------------------------------------- +-module(game_dict). + +-include("behavior3.hrl"). +-include("example.hrl"). + +%% API +-export([ + get_map_data/1, update_map_date/3, + get_initiative_btree_id/1, put_initiative_btree_id/2, erase_initiative_btree_id/1, + get_passivity_btree_id/1, put_passivity_btree_id/2, erase_passivity_btree_id/1, + get_role_state/1, put_role_state/2, erase_role_state/1 +]). + +-spec get_map_data(grid()) -> [uid()]. +get_map_data(Grid) -> + case erlang:get(Grid) of + undefined -> + []; + L -> + L + end. + +-spec update_map_date(grid(), grid(), uid()) -> ok. +update_map_date(OldGrid, NewGrid, UID) -> + erlang:put(OldGrid, lists:delete(UID, get_map_data(OldGrid))), + erlang:put(NewGrid, [UID | get_map_data(NewGrid)]), + ok. + +-spec get_initiative_btree_id(uid()) -> bt_uid(). +get_initiative_btree_id(UID) -> + erlang:get({initiative_btree_id, UID}). +put_initiative_btree_id(UID, BTreeID) -> + erlang:put({initiative_btree_id, UID}, BTreeID). +erase_initiative_btree_id(UID) -> + erlang:erase({initiative_btree_id, UID}). + +-spec get_passivity_btree_id(uid()) -> bt_uid(). +get_passivity_btree_id(UID) -> + erlang:get({passivity_btree_id, UID}). +put_passivity_btree_id(UID, BTreeID) -> + erlang:put({passivity_btree_id, UID}, BTreeID). +erase_passivity_btree_id(UID) -> + erlang:erase({passivity_btree_id, UID}). + +-spec get_role_state(uid()) -> map(). +get_role_state(UID) -> + erlang:get({role_state, UID}). +put_role_state(UID, State) -> + erlang:put({role_state, UID}, State). +erase_role_state(UID) -> + erlang:erase({role_state, UID}). diff --git a/examples/game_process.erl b/examples/game_process.erl new file mode 100644 index 0000000..dbbb443 --- /dev/null +++ b/examples/game_process.erl @@ -0,0 +1,127 @@ +%%%------------------------------------------------------------------- +%%% @author DY +%%% @copyright (C) 2020, +%%% @doc +%%% 游戏进程 +%%% @end +%%%------------------------------------------------------------------- +-module(game_process). + +-behaviour(gen_server). + +-include("example.hrl"). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% Spawning and gen_server implementation +%%%=================================================================== + +start_link() -> + gen_server:start_link(?MODULE, [], []). + +init([]) -> + {TitleMaps, TreeMaps, TreeNodeMaps} = behavior_tree:load_tree_file("examples/example.json"), + + ZombieList = [ +%% <<"HUNTER"/utf8>>, +%% <<"BOMMER"/utf8>>, +%% <<"JOCKEY"/utf8>>, +%% <<"SOMKER"/utf8>>, +%% <<"SPITTER"/utf8>>, +%% <<"CHARGER"/utf8>>, +%% <<"TANK"/utf8>>, + <<"WITCH"/utf8>> + ], + init_zombie(ZombieList, TitleMaps, TreeMaps, TreeNodeMaps), + + UID = <<"凯恩"/utf8>>, + init_patrol(UID, TitleMaps, TreeMaps, TreeNodeMaps), + erlang:put(uid_list, ZombieList ++ [UID]), + self() ! run, + {ok, #{frame => 1}}. + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Request, State) -> + {noreply, State}. + +handle_info(run, #{frame := Frame} = State) -> + try + ?INFO("第~w分钟", [Frame]), + Fun = fun(UID) -> run(UID) end, + lists:foreach(Fun, get(uid_list)), + erlang:send_after(1000, self(), run), + {noreply, State#{frame := Frame + 1}} + catch + Error:Reason:StackTrace -> + ?INFO("Error:~w Reason:~w ~n StackTrace:~p", [Error, Reason, StackTrace]), + {stop, normal, State} + end; + +handle_info(finish, State) -> + {stop, normal, State}; + +handle_info({died, UID}, State) -> + erlang:put(uid_list, lists:delete(UID, erlang:get(uid_list))), + {noreply, State}; + +handle_info(_Info, State) -> + {noreply, State}. +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +new_state(UID, Type, CurGrid) -> + case Type of + ?HUMAN -> + Power = ?MAX_POWER; + ?ZOMBIE -> + Power = ?RAND(?MAX_POWER div 2, ?MAX_POWER) + end, + State = #{ + uid => UID, + type => Type, + cur_state => ?STATE_TYPE_IDLE, + cur_power => Power, + cur_rage => 0, + grid_list => [], + cur_grid => CurGrid, + rival_list => [], + misc => #{} + }, + ?INFO("生成单位:~tp", [State]), + 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), + 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)}, + game_dict:put_role_state(UID, new_state(UID, ?ZOMBIE, Grid)), + init_zombie(T, TitleMaps, TreeMaps, TreeNodeMaps); +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), + 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)}, + game_dict:put_role_state(UID, new_state(UID, ?HUMAN, Grid)). + +run(UID) -> + BTreeID = game_dict:get_initiative_btree_id(UID), + #{cur_state := CurState} = State = game_dict:get_role_state(UID), + case ?IS_STATE(?STATE_TYPE_DEAD, CurState) of + true -> + ok; + false -> + {_, State1} = behavior_tree:execute(BTreeID, State), + game_dict:put_role_state(UID, State1) + end. \ No newline at end of file diff --git a/include/behavior3.hrl b/include/behavior3.hrl new file mode 100644 index 0000000..15259bf --- /dev/null +++ b/include/behavior3.hrl @@ -0,0 +1,64 @@ +-ifndef(behavior3_h). +-define(behavior3_h, true). + +-export_type([bt_uid/0, bt_status/0, bt_node_id/0, bt_state/0, properties/0, uninit_bt_node/0, tree_nodes/0, btree/0, bt_node/0]). + +-type bt_uid() :: reference(). + +-define(BT_SUCCESS, 1). +-define(BT_FAILURE, 2). +-define(BT_RUNNING, 3). +-define(BT_ERROR, 4). + +-type bt_status() :: ?BT_SUCCESS|?BT_FAILURE|?BT_RUNNING|?BT_ERROR. + +-type bt_node_id() :: string(). + +-type bt_state() :: #{ + '$global_maps' => map(), + '$local_maps' => map(), + term() => term() +}. + +-type properties() :: #{atom() => term()}. + +%% 未初始化的树节点 +-type uninit_bt_node() :: #{ + id => bt_node_id(), + name => atom() | bt_node_id(), + category => atom(), + properties => properties(), + children => [bt_node_id()] +}. + +-type tree_nodes() :: #{bt_node_id() => uninit_bt_node()}. + +%% 行为树 +-type btree() :: #{ + id => bt_node_id(), + root => bt_node_id(), + properties => properties(), + tree_nodes => tree_nodes() +}. + +%% 行为树节点 +-type bt_node() :: #{ + id := bt_uid(), + bt_node_id := bt_node_id(), + parent_id := bt_uid(), + name := atom(), + category := atom(), + properties := properties(), + children := [bt_uid()] +}. + +-define(BI_MOD_LIST, [ + error, failer, runner, succeeder, wait, + mem_priority, mem_sequence, priority, sequence, + inverter, limiter, max_time, repeat_until_failure, repeat_until_success, repeater +]). + +-define(BT_LOG(Format), io:format("~w:~w:~w [debug] ~p: " ++ Format ++ "~n", tuple_to_list(time()) ++ [?MODULE])). +-define(BT_LOG(Format, Args), io:format("~w:~w:~w [debug] ~p: " ++ Format ++ "~n", tuple_to_list(time()) ++ [?MODULE | Args])). + +-endif. diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..aeeda93 --- /dev/null +++ b/rebar.config @@ -0,0 +1,15 @@ +{erl_opts, [debug_info, {i, "include"} + %% {d, show_bt_log}, %% log_switch +]}. + +{deps, [ + jsx +]}. + +{xref_checks, [ + undefined_function_calls + , undefined_functions + , locals_not_used + , deprecated_function_calls + , deprecated_functions +]}. \ No newline at end of file diff --git a/src/action/error.erl b/src/action/error.erl new file mode 100644 index 0000000..4f45cba --- /dev/null +++ b/src/action/error.erl @@ -0,0 +1,22 @@ +-module(error). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([tick/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(_BTNode, BTState) -> + {?BT_ERROR, BTState}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- diff --git a/src/action/failer.erl b/src/action/failer.erl new file mode 100644 index 0000000..207c8f4 --- /dev/null +++ b/src/action/failer.erl @@ -0,0 +1,22 @@ +-module(failer). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([tick/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(_BTNode, BTState) -> + {?BT_FAILURE, BTState}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- diff --git a/src/action/runner.erl b/src/action/runner.erl new file mode 100644 index 0000000..f4d2c3e --- /dev/null +++ b/src/action/runner.erl @@ -0,0 +1,22 @@ +-module(runner). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([tick/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(_BTNode, BTState) -> + {?BT_RUNNING, BTState}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- diff --git a/src/action/succeeder.erl b/src/action/succeeder.erl new file mode 100644 index 0000000..b93df09 --- /dev/null +++ b/src/action/succeeder.erl @@ -0,0 +1,22 @@ +-module(succeeder). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([tick/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(_BTNode, BTState) -> + {?BT_SUCCESS, BTState}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- diff --git a/src/action/wait.erl b/src/action/wait.erl new file mode 100644 index 0000000..8df3a65 --- /dev/null +++ b/src/action/wait.erl @@ -0,0 +1,36 @@ +-module(wait). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([open/2, tick/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec open(bt_node(), bt_state()) -> bt_state(). +open(#{id := ID}, BTState) -> + blackboard:set(start_time, erlang:system_time(millisecond), ID, BTState). + +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{id := ID, properties := #{milliseconds := EndTime}} = _BTNode, BTState) -> + StartTime = blackboard:get(start_time, ID, BTState), + case erlang:system_time(millisecond) - StartTime > EndTime of + true -> + {?BT_SUCCESS, BTState}; + false -> + {?BT_RUNNING, BTState} + end. + +-spec close(bt_node(), bt_state()) -> bt_state(). +close(#{id := ID} = _BTNode, BTState) -> + blackboard:remove(start_time, ID, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- diff --git a/src/composite/mem_priority.erl b/src/composite/mem_priority.erl new file mode 100644 index 0000000..3c91363 --- /dev/null +++ b/src/composite/mem_priority.erl @@ -0,0 +1,43 @@ +-module(mem_priority). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([open/2, tick/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec open(bt_node(), bt_state()) -> bt_state(). +open(#{id := ID, children := Children} = _BTNode, BTState) -> + blackboard:set(running_children, Children, ID, BTState). + +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{id := ID} = _BTNode, BTState) -> + RunningChildren = blackboard:get(running_children, ID, BTState), + tick_1(RunningChildren, ID, BTState). + +-spec close(bt_node(), bt_state()) -> bt_state(). +close(#{id := ID} = _BTNode, State) -> + blackboard:remove(running_children, ID, State). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +tick_1([ChildID | T] = Children, ID, BTState) -> + case base_node:execute(ChildID, BTState) of + {?BT_FAILURE, BTState1} -> + tick_1(T, ID, BTState1); + {?BT_RUNNING, BTState1} -> + BTState2 = blackboard:set(running_children, Children, ID, BTState1), + {?BT_RUNNING, BTState2}; + {BTStatus, BTState1} -> + {BTStatus, BTState1} + end; +tick_1([], _ID, BTState) -> + {?BT_FAILURE, BTState}. diff --git a/src/composite/mem_sequence.erl b/src/composite/mem_sequence.erl new file mode 100644 index 0000000..0d78645 --- /dev/null +++ b/src/composite/mem_sequence.erl @@ -0,0 +1,43 @@ +-module(mem_sequence). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([open/2, tick/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec open(bt_node(), bt_state()) -> bt_state(). +open(#{id := ID, children := Children} = _BTNode, BTState) -> + blackboard:set(running_children, Children, ID, BTState). + +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{id := ID} = _BTNode, BTState) -> + RunningChildren = blackboard:get(running_children, ID, BTState), + tick_1(RunningChildren, ID, BTState). + +-spec close(bt_node(), bt_state()) -> bt_state(). +close(#{id := ID} = _BTNode, BTState) -> + blackboard:remove(running_children, ID, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +tick_1([ChildID | T] = Children, ID, BTState) -> + case base_node:execute(ChildID, BTState) of + {?BT_SUCCESS, BTState1} -> + tick_1(T, ID, BTState1); + {?BT_RUNNING, BTState1} -> + BTState2 = blackboard:set(running_children, Children, ID, BTState1), + {?BT_RUNNING, BTState2}; + {BTStatus, BTState1} -> + {BTStatus, BTState1} + end; +tick_1([], _ID, BTState) -> + {?BT_SUCCESS, BTState}. diff --git a/src/composite/priority.erl b/src/composite/priority.erl new file mode 100644 index 0000000..ecec02a --- /dev/null +++ b/src/composite/priority.erl @@ -0,0 +1,31 @@ +-module(priority). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([tick/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{children := Children} = _BTNode, BTState) -> + tick_1(Children, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +tick_1([ChildID | T], BTState) -> + case base_node:execute(ChildID, BTState) of + {?BT_FAILURE, BTState1} -> + tick_1(T, BTState1); + {BTStatus, BTState1} -> + {BTStatus, BTState1} + end; +tick_1([], BTState) -> + {?BT_FAILURE, BTState}. diff --git a/src/composite/sequence.erl b/src/composite/sequence.erl new file mode 100644 index 0000000..60a58f3 --- /dev/null +++ b/src/composite/sequence.erl @@ -0,0 +1,31 @@ +-module(sequence). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([tick/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{children := Children} = _BTNode, BTState) -> + tick_1(Children, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +tick_1([ChildID | T], BTState) -> + case base_node:execute(ChildID, BTState) of + {?BT_SUCCESS, BTState1} -> + tick_1(T, BTState1); + {BTStatus, BTState1} -> + {BTStatus, BTState1} + end; +tick_1([], BTState) -> + {?BT_SUCCESS, BTState}. diff --git a/src/core/base_node.erl b/src/core/base_node.erl new file mode 100644 index 0000000..257b83a --- /dev/null +++ b/src/core/base_node.erl @@ -0,0 +1,137 @@ +-module(base_node). + +-compile([inline, {inline_size, 100}]). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([execute/2]). + +%% Internal API +-export([do_init/4, do_open/2, do_tick/2, do_close/2]). + +%% 节点初始化 +-callback(init(bt_node()) -> bt_node()). + +%% 开启节点 +-callback(open(bt_node(), bt_state()) -> bt_state()). + +%% 执行节点 +-callback(tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}). + +%% 关闭节点 +-callback(close(bt_node(), bt_state()) -> bt_state()). + +-optional_callbacks([init/1, open/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +%% @doc 执行节点 +-spec execute(bt_uid(), bt_state()) -> {bt_status(), bt_state()}. +execute(NodeID, BTState) -> + BTNode = blackboard:get_btree_node(NodeID), + BTState1 = do_open(BTNode, BTState), + {BTStatus, BTState2} = do_tick(BTNode, BTState1), + case BTStatus of + ?BT_RUNNING -> + {?BT_RUNNING, BTState2}; + BTStatus -> + BTState3 = do_close(BTNode, BTState2), + {BTStatus, BTState3} + end. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +%% 初始化行为树 +-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 + #{name := Name, category := tree} -> + #{root := Root} = behavior_tree:get_btree(Name, TreeMaps), + do_init(ParentNodeID, Root, TreeMaps, TreeNodeMaps); + #{name := Name, children := Children} = BTNode -> + case code:ensure_loaded(Name) of + {module, Mod} -> + case erlang:function_exported(Mod, init, 1) of + true -> + BTNode1 = Mod:init(BTNode); + false -> + BTNode1 = BTNode + end, + ID = make_ref(), + BTNode2 = BTNode1#{ + id => ID, + bt_node_id => BTNodeID, + parent_id => ParentNodeID, + children => [do_init(ID, ChildBTNodeID, TreeMaps, TreeNodeMaps) || ChildBTNodeID <- Children] + }, + blackboard:set_btree_node(BTNode2), + {ok, ID}; + _ -> + {error, {btree_node_not_implement, Name}} + end + end. + +%% 如果树节点没有开启,并且有open/2函数实现,则执行open函数, +%% 如果树节点已经开启,则跳过 +-spec do_open(bt_node(), bt_state()) -> bt_state(). +do_open(#{id := ID, parent_id := ParentID, name := Mod} = BTNode, BTState) -> + case blackboard:get('$is_open', ID, false, BTState) of + true -> + BTState; + false -> + case erlang:function_exported(Mod, open, 2) of + true -> + BTState1 = Mod:open(BTNode, BTState); + false -> + BTState1 = BTState + end, + BTState2 = blackboard:set('$is_open', true, ID, BTState1), + case is_reference(ParentID) of + true -> + Children = blackboard:get('$children', ParentID, [], BTState2), + blackboard:set('$children', [ID | Children], ParentID, BTState2); + false -> + BTState2 + end + end. + + +%% 执行树节点tick函数 +-spec do_tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +do_tick(#{name := Mod} = BTNode, BTState) -> + Mod:tick(BTNode, BTState). + +%% @doc 如果树节点有close/2函数实现,则执行close函数 +-spec do_close(bt_node(), bt_state()) -> bt_state(). +do_close(#{id := ID, name := Mod} = BTNode, BTState) -> + Children = blackboard:get('$children', ID, [], BTState), + BTState1 = do_close_1(Children, BTState), + case erlang:function_exported(Mod, close, 2) of + true -> + BTState2 = Mod:close(BTNode, BTState1); + false -> + BTState2 = BTState1 + end, + BTState3 = blackboard:set('$is_open', false, ID, BTState2), + BTState4 = blackboard:set('$children', [], ID, BTState3), + blackboard:erase_node_local(ID, BTState4). + +do_close_1([NodeID | T], BTState) -> + BTState1 = do_close_1(T, BTState), + case blackboard:get('$is_open', NodeID, false, BTState1) of + true -> + BTNode = blackboard:get_btree_node(NodeID), + do_close(BTNode, BTState1); + false -> + BTState1 + end; +do_close_1([], BTState) -> + BTState. \ No newline at end of file diff --git a/src/core/behavior_tree.erl b/src/core/behavior_tree.erl new file mode 100644 index 0000000..f05c67e --- /dev/null +++ b/src/core/behavior_tree.erl @@ -0,0 +1,215 @@ +-module(behavior_tree). + +-compile([inline, {inline_size, 100}]). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-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 +]). + +%% Internal API +-export([get_btree_id_by_title/2, get_btree/2, get_btree_node/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +%% @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). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +%% 解析行为树 +-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/core/blackboard.erl b/src/core/blackboard.erl new file mode 100644 index 0000000..a12d464 --- /dev/null +++ b/src/core/blackboard.erl @@ -0,0 +1,157 @@ +-module(blackboard). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([ + get_btree_node/1, set_btree_node/1, erase_btree_node/1, + set_global/3, set/4, + get_global/2, get_global/3, get/3, get/4, + remove/2, remove/3 +]). + +%% Internal API +-export([init_maps_keys/1, erase_node_local/2, erase_btree/1]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +%% @doc +%% 获取树节点 +-spec get_btree_node(bt_uid()) -> bt_node()|undefined. +get_btree_node(NodeID) -> + get({btree_node, NodeID}). + +%% @doc +%% 设置树节点 +-spec set_btree_node(bt_node()) -> ok. +set_btree_node(#{id := NodeID} = BTNode) -> + put({btree_node, NodeID}, BTNode), + ok. + +%% @doc +%% 移除树节点 +-spec erase_btree_node(bt_uid()) -> bt_node(). +erase_btree_node(NodeID) -> + erase({btree_node, NodeID}). + +%% @doc +%% 设置全局变量 +-spec set_global(term(), term(), bt_state()) -> bt_state(). +set_global(Key, Value, #{'$global_maps' := GlobalMaps} = BTState) -> + BTState#{'$global_maps' := GlobalMaps#{Key => Value}}. + +%% @doc +%% 设置节点变量 +-spec set(term(), term(), bt_uid(), bt_state()) -> bt_state(). +set(Key, Value, NodeID, #{'$local_maps' := LocalMaps} = BTState) -> + case LocalMaps of + #{NodeID := ValueMaps} -> + LocalMaps1 = LocalMaps#{NodeID := ValueMaps#{Key => Value}}; + #{} -> + LocalMaps1 = LocalMaps#{NodeID => #{Key => Value}} + end, + BTState#{'$local_maps' := LocalMaps1}. + +%% @doc +%% 获取全局变量 +-spec get_global(term(), bt_state()) -> term()|undefined. +get_global(Key, BTState) -> + case BTState of + #{'$global_maps' := #{Key := Value}} -> + Value; + #{} -> + undefined + end. + +%% @doc +%% 获取全局变量,不存在则返回Default +-spec get_global(term(), term(), bt_state()) -> term(). +get_global(Key, Default, BTState) -> + case BTState of + #{'$global_maps' := #{Key := Value}} -> + Value; + #{} -> + Default + end. + +%% @doc +%% 获取节点变量 +-spec get(term(), bt_uid(), bt_state()) -> term()|undefined. +get(Key, NodeID, BTState) -> + case BTState of + #{'$local_maps' := #{NodeID := #{Key := Value}}} -> + Value; + #{} -> + undefined + end. + +%% @doc +%% 获取节点变量,不存在则返回Default +-spec get(term(), bt_uid(), term(), bt_state()) -> term(). +get(Key, NodeID, Default, BTState) -> + case BTState of + #{'$local_maps' := #{NodeID := #{Key := Value}}} -> + Value; + #{} -> + Default + end. + +%% @doc +%% 删除全局变量 +-spec remove(term(), bt_state()) -> bt_state(). +remove(Key, #{'$global_maps' := GlobalMaps} = BTState) -> + BTState#{'$global_maps' := maps:remove(Key, GlobalMaps)}. + +%% @doc +%% 删除节点变量 +-spec remove(term(), bt_uid(), bt_state()) -> bt_state(). +remove(Key, NodeID, #{'$local_maps' := LocalMaps} = BTState) -> + case LocalMaps of + #{NodeID := ValueMap} -> + LocalMaps1 = LocalMaps#{NodeID := maps:remove(Key, ValueMap)}, + BTState#{'$local_maps' := LocalMaps1}; + #{} -> + BTState + end. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +%% 初始化行为树相关结构 +-spec init_maps_keys(bt_state()) -> bt_state(). +init_maps_keys(BTState) -> + BTState1 = init_global(BTState), + init_local(BTState1). + +init_global(BTState) -> + case maps:is_key('$global_maps', BTState) of + true -> + BTState; + false -> + BTState#{'$global_maps' => #{}} + end. + +init_local(BTState) -> + case maps:is_key('$local_maps', BTState) of + true -> + BTState; + false -> + BTState#{'$local_maps' => #{}} + end. + +%% 移除节点局部变量结构 +-spec erase_node_local(bt_uid(), bt_state()) -> bt_state(). +erase_node_local(NodeID, #{'$local_maps' := LocalMaps} = BTState) -> + BTState#{'$local_maps' := maps:remove(NodeID, LocalMaps)}. + +%% 移除行为树相关结构 +-spec erase_btree(bt_state()) -> bt_state(). +erase_btree(BTState) -> + BTState1 = maps:remove('$global_maps', BTState), + maps:remove('$local_maps', BTState1). diff --git a/src/decorator/inverter.erl b/src/decorator/inverter.erl new file mode 100644 index 0000000..2a81ee3 --- /dev/null +++ b/src/decorator/inverter.erl @@ -0,0 +1,31 @@ +-module(inverter). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([tick/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{children := [ChildID]} = _BTNode, BTState) -> + case base_node:execute(ChildID, BTState) of + {?BT_SUCCESS, BTState1} -> + {?BT_FAILURE, BTState1}; + {?BT_FAILURE, BTState1} -> + {?BT_SUCCESS, BTState1}; + {BTStatus, BTState1} -> + {BTStatus, BTState1} + end; +tick(_BTNode, BTState) -> + {?BT_ERROR, BTState}. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- diff --git a/src/decorator/limiter.erl b/src/decorator/limiter.erl new file mode 100644 index 0000000..3671234 --- /dev/null +++ b/src/decorator/limiter.erl @@ -0,0 +1,43 @@ +-module(limiter). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([open/2, tick/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec open(bt_node(), bt_state()) -> bt_state(). +open(#{id := ID} = _BTNode, BTState) -> + blackboard:set(i, 0, ID, BTState). + +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{id := ID, children := [ChildID], properties := #{max_loop := MaxLoop}} = _BTNode, BTState) -> + case blackboard:get(i, ID, BTState) of + I when I < MaxLoop -> + case base_node:execute(ChildID, BTState) of + {BTStatus, BTState1} when BTStatus =:= ?BT_SUCCESS orelse BTStatus =:= ?BT_FAILURE -> + BTState2 = blackboard:set(i, I + 1, ID, BTState1), + {BTStatus, BTState2}; + {BTStatus, BTState1} -> + {BTStatus, BTState1} + end; + _I -> + {?BT_FAILURE, BTState} + end; +tick(_BTNode, BTState) -> + {?BT_ERROR, BTState}. + +-spec close(bt_node(), bt_state()) -> bt_state(). +close(#{id := ID} = _BTNode, BTState) -> + blackboard:remove(i, ID, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- diff --git a/src/decorator/max_time.erl b/src/decorator/max_time.erl new file mode 100644 index 0000000..4cd61cd --- /dev/null +++ b/src/decorator/max_time.erl @@ -0,0 +1,39 @@ +-module(max_time). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([open/2, tick/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec open(bt_node(), bt_state()) -> bt_state(). +open(#{id := ID} = _BTNode, BTState) -> + blackboard:set(start_time, erlang:system_time(millisecond), ID, BTState). + +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{id := ID, children := [ChildID] , properties := #{max_time := MaxTime}} = _BTNode, BTState) -> + StartTime = blackboard:get(start_time, ID, BTState), + {BTStatus, BTState1} = base_node:execute(ChildID, BTState), + case MaxTime > erlang:system_time(millisecond) - StartTime of + true -> + {BTStatus, BTState1}; + false -> + {?BT_FAILURE, BTState} + end; +tick(_BTNode, BTState) -> + {?BT_ERROR, BTState}. + +-spec close(bt_node(), bt_state()) -> bt_state(). +close(#{id := ID} = _BTNode, BTState) -> + blackboard:remove(start_time, ID, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- diff --git a/src/decorator/repeat_until_failure.erl b/src/decorator/repeat_until_failure.erl new file mode 100644 index 0000000..77cd287 --- /dev/null +++ b/src/decorator/repeat_until_failure.erl @@ -0,0 +1,44 @@ +-module(repeat_until_failure). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([open/2, tick/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec open(bt_node(), bt_state()) -> bt_state(). +open(#{id := ID} = _BTNode, BTState) -> + blackboard:set(i, 0, ID, BTState). + +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{id := ID, children := [ChildID], properties := #{max_loop := MaxLoop}} = _BTNode, BTState) -> + I = blackboard:get(i, ID, BTState), + {I1, BTStatus, BTState1} = tick_1(MaxLoop, I, ChildID, ?BT_ERROR, BTState), + BTState2 = blackboard:set(i, I1, ID, BTState1), + {BTStatus, BTState2}; +tick(_BTNode, BTState) -> + {?BT_ERROR, BTState}. + +-spec close(bt_node(), bt_state()) -> bt_state(). +close(#{id := ID} = _BTNode, BTState) -> + blackboard:remove(i, ID, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +tick_1(MaxLoop, I, ChildID, _BTStatus, BTState) when MaxLoop < 0 orelse I < MaxLoop -> + case base_node:execute(ChildID, BTState) of + {?BT_SUCCESS, BTState1} -> + tick_1(MaxLoop, I + 1, ChildID, ?BT_SUCCESS, BTState1); + {BTStatus, BTState1} -> + {I, BTStatus, BTState1} + end; +tick_1(_MaxLoop, I, _ChildID, BTStatus, BTState) -> + {I, BTStatus, BTState}. diff --git a/src/decorator/repeat_until_success.erl b/src/decorator/repeat_until_success.erl new file mode 100644 index 0000000..bbd8cf1 --- /dev/null +++ b/src/decorator/repeat_until_success.erl @@ -0,0 +1,45 @@ +-module(repeat_until_success). + + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([open/2, tick/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec open(bt_node(), bt_state()) -> bt_state(). +open(#{id := ID} = _BTNode, BTState) -> + blackboard:set(i, 0, ID, BTState). + +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{id := ID, children := [ChildID], properties := #{max_loop := MaxLoop}} = _BTNode, BTState) -> + I = blackboard:get(i, ID, BTState), + {I1, BTStatus, BTState1} = tick_1(MaxLoop, I, ChildID, ?BT_ERROR, BTState), + BTState2 = blackboard:set(i, I1, ID, BTState1), + {BTStatus, BTState2}; +tick(_BTNode, BTState) -> + {?BT_ERROR, BTState}. + +-spec close(bt_node(), bt_state()) -> bt_state(). +close(#{id := ID} = _BTNode, BTState) -> + blackboard:remove(i, ID, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +tick_1(MaxLoop, I, ChildID, _BTStatus, BTState) when MaxLoop < 0 orelse I < MaxLoop -> + case base_node:execute(ChildID, BTState) of + {?BT_FAILURE, BTBTState1} -> + tick_1(MaxLoop, I + 1, ChildID, ?BT_FAILURE, BTBTState1); + {BTStatus, BTBTState1} -> + {I, BTStatus, BTBTState1} + end; +tick_1(_MaxLoop, I, _ChildID, BTStatus, BTState) -> + {I, BTStatus, BTState}. diff --git a/src/decorator/repeater.erl b/src/decorator/repeater.erl new file mode 100644 index 0000000..d36dcfd --- /dev/null +++ b/src/decorator/repeater.erl @@ -0,0 +1,44 @@ +-module(repeater). + +%%-------------------------------------------------------------------- +%% include +%%-------------------------------------------------------------------- +-include("behavior3.hrl"). + +%%-------------------------------------------------------------------- +%% export API +%%-------------------------------------------------------------------- +-export([open/2, tick/2, close/2]). + +%%-------------------------------------------------------------------- +%% API functions +%%-------------------------------------------------------------------- +-spec open(bt_node(), bt_state()) -> bt_state(). +open(#{id := ID} = _BTNode, BTState) -> + blackboard:set(i, 0, ID, BTState). + +-spec tick(bt_node(), bt_state()) -> {bt_status(), bt_state()}. +tick(#{id := ID, children := [ChildID], properties := #{max_loop := MaxLoop}} = _BTNode, BTState) -> + I = blackboard:get(i, ID, BTState), + {I1, BTStatus, BTState1} = tick_1(MaxLoop, I, ChildID, ?BT_SUCCESS, BTState), + BTState2 = blackboard:set(i, I1, ID, BTState1), + {BTStatus, BTState2}; +tick(_BTNode, BTState) -> + {?BT_ERROR, BTState}. + +-spec close(bt_node(), bt_state()) -> bt_state(). +close(#{id := ID} = _BTNode, BTState) -> + blackboard:remove(i, ID, BTState). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +tick_1(MaxLoop, I, ChildID, _BTStatus, BTState) when MaxLoop < 0 orelse I < MaxLoop -> + case base_node:execute(ChildID, BTState) of + {BTStatus, BTState1} when BTStatus =:= ?BT_SUCCESS; BTStatus =:= ?BT_FAILURE -> + tick_1(MaxLoop, I + 1, ChildID, BTStatus, BTState1); + {BTStatus, BTState1} -> + {I, BTStatus, BTState1} + end; +tick_1(_MaxLoop, I, _ChildID, BTStatus, BTState) -> + {I, BTStatus, BTState}. \ No newline at end of file diff --git a/src/eBhv3.app.src b/src/eBhv3.app.src new file mode 100644 index 0000000..b6bee20 --- /dev/null +++ b/src/eBhv3.app.src @@ -0,0 +1,10 @@ +{application, eBhv3, + [{description, "An OTP library"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, [kernel, stdlib]}, + {env,[]}, + {modules, []}, + {licenses, ["MIT"]}, + {links, []} + ]}. diff --git a/src/eBhv3.erl b/src/eBhv3.erl new file mode 100644 index 0000000..f6e4544 --- /dev/null +++ b/src/eBhv3.erl @@ -0,0 +1,3 @@ +-module(eBhv3). + +-export([]).