untiy 3d结合Brainiac Designer做游戏Ai(一)

谈到游戏Ai,总是避免不了谈到行为树,虽然untiy3d有很多非常优秀的行为树插件可以实现Ai,但是很多都是已经被封装好了,不容易修改其中的源码,Brainiac Designer是一个非常好用的开源的行为树编辑器,可以生成cpp、lua、c#……等等代码,非常容易结合到游戏中,这里只介绍Brainiac Designer怎么跟unity3d结合实现Ai。

首先到http://brainiac.codeplex.com/ 下载Brainiac Designer的源码,然后生成一个Plugin,生成Plugin的方法看官网,这里不累赘,不过要注意一点是Plugin的.net framework的版本要用2.0。

生成Plugin后,我们来实现下面这个简单的Ai

这个Ai逻辑就是首先检测玩家是否在攻击范围内,是就是攻击,不是就看是否能找到玩家,找不到就巡逻,找到就追逐过去

我们在Brainiac Designer中新建一个顺序节点SequenceLinear.cs

using Brainiac.Design.Nodes;
using GameAi.Properties;
using System;
using System.Collections.Generic;
using System.Text;

namespace GameAi.Nodes
{
    public class SequenceLinear : Sequence
    {
        public SequenceLinear()
            : base(Resources.SequenceLinear, Resources.SequenceLinearDesc)
        {
        }
    }
}

再建一个选择节点SelectorLinear.cs

using Brainiac.Design.Nodes;
using GameAi.Properties;
using System;
using System.Collections.Generic;
using System.Text;

namespace GameAi.Nodes
{
    public class SelectorLinear : Selector
    {
        public SelectorLinear()
            : base(Resources.SelectorLinear, Resources.SelectorLinearDesc)
        {
        }
    }
}
建一个条件节点,玩家是否在攻击范围内,IsPlayerInAttackRange.cs

using Brainiac.Design.Nodes;
using GameAi.Properties;
using System;
using System.Collections.Generic;
using System.Text;

namespace GameAi.Nodes
{
    public class IsPlayerInAttackRange : Condition
    {
        public IsPlayerInAttackRange()
            : base(Resources.IsPlayerInAttackRange, Resources.IsPlayerInAttackRangeDesc)
		{
		}
    }
}

再建一个条件节点,能否找到玩家,CanNotFindPlayer.cs

using Brainiac.Design.Nodes;
using GameAi.Properties;
using System;
using System.Collections.Generic;
using System.Text;

namespace GameAi.Nodes
{
    public class CanNotFindPlayer: Condition
    {
        public CanNotFindPlayer()
            : base(Resources.CanNotFindPlayer, Resources.CanNotFindPlayer)
		{
		}
    }
}

再建一个条件节点,找到了玩家,FindPlayer.cs 
using Brainiac.Design.Nodes;
using GameAi.Properties;
using System;
using System.Collections.Generic;
using System.Text;

namespace GameAi.Nodes
{
    public class FindPlayer:Condition
    {
        public FindPlayer()
            : base(Resources.FindPlayer, Resources.FindPlayerDesc)
		{
		}
    }
}

我们建一个攻击的行为节点,Attack.cs

using Brainiac.Design.Nodes;
using GameAi.Properties;
using System;
using System.Collections.Generic;
using System.Text;

namespace GameAi.Nodes
{
    public class Attack : Action
    {
        public Attack()
            : base(Resources.Attack, Resources.AttackDesc)
		{
		}
    }
}


追逐的行为节点,Chase.cs

using Brainiac.Design.Nodes;
using GameAi.Properties;
using System;
using System.Collections.Generic;
using System.Text;

namespace GameAi.Nodes
{
    public class Chase:Action
    {
        public Chase()
            : base(Resources.Chase, Resources.ChaseDesc)
		{
		}
    }
}

巡逻节点,传一个参数,巡逻半径,Patrol.cs

using Brainiac.Design.Attributes;
using Brainiac.Design.Nodes;
using GameAi.Properties;
using System;
using System.Collections.Generic;
using System.Text;

namespace GameAi.Nodes
{
    public class Patrol:Action
    {
        protected int _patrolRange = 5;
        [DesignerInteger("_patrolRange", "ActionPatrolPropertyDesc", "CategoryBasic",
            DesignerProperty.DisplayMode.List, 0, DesignerProperty.DesignerFlags.NoFlags, 1, 10, 1, "UnitsMeters")]
        public int PatrolRange
        {
            get { return _patrolRange; }
            set { _patrolRange = value; }
        }

        protected override void CloneProperties(Brainiac.Design.Nodes.Node newnode)
		{
			base.CloneProperties(newnode);

			Patrol node= (Patrol)newnode;
            node._patrolRange = _patrolRange;
		}

        public Patrol()
            : base(Resources.Patrol, Resources.PatrolDesc)
		{
		}
    }
}

然后把这些节点都加到编辑器里

using Brainiac.Design;
using GameAi.Properties;
namespace GameAi
{
    public class GameAi:Plugin
    {
        public GameAi()
        {
            AddResourceManager(Resources.ResourceManager);
            _fileManagers.Add(new FileManagerInfo(typeof(Brainiac.Design.FileManagers.FileManagerXML), "Behavior XML (*.xml)|*.xml", ".xml"));
            _exporters.Add(new ExporterInfo(typeof(Exporters.ExportersCsUseGameAiFormat), "C# Behavior Exporter (Use GameAiFormat)", true, "C#Parameters"));
            NodeGroup actions = new NodeGroup(Resources.NodeGroupActions, NodeIcon.FlagBlue, null);
            _nodeGroups.Add(actions);
            actions.Items.Add(typeof(Nodes.Attack));
            actions.Items.Add(typeof(Nodes.Chase));
            actions.Items.Add(typeof(Nodes.Patrol));

            NodeGroup conditions = new NodeGroup(Resources.NodeGroupConditions, NodeIcon.FlagGreen, null);
            _nodeGroups.Add(conditions);
            conditions.Items.Add(typeof(Nodes.CanNotFindPlayer));
            conditions.Items.Add(typeof(Nodes.IsPlayerInAttackRange));
            conditions.Items.Add(typeof(Nodes.FindPlayer));

            NodeGroup selectorLinears = new NodeGroup(Resources.NodeGroupSelectorLinear, NodeIcon.FlagRed, null);
            _nodeGroups.Add(selectorLinears);
            selectorLinears.Items.Add(typeof(Nodes.SelectorLinear));

            NodeGroup sequenceLinears = new NodeGroup(Resources.NodeGroupSequenceLinear, NodeIcon.FlagRed, null);
            _nodeGroups.Add(sequenceLinears);
            sequenceLinears.Items.Add(typeof(Nodes.SequenceLinear));
        }
    }
}

编译就可以在编辑器看到刚才的那些节点了,按上面的图把各节点拖过去就可以得到刚才的那个行为树了

最后导出行为树的代码,可以导出很多种代码,只要新建一个类继承Exporter就可以定义自己导出的代码格式了,不过要配合在untiy写的Ai框架,这里给出一个示例

using Brainiac.Design;
using Brainiac.Design.Attributes;
using Brainiac.Design.Exporters;
using Brainiac.Design.Nodes;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace GameAi.Exporters
{
    public class ExportersCsUseGameAiFormat : Exporter
    {
        // the namespace the behaviours are exported to
        protected const string _usedNamespace = "GameAi";
        protected const string _parentClassName = "BehaviorTreeRoot";

        public ExportersCsUseGameAiFormat(BehaviorNode node, string outputFolder, string filename)
            : base(node, outputFolder, filename + ".cs")
		{
		}

        /// <summary>
        /// Exports a behaviour to the given file.
        /// </summary>
        /// <param name="file">The file we want to export to.</param>
        /// <param name="behavior">The behaviour we want to export.</param>
        protected void ExportBehavior(StreamWriter file, BehaviorNode behavior)
        {
            string namspace = _usedNamespace;
            string classname = Path.GetFileNameWithoutExtension(behavior.FileManager.Filename).Replace(" ", string.Empty);

            // write comments
            file.Write(string.Format("// Exported behavior: {0}\r\n", _filename));
            file.Write(string.Format("// Exported file:     {0}\r\n\r\n", behavior.FileManager.Filename));

            // create namespace and class
            file.Write(string.Format("namespace {0}\r\n{{\r\n", namspace));
            file.Write(string.Format("\tpublic sealed class {0} : {1}\r\n\t{{\r\n", classname, _parentClassName));

            // create instance and accessors
//             file.Write(string.Format("\t\tprivate static {0} _instance = null;\r\n", classname));
//             file.Write(string.Format("\t\tpublic static {0} Instance\r\n\t\t{{\r\n", classname));
//             file.Write("\t\t\tget\r\n\t\t\t{\r\n");
//             file.Write("\t\t\t\tif(_instance == null)\r\n");
//             file.Write(string.Format("\t\t\t\t\t_instance = new {0}();\r\n\r\n", classname));
//             file.Write("\t\t\t\treturn _instance;\r\n\t\t\t}\r\n\t\t}\r\n\r\n");

            // create constructor
            file.Write(string.Format("\t\tpublic {0}()\r\n\t\t{{\r\n", classname));

            // export nodes
            int nodeID = 0;

            // export the children
            foreach (Node child in ((Node)behavior).Children)
                ExportNode(file, namspace, behavior, "this", child, 3, ref nodeID);

            // close constructor
            file.Write("\t\t}\r\n");

            // close namespace and class
            file.Write("\t}\r\n}\r\n");
        }

        protected virtual void ExportConstructorAndProperties(StreamWriter file, Node node, string indent, string nodeName, string classname)
        {
            // create a new instance of the node
            file.Write(string.Format("{0}\t{2} {1} = new {2}();\r\n", indent, nodeName, classname));

            // assign all the properties
            ExportProperties(file, nodeName, node, indent);
        }

        /// <summary>
        /// Exports a node to the given file.
        /// </summary>
        /// <param name="file">The file we want to export to.</param>
        /// <param name="namspace">The namespace of the behaviour we are currently exporting.</param>
        /// <param name="behavior">The behaviour we are currently exporting.</param>
        /// <param name="parentName">The name of the variable of the node which is the parent of this node.</param>
        /// <param name="node">The node we want to export.</param>
        /// <param name="indentDepth">The indent of the ocde we are exporting.</param>
        /// <param name="nodeID">The current id used for generating the variables for the nodes.</param>
        protected void ExportNode(StreamWriter file, string namspace, BehaviorNode behavior, string parentName, 
            Node node, int indentDepth, ref int nodeID)
        {
            // generate some data
            string classname = node.ExportClass.Replace("GameAi.Nodes.", string.Empty);
            string nodeName = string.Format("node{0}", ++nodeID);

            // generate the indent string
            string indent = string.Empty;
            for (int i = 0; i < indentDepth; ++i)
                indent += '\t';

            string parentClassname = node.Parent.ExportClass;
            string addChildValue = (parentClassname.IndexOf("Decorator") != -1) ? "Proxy" : "AddChild";
            // we have to handle a referenced behaviour differently
            if (node is ReferencedBehaviorNode)
            {
                // generate the namespace and name of the behaviour we are referencing
                string refRelativeFilename = behavior.MakeRelative(((ReferencedBehaviorNode)node).ReferenceFilename);
                string refNamespace = GetNamespace(namspace, refRelativeFilename);
                string refBehaviorName = Path.GetFileNameWithoutExtension(((ReferencedBehaviorNode)node).
                    ReferenceFilename.Replace(" ", string.Empty));

                // simply add the instance of the behaviours we are referencing
                file.Write(string.Format("{0}{1}.{4}({2}.{3}.Instance);\r\n",
                    indent, parentName, refNamespace, refBehaviorName, addChildValue));
            }
            else
            {
                // open some brackets for a better formatting in the generated code
                file.Write(string.Format("{0}{{\r\n", indent));

                // export the constructor and the properties
                ExportConstructorAndProperties(file, node, indent, nodeName, classname);

                // add the node to its parent
                file.Write(string.Format("{0}\t{1}.{3}({2});\r\n", indent, parentName, nodeName, addChildValue));

                // export the child nodes
                foreach (Node child in node.Children)
                    ExportNode(file, namspace, behavior, nodeName, child, indentDepth + 1, ref nodeID);

                // close the brackets for a better formatting in the generated code
                file.Write(string.Format("{0}}}\r\n", indent));
            }
        }

        /// <summary>
        /// Exports all the properties of a ode and assigns them.
        /// </summary>
        /// <param name="file">The file we are exporting to.</param>
        /// <param name="nodeName">The name of the node we are setting the properties for.</param>
        /// <param name="node">The node whose properties we are exporting.</param>
        /// <param name="indent">The indent for the currently generated code.</param>
        protected void ExportProperties(StreamWriter file, string nodeName, Node node, string indent)
        {
            // export all the properties
            IList<DesignerPropertyInfo> properties = node.GetDesignerProperties();
            for (int p = 0; p < properties.Count; ++p)
            {
                // we skip properties which are not marked to be exported
                if (properties[p].Attribute.HasFlags(DesignerProperty.DesignerFlags.NoExport))
                    continue;

                // create the code which assigns the value to the node's property
                file.Write(string.Format("{0}\t{1}.{2} = {3};\r\n", indent, nodeName, properties[p].Property.Name, properties[p].GetExportValue(node).Replace("GameAi.", string.Empty)));
            }
        }

        /// <summary>
        /// Generates the namespace used for a referenced behaviour.
        /// </summary>
        /// <param name="currentNamespace">The namespace we are currently at.</param>
        /// <param name="relativeFilename">The relative filename of the ebhaviour we are referencing.</param>
        /// <returns></returns>
        protected string GetNamespace(string currentNamespace, string relativeFilename)
        {
            // if we stay in the same folder/namespace, just return the current one.
            if (Path.GetFileName(relativeFilename) == relativeFilename)
                return currentNamespace;

            // generate the namespace we are using
            string file = Path.GetDirectoryName(currentNamespace.Replace('.', '\\') + '\\' + relativeFilename);

            // make sure we remove any ..\ we found
            string full = Path.GetFullPath(file);
            string folder = Path.GetFullPath(".");

            Debug.Check(full.StartsWith(folder));

            // get a relative path for the namespace
            string namespaceFile = full.Substring(folder.Length + 1);

            // turn the path into a namespace and remove all spaces
            return namespaceFile.Replace('\\', '.').Replace(" ", string.Empty);
        }

        /// <summary>
        /// Export the assigned node to the assigned file.
        /// </summary>
        public override void Export()
        {
            // get the abolute folder of the file we want toexport
            string folder = Path.GetDirectoryName(_outputFolder + '\\' + _filename);

            // if the directory does not exist, create it
            if (!Directory.Exists(folder))
                Directory.CreateDirectory(folder);

            // export to the file
            StreamWriter file = new StreamWriter(_outputFolder + '\\' + _filename);
            ExportBehavior(file, _node);
            file.Close();
        }
    }
}

到这里Brainiac Designer部分已经差不多了,下一篇再讲怎么跟unity结合

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值