IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> AI行为树BehaviourTree 接入Lua -> 正文阅读

[游戏开发]AI行为树BehaviourTree 接入Lua

目录

一.认识行为树(以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选择节点

选择节点:

  1. 顺序执行,当子节点全失败时返回“失败”,
  2. 出现第一个成功时,结束执行,返回“成功”
  3. 运行中时返回“运行中”

2.次序节点? Sequencer

次序节点:

  1. 按顺序执行所有子分支,子分支全成功时返回“成功”
  2. 出现第一个失败时,结束执行,返回“失败”
  3. 运行中时返回“运行中”

3.条件节点? Condition

条件节点:

  1. 判断条件是否符合
  2. 返回相应结果

4.行为节点Action

行为节点:

  1. 执行对应的行为
  2. 返回结果主要为:成功、失败、运行中
  3. 如果处于返回结果为运行中,那么父节点下一次执行时,会跳过其他节点,从这个节点开始继续执行

二.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();
    }
}

后面再更

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-12-26 22:33:05  更:2021-12-26 22:34:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 20:33:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码