目录
一.认识行为树(以NodeCanvas)
????????1. Selector选择节点
? ? ? ? 2.Sequencer次序节点
? ? ? ? 3.Action行为节点
? ? ? ? 4.Conditions条件节点
二.NodeCanvas扩展节点
三.BehaviourTree接入lua的意义
四.Lua版BehaviourTree的实现
五.AI行为树的存储方式
六.Unity编辑器模式下交互
一.认识行为树(以NodeCanvas)
?官方插件地址NodeCanvas | Visual Scripting | Unity Asset Store
官方的行为树插件,我这里因为是仿照它写的,就只介绍四种最重要的节点
?1. Selector选择节点
选择节点:
- 顺序执行,当子节点全失败时返回“失败”,
- 出现第一个成功时,结束执行,返回“成功”
- 运行中时返回“运行中”
2.次序节点? Sequencer
次序节点:
- 按顺序执行所有子分支,子分支全成功时返回“成功”
- 出现第一个失败时,结束执行,返回“失败”
- 运行中时返回“运行中”
3.条件节点? Condition
条件节点:
- 判断条件是否符合
- 返回相应结果
4.行为节点Action
行为节点:
- 执行对应的行为
- 返回结果主要为:成功、失败、运行中
- 如果处于返回结果为运行中,那么父节点下一次执行时,会跳过其他节点,从这个节点开始继续执行
二.NodeCanvas扩展节点
?仿照插件的节点写就行了
一个完整的流程大概是这样的
?具体怎么使用这个插件我就不过多描述了,贴个大佬的链接
Unity高级开发(九)-AI插件:NodeCanvas - 简书
三.BehaviourTree接入lua的意义
? ? ? ?项目要求,以便于维护,c#写的话不免会出问题,lua做好框架了,出问题也能热更出去
四.Lua版BehaviourTree的实现
? ? ? ? ?一个bt的基础流程是 Select -------> Sequencer ------------->Condition------------>Action
? ? ? ? 或select -------> Sequencer ------------->Action
? ? ? ? 每种节点四种状态Init(初始)、Pending(执行中)、Finish(成功)、Fail(失败)
? ? ? ? 我这里设置子类的方法使用的是拷贝方法
????????我的文章lua模拟类_qq_40314347的博客-CSDN博客
1.BTNode类
local BattleDefine = require "Common.BattleLogic.BattleDefine" --就是个枚举,lua里面是table
BTNode = {}
BTNode.__index = BTNode
--class method
function BTNode:DefineSubClass(name) -- 通过拷贝的方式模拟类的继承
local newClassTable = {}
setmetatable(newClassTable, self)
newClassTable.base = self
local NewFunc = function(...)
local instance = setmetatable({}, newClassTable)
instance:ctor(...)
--warning("=========================== %s" , var2str(instance))
return instance
end
if self.New then
newClassTable.New = NewFunc
end
if self.__New then
newClassTable.__New = NewFunc
end
return newClassTable
end
--btnode的属性
function BTNode:ctor()
rawset(self, "state", BattleDefine.BTNodeState.Init)
rawset(self, "idx", 0)
rawset(self, "parent", nil)
rawset(self, "_isCanAddSubNode", false)
rawset(self, "tips", nil)
if UNITY_EDITOR then
getmetatable(self).__newindex = function(t, k ,v) warning("基类. 控制不能新建属性 %s %s" , k , debug.traceback()) end
end
end
--用一个Table存下btnode的节点
function _M:Signin(aiMap)
aiMap[self.idx] = self
end
--返回下标
function _M:GetTips()
return self.tips
end
--返回能否添加子节点
function _M:CanAddSubNode()
return self._isCanAddSubNode
end
--重置btnode节点为init
function _M:Reset()
self.state = BattleDefine.BTNodeState.Init
end
--重置btnode节点为init
function _M:SetState(aiCtrl, state)
self.state = state
aiCtrl:EnqueuePendingChain(self.idx, self.state)
end
--节点进入执行中
function _M:Execute(aiCtrl)
self:SetState(aiCtrl, BattleDefine.BTNodeState.Pending)
end
--节点结束
function _M:Finish(aiCtrl, state)
self:SetState(aiCtrl, state)
if not self.parent then
return
end
self.parent:TryMoveNext(aiCtrl, self, state)
end
--pending中的检查
function _M:PendingCheck(aiCtrl)
--warning("PendingCheck:" .. self.idx)
end
---------- 返回state
function _M:GetState()
return self.state
end
--刷新节点下标加1
function _M:RefreshIndex(index)
rawset(self, "idx", index)
return index + 1
end
--返回节点下标
function _M:GetIdx()
return self.idx
end
? 2.BTSelectorNode
MyBTNode = require("MyBTNode")
local _M = MyBTNode:DefineSubClass("MyBTSelectorNode")
function _M:ctor()
_M.base.ctor(self)
rawset(self, "childs", {})
rawset(self, "currChildIdx", 0)
rawset(self, "_isCanAddSubNode", true)
end
---------------- override
--在aimap中记录select节点
function _M:Signin(aiMap)
_M.base.Signin(self, aiMap)
local count = #self.childs
for _, child in ipairs(self.childs) do
child:Signin(aiMap)
end
end
--重置
function _M:Reset()
_M.base.Reset(self)
local count = #self.childs
for i = count, 1, -1 do
local child = self.childs[i]
child:Reset()
end
end
--进入执行
function _M:Execute(aiCtrl)
_M.base.Execute(self, aiCtrl)
self.currChildIdx = 0
self:MoveNext(aiCtrl)
end
--进入子类
function _M:PendingCheck(aiCtrl)
for _, child in ipairs(self.childs) do
if child.state == BattleDefine.BTNodeState.Pending then
child:PendingCheck(aiCtrl)
break
end
end
end
-----------------------------------------------------------
--------------------new func--------------
--在父类btnode中Fnish中self.parent:TryMoveNext(aiCtrl, self, state)中调用,代表是进入父类中下个和自己同级的兄弟节点
function _M:TryMoveNext(aiCtrl, curChildNode, state)
local pass = self:CheckContinue(aiCtrl, state)
if not pass then
return
end
if not aiCtrl:IsEnable() then
self:Finish(aiCtrl, BattleDefine.BTNodeState.Fail)
return
end
local index = table.find(self.childs, curChildNode)
self.currChildIdx = index
self:MoveNext(aiCtrl)
end
--该节点的子节点执行
function _M:ChildMoveNext(aiCtrl)
if self.currChildIdx < #self.childs then
local nextIdx = self.currChildIdx + 1
local nextChild = self.childs[nextIdx]
self.currChildIdx = nextIdx
nextChild:Execute(aiCtrl)
else
self:MoveEnd(aiCtrl)
end
end
--该节点返回失败,调用finish,中存在这个方法self.parent:TryMoveNext(aiCtrl, self, state)
function _M:MoveEnd(aiCtrl)
self:Finish(aiCtrl, BattleDefine.BTNodeState.Fail)
end
function _M:CheckContinue(aiCtrl, state)
if state == BattleDefine.BTNodeState.Finish then
self:Finish(aiCtrl, state)
return false
end
return true
end
--添加子节点
function _M:AddChild(node)
if table.find(self.childs, node) ~= -1 then
return
end
if node.parent then
node.parent:RemoveChild(node)
end
table.insert(self.childs, node)
rawset(node, "parent", self)
--node.parent = self
self.currChildIdx = 0
end
--添加子节点按index
function _M:AddChildAt(node, index)
if table.find(self.childs, node) ~= -1 then
return
end
if node.parent then
node.parent:RemoveChild(node)
end
index = index + 1 -- adjust lua index
assert(index > 0, "lua index must bigger than 0 ")
table.insert(self.childs, index, node)
rawset(node, "parent", self)
self.currChildIdx = 0
end
--移除子节点
function _M:RemoveChild(node)
local index = table.find(self.childs, node)
if index == -1 then
warning("RemoveChild")
return
end
rawset(node, "parent", nil)
table.remove(self.childs, index)
end
---------- for editor
--得到子节点的数量
function _M:GetChildCount()
return #self.childs
end
function _M:GetChildAt(index)
return self.childs[index + 1]
end
function _M:RefreshIndex(index)
local nextIdx = _M.base.RefreshIndex(self, index)
for _, child in ipairs(self.childs) do
nextIdx = child:RefreshIndex(nextIdx)
end
return nextIdx
end
return _M
3.BTSequenceNode
MyBTSelectorNode = require("MyBTSelectorNode")
local MyBTSequenceNode = MyBTSelectorNode:DefineSubClass("MyBTSequenceNode")
--次序节点和select节点区别就是,监测条件,只需要一个返回Fail就select节点就返回失败
function _M:CheckContinue(aiCtrl, state)
if state == BattleDefine.BTNodeState.Fail then
self:Finish(aiCtrl, state)
return false
end
return true
end
--次序节点和select节点区别就是,所以节点执行完
function _M:MoveEnd(aiCtrl)
self:Finish(aiCtrl, BattleDefine.BTNodeState.Finish)
end
4.actionNode
MyBTNode = require("MyBTNode")
local _M = MyBTNode:DefineSubClass("MyActionNode")
--ActionNode must implement this function
function _M:Execute(aiCtrl)
_M.base.Execute(self, aiCtrl)
end
return _M
5.MyBTConditionNode
MyBTNode = require("MyBTNode")
local _M = MyBTNode:DefineSubClass("MyBTConditionNode")
function _M:ctor()
_M.base.ctor(self)
rawset(self, "isTrue", true) --TODO need be param
end
function _M:Execute(aiCtrl)
_M.base.Execute(self, aiCtrl)
local condPass = self:Condition(aiCtrl)
if condPass == self.isTrue then
self:Finish(aiCtrl, BattleDefine.BTNodeState.Finish)
else
self:Finish(aiCtrl, BattleDefine.BTNodeState.Fail)
end
end
function _M:Condition(aiCtrl)
return false
end
return _M
然后是一个AI控制的类
function _M:ApplyAI(luaTab)
self:TryClearLastAI()
self._logicAIRootNode = luaTab
self._logicAIMap = {}
self._pendingQueue = {}
if self._logicAIRootNode == nil then
self._enable = false
return
end
self._logicAIRootNode:Signin(self._logicAIMap)
self._enable = true
return self._logicAIRootNode
end
function _M:Update(dt)
if not self._logicAIRootNode then
return
end
if not self._enable then
return
end
local isHandle = self:TryUpdatePendingNode()
if isHandle then
return
end
self._logicAIRootNode:Execute(self)
end
function _M:TryUpdatePendingNode()
local isHandle = false
repeat
local lastPendingNodeIdx = self:GetLastPendingNode()
if lastPendingNodeIdx == -1 then
break
end
local currNode = self._logicAIMap[lastPendingNodeIdx]
currNode:PendingCheck(self)
isHandle = true
until true
return isHandle
end
return _M
反正思路大概就是这样,自己琢磨一下吧.
五.AI行为树的存储方式
?六.Unity编辑器模式下交互
? ? ? ? 一个AICompent类,tolua打成wrap类,在lua中调用这个方法的init并且初始化aicontrol
/* just for editor */
using UnityEngine;
using LuaInterface;
public class AIComponent : MonoBehaviour
{
protected LuaTable _aiControl;
protected LuaFunction _funcApplyAI;
public void Init(LuaTable aiControl)
{
if (Clear())
Logger.Error("AIComponent has been inited");
_aiControl = aiControl;
_funcApplyAI = _aiControl.GetLuaFunction("ApplyAI");
}
public void ApplyAI(LuaTable luaTab)
{
_funcApplyAI.Call(_aiControl, luaTab);
}
public LuaTable GetAIRootNode()
{
LuaTable rootNode = _aiControl.GetTable<LuaTable>("_logicAIRootNode");
return rootNode;
}
bool Clear()
{
bool inited = false;
if (_funcApplyAI != null)
{
_funcApplyAI.Dispose();
_funcApplyAI = null;
inited = true;
}
if (_aiControl != null)
{
_aiControl.Dispose();
_aiControl = null;
inited = true;
}
return inited;
}
public void OnDestroy()
{
Clear();
}
}
后面再更
|