Hi
Its been a while, that’s because for most evenings I’ve been busy working on my PhD project. So finally, I have something to talk about.
I won’t go into full detail of what my project will be as I will save that for other post. But it does involve using artificial intelligence in creating and managing character based narratives in video games.
Because of the complex nature of this (I will be using three different AI systems, Utility, STRIPS type goal manager, Multiagent cooperation), I will develop a flow based programming tool to aid in its development and debugging.
These tools have become quite popular in recent times i.e. Unreal engine’s blueprint system. Unity doesn’t really have this unless you buy an asset (It does now it’s called Bolt but its is still and optional addon).
So, I decided to build my own, which is straight forward (I highly recommend a booked called Flow based Programming by J. Paul Morrison), the main challenge is data and how it is presented to the user interface.
Because I will be designing this to use Unity’s DOTS system representing the data in code is actually very simple, it will just be a simple struct, which will be attached to node to do some sort of processing on.
The big question for me after this was how do I represent that data visually on the node, and how do I do it so I don’t have to write something like make text box at x, y with this data every time I need to make a modification to a node or create a new node. This would become very time consuming and difficult to manage as the project grows.
Could I borrow what Unreal engine does with its UPROPERTY tags? Well C# does allow for custom attributes, these are basically meta data that helps describe a piece of code it’s attached to.
This would work great because all I would have to do is look for the custom attributes, parse them and create the GUI controls automatically for that node.
So, I will want to create two attributes, the first I will call [NodeData] this will tell the system that this piece of data we want exposing to the node interface. To create this is just this simple piece of code below.
DISCLAIMER THE CODE BELOW IS HEAVLY EDITED TO JUST GIVE THE IMPORTANT BITS
[System.AttributeUsage( System.AttributeTargets.Field )]
public class NodeData : System.Attribute
{
// We don't need to store anything in here yet just used
// as a tag to say this is what data we want on the node
public NodeData()
{
}
}
The second is [DataAttribute], this will be slightly more complex it will include a name string that will show up next to the GUI control, and two Boolean flags to say if this piece of data will allow for inputs and outputs.
[System.AttributeUsage(System.AttributeTargets.Field)]
public class DataAttribute : System.Attribute
{
string _name;
bool _input;
bool _output;
public string Name => _name;
public bool Input => _input;
public bool Output => _output;
/// <summary>
/// These are tags that are attached to data field within a node
/// </summary>
/// <param name="name">Name that will apear in the node</param>
/// <param name="input">Has input port</param>
/// <param name="output">Has output port</param>
public DataAttribute(string name, bool input, bool output)
{
_name = name;
_input = input;
_output = output;
}
}
We now use a process known as reflection to go through the code, find the tags and add the GUI controls to the node. Our node tag will look like
// For the test data to appear in the node
// we have to tag it.
[NodeData]
public TestData customDataField;
And the test data struct will look like
public struct TestData
{
[DataAttribute("Test Float",true, true)]
public float testFLOAT;
[DataAttribute("Test Bool",true, true)]
public bool testBOOL;
[DataAttribute("Test Int", true, true)]
public int testINT;
[DataAttribute( "Test Vector2", true, true )]
public Vector2 testVECTOR2;
[DataAttribute("Test Vector3", true, true)]
public Vector3 testVECTOR3;
}
So first we go though our node class that contains the data and look for the [NodeData] attribute.
MemberInfo[] nodeMembers = thisNode.GetType().GetMembers();
// Go through each member and look for NodeData attribute
for ( int i = 0; i != nodeMembers.Length; ++i )
{
// Loop through the custom attributes from this member
object[] customAttribute = nodeMembers[i].GetCustomAttributes( false );
if ( customAttribute != null )
{
// Look for the NodeData attribute
for ( int j = 0; j != customAttribute.Length; ++j )
{
if ( customAttribute[j].ToString() == "NodeData" )
{
// Parse the node data
}
}
}
}
We then go through that object and look for the [DataAttribute] attributes
MemberInfo[] dataMembers = nodeDataType.GetMembers();
// Look through each member as search for DataAttribute
foreach ( MemberInfo dataMember in dataMembers )
{
object[] attributes = dataMember.GetCustomAttributes( false );
foreach ( object attribute in attributes )
{
if ( attribute.ToString() == "DataAttribute" )
{
}
}
}
Finally we build our gui control. To do this we need to know the value type and a method to create the correct control for that type, this simplest way to do this is by using a dictionary.
static Dictionary<Type, Delegate> _GUIValueList = new Dictionary<Type, Delegate>();
By using the type as the key and a delegate as the data we just need to pass the value type into the dictionary which will then call the correct method to create that GUI control
Type nodeDataType = nodeDataObject.GetType();
FieldInfo fieldInfo = nodeDataType.GetField( dataMember.Name );
object value = fieldInfo.GetValue( nodeDataObject );
Type valueType = value.GetType();
_GUIValueList[valueType].DynamicInvoke( name, input, output, nodeDataObject, fieldInfo, nodeControls);
And that’s pretty much it. So now when I create a node I just add the data I need and it will automatically create and setup the GUI controls needed to display on that node. For example the test data struct above will create this a node like this

Now if I want to get rid of vector 3 and add an int I would do the following
public struct TestData
{
[DataAttribute("Test Float",true, true)]
public float testFLOAT;
[DataAttribute("Test Bool",true, true)]
public bool testBOOL;
[DataAttribute("Test Int", true, true)]
public int testINT;
[DataAttribute( "Test Vector2", true, true )]
public Vector2 testVECTOR2;
[DataAttribute( "Test Int 2", true, true )]
public int test2INT;
}

My next post will be either more work on a game I am doing in my spare time or how I developed the next part of my PhD project which will be a utility based ai editor/system.
See you all then. 🙂