Unity3d的可视逻辑编辑器。 第一部分

引言


尊敬的读者您好,在今天的文章中,我想谈谈Unity3d上开发应用程序时出现的一种现象, 即可视化开发,或更准确地说,是使用可视化代码和逻辑表示的开发。 而且,在继续之前,我想立即澄清一下,这与视觉编程无关,从“绝对”一词开始,Unity世界中没有Blueprint版本,也没有C#代码生成。 那么,可视逻辑编辑器意味着什么? 如果您对这个问题的答案感兴趣,欢迎来到cat下。

该系列文章:
视觉逻辑编辑器第2部分

什么是视觉逻辑编辑器


很多时候,有人认为,在开发过程中,程序员经常编写许多不同的代码,它们执行从系统代码到游戏机制的许多不同事情。 如果程序员是“真实的”,那么这些代码通常会被统一和隔离,以便可以重复使用(在Unity中,这些代码是组件,它们也是MonoBehavior的继承人)。 不难想象会有很多这样的代码,特别是如果这不是第一个项目的话。 现在想象一下,我们正在开始一个新项目,我们需要制作许多快速且不同的原型,并且程序员团队有限,整个团队都在忙于一个或多个主要项目。 游戏设计师很愤慨,他们需要测试,检查,制作人在经理周围奔波,试图让自己成为一名程序员,金钱有限,时间不多,等等。

硬币的另一面,我们(程序员)以组件的形式编写了很多代码,它们悬挂在场景中不同对象上的大量列表中。 因此,我们扩大了团队,聘请了新的程序员,他打开了进行整理的阶段,并淹没在一个混乱的问题中:谁导致谁,以什么顺序,哪个组件与哪个以及如何连接等。您可以合理地说:-文档?” 有文档(尽管根本没有事实),但这是关于新人加入团队的门槛应尽可能低,并且此过程的时间应尽可能短。

如何解决上述情况? 本文标题中的答案是Visual Logic Editor。 这是什么 这是一个环境,使您可以直观地操作逻辑的各个组件并配置它们的关系(在“软”版本中),以及从场景间接地操纵场景的对象。 如果您以一种非常简单的形式来描述它,那么就像在童年时期就用多维数据集组装不同的设计(仅在我们这种情况下,多维数据集没有紧密连接,移除了底部,我们的设计不会跌落)。

因此,我们确定了定义,但这最终给我们带来了什么?

  • 您可以组装可在项目中重用的通用设计,从而减少了后续例程。 想象一个纯粹的领域,这是我们的项目,我们只是从另一个游戏中提取组装的结构,然后将其放在领域上就这样。
  • 您可以创建一个包含孤立的“多维数据集”(机械,逻辑,功能)的数据库,非程序员可以从中独立构建结构。
  • 可以用其他动态替换设计,从而改变逻辑行为。
  • 您可以在延迟模式下使用构造,例如,如果现在世界上不存在NPC,那么在我们的“字段”上将不存在与之关联的逻辑。
  • 由于我们的多维数据集不是通过刚性关系连接的,因此我们可以根据需要关闭和打开它们,并实现任意复杂的条件分支和无条件分支。

好吧,听起来不错吗? 但是实际上呢? 如果您打开资源商店并看到“ 可视脚本”部分,则原则上可以看到大量不同的插件。 它们大多数是虚幻引擎对Blueprint主题的变体,即实质上是代码生成。 几乎没有适合视觉逻辑编辑器概念的系统。 最接近的含义是:

  1. 游戏制作人 是的,它是一个FSM插件,但是,它允许您编写自己的Action。 从界面的角度来看,它不是那么方便,但是对于某些事情来说,它是非常好的。 暴雪在炉石传说中没有白费。
  2. 行为设计师/ MoonBehavior等 状态树插件。 它与上面的描述更接近,但是有很多限制,毕竟,状态树不是组件上的完整逻辑。
  3. 编码 这是组织者的模拟,实际上也是状态机。

亲爱的读者,您有没有解决的办法? 我只找到了一个,写出了我写的系统,但是这样做的路很长而且很棘手。

方式


很早以前就出现了为Unity3D的可视逻辑编辑器开发插件的想法。 最初,只有这样的想法会很酷。 这些想法出现在一个项目中,该项目中有很多类似的游戏,需要非常非常快地完成20多个游戏。 第一个实现在界面方面很糟糕,尽管它当然使我们能够以给定的速度成功开发整套游戏。

对于下一个项目,决定制作一个完整的可视化编辑器,但结果是,由于经验不足,实现不成功,一切都非常缓慢,连接数量等超出规模,以至于无法弄清楚什么和在哪里(参见屏幕截图,请不要害怕)。

图片

此后,这个想法被推迟了一段时间。 我已经在纯代码上完成了以下项目,但是这个想法仍然在我的脑海中盘旋。 考虑到过去的错误,逐渐形成了最终的(在我看来)愿景和要求清单。 在2017年,下一个自由职业者项目完成之后,我决定我有能力花6-7个月的时间来处理此插件,并尝试将其放入Asset Store (它仍然存在,并称为Panthea VS )。 从从事这样一个复杂项目的经验来看,一切都很酷,财务方面令人遗憾,仍然能够编程和能够出售是两件不同的事情。 那是2017年11月,此后我有点失去动力,离婚,改变了我的城市,彻底改变了我的生活,为了不陷入萨摩耶教义,我决定以不同的角度讨论视觉逻辑编辑器的主题。 结果是uViLEd ,我决定免费发布。 自从我签订了一份全职合同以来,我不得不在周末和节假日进行这项工作,这花了我整个2018年和2019年初的时间。 uViLEdPanthea VS的重大重新思考,它是Roslyn编译器(C#7+)的代码的完整重构,因此所有功能仅适用于Unity3d 2018.3版本。

注意Panthea VS启动了多个项目(Android和iOS,尤其是Lev的Truck和cars),原则上,使用它的经验很成功,但是当下浮出水面的是,写一个编辑器是一回事,另一件事是学习如何正确使用它(无论听起来多么奇怪)。 )

uViLEd及其使用方法


引言


因此,最后发生的事情是,首先我们查看图片,然后继续(会有更多图片)。

图片

可视逻辑编辑器基于什么?

图片

在这里:

  • 组件 -这是我们的代码,实现一种或另一种功能,实际上是MonoBehaviour的类似物,仅在我们这种情况下,所有组件都是从LogicComponent类继承的,而LogicComponent类又是ScriptableObject
  • 变量是允许存储数据的特殊ScriptableObject (任何对象,包括自定义结构和类,对场景对象的引用,预制件和资产)。 如果有必要使组件之间共享数据,则需要变量,也就是说,每个组件都可以引用所需类型的变量,它将是一个。
  • 关系是以视觉形式描述什么组件,如何以及以什么顺序调用彼此的方法。 组件之间的关系使用类型为INPUT_POINTOUTPUT_POINT的两个专用字段确定。 链接始终形成为一个组件的输出点到另一个组件的输入点。 这些连接不是严格的,也就是说,它们对于程序员是不可见的,并且它们也不在代码中。 它们仅在编辑器中存在,然后在场景开始时,逻辑控制器本身会理解代码。 如何发生这种情况,我们将在另一篇文章中讨论。

隔离专区中的所有内容(组件,变量和关系)形成逻辑 。 通常,没有什么超级复杂的事情,程序员编写组件和变量的代码,然后游戏设计师或另一个(或同一个)程序员/脚本编写者通过将这些组件放置在编辑器中并设置连接和参数来形成逻辑。

uViLEd的主要功能


  • 以延迟模式运行逻辑(一组组件,变量和关系),包括从外部源(硬盘或服务器)启动
  • 设置组件之间的连接(呼叫顺序,激活和停用)
  • 轻松集成组件和任何其他代码,包括MonoBehavior后代
  • 覆盖编辑器中组件的外观(类似于CustomPropertyDrawer)
  • 通过Unity3d检查器配置组件设置
  • 通过拖放脚本文件或目录轻松将组件添加到逻辑中
  • 在逻辑编辑器中对组件进行分组
  • 在编辑器中设置组件的显示(反转,最小化,激活和停用)
  • 直接从逻辑编辑器打开组件代码编辑器
  • 在编辑器中启动期间,直接在逻辑编辑器中显示调试数据
  • 缩放可视逻辑编辑器
  • 如果逻辑中突然包含大量组件,则在选择时有可能重点搜索它们(这适用于变量)
  • 在Unity3d编辑器中以场景启动模式进行逐步调试,跟踪组件和变量值之间的所有传输数据
  • 支持MonoBehaviour方法并设置其调用顺序。 注意 :这里是指默认情况下,SO中没有诸如Start,Update等方法。 因此,它们的支持已添加到组件本身。 同时,使用ExecuteOrder属性,可以配置调用Start,Update等方法的顺序。
  • 支持检查程序的协程,异步/等待和所有Unity3d属性,以及对CustomPropertyDrawer的支持

与编辑一起工作


要开始使用编辑器,必须打开场景,然后启动编辑器本身。

图片

启动编辑器后,我们初始化场景(编辑器中的更新按钮),然后可以在场景中创建或添加现有逻辑。
创建逻辑(一个描述组件,它们的参数,组件之间的关系,变量和它们的值的文件)后,您可以在其中填充含义。 要添加组件或变量,只需将相应的脚本拖到逻辑编辑器的区域中即可。 另一种选择是使用使用ComponentDefinition属性自动生成的目录。

图片

在逻辑中添加了几个组件之后,就可以移动它们,包括成组移动或组合成可视组。

图片

让我们更详细地考虑在可视化编辑器中表示组件本身的内容。

图片

在这里:

  • 组件的菜单按钮,打开一个下拉菜单,您可以使用以下菜单:
    • 激活或停用组件
    • 最小化组件(也可以通过双击标题来完成)
    • 反转组件(交换输入和输出点)
    • 隐藏或显示组件选项区域
    • 打开组件的代码编辑器
  • 组件参数区域是显示组件关键参数值的地方,其组成取决于程序员

要配置参数(公共字段或具有Seri​​alizeField属性的字段),您需要在逻辑编辑器中选择组件并打开Unity3d检查器。

图片

在这里:

  • 右上角是一个按钮,可让您更改标题的颜色,该颜色显示在逻辑编辑器中
  • 名称 -用于设置组件实例名称的字段
  • 注释 -用于在组件实例上设置注释的字段;当您将鼠标悬停在组件上时,它将显示在逻辑编辑器中
  • 组件参数 -显示组件参数的区域(公共字段和标有Seri​​alizeField的字段)
  • 变量链接 -用于设置对变量的引用的区域(有关变量的更多信息将在使用变量的部分中进行讨论)。

为了可视化地对对象进行分组,您需要选择它们,然后按右键并在菜单中选择相应的项目。 可以重命名组以及更改其配色方案。

图片

要使用鼠标滚轮缩放组件的视觉表示,一切都非常简单。

最后我要注意的是使用组件之间的连接。
要建立连接,必须将一个组件的输出点与另一个组件的输入点连接。

图片

图片

图片

基于点发送和接收的类型匹配规则来建立关系。 不接收数据的输入点有例外;可以将任何输出点连接到该输入点。 建立连接后,系统会自动检查类型匹配并显示是否可以建立此连接。 使用以下类在组件代码中设置输入和输出点:

INPUT_POINT OUTPUT_POINT INPUT_POINT<T> OUTPUT_POINT<T> 

前两个类别用于不接受参数的输入和输出点,第二个类别则相反。 T可以是任何类型。

 public INPUT_POINT <float> InputFloatValue = new INPUT_POINT<float>(); public OUTPUT_POINT <float> OutputFloatValue = new OUTPUT_POINT<float>(); 

要调用链接链,必须使用Execute函数。

 OutputFloatValue.Execute(5f); 

为了处理这样的调用,有必要为组件代码中的输入点设置一个处理程序(大约稍后我们将讨论的确切位置)。

 InputFloatValue.Handler = value => Debug.Log(value); 

最后,我想提到有关连接的重要一点。 如果从一个点有多个链接,则可以在编辑器中调整其调用顺序。

使用变量


如前所述,变量是特殊的对象,使您可以通过指向它们的链接在组件之间共享数据。 像组件一样的变量是由程序员创建的。

 [ComponentDefinition(Name = "Float", Path = "uViLEd Components/Base/Variable/Base", Tooltip = "Variable for a floating-point number", Color = VLEColor.Cyan)] public class VariableFloat : Variable<float> { } 

如您所见,变量的基类是泛型类Variable,其中T是变量中包含的数据类型,T可以是可以序列化的任何类型。

在编辑器中,变量显示如下:

图片

要更改变量值的显示,只需在类型T中重新定义ToString方法。

 public struct CustomData { public readonly int Value01; public readonly int Value02; public CustomData (int value01, int value02) { Value01= value01; Value02= value02; } public override string ToString() { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Value01 = {0}".Fmt(Value01)); stringBuilder.Append("Value02 = {0}".Fmt(Value02)); return stringBuilder.ToString(); } } 

因此,这种类型的变量如下所示:

 public class VariableCustomData : Variable<CustomData> { } 

为了在组件中添加对变量的引用,必须使用特殊的类。

 public VARIABLE_LINK<CustomData> CustomVariableLink = new VARIABLE_LINK<CustomData>(); 

此后,可以在检查器中设置链接,然后在下拉菜单中仅显示CustomData类型的变量,这大大简化了使用它们的工作。

为了方便使用变量,有一些特殊的方法可让您确定何时变量已更改其值或何时为其设置任何数据。

 CustomVariableLink.AddSetEventHandler(CustomDataSet); CustomVariableLink.AddChangedEventHandler(CustomDataChanged); 

应当牢记,Changed在Equals条件下起作用,因此,如果使用结构和类,则必须重新定义此方法以确保正确操作。

使用Unity对象


由于uViLEd系统的性质,无法在其中使用指向Unity对象的直接链接,因为在加载逻辑时无法还原它们。 为了解决此问题,创建了专用的VLObject Shell,该外壳允许您创建此类链接以及保存和加载它们。 除其他外,此shell具有一个特殊的属性编辑器,如果您要访问它们,则可以从场景中的任何对象获取组件(请参见下图)。 使用VLObject,您不仅可以存储到场景对象及其组件的链接还可以存储到预制件和资源文件(例如纹理,声音等)的链接。

图片

注意 :如果在另一个场景中使用现有逻辑,则将丢失对对象的引用,包括对预制件的引用,因为该场景充当其存储位置。 如果您打算将逻辑用作模板,则也应该考虑到这一点,在这种情况下,最好的选择是从外部(例如,从附加到场景的逻辑)转移到它的必要链接。

也可以限制将在VLObject中安装的Unity对象的类型。 这仅影响Unity检查器,并为方便使用它们而使用。

 [SerializeField] [TypeConstraint(typeof(Button))] private VLObject _button; 

创建一个逻辑组件


为了创建逻辑组件,程序员将一个简单的C#脚本文件添加到项目就足够了(您也可以通过“项目”选项卡中的特殊菜单立即创建组件或变量)并将其中的代码更改为以下内容:

 [ComponentDefinition(Name = "MyComponent", Path = "MyFolder/MySubfolder", Tooltip = "this my logic component", Color = VSEColor.Green)] public class MyLogicComponent : LogicComponent { } 

如前所述, ComponentDefinition是允许您自动创建组件目录的属性,在此应注意,Color(标题的颜色)在字符串中设置为十六进制格式。

LogicComponent是所有组件的基类,而这些组件又是ScripatableObject的后代。

以下是一个组件的简单示例,该组件按bool类型的传入值进行分支:

 public class IfBool : LogicComponent { public INPUT_POINT<bool> ValueToBeChecked = new INPUT_POINT<bool>(); public OUTPUT_POINT True = new OUTPUT_POINT(); public OUTPUT_POINT False = new OUTPUT_POINT(); public override void Constructor() { ValueToBeChecked.Handler = ValueToBeCheckedHandler; } private void ValueToBeCheckedHandler(bool value) { if(value) { True.Execute(); }else { False.Execute(); } } } 

因此,正如您从代码中看到的那样,我们创建了组件的输入点,该输入点采用bool类型的值,并根据获得的值调用两个输出点。

也许您现在有一个问题,这什么样的构造函数 ? 我解释一下 默认情况下, ScriptableObject不支持诸如StartUpdate等方法,但也支持AwakeOnEnableOnDisableOnDestroy方法 。 因此,这里是Awake (类似于OnEnable ),如果ScriptableObject是通过CreateInstance方法创建的,则始终会调用它,而实际上这就是问题所在。 由于实际上是在编辑器模式下在内存中创建对象以进行序列化的事实,因此此时必须排除组件代码的工作,因此添加了Awake模拟作为Constructor方法,当在编辑器中删除对象时,同样适用于OnDisableOnDestroy方法。 如果需要正确处理组件的移除(例如,在卸载场景时),则必须使用IDisposable接口。

如您所见,一般而言,创建组件并不困难。 这是一个常规类,其中可以包含您想要的任何代码。 在特定情况下,这些组件可能根本不包含输入和输出点,而是使用全局消息进行通信。 顺便说一下,为此,在uViLEd中有一个GlobalEvent类-它是一个基于数据类型的消息系统(有关更多信息,请参见我的文章)。

我要说的最后一件事是能够根据组件的参数配置组件的输入和输出点的能力。

图片

为此,在组件代码中,实现IInputPointParseIOutputPointParse接口中的一个或两个就足够了。 下面是一个用于Switch分支组件的抽象通用类代码的示例;根据SwitchValues参数在此处自动创建输出点。

SwitchAbstract类代码
 public abstract class SwitchAbstract<T> : LogicComponent, IOutputPointParse { [Tooltip("input point for transmitting value, which should be checked")] public INPUT_POINT<T> ValueToBeChecked = new INPUT_POINT<T>(); [Tooltip("set of values for branching")] public List<T> SwitchValues = new List<T>(); protected Dictionary<string, object> outputPoints = new Dictionary<string, object>(); public override void Constructor() { ValueToBeChecked.Handler = ValueToBeCheckedHandler; } protected virtual bool CompareEqual(T first, T second) { return first.Equals(second); } protected virtual string GetValueString(T value) { var outputPontName = value.ToString(); #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { if (outputPoints.ContainsKey(outputPontName)) { outputPontName += " ({0})".Fmt(outputPoints.Count); } } #endif return outputPontName; } private void ValueToBeCheckedHandler(T checkedValue) { foreach (var value in SwitchValues) { if (CompareEqual(checkedValue, value)) { ((OUTPUT_POINT)outputPoints[GetValueString(value)]).Execute(); return; } } } public IDictionary<string, object> GetOutputPoints() { #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) { outputPoints.Clear(); } #endif if (outputPoints.Count == 0) { foreach (var value in SwitchValues) { outputPoints.Add(GetValueString(value), new OUTPUT_POINT()); } } return outputPoints; } } 


逻辑调试


UViLEd提供了几种调试逻辑的机制:

  1. 在场景启动模式下,可以在逻辑编辑器中显示内部变量及其值的功能。 为此,请使用ViewInDebugMode属性
  2. 能够在场景启动模式下查看逻辑变量的值
  3. 可以逐步调试组件之间的调用并查看它们之间传输的数据

UViLEd对最后一项具有特殊的模式,该模式在场景开始时打开。

图片

不幸的是,该模式具有与场景之间的过渡相关联的某些限制。 在这种情况下,先前场景和逻辑中的调试数据将丢失,而在新场景和逻辑中,仅从在编辑器中激活逻辑的那一刻开始显示调试数据。

结论


在本文中,我试图向您简要介绍我在当前项目中使用的开发方法。 尽管最初有怀疑(包括我的怀疑),实践还是显示出使用它的便利性,尤其是在制作原型时。 除其他事项外,游戏设计师的工作已大大简化,他们无需进入现场,不戳物体来配置游戏过程,而是使用一组变量和组件数据为他们创建了单独的逻辑,他们可以在其中轻松配置所有内容。 另外一个很大的优点是,在我的项目中,通常是从外部下载内容。 使用可视逻辑编辑器,我可以在不更新主应用程序的情况下更新游戏流程的平衡,在某些情况下,可以更改逻辑本身。

对我自己来说,我认为这样的开发方法是很合适的,当然它不适用于大型项目,但是可以将其用于一些游戏脚本,从而在关卡设计等方面重振整个世界。正在进行的项目(儿童部分),到目前为止,他显示出了出色的成绩。

接下来呢?

这是有关逻辑uViLEd的可视化编辑器的系列文章的第一部分,然后将涉及以下内容:

  1. 系统的核心 :如何加载逻辑,为什么选择ScriptableObject,如何安排API,允许您执行的操作等等,出现了什么困难以及如何解决所有问题。
  2. 编辑器 :我现在将重述它的开发方式,构建方式,存在的问题和解决方案等。

如果您有任何我想在后续文章中讨论的特定问题,请在评论中写下。

PS :我试图谈论uViLEd的关键点,如果您有需要,可以通过从Asset Store下载插件来熟悉它,这里有完整的文档(尽管是英文):用户手册,程序员和API指南。

视觉逻辑编辑器第2部分

UViLEd视觉逻辑编辑器
全球消息文章

Source: https://habr.com/ru/post/zh-CN448190/


All Articles