您的位置:首页 > 大数据 > 人工智能

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

2015-05-16 12:17 155 查看
谈到游戏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结合
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: