Unity lua行为树实现(可实现rpg挂机自动战斗)

在使用Unity开发手游项目中,用Lua作为热更脚本时,也许有的RPG项目会有连战斗也要求热更,对于角色挂机自动战斗,Unity有行为树插件Behavior Designer可以实现,但不能实现战斗逻辑热更,所以我用Lua对着Behavior Designer重新实现了部分基础功能,这样,使用Lua版的行为树实现挂机自动战斗,就可以热更啦!

前提说明:
1,本文假设读者对树插件Behavior Designer有些了解,因为我是对着它思路来实现的,不了解可以去看一下,这里可能不打算介绍行为树知识。
2,我使用的时ulua的LuaFramework_UGUI来实现的,如果你使用xLua也不影响移植。
3,这当然只是实现比较简单的基础功能,不能像Behavior Designer那样有丰富的配置,但也可以继续拓展呀,如遍历行为树时间间隔为每帧,不服可以改成0.02s的配置。

实现思路始于此图:
Unity lua行为树实现(可实现rpg挂机自动战斗)_第1张图片

行为树启动后,每帧tick一次,检测行为树的Task(行为树的每个节点都是一个Task)。
然后基础Task大致可以分为几大类Composites、Decorator、Action等
Unity lua行为树实现(可实现rpg挂机自动战斗)_第2张图片

然后得出代码结构:
文件名和类(表)名尽量跟Behavior Designer一样。
关于Lua行为树实现基础代码都在 “LuaFramework\Lua\BehaviorTree” 文件夹下
大体代码结构如下:
Unity lua行为树实现(可实现rpg挂机自动战斗)_第3张图片

系不系有点相似。

看实现之前,不如先到过来看,完成了怎么使用,再去了解它的实现。
使用方法,如,我们要完成Behavior Designer中这样的一颗行为树
Unity lua行为树实现(可实现rpg挂机自动战斗)_第4张图片

行为树框架之外需要新建3个lua文件,2个自定义节点xxx.lua文件和一个拼接操作Test.lua文件,最后在游戏入口处Game.lua调用,3个文件即
Unity lua行为树实现(可实现rpg挂机自动战斗)_第5张图片

TestConditional.lua:

TestConditionalTask = BehTree.IConditional:New()
local this = TestConditionalTask
this.name = 'TestConditionalTask'
testt = {}
idnex = 1
--
function this:OnUpdate()
	log('----------TestConditionalTask---------Running')
	log(self:ToString())
	--模拟Behavior Designer IsNullOrEmpty节点
	--IsNullOrEmpty == false
	return BehTree.TaskStatus.Failure
end

ActionLogTask.lua

ActionLogTask = BehTree.IAction:New()
local this = ActionLogTask
this.name = 'ActionLogTask'
-- 模拟Behavior Designer Log节点
function this:OnUpdate()
	log('-----------ActionLogTask Success')
	return BehTree.TaskStatus.Success
end

Test.lua

require 'BehaviorTree/Test/TestConditionalTask'
require 'BehaviorTree/Test/ActionLogTask'

--[[
代码拼接行为树有代码结构顺序要求,
代码顺序也遵从行为树的图示,上到下,从左到右拼接
上层或者本节点的前一个节点完成才能进行下一个
]]
local function BuildTree()
	local root = BehTree.TaskRoot:New()

	--这里直接使用Repeater作为入口并且检测,相当于Entry
	local entry = BehTree.Repeater:New()
	entry.name = '第0个复合节点repeat == Entry '
	--根节点添加layer:1
	root:PushTask(entry)

--------layer:2
	local selector1 = BehTree.Selector:New()
	selector1.name = '第1个复合节点selector == Selector '
	entry:AddChild(selector1)
	
	-----layer3
	local sequence2 = BehTree.Sequence:New()
	sequence2.name = '第2个复合节点sequence == Sequence'
	selector1:AddChild(sequence2)

	--layer:4,并行
	local testConditionalTask = TestConditionalTask:New()
	testConditionalTask.name = '并行第3个叶子节点 == Is Null Or Empty'
	local actionLogTask = ActionLogTask:New()
	actionLogTask.name = '并行第3个叶子节点 == Log'
	--添加
	sequence2:AddChild(testConditionalTask)--child:1
	sequence2:AddChild(actionLogTask)--child:2

	return root
end

return BuildTree()



最后启动游戏时调用,在Game.lua中加入这3行代码,初始化和启动行为树

require 'BehaviorTree/BehaviorTreeManager'
local tree = require 'BehaviorTree/Test/Test'
BehTree.BehaviorTreeManager.RunTree(tree)

再启动游戏就能看到行为树的打印了Log了
Unity lua行为树实现(可实现rpg挂机自动战斗)_第6张图片

最基本的用法就这样完成了!

那,实现代码呢?
关于行为树的实现,从BehaviorTreeManager.lua看起,看到Gmae.lua中启动的方法BehTree.BehaviorTreeManager.RunTree(tree)
BehaviorTreeManager.lua

BehTree={}
require 'BehaviorTree/Base/Enum'
require 'BehaviorTree/Base/StackList'
require 'BehaviorTree/Base/TaskRoot'
require 'BehaviorTree/Base/ITask'
require 'BehaviorTree/Base/IParent'
require 'BehaviorTree/Base/IAction'
require 'BehaviorTree/Base/IComposite'
require 'BehaviorTree/Base/IConditional'
require 'BehaviorTree/Base/IDecorator'
--复合节点()
require 'BehaviorTree/Composite/Selector'
require 'BehaviorTree/Composite/Sequence'
--修饰节点
require 'BehaviorTree/Decorator/Repeater'
require 'BehaviorTree/Decorator/ReturnFailure'
require 'BehaviorTree/Decorator/ReturnSuccess'
require 'BehaviorTree/Decorator/UntilFailure'
require 'BehaviorTree/Decorator/Inverter'
--Action节点
require 'BehaviorTree/Action/Wait'


BehTree.BehaviorTreeManager={}
local this = BehTree.BehaviorTreeManager
function this.Init()
end
--从这里开始启动一颗行为树的入口跟节点
function this.RunTree(enter)
	this.bhTree =enter
	coroutine.start(this.OnUpdate)
end

--重置树下所有Action
function this.ResetTreeActions()
	local treeRoot = this.GetCurTreeRoot()
	treeRoot:ResetAllActionsState()
end

function this.OnUpdate() 
	while true do
		coroutine.step()
		this.UpdateTask()
	end
end
function this.UpdateTask()
	local status = this.bhTree:OnUpdate()
	if status ~= BehTree.TaskStatus.Running then
		table.remove(this.curTrees, key)
	end
	
end

总的核心思想就这样,不停的每帧去遍历自己拼装好的行为树节点,剩下的也就是节点之间的层级等关系的实现。
回到最初说的,每个节点都是一个Task,所以上面看到的Selector.lua、Sequence.lua、IComposite.lua等都是ITask.lua的子类,如此思路,举例Sequence.lua:基类->IComposite.lua:基类->IParent.lua:基类->ITask.lua

BehTree.Sequence = BehTree.IComposite:New()
local this = BehTree.Sequence
--初始默认未激活
this.curReturnStatus = BehTree.TaskStatus.Inactive
this.name = 'Sequence'
function this:OnUpdate()
	if self:HasChildren() == false then
		logError(self.name..'父节点类型没有子节点!!')
		return BehTree.TaskStatus.Failure
	end

	if self.curRunTask == nil then
		--选择(or)节点肯定是去找子节点
		self.curRunTask = self:GetNextChild()
				--如下不该发生
		if self.curRunTask == nil then
			--如果没有子节点
			logError('错误的节点配置!:没有子节点或已越界!!'..self.name..'子节点长度:'..self:GetChildCount()..'   尝试访问:'..self:GetCurChildIndex()+1)
			return BehTree.TaskStatus.Failure
		end
	end
	return self:RunChildByAnd()
end
--and:遇到一个false就中断执行
--序列组合节点:AND逻辑,所有子节点Success才返回Success
function this:RunChildByAnd()

	while self.curRunTask ~= nil do
		self.curReturnStatus = self.curRunTask:OnUpdate() 
		self.curRunTask:ResetTaskStatus()
		--找到false或者running直接返回,就中断执行,这一帧到此结束
		if self.curReturnStatus == BehTree.TaskStatus.Failure then
			--返回Failure说明这次Sequence走完了,重置等下一轮
			self:Reset()
			return BehTree.TaskStatus.Failure
		 elseif self.curReturnStatus == BehTree.TaskStatus.Running then
			return BehTree.TaskStatus.Running
		else
			--没找到false就一直执行下去
			self.curRunTask = self:GetNextChild()
		end
	end

	--找完了所有节点没有false,那么success
	--说明这次Sequence走完了,重置等下一轮
	self.curReturnStatus = BehTree.TaskStatus.Success
	self:Reset()
	return BehTree.TaskStatus.Success
end
--重置
function this:Reset()
	self:ResetChildren()
end

IComposite.lua

--[[
常用于Sequence的第一个节点判断
]]
BehTree.IComposite = BehTree.IParent:New()
local this = BehTree.IComposite
this.taskType = BehTree.TaskType.Composite

IParent.lua

--[[
父任务 Parent Tasks
behavior tree 行为树中的父任务 task 
包括:composite(复合),decorator(修饰符)!
虽然 Monobehaviour 没有类似的 API,但是并不难去理解这些功能:
]]

BehTree.IParent = BehTree.ITask:New({})
local this = BehTree.IParent
--此时this把ITask设为元表的表
--提供共有函数
function this:New(o)
	o = o or {}
	o.curChilIndex = 0
	o.curRunTask = nil
	o.childTasks={}
	--o把BehTree.IParentTask设为元表,
	--而BehTree.IParentTask把ITask设为元表
	--从而保持类的属性独立,不共用
	setmetatable(o, self)
	self.__index = self
	return o
end
--重置当前访问的子节点位置为第一个
function this:ResetChildren()
	self.curRunTask = nil
	self.curChilIndex = 0
end

function this:GetCurChildIndex()
	return self.curChilIndex
end

--对于ReaterTask等只能有一个子节点的
function this:GetOnlyOneChild()
	if self:GetChildCount() ~= 1 then
		logError('---------'..self.name..'应该有且只有一个子节点!but:childCount:'..self:GetChildCount())
		return nil
	end
	return self.childTasks[1]
end
--添加子节点有顺序要求
function this:AddChild(task)
	log('------------------'..self.name..'  添加子节点 : '..task.name)
	if task == nil then
		logError('---------------------add task is nil !!')
		return
	end
	local index = #self.childTasks+1
	task.index = index
	task.layer = self.layer + 1
	task.parent = self
	task.root = self.root
	self.childTasks[index] = task
	self.root:AddGlobalTask(task.tag, task)
	return self
end
function this:ClearChildTasks()
	self.curIndex = 0
	self.childTasks = nil
	self.childTasks = {}
end
function this:HasChildren()
	if #self.childTasks <= 0 then
		return false
	else
		return true
	end
end
function this:GetChildCount()
	return #self.childTasks
end
function this:GetNextChild()
	if #self.childTasks >= (self.curChilIndex+1) then
		--指向當前正執行的
		self.curChilIndex = self.curChilIndex + 1
		local nextChild = self.childTasks[self.curChilIndex]
		return nextChild
	else
		return nil 
	end
end
--获取前一个子节点,不移动指针
function this:GetCurPrivousTask()
	if self.curChilIndex <=1 then
		logError(self.name..' GetCurPrivousTask : 已经是最前的Task或childtask为空')
		return nil
	else
		return self.childTasks[self.curChilIndex-1]
	end
end
--获取下一个子节点,不移动指针
function this:GetCurNextTask()
	if self.curChilIndex >= #self.childTasks then
		--logError(self.name..' GetCurNextTask : 已经是最后的Task或childtask为空')
		return nil
	else
		return self.childTasks[self.curChilIndex+1]
	end
end

ITask.lua

--[[
所有task基础
]]
BehTree.ITask={
	--不需要主动设置参数
	--由树结构的机制驱动的参数,
	taskStatus = BehTree.TaskStatus.Running,
	curReturnStatus = BehTree.TaskStatus.Inactive,
	taskType = BehTree.TaskType.UnKnow,
	root = nil,
	index = 1,
	parent = nil,
	layer = 1,

	--主动设置参数
	name = '暂未设置名称',
	tag = 'UnTag',--用于搜索
	desc = '暂无描述'
}
local this = BehTree.ITask
function this:New(o)
	o = o or {}
	setmetatable(o, self)
	self.__index = self
	return o
end

function this:ResetTaskStatus()
end
--获取同一层layer的上一个节点
function this:GetPriviousTask()
	if self.parent == nil then
		logError(self.name..' 找不到父节点 try call GetPriviousTask')
		return nil
	end
	if self.layer <= 1 then
		logError(self.name..' GetPriviousTask已经是最顶层,单独Task')
		return nil
	end
	local priviousTask = self.parent:GetCurPrivousTask()
	return priviousTask
end
--获取同一层layer下一个task
function this:GetNextTask()
	if self.parent == nil then
		logError(self.name..' 找不到父节点 try call GetNextTask')
		return nil
	end
	if self.layer <= 1 then
		logError(self.name..' GetNextTask已经是最顶层,单独Task')
		return nil
	end
	local nextTask = self.parent:GetCurNextTask()
	return nextTask
end

function this:ToString()
	local name = '名称 : '..self.name..'\n'
	local layer = '所处层次 :'..self.layer..'\n'
	local parent = '父节点 : '..self.parent.name..'\n'
	local index = '作为子节点顺序 : '..self.index..'\n'
	local desc = '描述 : '..self.desc..'\n'
	local status = 'UnKnow'
	if self.curReturnStatus == 1 then
		status = 'Inactive'
	elseif self.curReturnStatus == 2 then
		status = 'Failure'
	elseif self.curReturnStatus == 3 then
		status = 'Success'
	elseif self.curReturnStatus == 4 then
		status = 'Running'
	end
	local curReturnStatus = '运行返回结果:'..status..'\n'
	return name..desc..layer..parent..index..curReturnStatus
end

关于Sequence部分差不多这样,其他代码略多我就不贴完了,我传上去,可以下载来看看,
但也只有LuaFramework中的LuaFramework\Lua\BehaviorTree部分代码,而不是整个ulua工程,
记住:调用时记得在Game.lua等游戏启动入口写上这3行来启动行为树。

require 'BehaviorTree/BehaviorTreeManager'
local tree = require 'BehaviorTree/Test/Test'
BehTree.BehaviorTreeManager.RunTree(tree)

下载地址:
https://github.com/HengyuanLee/LuaBehaviorTree


你可能感兴趣的:(Unity开发,编程语言类,Lua行为树)