
Visual logic editor for Unity3d. Part 1
Introduction
Hello dear readers, in today's article I would like to dwell on such a phenomenon in the development of applications on Unity3d as visual development or, more precisely, development using visual representation of code and logic. And, before continuing, I want to clarify right away, we are not talking about visual programming, from the word “absolutely”, no Blueprint variations in the Unity world and no C # code generations. So what then is meant by a visual logic editor? If you are interested in the answer to this question, welcome under cat.
What is a visual logic editor
Very often, and some argue that always, during development, programmers write a lot of different code that does a lot of different things, from system ones to the game mechanic. This code, if the programmer is “real”, is usually made unified and isolated so that it can be reused (within Unity, this code is the components, they are also the heirs of MonoBehavior) It is not difficult to imagine that there can be a lot of such code, especially if this is not the first project. Now let’s imagine that we are starting a new project and we need to make a lot of fast and different prototypes, and the team of programmers is limited, and everyone is busy with the main project or projects. Game designers are indignant, they need to test, check, producers run around managers trying to get a programmer out for themselves, money is limited, time is running out, etc.
The other side of the coin, we (programmers) wrote a lot of code in the form of components, they hang in a large list on different objects in the scene. And so we expand the team, hired a new programmer, he opens the stage to sort it out and drowns in a mess of questions: who causes whom, in what order, which component is connected with which and how, etc. You reasonably say: - “And Is there documentation? ”There is documentation (although not at all a fact), but the point here is that the threshold for new people to join the team is as low as possible, and the time for this process as short as possible.
How to solve the situations described above? The answer in the title of the article is Visual Logic Editor. What is it? This is an environment that allows you to visually operate on various components of the logic and configure their relationships (in the “soft” version), as well as manipulate objects of the scene indirectly from the scene. If you describe it in a fabulously simple form, then it’s like in childhood to assemble different designs from cubes (only in our case, the cubes are not tightly connected, removing the bottom, our design will not fall).
So, we figured out the definition, but what does this give us in the end?
- You can collect universal designs that can be reused in projects, which reduces the subsequent routine. Imagine a kind of pure field, which is our project, we just take the assembled structure from another game, put it on the field and that's it.
- You can create a database of isolated “cubes” (mechanic, logic, functional) from which people who are not programmers could assemble the constructs yourself.
- It is possible to replace designs on the fly with others, thereby changing the behavior of logic.
- You can use constructions in deferred mode, for example, if the NPC is not present in the world now, then no logic associated with it will exist on our “field”.
- Since our cubes are not connected by rigid relationships, we can turn them off and on as we please and implement arbitrarily complex conditional and unconditional branching.
Well, does that sound pretty good? But what in reality? If you open the Asset Store and see the Visual Scripting section , you can see, in principle, a large number of different plugins. Most of them are variations on the Blueprint theme from the Unreal Engine, i.e., in essence, code generation. There are practically no systems that fit the concepts of a visual logic editor. The closest in meaning are:
- Playmaker . Yes it is an FSM plugin, but nonetheless, it allows you to write your own Actions. It is not so convenient from the point of view of the interface, but for certain things it is very good. Blizzard was not in vain used in Hearthstone.
- Behavior Designer / MoonBehavior , etc. state tree plugins. It’s closer to what was described above, but there are a lot of limitations, after all, the state tree is not a full-fledged logic on the components.
- ICODE . This is an analogue of playmaker, that is, in fact, also a state machine.
Is there a way out, you ask, dear readers? I found only one, write my system, which I did, but the path to it was quite long and thorny.
Way
The idea to develop a plugin for the visual logic editor for Unity3D arose a long time ago. At first it was only thoughts that, if so, it would be cool. These thoughts appeared in the process of working on a project in which there were a lot of similar games, more than 20 pieces that needed to be done very, very quickly. The first implementation was terrible in terms of interface, although, of course, it allowed us to successfully develop the entire set of games at a given speed.
For the next project, it was decided to make a full-fledged visual editor, but as a result, due to little experience, the implementation was not successful, everything was wildly slow, the number of connections, etc., went off scale so much that it was impossible to figure out what and where (see screenshot and do not be scared).

After that, the idea was postponed for some time. The following projects I already did on pure code, but the idea still hovered in my head. Gradually, taking into account past mistakes, a final (as it seemed to me) vision and list of requirements was formed. And in 2017, after the completion of the next freelance project, I decided that I can afford to spend 6-7 months working on this plugin and try to put it in the Asset Store (it still lies and is called Panthea VS) From the point of view of experience working on such a complex project, it was all very cool, the financial side is alas sad, nevertheless, being able to program and be able to sell are two different things. It was November 2017, after which I lost my motivation a little, got divorced, changed my city, completely changed my life, and in order not to fall into samoyedism I decided to look at a different angle on the topic of the visual logic editor. The result was uViLEd , which I decided to post for free. Since I signed a fulltime contract, I had to work on it on weekends and holidays and it took me all of 2018 and the beginning of 2019. uViLEd is a big rethinking of Panthea VS , a complete refactoring of the code for the Roslyn compiler (C # 7+), so everything works only from the Unity3d 2018.3 version.
Note : Panthea VS launched several projects (Android and iOS, in particular, Lev’s Truck and cars), in principle, the experience of using it was successful, but the moment surfaced that it’s one thing to write an editor, another thing is to learn how to use it correctly (no matter how strange it sounds) )
uViLEd and how to use it
Introduction
So, what happened in the end, first we look at the picture, and then continue (there will be more pictures).

What is the visual logic editor based on?

Here:
- Components - this is our code that implements this or that functionality, in fact, an analog of MonoBehaviour , only in our case all components are inherited from the LogicComponent class, which in turn is a ScriptableObject .
- Variables are such special ScriptableObjects that are allowed to store data (any, including custom structures and classes, references to scene objects, prefabs and assets). Variables are needed if it is necessary to make data shared between components, that is, each component can refer to a variable of the desired type, and it will be one.
- Connections are a description in a visual form of what components, how and in what order call each other's methods. Relations between components are determined using two specialized fields of types INPUT_POINT and OUTPUT_POINT . A link is always formed as the output point of a component to the input point of another component. These connections are not rigid, that is, they are not visible to the programmer and they are not in the code either. They are present only in the editor, and then when the scene starts, the logic controller itself understands the code. How this happens we will talk in a separate article.
Everything in the compartment — components, variables, and relationships — form the logic . In general, nothing super complicated, the programmer writes the code of components and variables, and the game designer or another (or maybe the same) programmer / scripter forms the logic by placing these components in the editor and setting up the connections and parameters.
Key features of uViLEd
- Running logic (set of components, variables, and relationships) in deferred mode, including starting from an external source (hard drive or server)
- Configuring connections between components (call order, activation and deactivation)
- Easy integration of components and any other code, including MonoBehavior descendants
- Overriding the appearance of components in the editor (analogous to CustomPropertyDrawer)
- Configuring component settings through the Unity3d inspector
- Easily add components to the logic through the drag & drop script file or through the directory
- Grouping components in the logic editor
- Setting the display of components in the editor (inversion, minimization, activation and deactivation)
- Opening component code editor directly from logic editor
- Display debugging data directly in the logic editor during startup in the editor
- Scaling visual logic editor
- If suddenly a large number of components are contained in the logic, then there is the possibility of searching them with focus when selecting (this applies to variables)
- Step-by-step debugging in scene launch mode in the Unity3d editor with tracking of all transmitted data between components and variable values
- Support for MonoBehaviour methods and setting the order of their call. Note : here we mean that by default in SO there are no methods like Start, Update, etc. therefore, their support has been added to the components themselves. At the same time, using the ExecuteOrder attribute, you can configure the order in which the Start, Update, and so on methods are called.
- Support for Coroutine, async / await, and all Unity3d attributes for the inspector, as well as support for CustomPropertyDrawer
Work with the editor
To start working with the editor, you must open the scene, and then start the editor itself.

After starting the editor, we initialize the scene (the update button in the editor), after which it will be possible to create or add existing logic to the scene.
After creating the logic (a file that will describe the components, their parameters, relationships between components, variables and their values), you can fill it with meaning. To add a component or variable, just drag the corresponding script into the area of the logic editor. An alternative option is to use a directory that is automatically generated using the ComponentDefinition attribute .

After we have added several components to the logic, they can be moved, including in groups or combined into a visual group.

Let us consider in more detail what we represent the component itself in a visual editor.

Here:
- The component’s menu button, opens a drop-down menu with which you can:
- Activate or deactivate a component
- Minimize the component (this can also be done by double-clicking on the header)
- Invert component (swap input and output points)
- Hide or show component options area
- Open the code editor for the component
- The area of the component parameters is the place where the values of the key parameters of the component are displayed, the composition of which depends on the programmer
To configure parameters (public fields or fields with the SerializeField attribute), you need to select the component in the logic editor and open the Unity3d inspector.

Here:
- In the upper right corner is a button that allows you to change the color of the header, which is displayed in the logic editor
- Name - field for setting the name of the component instance
- Comment - a field for setting a comment on the component instance; it is displayed in the logic editor when you hover over the component
- Component Parameters - the area where the component parameters are displayed (public fields and fields marked with SerializeField)
- Variable Links - an area for setting references to variables (more about them will be discussed in the section on working with variables).
In order to visually group objects, you need to select them, and then press the right button and select the corresponding item in the menu. Groups can be renamed, as well as change their color scheme.

To scale the visual representation of the components, use the mouse wheel, everything is quite simple here.
And the last thing I want to pay attention to is working with the connections between the components.
To establish communication, it is necessary to connect the output point of one component with the input point of another.



Relationships are established based on the type matching rule that a point transmits and receives. Exceptions are made to an input point that does not receive data; any output point can be connected to it. When a connection is established, the system automatically checks the type match and shows whether or not this connection can be established. Input and output points are set in the component code using the following classes:
INPUT_POINT
OUTPUT_POINT
INPUT_POINT
OUTPUT_POINT
The first two classes are used for input and output points that do not accept parameters, the second, respectively, vice versa. T can be any type.
public INPUT_POINT InputFloatValue = new INPUT_POINT();
public OUTPUT_POINT OutputFloatValue = new OUTPUT_POINT();
In order to call a chain of links, you must use the Execute function.
OutputFloatValue.Execute(5f);
In order to process such a call, it is necessary to set a handler for the input point in the component code (about where exactly we will talk a little later).
InputFloatValue.Handler = value => Debug.Log(value);
And finally, I want to mention an important point about connections. If there are several links from one point, then in the editor it is possible to adjust the order of their call.
Work with variables
As mentioned earlier, variables are special objects that allow you to share data between components through links to them. Variables, like components, are created by programmers.
[ComponentDefinition(Name = "Float",
Path = "uViLEd Components/Base/Variable/Base",
Tooltip = "Variable for a floating-point number",
Color = VLEColor.Cyan)]
public class VariableFloat : Variable { }
As you can see, the base class for variables is the generic class Variable, where T is the type of data that is enclosed in the variable, T can be any type that can be serialized.
In the editor, the variables are displayed as follows:

To change the display of the variable values, it is enough to redefine the ToString method in type T.
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();
}
}
Thus, a variable of this type will look like:
public class VariableCustomData : Variable { }
In order to add a reference to a variable in a component, you must use a special class.
public VARIABLE_LINK CustomVariableLink = new VARIABLE_LINK();
After that, the link can be set in the inspector, and in the drop-down menu only variables of the CustomData type will be shown , which greatly simplifies working with them.
For the convenience of working with variables, there are special methods to determine when a variable has changed its value or when any data has been set to it.
CustomVariableLink.AddSetEventHandler(CustomDataSet);
CustomVariableLink.AddChangedEventHandler(CustomDataChanged);
It should be borne in mind that Changed works by the Equals condition , therefore, if structures and classes are used, this method must be redefined to ensure correct operation.
Working with Unity Objects
Due to the features of the uViLEd system, direct links to Unity objects cannot be used in it, since they cannot be restored when loading logic. To solve this problem, a specialized VLObject shell was created , which allows you to create such links, as well as save and load them. Among other things, this shell has a special property editor that allows you to get components from any object in the scene (see the figure below) if you want to access them. With VLObject, you can store links not only to scene objects and their components, but also to prefabs and resource files, such as textures, sounds, etc.

Note: if the existing logic is used in another scene, references to objects will be lost, including references to prefabs, because the scene acts as their repository. This should also be taken into account if you plan to use logic as a template, in this case, the best option would be to transfer the necessary links to it from the outside (for example, from logic attached to the scene).
It is also possible to restrict the type of Unity object that will be installed in VLObject . This affects only the Unity inspector and is used for the convenience of working with them.
[SerializeField] [TypeConstraint(typeof(Button))] private VLObject _button;
Creating a logic component
In order to create a logic component, it is enough for the programmer to add a simple C # script file to the project (you can also create a component or a variable immediately through a special menu in the Project tab) and change the code in it to the following:
[ComponentDefinition(Name = "MyComponent", Path = "MyFolder/MySubfolder", Tooltip = "this my logic component", Color = VSEColor.Green)]
public class MyLogicComponent : LogicComponent
{
}
As it was said earlier, ComponentDefinition is an attribute that allows you to automatically create a catalog of components, here it should be noted that Color (header color) is specified in the string as a HEX format.
LogicComponent is the base class of all components, which in turn is a descendant of ScripatableObject .
The following is a simple example of a component that does branching by an incoming value of type bool:
public class IfBool : LogicComponent
{
public INPUT_POINT ValueToBeChecked = new INPUT_POINT();
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();
}
}
}
So, as you can see from the code, we created the input point of the component, which takes a value of type bool and two output points that are called depending on what value we got.
Perhaps you now have a question, what kind of Constructor is this? I explain. By default, ScriptableObject does not support methods like Start , Update , etc., but it does support Awake , OnEnable , OnDisable, and OnDestroy . So Awake (like OnEnable ), in case ScriptableObject is created through the CreateInstance methodalways called, and this, in fact, is the problem. Due to the fact that the object is created in memory for serialization in the editor mode, it was necessary to exclude the component code from working at this moment, therefore the Awake analogue was added as the Constructor method , the same applies to the OnDisable and OnDestroy methods when deleting an object in the editor. If you need to correctly process the removal of a component (when unloading a scene, for example), then you must use the IDisposable interface .
In general, as you can see, there is nothing difficult in creating components. This is a regular class in which there can be any code you want. In the particular case, the components may not contain input and output points at all, but communicate using global messages. By the way, for this, in uViLEd there is a GlobalEvent class - this is a message system based on data types (more about it can be found in my article).
The last thing I would like to mention is the ability to configure the input and output points of the component depending on the parameters of the component.

To do this, in the component code, it is enough to implement one or both of the IInputPointParse and IOutputPointParse interfaces. Below is an example of abstract generic class code for Switch branch components ; output points are automatically generated here, depending on the SwitchValues parameter .
SwitchAbstract Class Code
public abstract class SwitchAbstract : LogicComponent, IOutputPointParse
{
[Tooltip("input point for transmitting value, which should be checked")]
public INPUT_POINT ValueToBeChecked = new INPUT_POINT();
[Tooltip("set of values for branching")]
public List SwitchValues = new List();
protected Dictionary outputPoints = new Dictionary();
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 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;
}
}
Logic debugging
UViLEd provides several mechanisms for debugging logic:
- The ability to display internal variables and their values in the logic editor in scene launch mode. To do this, use the ViewInDebugMode attribute
- Ability to view values of logic variables in scene launch mode
- Possibility of step-by-step debugging of calls between components and viewing data that is transferred between them
UViLEd has a special mode for the last item, which turns on when the scene starts.

This mode, unfortunately, has certain limitations associated with the transition between scenes. In this case, the debugging data from the previous scene and logics will be lost, and in the new one they will begin to be displayed only from the moment the logic is activated in the editor.
Conclusion
In this article I tried to briefly introduce you to the development approach that I use in my current projects. Despite the initial skepticism (including mine), practice shows significantly the convenience of its use, especially when prototyping. Among other things, the work of game designers has been greatly simplified, they do not go into the scene, do not poke at objects to configure the game process, a separate logic is created for them with a set of data of variables and components in which they can easily configure everything. Also a great advantage is the fact that in my projects, often the content is downloaded from the outside. Using the visual logic editor, I can update the balance of the game process without updating the main application, in some cases, the logic itself can be changed.
For myself, I decided that such an approach to development is quite the place to be, of course it is not applicable to large projects, but it can be used there for some gameplay scripts to revitalize the world, in level design, etc. In my ongoing projects (children's segment), so far, he is showing great results.
What next?
This was the first part of a series of articles about the visual editor of logic uViLEd, then there will be parts about:
- The core of the system : how the logic loading occurs, why the ScriptableObject is selected, how the API is arranged, which allows you to do, etc., what difficulties arose and how everything was solved.
- Editor : how it was developed, how it was built, what problems and what solutions, etc. things, that I would remake now.
Write in the comments if you have any specific questions that you would like me to cover in subsequent articles.
PS : I tried to talk about the key points of uViLEd , if you have a desire, you can familiarize yourself with it by downloading the plugin from the Asset Store, there is full documentation (though in English): a user manual, a guide for programmers and APIs.
UViLEd visual logic editor Global messaging
article