Creating custom ProtoFlux nodes comes in two parts, the node itself and the binding. The node itself does all the calculations while the binding tells FrooxEngine how to generate the in game node[Citation needed].
There are many types of ProtoFlux nodes, and this page will cover most of them. This page will not cover writing ProtoFlux bindings, due to the much more complicated nature of those.
Getting Started
- Add references to
- FrooxEngine
- FrooxEngine.ProtoFlux
- ProtoFlux.Core
- ProtoFlux.Runtimes.Execution
- Assign a namespace to your node.
- This namespace should contain your node's path.
- Your namespace must start with
ProtoFlux.Runtimes.Execution.Nodes
- A good example:
ProtoFlux.Runtimes.Execution.Nodes.MyPlugin.Users
, for a node in theMyPlugin/Users
category.
- Assign a node category
- To do this, add
[NodeCategory("PATH")]
before your node class. - Some examples:
[NodeCategory("MyPlugin/Math/Constants")]
[NodeCategory("MyPlugin/Users")]
- To do this, add
- Optional: Set a friendly name for your node.
- These convert your node's class name into another name, such as how the Conditional nodes appear as
?:
- To do this: Add in
[NodeName("NAME", SV)]
underneath your node category, whereNAME
is your node name andSV
is if this custom name only shows in overview mode.
- These convert your node's class name into another name, such as how the Conditional nodes appear as
- Optional: Set if your node is Continuously Changing.
- This makes it so your node is constantly evaluated, rather than whenever needed. Adding this flag means your output doesn't need to run through a Continuously Changing Relay to be updated every cycle.
- To do this: Add in
[ContinuouslyChanging]
before your class definition. - NOTE: This is incompatible with callable nodes, as those are evaluated every time they are called.
Creating input and output variables
- Private variables are immune from any of the following rules and are defined the same way as in regular C#.
All inputs and outputs must follow the following rules:
- They must be the ProtoFlux Wrapped Equivalent of your original type. Such as
ValueInput<T>
, whereT
is your target type. - They must be
readonly
- The name of the variable is what will show up in game
Inputs go immediately after your node class definition and outputs are placed after the inputs.
A warning about inputs
Node inputs can return any of:
- null
- your_value?
- your_value
and should be handled accordingly. Failure to handle a null correctly will result in a session crash upon the node's evaluation. Here are some examples of ways to handle null, by simply returning null.
Basic "if" check:
if (MyWrappedVariable.Evaluate(context) == null) { return null; } return MyWrappedVariable.Element;
Null conditional operators
return MyWrappedVariable?.Element;
Or you can use your own null handling, such as assigning default values if the input is null!
Node specific tutorials
Value/Object Function Nodes
These are the most simple type of node, they take a number of variables as inputs and return a singular output. Choosing whether to use a Value Function or Object Function is dependent on your output type. If you're outputting a value, you use the Value Function; if outputting an object, you use the Object Function.
Creating a Value/Object Function class
- Create a new public class which inherits
ValueFunctionNode<C, T>
orObjectFunctionNode<C, T>
- It should look like this:
public class MyNode : ValueFunctionNode<C, T>
orpublic class MyNode : ObjectFunctionNode<C, T>
- C is a Node Context type
- T is your output type.
- It should look like this:
- Add in your variables, following the guide above. You don't need to define an output for these node types, as it's implicit through the class type (T)
Adding functionality
To add functionality into your node, you need to override the Compute(context)
function and return a singular value.
To get the data from an input, you should use MyInput.Evaluate(context)
An example of returning a user's username:
using FrooxEngine; using FrooxEngine.ProtoFlux; using ProtoFlux.Core; using ProtoFlux.Runtimes.Execution; namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Users; [NodeCategory("Obsidian/Users")] [NodeName("User Username", false)] [ContinuouslyChanging] public class ExampleNode : ObjectFunctionNode<FrooxEngineContext, string> { public readonly ObjectInput<User> WrappedUser; protected override string Compute(FrooxEngineContext context) { // Unwrap our user from the input. User unwrapped_user = WrappedUser.Evaluate(context); // I assume you read the warning above, but if not: // WARNING! If your unwrapped variable is null, you should handle it accordingly! // Here we return null if the variable is null. // (you could also use "return unwrapped_user?.UserName;" to return null or the username safely.) // Failure to handle a null correctly will result in a session crash! if (unwrapped_user == null) { return null; } return unwrapped_user.UserName; } }
When used in game (and bound), this creates a node called ExampleNode
in the browser but says "User Username" as the name. It takes one input, called WrappedUser
(as that's what we called it in code) and outputs the user's username!
Void Nodes
TODO
Next steps
After creating your node, you might rush to see it in game but it wont appear yet: it still needs a binding. You either need to create your own or use a binding generator. Here are some open source generators:
- TODO