我正在尝试实现一个行为树,并且在其组成功能方面苦苦挣扎。 基本上,我需要在下面实现的Tick()来调用由嵌入位置定义的方法。

这是behavior.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type IBehavior interface {
  Tick() Status
  Update() Status
}

type Behavior struct {
  Status Status
}

func (n *Behavior) Tick() Status {
  fmt.Println("ticking!")
  if n.Status != RUNNING { n.Initialize() }
  status := n.Update()
  if n.Status != RUNNING { n.Terminate(status) }

  return status
}

func (n *Behavior) Update() Status {
  fmt.Println("This update is being called")
  return n.Status
}

这是嵌入的Behavior结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type IBehaviorTree interface {
  IBehavior
}

type BehaviorTree struct {
  Behavior

  Root IBehavior
}

func (n *BehaviorTree) Update() Status {
  fmt.Printf("Tree tick! %#v\
", n.Root)
  return n.Root.Tick()
}

使该示例更有意义的一些文件:

1
2
3
4
5
6
7
type ILeaf interface {
  IBehavior
}

type Leaf struct {
  Behavior
}

和这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Test struct {
  Leaf

  Status Status
}

func NewTest() *Test {
    return &Test{}
}

func (n Test) Update() Status {
    fmt.Println("Testing!")
    return SUCCESS
}

这是其用法示例:

1
2
3
4
5
tree := ai.NewBehaviorTree()
test := ai.NewTest()
tree.Root = test

tree.Tick()

我期望通过打印以下内容使树正常打勾:

1
2
ticking!
Tree tick!

但是相反,我得到了:

1
2
ticking!
This update is being called

有人可以帮我解决这个问题吗?

  • 在您的用法示例中,将test分配给tree.Root,但未定义test。它是什么,它是从哪里来的?
  • Go绝对没有继承的概念(嵌入不是继承),并且您根本无法在Go中做父母/孩子的事情。重新设计。
  • @Adrian添加了该信息。
  • @Volker我明白了,但是我之所以在这里是因为我不愿意尝试这种设计代码的方式,所以我一直试图找到那种设计。如果您能提供更多帮助,而不仅仅是说"重新设计",那将是很有启发的,这根本没有帮助。
  • 没有继承,但是组合确实允许父/子关系;不知道@Volker在这里。无论如何,在您的用法示例中,它仍然没有显示如何创建分配给tree.Root的变量test。应该是tree.Root = n1吗?
  • @FelipeRocha这是一个很好的问题,不要介意downvotes。由于某种原因,该网站上的golang社区会大胆地拒绝问题,无论其有效性如何。也许尝试将您的问题简化为单个函数中的独立示例(例如,不需要多个文件)。
  • @Adrian哦!抱歉!错别字! n1应该读取test。不好解决!
  • 另请参阅stackoverflow.com/questions/29390736/和stackoverflow.com/questions/45974464/
  • @maerics是的,这不是我第一次遇到选票,这就是为什么我问。抱歉,但是我已经尝试将其提炼为基本内容!没有比这更多的了,这个问题就没有多大意义了。
  • 不幸的是,这已经没有多大意义了。该结构非常复杂,难以快速跟踪执行。这是将其精简为MCVE的好处-通常,当您删除一些不必要的复杂性时,问题便会暴露出来。
  • @FelipeRocha我想您仍然可以简化您的问题,有几行与此问题相关的代码(类型Status,代码清单,未使用的接口方法等)。问题域在这里无关紧要,仅接口,组合和动态调度的细节。
  • 抱歉。我在上班,正在等午餐回来。我删除了所有无关的信息以帮助澄清问题

您的问题是Tick()没有在BehaviorTree结构上定义。 结果,当您调用tree.Tick()时,没有定义直接方法,因此它将调用嵌入式Behavior结构的提升的Tick()方法。 该Behavior结构不知道BehaviorTree是什么! 在Go的伪继承嵌入样式中,"子"类型没有其"父级"概念,也没有对其的引用或访问。 嵌入式方法以嵌入式类型作为其接收方而不是嵌入式结构被调用。

如果希望获得预期的行为,则需要在BehaviorTree类型上定义一个Tick()方法,并具有该方法以调用其自己的Update()方法(然后,如果需要,则调用sub Tick()Update()方法) 希望)。 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type BehaviorTree struct {
  Behavior

  Root IBehavior
}

func (n *BehaviorTree) Tick() Status {
    n.Update() // TODO: do you want this status or the Root.Tick() status?
    return n.Root.Tick()
}

func (n *BehaviorTree) Update() Status {
  fmt.Printf("Tree tick! %#v\
", n.Root)
  return nil
}
  • 有趣。 我试图避免重新定义该行为...真的没有办法解决吗?
  • 您可以嵌入定义方法的接口并调用它。 但是,没有,除非您有循环引用(即,子代有一个句柄),否则无法调用"子代"对象的方法,然后让该"子代"对象调用"父代"对象的方法。 父级)。 从嵌入的角度来看,一般的解决方案是具有一个定义外部方法的结构,并且嵌入(或更常见的情况)只是具有一个接口字段,用于保存要动态定义的方法。

正如沃尔克所说

Go has absolutely no notion of inheritance (embedding is not inheritance) and you simply cannot do parent/child stuff in Go. Redesign

据我所知,您想要的是一个使用接口多次执行相同任务的函数。

1
2
3
4
5
6
7
func Tick(n IBehavior) Status {
  fmt.Println("ticking!")
  if n.Status != RUNNING { n.Initialize() }
  status := n.Update()
  if n.Status != RUNNING { n.Terminate(status) }
  return status
}

当然,初始化必须在接口中。