@ Ashaman73
that's a very interesting read cool!,
could I see your implementation of that behavior tree, it would help immensely with understanding it.
Sry, but I like to keep my source closed. Nevertheless, here is a compact overview/example:
The tree is build up of nodes like this one:
node =
{
-- returns true, if this node is ready to be executed
accept = function(...),
-- only called once
enter = function(...),
-- return true, if finished and succeed, false on failure and nil if it is still processing
update = function(...),
-- only called once
exit = function(...),
child_nodes = {...}
}
You always hold one valid current path, but you always evaluate the tree depending on the path. A simple example (pseudo-code quality
)
tree =
{
flee =
{
-- accept this node ?
accept = function(_node , _obj)
return _obj.health<50
end,
-- enter this node, add to path
enter = function(_node, _obj, _path, _depth)
-- add this node to the path
_path.nodes[_depth] = _node
-- do some other stuff
_obj.startSound("scream")
end,
-- update this node
update = function(_node, _obj, _path,depth)
local result = nil
-- priority behavior
for i,child_node in pairs(_node.child_nodes) do
-- same node, just execute, dont call accept again
if child_node==_path.nodes[depth+1] then
result = child_node.update(child_node, _obj, _path,depth+1)
-- done
break
else
-- node on path has not been visited yet, therefor this node seems to have a higher priority
-- check if a node with a higher priority needs to be executed
if child_node.accept(child_node,_obj)==true then
-- ok, exit all nodes
_path.nodes[depth+1].exit(__path.nodes[depth+1], _obj, _path, _depth+1)
-- enter new node
child_node.enter(child_node, _obj, _path, _depth+1)
-- done, node will be updated next frame
break
end
end
end
-- handle result, nil=>still running, true/false=succeed/failed
if result~=nil then
-- call exit
_node.exit(_node, _obj, _path, _depth)
end
return result
end
-- exit this node, remove from path
exit = function(_node, _obj, _path, _depth)
-- ensure to clear path behind depth !
if _path.nodes[_depth+1]~=nil then
_path.nodes[_depth+1].exit(_path.nodes[_depth+1], _obj, _path, _depth+1)
end
-- remove this node from the path
_path[_depth] = nil
-- stop screaming !!
_obj.stopSound()
end,
-- child nodes, top nodes have higher priority
child_nodes =
{
run_to_ally = { accept= .. ally near => run to ally.. },
run_to_shelter = {accept= .. shelter near => run to shelter..},
run_in_random_direction = { accept = ..always ..},
}
}
}
This way you only need some simple template nodes like
- priority (see above)
- sequence (execute all nodes in a sequence)
- choice (choose a child node depending on some stimuli/properties)
- loop (loop through all nodes)
Some special nodes
- scan (scan the environment for other objects, eg enemies)
- move (calculate path and move to target)
- action (interact with environment/objects)
Important is, that you only updates a single node once per frame, thought you can update more than one node on the path.
BHT helps a lot to manage more complex behavior, my code alone has several thousands lines of code only for BHT behavior and is purely data driven to support multi-core AI (which is really hard with Lua).
I hope it gives you some insight, happy coding