独立游戏的诞生

记录游戏制作的点点滴滴。

Parachute

Unity Web Player | Parachute

Behavior Tree(行为树)的C#实现

在做一个战场上士兵自动战斗的功能的时候,查看了很多关于Behavior Tree的相关内容。在Unity中也有很多现成的可视化插件工具,可以很方便的定义和实现Behavior Tree。但是因为种种不符合需求的问题,最终还是选择自己来实现Behavior Tree的机制。

定义士兵的AI,使用的是XML配置文件,XML的结构与实际生成的Behavior Tree结构非常一致。

Behavior Tree的结点类型包括Composite Node、Decorator Node、Condition Node、Action Node。其中Action Node是AI主体所能执行的具体行为,而通往各个叶子结点(Action Node)的路径就是Behavior Tree根据环境所做的决策过程。

先来看一段士兵的Behavior Tree的配置文件

Solider Behavior Tree
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<Selector name="Solider">
  <Action name="Battle.DieAction">
      <Condition name="Battle.DieCondition" />
 </Action>
  <Action name="Battle.IdelAction">
      <Condition name="Battle.NoEnemyCondition" />
 </Action>
  <Decorator name="RepeatDecorator" repeatCount="1">
      <Sequence name="Attack_A">
          <Action name="Battle.AttackAAction">
          </Action>
     </Sequence>
  </Decorator>
 <Decorator name="RepeatToSuccessDecorator" repeatCount="1">
     <Selector name="Move">
         <Sequence name="MoveToTracingTarget">
             <Action name="Battle.ChooseNewMoveTarget" />
              <Action name="Battle.MoveToTowoardTargetAttackingPlaceAction" />
         </Sequence>
      </Selector>
 </Decorator>
  <Selector name="Solider_Attacked">
      <Condition name="NOTCondition">
          <Condition name="EqualCondition" property="attackStatus" value="0" />
     </Condition>
      <Action name="Battle.BackAction">
          <Condition name="EqualCondition" property="attackStatus" value="2" />
     </Action>
  </Selector>
</Selector>

这段配置定义了士兵的行为,包括死亡、Idle、攻击、移动的相关决策行为和决策条件。通过结点的名称也很容易就能够推断出所要实现的功能。

Behavior的实现

Delegates Events and Actions in C#

  • delegate example

使用delegate的目的是在方法中传递方法指针。通过声明delegate的方法签名,可以在运行时在符合delegate方法签名的方法中选择要执行的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ModernLanguageConstructs
{
    class Program
    {
        // Part 1 - Explicit declaration of a delegate 
        //(helps a compiler ensure type safety)
        public delegate double delegateConvertTemperature(double sourceTemp);

        // A sample class to play with
        class TemperatureConverterImp
        {
            // Part 2 - Will be attached to a delegate later in the code
            public double ConvertToFahrenheit(double celsius)
            {
                return (celsius * 9.0/5.0) + 32.0;
            }

            //  Part 3 - Will be attached to a delegate later in the code
            public double ConvertToCelsius(double fahrenheit)
            {
                return (fahrenheit - 32.0) * 5.0 / 9.0;
            }
        }


        static void Main(string[] args)
        {
            //  Part 4 - Instantiate the main object
            TemperatureConverterImp obj = new TemperatureConverterImp();

            //  Part 5 - Intantiate delegate #1
            delegateConvertTemperature delConvertToFahrenheit =
                         new delegateConvertTemperature(obj.ConvertToFahrenheit);

            //  Part 6 - Intantiate delegate #2
            delegateConvertTemperature delConvertToCelsius =
                         new delegateConvertTemperature(obj.ConvertToCelsius);

            // Use delegates to accomplish work

            //  Part 7 - delegate #1
            double celsius = 0.0;
            double fahrenheit = delConvertToFahrenheit(celsius);
            string msg1 = string.Format("Celsius = {0}, Fahrenheit = {1}",
                                         celsius, fahrenheit);
            Console.WriteLine(msg1);

            //  Part 8 - delegate #2
            fahrenheit = 212.0;
            celsius = delConvertToCelsius(fahrenheit);
            string msg2 = string.Format("Celsius = {0}, Fahrenheit = {1}",
                                         celsius, fahrenheit);
            Console.WriteLine(msg2);
        }
    }
}

  • Event example
    Event是一种特殊类型的delegate,使用Event可以很方便的实现观察者模式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SampleEventArgs
{
    public SampleEventArgs(string s) { Text = s; }
    public String Text {get; private set;} // readonly
}
public class Publisher
{
    // Declare the delegate (if using non-generic pattern).
    public delegate void SampleEventHandler(object sender, SampleEventArgs e);

    // Declare the event.
    public event SampleEventHandler SampleEvent;

    // Wrap the event in a protected virtual method
    // to enable derived classes to raise the event.
    protected virtual void RaiseSampleEvent()
    {
        // Raise the event by using the () operator.
        if (SampleEvent != null)
            SampleEvent(this, new SampleEventArgs("Hello"));
    }
}
  • Action example
    Action是一种语法糖,它是一种不需要提前声明的delegate,使得delegate的使用更加方便,局限性就是不能定义返回值。(Func<> Delegates是一种可以定义返回值的语法糖)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ModernLanguageConstructs
{
    class Program
    {
        static void Main(string[] args)
        {
            // Part 1 - First action that takes an int and converts it to hex
            Action<int> displayHex = delegate(int intValue)
            {
                Console.WriteLine(intValue.ToString("X"));
            };

            // Part 2 - Second action that takes a hex string and 
            // converts it to an int
            Action<string> displayInteger = delegate(string hexValue)
            {
                Console.WriteLine(int.Parse(hexValue,
                    System.Globalization.NumberStyles.HexNumber));
            };

            // Part 3 - exercise Action methods
            displayHex(16);
            displayInteger("10");
        }
    }
}

参考资料:
1. C# Delegates, Actions, Funcs, Lambdas–Keeping it super simple
2. event(C# 参考)