├── BehaviorTree.lua ├── LICENSE └── README.md /BehaviorTree.lua: -------------------------------------------------------------------------------- 1 | -- 行为树 2 | local BehaviorTree = class("BehaviorTree") 3 | 4 | local EVALUATE_TYPE = { 5 | SELECTOR = "SEL", 6 | PARALLEL = "PAR", 7 | SEQUENCE = "SEQ", 8 | CONDITIONAL = "CON", 9 | BEHAVIOR = "ACT", 10 | } 11 | 12 | 13 | local table_insert = table.insert 14 | local table_remove = table.remove 15 | local string_upper = string.upper 16 | local string_match = string.match 17 | local string_rep = string.rep 18 | local string_len = string.len 19 | local string_find = string.find 20 | local string_sub = string.sub 21 | local string_format = string.format 22 | local ipairs = ipairs 23 | local type = type 24 | local tostring = tostring 25 | local string_split = string.split or function (input, delimiter) 26 | input = tostring(input) 27 | delimiter = tostring(delimiter) 28 | if (delimiter=='') then return false end 29 | local pos,arr = 0, {} 30 | -- for each divider found 31 | for st,sp in function() return string_find(input, delimiter, pos, true) end do 32 | table_insert(arr, string_sub(input, pos, st - 1)) 33 | pos = sp + 1 34 | end 35 | table_insert(arr, string_sub(input, pos)) 36 | return arr 37 | end 38 | local util_filter = function(fn, datas) 39 | local result = {} 40 | for i, data in ipairs(datas) do 41 | if fn(data) then 42 | result[#result + 1] = data 43 | end 44 | end 45 | return result 46 | end 47 | local util_find = function ( fn, datas ) 48 | for i, data in ipairs(datas) do 49 | if fn(data) then return data end 50 | end 51 | end 52 | -- 前期调试用,确定后改为nil或false 53 | local debug = false 54 | 55 | --[[ 56 | 57 | 把字符串格式的AI行为树转换成程序使用的table格式 58 | 59 | @param aistring: string 字符串格式的AI行为树,参看e.g. 60 | 61 | ** e.g. ** 62 | SEL:AI 63 | SEQ:去球场打球 64 | CON:不是情人节:isNotValentinesDay 65 | ACT:去球场:gotoCourt 66 | ACT:打球:playBall 67 | SEL:约会 68 | SEL:买花 69 | SEQ:回家拿钱 70 | CON:女友没花:hasnotFlowerGF 71 | CON:自己没花没带够钱:hasnotFlowerSelf:100 72 | ACT:走回家:goHome 73 | ACT:拿钱:fetchMoney 74 | SEQ:去花店买花 75 | CON:女友没花:hasnotFlowerGF 76 | CON:自己没花:hasnotFlowerSelf 77 | ACT:走去花店:gotoFlowerShop 78 | ACT:买花:buyFlower 79 | SEQ:见女友 80 | ACT:去找女友:gotoGF 81 | CON:女友还在:isHereGF 82 | CON:女友没花:hasnotFlowerGF 83 | ACT:送花:giveFlower 84 | --]] 85 | local function createAItable_(aistring) 86 | local aiarray = string_split(aistring, "\n") 87 | local defaultSpaceLen = nil 88 | local regString = "(%s*)(%S*)" 89 | local result = {} 90 | local stack = {} 91 | table_insert(stack, result) 92 | result.level = 1 93 | local function perpareInfo(singleString) 94 | local space, text = string_match(singleString, regString) 95 | local level = 1 96 | local spaceLen = string_len(space) 97 | if spaceLen > 0 then 98 | if not defaultSpaceLen then defaultSpaceLen = spaceLen end 99 | end 100 | if defaultSpaceLen then 101 | level = spaceLen / defaultSpaceLen + 1 102 | end 103 | local infos = string_split(text, ":") 104 | return level, string_upper(infos[1]), infos[2], infos[3], infos[4] 105 | end 106 | local function perpareAITable(level , bttype, name, fun, param) 107 | local cur = stack[#stack] 108 | if level > 1 and cur.level then 109 | while cur.level >= level do 110 | table_remove(stack) 111 | cur = stack[#stack] 112 | end 113 | end 114 | local ischildren = false 115 | if #stack > 1 then 116 | local curParent = stack[#stack - 1] 117 | if curParent.children and curParent.children == cur then 118 | ischildren = true 119 | end 120 | end 121 | if not ischildren then 122 | cur.level = level 123 | if string_find(bttype, "_") then 124 | local bttypeTable = string_split(bttype, "_") 125 | cur.bttype = bttypeTable[1] 126 | cur.bttypeParam = bttypeTable[2] 127 | else 128 | cur.bttype = bttype 129 | end 130 | if debug then 131 | cur.name = name 132 | end 133 | cur.fun = fun 134 | cur.param = param 135 | local tempBttype = cur.bttype 136 | if tempBttype == EVALUATE_TYPE.SELECTOR 137 | or tempBttype == EVALUATE_TYPE.SEQUENCE 138 | or tempBttype == EVALUATE_TYPE.PARALLEL 139 | then 140 | cur.children = {} 141 | cur.children.level = level 142 | table_insert(stack, cur.children) 143 | else 144 | table_remove(stack) 145 | end 146 | else 147 | local newChild = {} 148 | table_insert(cur, newChild) 149 | table_insert(stack, newChild) 150 | perpareAITable(level , bttype, name, fun, param) 151 | end 152 | end 153 | for i, v in ipairs(aiarray) do 154 | if v ~= "" then 155 | local level , bttype, name, fun, param = perpareInfo(v) 156 | perpareAITable(level , bttype, name, fun, param) 157 | end 158 | end 159 | return result 160 | end 161 | 162 | function BehaviorTree:ctor( fulltree ) 163 | if type(fulltree) == "string" then 164 | self.fulltree = createAItable_(fulltree) 165 | else 166 | self.fulltree = fulltree 167 | end 168 | end 169 | 170 | function BehaviorTree:behave( entity ) 171 | self:behave_(self.fulltree, entity) 172 | end 173 | 174 | local function logState_( tree, entity ) 175 | -- if true and entity.params_.debug then 176 | print(string_format("%d.%s:%s:%s", tree.level, string_rep(" ", tree.level), tree.bttype, tree.name)) 177 | -- end 178 | end 179 | 180 | function BehaviorTree:behave_( tree, entity ) 181 | local behaveType = tree.bttype 182 | if behaveType == EVALUATE_TYPE.CONDITIONAL then 183 | -- 处理条件判断 CON 184 | return self:condition_(tree, entity) 185 | elseif behaveType == EVALUATE_TYPE.BEHAVIOR then 186 | -- 处理动作 ACT 187 | return self:action_(tree, entity) 188 | elseif behaveType == EVALUATE_TYPE.SELECTOR then 189 | -- 处理选择执行 SEL 190 | return self:select_(tree, entity) 191 | elseif behaveType == EVALUATE_TYPE.SEQUENCE then 192 | -- 处理顺序执行 SEQ 193 | return self:sequence_(tree, entity) 194 | elseif behaveType == EVALUATE_TYPE.PARALLEL then 195 | -- 处理并列执行 PAR 196 | return self:parallel_(tree, entity) 197 | end 198 | return false 199 | end 200 | 201 | function BehaviorTree:select_( tree, entity ) 202 | local children = tree.children 203 | local result = util_find(function ( child, index ) 204 | return self:behave_(child, entity) 205 | end, children) 206 | return result ~= nil 207 | end 208 | 209 | function BehaviorTree:sequence_( tree, entity ) 210 | local children = tree.children 211 | local result = util_find(function ( child, index ) 212 | return not self:behave_(child, entity) 213 | end, children) 214 | return result == nil 215 | end 216 | 217 | function BehaviorTree:parallel_( tree, entity ) 218 | local children = tree.children 219 | local filterResult = util_filter(function ( child, index ) 220 | return self:behave_(child, entity) 221 | end, children) 222 | local trueCount = #filterResult 223 | local allCount = #children 224 | local bttypeParam = tree.bttypeParam or "ALL" 225 | local result = false 226 | if "ALL" == bttypeParam then 227 | -- Parallel Succeed On All Node: 所有True才返回True,否则返回False。 228 | result = trueCount == allCount 229 | elseif "TRUE" == bttypeParam then 230 | result = true 231 | elseif "FALSE" == bttypeParam then 232 | result = false 233 | else 234 | -- Parallel Hybird Node: 指定数量的Child Node返回True或False后才决定结果 235 | local count = tonumber(bttypeParam) 236 | if count then 237 | result = trueCount >= count 238 | end 239 | end 240 | return result 241 | end 242 | 243 | function BehaviorTree:condition_( tree, entity ) 244 | return self:do_(tree, entity, "condition") 245 | end 246 | 247 | function BehaviorTree:action_( tree, entity ) 248 | return self:do_(tree, entity, "action") ~= false 249 | end 250 | 251 | function BehaviorTree:do_( tree, entity, typeStr ) 252 | if debug then 253 | logState_( tree, entity ) 254 | local fun = tree.fun 255 | if not type(entity[fun]) == "function" then 256 | error("BehaviorTree %s_() can't find a function [%s(%s)] in entity" 257 | , typeStr, fun, tree.param or "") 258 | end 259 | end 260 | return entity[tree.fun](entity, tree.param) 261 | end 262 | 263 | return BehaviorTree -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Chengfu Bao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Behavior Tree 2 | ```txt 3 | 纯lua实现的一个简单的行为树 4 | 编写AI形式如下,可用思维导图制作后写个脚本导出 5 | SEL:AI 6 | SEQ:去球场打球 7 | CON:不是情人节:isNotValentinesDay 8 | ACT:去球场:gotoCourt 9 | ACT:打球:playBall 10 | SEL:约会 11 | SEL:买花 12 | SEQ:回家拿钱 13 | CON:女友没花:hasnotFlowerGF 14 | CON:自己没花没带够钱:hasnotFlowerSelf:100 15 | ACT:走回家:goHome 16 | ACT:拿钱:fetchMoney 17 | SEQ:去花店买花 18 | CON:女友没花:hasnotFlowerGF 19 | CON:自己没花:hasnotFlowerSelf 20 | ACT:走去花店:gotoFlowerShop 21 | ACT:买花:buyFlower 22 | SEQ:见女友 23 | ACT:去找女友:gotoGF 24 | CON:女友还在:isHereGF 25 | CON:女友没花:hasnotFlowerGF 26 | ACT:送花:giveFlower 27 | 28 | 使用方式如下,其中entity为人物model对象,上面定义的方法实现在model中 29 | local tree = BehaviorTree.new(ai_string) 30 | tree:behave(entity) 31 | 忘了说了,这是要每帧调用的 32 | ``` 33 | 34 | 35 | --------------------------------------------------------------------------------