@ -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 |
@ -0,0 +1,21 @@ | |||
The MIT License | |||
Copyright (c) 2019-2020 alisdair sullivan <alisdairsullivan@yahoo.ca> | |||
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. |
@ -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) |
@ -0,0 +1,21 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,20 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,20 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,20 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,20 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,60 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,45 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. | |||
@ -0,0 +1,26 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,22 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,21 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,20 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,21 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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)}}. |
@ -0,0 +1,21 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,21 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,22 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}. |
@ -0,0 +1,44 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,24 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,23 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,23 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,24 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,24 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,24 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,24 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,24 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,28 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,24 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -0,0 +1,32 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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). |
@ -0,0 +1,59 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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}). |
@ -0,0 +1,127 @@ | |||
%%%------------------------------------------------------------------- | |||
%%% @author DY | |||
%%% @copyright (C) 2020, <COMPANY> | |||
%%% @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. |
@ -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. |
@ -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 | |||
]}. |
@ -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 | |||
%%-------------------------------------------------------------------- |
@ -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 | |||
%%-------------------------------------------------------------------- |
@ -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 | |||
%%-------------------------------------------------------------------- |
@ -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 | |||
%%-------------------------------------------------------------------- |
@ -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 | |||
%%-------------------------------------------------------------------- |
@ -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}. |
@ -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}. |
@ -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}. |
@ -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}. |
@ -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. |
@ -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(<<C/utf8, Other/binary>>) when $A =< C, C =< $Z -> | |||
Other1 = name_convert_1(Other), | |||
<<"_"/utf8, (C + 32)/utf8, Other1/binary>>; | |||
name_convert_1(<<C/utf8, Other/binary>>) -> | |||
Other1 = name_convert_1(Other), | |||
<<C/utf8, Other1/binary>>; | |||
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. |
@ -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). |
@ -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 | |||
%%-------------------------------------------------------------------- |
@ -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 | |||
%%-------------------------------------------------------------------- |
@ -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 | |||
%%-------------------------------------------------------------------- |
@ -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}. |
@ -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}. |
@ -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}. |
@ -0,0 +1,10 @@ | |||
{application, eBhv3, | |||
[{description, "An OTP library"}, | |||
{vsn, "0.1.0"}, | |||
{registered, []}, | |||
{applications, [kernel, stdlib]}, | |||
{env,[]}, | |||
{modules, []}, | |||
{licenses, ["MIT"]}, | |||
{links, []} | |||
]}. |
@ -0,0 +1,3 @@ | |||
-module(eBhv3). | |||
-export([]). |