热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

go版本行为树

概念行为树一般在做游戏AI的时候经常用到,Unity3d就有一款专门的行为树插件,Unreal4里面也在引擎层支持行为树编辑。本人之前是做Unity3d手游的,对这些有所涉猎,最近在用go

概念

行为树一般在做游戏AI的时候经常用到,Unity3d就有一款专门的行为树插件,Unreal4里面也在引擎层支持行为树编辑。本人之前是做Unity3d手游的,对这些有所涉猎,最近在用go语言写后台,我们刚开发了几个服务器,需要在后台编写一个程序监控服务的可用性,如果不可用可用报警之类的,然后我就想到了用行为树来实现这个功能。

大家可能一直都是用插件的那种可视化工具配置行为树,顶多就是写写树的执行节点,这里我为大家写一整套树的实现。

对于没有接触过行为树概念的人来讲可能有些困难,这里我简单做一些介绍,如果还是有疑惑,可用自行脑补。
这里写图片描述

这就是一个行为树了,有根节点,有子节点,而且子节点个数是任意个,Patrol,Attack,Retreat,可以看成是一个士兵的行为,如果有更多的行为可以自行加进去,每次执行AI时,系统都会从根节点遍历整颗树,父节点执行子节点,根据子节点返回的结果判断接下来执行什么。

常见的基础类型节点有以下几类:
1)顺序节点(Sequence):属于组合节点,顺序执行子节点,只要碰到一个子节点返回false,则停止继续执行,并返回false,否则返回true,类似于程序中的逻辑与。

  2)选择节点(Selector):属于组合节点,顺序执行子节点,只要碰到一个子节点返回true,则停止继续执行,并返回true,否则返回false,类似于程序中的逻辑或。

  3)平行节点(Parallel Node):提供了平行的概念,无论子节点返回值是什么都会遍历所有子节点。所以不需要像Selector/Sequence那样预判哪个Child Node应摆前,哪个应摆后。Parallel Node增加方便性的同时,也增加实现和维护复杂度。

  4)条件节点(Condition):属于叶子节点,判断条件是否成立。

  5)执行节点(Action):属于叶子节点,执行动作,一般返回true。

实例

接下来我就来讲解我是怎么用golang来实现一个行为树基础框架的,代码开源,文章末尾会贴出github地址,欢迎来沟通

节点描述

//ResultType result enums   节点返回结果枚举
type ResultType uint16
const (
    B_FALSE ResultType = iota
    B_TRUE
    B_RUNING
)

//BaseNode basenode   节点interface
type BaseNode interface { IsInit() bool SetParent(parent BaseNode) SetIndex(index int) Tostring() WhoAmI() string SetTree(t *Tree) OnInstall() OnUnstall() OnEnter() OnExit() ExcuserNode(child BaseNode) SendParentResult(exitNode BaseNode, result ResultType) OnChildrenFinish(result ResultType, childIndex int, owner string) }

//AiNode node describe 节点数据描述
type AiNode struct { ChildCount int IsAlInit bool Parent BaseNode nodeList []BaseNode curNode BaseNode IdxInParent int Owner string }

sequence顺序节点实现

//AddNode add node 变长参数加入节点
func (s *SequenceAINode) AddNode(arg ...BaseNode) {
    for i, v := range arg {
        v.SetIndex(i)
        v.SetParent(s)
        s.nodeList = append(s.nodeList, v)
    }
    s.ChildCount = len(arg)
}
//OnEnter enter  sequence进入
func (s *SequenceAINode) OnEnter() {
    if len(s.nodeList) > 0 {
        s.curNode = s.nodeList[0]
        s.ExcuserNode(s.curNode)
    } else {
        s.OnExit()
        s.SendParentResult(s, B_TRUE)
    }
}
//OnChildrenFinish child is done   sequence节点结束
func (s *SequenceAINode) OnChildrenFinish(result ResultType, childrenIndex int, owner string) {
    fmt.Printf("%s childNode return %d\n", owner, result)

    if result == B_FALSE {
        s.SendParentResult(s, B_FALSE)
        return
    }
    fmt.Printf("childrenIndex:%d childCount:%d\n", childrenIndex, len(s.nodeList))
    if childrenIndex .nodeList)-1 {
        s.curNode = s.nodeList[childrenIndex+1]
        s.ExcuserNode(s.curNode)
    } else {
        s.SendParentResult(s, B_TRUE)
    }
}

behaviour 具体行为配置

//Init init
func (b *Behaviourer) Init() {
    b.executor = baseai.NewTree()
    root := baseai.NewRootNode()

    seq := baseai.NewSequence()
    action1 := NewAction1()
    action2 := NewAction2()
    seq.AddNode(action1, action2)

    root.AddNode(seq)
    b.executor.SetRoot(*root)
}

//Run run
func (b *Behaviourer) Run() {
    b.executor.Run()
}

这边我写了2个执行节点Action1和Action2,执行结果如下

Action1 SetParent
Action2 SetParent
Rootnode AddNode
tree excuser
Rootnode OnInstall
Rootnode OnEnter
excuse SequenceAINode node
excuse action1 node
Action1 OnInstall
Action1 OnEnter
Action1 OnExit
action1 childNode return 1
childrenIndex:0 childCount:2
excuse action2 node
Action2 OnInstall
Action2 OnEnter
Action2 OnExit
action2 childNode return 0
SequenceAINode root childNode return 0
node:SequenceAINode run over!

这里看到Action1先执行完了再执行Action2,按顺序执行,如果Action1执行失败的话就不会执行Action2,这里需要说明一哈,执行节点就是具体你需要去做的行为,行为树控制你的行为逻辑。好比文章开头的行为树,执行节点可以是去巡逻,攻击等,先巡逻,然后发现敌人,最后攻击。用行为树来做这样的事情是最合适的,而且维护拓展也比较方便。

这边我只写了顺序节点的实现,暂时只用到了这个,有兴趣的同学可以写其他基础节点
源码地址:https://github.com/fdgggy/BehaviourTree_Golang


推荐阅读
author-avatar
墨镜猛女班长
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有