在使用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. Java项目:就业管理系统设计和实现(java+springboot+ssm)
  2. Improved long read correction for de novo assembly using an FM-index
  3. 企业文化建设不能仅仅靠大声疾呼
  4. 基于SSM实现校园失物招领系统
  5. Python基础知识(第一天)
  6. mysql 扩展存储过程_MySQL4:存储过程和函数
  7. 江西财经大学计算机排名2019,2019年全国商科院校评价报告出炉 江西财经大学排名第七...
  8. [专栏精选]UI布局
  9. git创建仓库,并提交代码(第一次创建并提交)(转)
  10. Windows与Linux下查看占用端口的进程
  11. eclipse java 源代码,java – 下载Eclipse源代码
  12. 【数据库基础随手记】 Oracle DB及SQL语句的一些细节
  13. echarts 直方图加正态_直方图和正态分布图(只需填入待分析数据_自动分析_自动生成图)...
  14. 分数阶微积分学薛定宇电子版_分数阶微积分学与分数阶控制
  15. qmoc文件_Qt(2):MOC文件解析
  16. 我的美国CS面试经验分享
  17. 大咖发声 | 聊聊互联网安全建设从0到1的那些事儿
  18. 2021年,这个岗位发展前景广,刚入行月薪上万?
  19. python:实现用户输入用户名和密码,当用户名为 seven 或 alex 且 密码为 123 时,显示登陆成功,否则登陆失败,失败时允许重复输入三次.
  20. 不用PS也能设计出精美图片?这几个强大的在线设计网站了解一下~

热门文章

  1. vue项目实现百度离线地图开发
  2. android 4.4新功能介绍(Kitkat)
  3. qq轻聊版打开后显示服务器返回数据错误,电脑qq登录报错误报告如何处理_qq打开显示错误报告的解决方法...
  4. 数据库根据字段查询对应所在的表或者对应的数据库
  5. MAC CPU温度监视及风扇速度控制软件
  6. 七夕情人节在一起告白HTML源码(程序员专属情人节表白网站)
  7. 虹科方案 | 全天候监控维护射频网络稳定运行,你不知道的新方案
  8. 动画设计要考计算机证书吗,影视动画要考哪些证书
  9. Nanopore 16S测序数据分析流程之blast/last
  10. 映象劫持使部分程序不可运行的解决方法