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
再建一个选择节点SelectorLinear.cs
再建一个条件节点,能否找到玩家,CanNotFindPlayer.cs
再建一个条件节点,找到了玩家,FindPlayer.cs
我们建一个攻击的行为节点,Attack.cs
追逐的行为节点,Chase.cs
巡逻节点,传一个参数,巡逻半径,Patrol.cs
然后把这些节点都加到编辑器里
编译就可以在编辑器看到刚才的那些节点了,按上面的图把各节点拖过去就可以得到刚才的那个行为树了
最后导出行为树的代码,可以导出很多种代码,只要新建一个类继承
Exporter就可以定义自己导出的代码格式了,不过要配合在untiy写的Ai框架,这里给出一个示例
到这里Brainiac Designer部分已经差不多了,下一篇再讲怎么跟unity结合
首先到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结合
相关文章推荐
- untiy 3d结合Brainiac Designer做游戏Ai(二)
- Untiy3D制作简单的矩形游戏小地图(NGUI)
- 结合directx3D函数库3D视角游戏(软件工程第二次作业)
- untiy 3d ShaderLab读后感_1_第 1 章 Shader(着色器)的概念和在 3D 游戏中的作用
- < Unity 3D专栏 >游戏中 - 怪物AI基础篇
- Unity3D——利用协同程序实现游戏中的AI(自动行走和发现目标)
- 总结使用Unity 3D优化游戏运行性能的经验
- untiy初识之如何让3d物体显示在Ui中,
- 使用行为树(Behavior Tree)实现游戏AI
- 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程04:技能的输入与检测》
- AI—2017大数据版图发布:AI、大数据与云计算结合是大势所趋
- 使用行为树(Behavior Tree)实现游戏AI
- 2009年末最强梅麻呂3D动画游戏大作 汉化补丁
- 闲聊一下android 3D 网络游戏
- Unity 3D:在现有的Android游戏场景中显示AdMob的横幅
- 这个游戏开发巨头的神秘AI项目,或许对自动驾驶开发者有「致命」吸引力
- 区块链和游戏产业结合会怎样?
- MMORPG大型游戏设计与开发(服务器 AI 事件)
- 3D游戏从入门到精通-26
- Unity编程笔录--实现AR与3D场景结合效果