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

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

实现思路始于此图:

行为树启动后,每帧tick一次,检测行为树的Task(行为树的每个节点都是一个Task)。
然后基础Task大致可以分为几大类Composites、Decorator、Action等

然后得出代码结构:
文件名和类(表)名尽量跟Behavior Designer一样。
关于Lua行为树实现基础代码都在 “LuaFramework\Lua\BehaviorTree” 文件夹下
大体代码结构如下:

系不系有点相似。

看实现之前,不如先到过来看,完成了怎么使用,再去了解它的实现。
使用方法,如,我们要完成Behavior Designer中这样的一颗行为树

行为树框架之外需要新建3个lua文件,2个自定义节点xxx.lua文件和一个拼接操作Test.lua文件,最后在游戏入口处Game.lua调用,3个文件即

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 == falsereturn 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作为入口并且检测,相当于Entrylocal entry = BehTree.Repeater:New()entry.name = '第0个复合节点repeat == Entry '--根节点添加layer:1root:PushTask(entry)--------layer:2local selector1 = BehTree.Selector:New()selector1.name = '第1个复合节点selector == Selector 'entry:AddChild(selector1)-----layer3local 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:1sequence2:AddChild(actionLogTask)--child:2return root
endreturn BuildTree()

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

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

再启动游戏就能看到行为树的打印了Log了

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

那,实现代码呢?
关于行为树的实现,从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 =entercoroutine.start(this.OnUpdate)
end--重置树下所有Action
function this.ResetTreeActions()local treeRoot = this.GetCurTreeRoot()treeRoot:ResetAllActionsState()
endfunction this.OnUpdate() while true docoroutine.step()this.UpdateTask()end
end
function this.UpdateTask()local status = this.bhTree:OnUpdate()if status ~= BehTree.TaskStatus.Running thentable.remove(this.curTrees, key)endend

总的核心思想就这样,不停的每帧去遍历自己拼装好的行为树节点,剩下的也就是节点之间的层级等关系的实现。
回到最初说的,每个节点都是一个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 thenlogError(self.name..'父节点类型没有子节点!!')return BehTree.TaskStatus.Failureendif 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.Failureendendreturn self:RunChildByAnd()
end
--and:遇到一个false就中断执行
--序列组合节点:AND逻辑,所有子节点Success才返回Success
function this:RunChildByAnd()while self.curRunTask ~= nil doself.curReturnStatus = self.curRunTask:OnUpdate() self.curRunTask:ResetTaskStatus()--找到false或者running直接返回,就中断执行,这一帧到此结束if self.curReturnStatus == BehTree.TaskStatus.Failure then--返回Failure说明这次Sequence走完了,重置等下一轮self:Reset()return BehTree.TaskStatus.Failureelseif self.curReturnStatus == BehTree.TaskStatus.Running thenreturn BehTree.TaskStatus.Runningelse--没找到false就一直执行下去self.curRunTask = self:GetNextChild()endend--找完了所有节点没有false,那么success--说明这次Sequence走完了,重置等下一轮self.curReturnStatus = BehTree.TaskStatus.Successself: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 = 0o.curRunTask = nilo.childTasks={}--o把BehTree.IParentTask设为元表,--而BehTree.IParentTask把ITask设为元表--从而保持类的属性独立,不共用setmetatable(o, self)self.__index = selfreturn o
end
--重置当前访问的子节点位置为第一个
function this:ResetChildren()self.curRunTask = nilself.curChilIndex = 0
endfunction this:GetCurChildIndex()return self.curChilIndex
end--对于ReaterTask等只能有一个子节点的
function this:GetOnlyOneChild()if self:GetChildCount() ~= 1 thenlogError('---------'..self.name..'应该有且只有一个子节点!but:childCount:'..self:GetChildCount())return nilendreturn self.childTasks[1]
end
--添加子节点有顺序要求
function this:AddChild(task)log('------------------'..self.name..'  添加子节点 : '..task.name)if task == nil thenlogError('---------------------add task is nil !!')returnendlocal index = #self.childTasks+1task.index = indextask.layer = self.layer + 1task.parent = selftask.root = self.rootself.childTasks[index] = taskself.root:AddGlobalTask(task.tag, task)return self
end
function this:ClearChildTasks()self.curIndex = 0self.childTasks = nilself.childTasks = {}
end
function this:HasChildren()if #self.childTasks <= 0 thenreturn falseelsereturn trueend
end
function this:GetChildCount()return #self.childTasks
end
function this:GetNextChild()if #self.childTasks >= (self.curChilIndex+1) then--指向當前正執行的self.curChilIndex = self.curChilIndex + 1local nextChild = self.childTasks[self.curChilIndex]return nextChildelsereturn nil end
end
--获取前一个子节点,不移动指针
function this:GetCurPrivousTask()if self.curChilIndex <=1 thenlogError(self.name..' GetCurPrivousTask : 已经是最前的Task或childtask为空')return nilelsereturn self.childTasks[self.curChilIndex-1]end
end
--获取下一个子节点,不移动指针
function this:GetCurNextTask()if self.curChilIndex >= #self.childTasks then--logError(self.name..' GetCurNextTask : 已经是最后的Task或childtask为空')return nilelsereturn 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 = selfreturn o
endfunction this:ResetTaskStatus()
end
--获取同一层layer的上一个节点
function this:GetPriviousTask()if self.parent == nil thenlogError(self.name..' 找不到父节点 try call GetPriviousTask')return nilendif self.layer <= 1 thenlogError(self.name..' GetPriviousTask已经是最顶层,单独Task')return nilendlocal priviousTask = self.parent:GetCurPrivousTask()return priviousTask
end
--获取同一层layer下一个task
function this:GetNextTask()if self.parent == nil thenlogError(self.name..' 找不到父节点 try call GetNextTask')return nilendif self.layer <= 1 thenlogError(self.name..' GetNextTask已经是最顶层,单独Task')return nilendlocal nextTask = self.parent:GetCurNextTask()return nextTask
endfunction 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 thenstatus = 'Inactive'elseif self.curReturnStatus == 2 thenstatus = 'Failure'elseif self.curReturnStatus == 3 thenstatus = 'Success'elseif self.curReturnStatus == 4 thenstatus = 'Running'endlocal 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行为树实现(可实现rpg挂机自动战斗)相关推荐

  1. 【转】【Unity+Lua】实测如何性能优化(Lua和C#交互篇)

    [转][Unity+Lua]实测如何性能优化(Lua和C#交互篇) https://blog.csdn.net/swj524152416/article/details/71125478 posted ...

  2. 【远程文件浏览器】Unity+Lua开发调试利器

    Python微信订餐小程序课程视频 https://blog.csdn.net/m0_56069948/article/details/122285951 Python实战量化交易理财系统 https ...

  3. unity lua 交互比较好的文章

    unity lua方案 整理Lua和Unity和Lua交互文章链接 - 勇敢的公爵 - 博客园 Lua ,C,C#互相调用的理解 - 知乎 https://chenanbao.github.io/20 ...

  4. uLua,一个Unity+Lua热更新解决方案!

    原文:http://game.ceeger.com/forum/read.php?tid=16483&fid=16 看了坛子上同学用Kopilua,以为真的跨平台没问题,就实验了安卓手机,然后 ...

  5. uLua最新的Unity+Lua热更新解决方案!!!

    看了坛子上同学用Kopilua,以为真的跨平台没问题,就实验了安卓手机,然后就开始铺游戏框架,干了一星期到昨晚想起来到ipad上跑跑,然后我跟我的小Demo一起崩溃了.今天搜索luajit,终于在u3 ...

  6. Unity(lua) Text添加空格导致换行问题

    Unity(lua) Text添加空格导致换行问题 在Text控件里面添加一个空格,会导致空格后面的内容自动换行.这是因为Unity用于英语等西文的,是为了保证单词不会分开显示,所以第一行空格后面的字 ...

  7. Unity 2D 混合树小例子

    首先大致介绍下几种Unity混合树:Blend Tree,可以有多种模式: 1. 1D混合:只有一个参数,即横坐标.纵坐标是动作权重,横轴上每一个点,对应的各动作权重之和为100%.每个动作分支有自己 ...

  8. unity lua热重载,编辑器下检查lua文件的变化,前端自动热更lua代码

    FileSystemWatcher这里主要用到的一个c#系统类 https://docs.microsoft.com/zh-cn/dotnet/api/system.io.filesystemwatc ...

  9. Unity 愤怒的小鸟拖尾效果——基于Pocket RPG Weapon Trails插件

    Pocket RPG Weapon Trails 武器拖尾插件 前言 导入插件 给小鸟加入子物体并添加拖尾WeaponTrail脚本 接下来给小鸟加入TestMyTrail脚本控制拖尾的开始和结束时间 ...

  10. Unity——lua文件(.lua后缀的文件)无法被Unity识别问题

    官方手册:ScriptedImporter官方手册说明 解决方法: 将如下文件放入Editor文件夹下,等Unity自动刷新或重新打开Unity即可识别. using System.IO; using ...

最新文章

  1. appium-chromedriver@3.0.1 npm ERR! code ELIFECYCLE npm ERR! errno 1
  2. Go学习笔记07-结构体与方法
  3. Java基础学习(一)—方法
  4. pandas的DataFrame转化为Datatable的DataFrame
  5. C代码中如何调用C++ C++中如何调用C
  6. 各浏览器CSS兼容问题
  7. osg动态加载模型不显示_OSG仿真案例(8)——读取FBX格式文件并显示(无动画)...
  8. filterwriter_Java FilterWriter flush()方法与示例
  9. post和get两种提交方式的区别
  10. 网络查找文档比自己电脑上还方便?
  11. mysql报错:You must at least set –server-id to enable either a master or a slave
  12. 汉澳Sinox2014X64server高级桌面服务器版操作系统发布
  13. LOGO以及手绘签名完美导入CAD
  14. 163个人邮箱你了解多少?如何注册下载163邮箱呢?
  15. linux实用技巧:通过命令行安装deb软件包
  16. Axure-产品交互设计师的利器
  17. 网络营销策略——4P
  18. 参考文献格式要首行缩进吗_参考文献顶格写吗
  19. 在GitHub/Gitee上,搭建一个简单的所见即所得博客
  20. 程序员七夕特刊,绝无狗粮添加

热门文章

  1. 为何最简单的破坏命令通过了众多杀软
  2. docker-reviewboard
  3. rpg人物制作软件_RPG游戏制作教程
  4. 【原】小软件开发心得(一)——需求、开发
  5. WIN10_用户获取最高的管理员权限(关闭UAC控制)
  6. linux如何设置显示器亮度调节软件,为 Linux 启用色温和亮度调节工具
  7. 网络工程师考试试题讲解视频教程
  8. 如何准备PMP新版大纲考试?
  9. 数学笔记12——常微分方程和分离变量
  10. [012计算机网络系列] | Cisco packet 怎样清空一台PC的ARP Table?