IN THIS ARTICLE
Creating Custom Free Function Nodes in Script Canvas
Instructions
This topic guides you how to create custom Script Canvas Free Function Nodes step by step.
Note:The ScriptCanvasPhysics Gem demonstrates the finished Gem that creates custom free function nodes in ScriptCanvas. You can reference the ScriptCanvasPhysics Gem as you follow along this tutorial.
Step 1: Adding support for custom free function nodes to a Gem
Note:This step is only required once for the first time custom free function node creation.
In your Gem’s Code/CMakeLists.txt
, add a section for AUTOGEN_RULES
and declare Gem::ScriptCanvas.Extensions
as a build dependency.
The precise place for this section will vary depending on how your Gem is configured.
However, we recommend that your Gem define a STATIC
library to make the code available to both runtime and editor projects.
As an example, here is partial definition of Gem’s Code/CMakeLists.txt
that supports Script Canvas custom nodes with following required changes:
Gem::ScriptCanvas.Extensions
must be declared asBUILD_DEPENDENCIES
ofSTATIC
libraryAdd
AUTOGEN_RULES
section for custom free function underSTATIC
libraryAUTOGEN_RULES *.ScriptCanvasFunction.xml,ScriptCanvasFunctionRegistry_Header.jinja,AutoGenFunctionRegistry.generated.h *.ScriptCanvasFunction.xml,ScriptCanvasFunctionRegistry_Source.jinja,AutoGenFunctionRegistry.generated.cpp
STATIC
library must be declared directly asBUILD_DEPENDENCIES
of Gem runtime module (and it should be included as part of editor module build dependencies hierarchy)MyGem.Static
includes two .cmake file lists.- We include the common files and the platform specific files which are set in
mygem_files.cmake
. - We include AzAutoGen ScriptCanvas free function required templates which are set in
mygem_autogen_files.cmake
(We recommend to keep this file separately for clear scope)
Example contents of
mygem_autogen_files.cmake
:set(FILES ${LY_ROOT_FOLDER}/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvas_Macros.jinja ${LY_ROOT_FOLDER}/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasFunctionRegistry_Header.jinja ${LY_ROOT_FOLDER}/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasFunctionRegistry_Source.jinja )
The list of autogen templates might be different if you create custom templates for your own purposes. For example, if you were to extend Script Canvas to do something beyond what it provides “out of the box”, you could have your own set of templates to generate code in the syntax that you define. For more information, refer to the documentation on AzAutoGen.
- We include the common files and the platform specific files which are set in
...
ly_add_target(
NAME MyGem.Static STATIC
NAMESPACE Gem
FILES_CMAKE
mygem_files.cmake
mygem_autogen_files.cmake # 4
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
AZ::AzFramework
Gem::ScriptCanvas.Extensions # 1
AUTOGEN_RULES # 2
*.ScriptCanvasFunction.xml,ScriptCanvasFunctionRegistry_Header.jinja,AutoGenFunctionRegistry.generated.h
*.ScriptCanvasFunction.xml,ScriptCanvasFunctionRegistry_Source.jinja,AutoGenFunctionRegistry.generated.cpp
)
ly_add_target(
NAME MyGem ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE Gem
FILES_CMAKE
mygem_shared_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
Gem::MyGem.Static # 3
)
...
Step 2: Create an XML file for code generation
Prepare for code generation by creating an XML file that contains information about:
- (Required) The header file of functions.
- (Recommended) The namespace of functions, which is used to distinguish duplicate function name.
- (Optional) The category of functions, if not presented, will use
Global Methods
instead - (Required) The function name of each function.
AzAutoGen uses this file to generate C++ code for function registration and reflection.
For example, HelloWorldFunctions.ScriptCanvasFunction.xml
<?xml version="1.0" encoding="utf-8"?>
<ScriptCanvas>
<Library
Include="HelloWorldFunctions.h" # 1
Namespace="MyGem::HelloWorldFunctions" # 2
Category="Hello World"> # 3
<Function Name="HelloWorld"/> # 4
<Function Name="HelloWorldTo"/> # 4
</Library>
</ScriptCanvas>
Step 3: Create the function source files
The next step is to implement the C++ functions that will be invoked by the Script Canvas node.
There are two requirements need to keep in mind:
- The namespace of functions should match with
Namespace
provided in XML file. - The function name should match with specific one provided in XML file. (function overload is not supported)
For example, HelloWorldFunctions.h
#pragma once
#include <AzCore/std/string/string.h>
namespace MyGem
{
namespace HelloWorldFunctions
{
AZStd::string HelloWorld();
AZStd::string HelloWorldTo(AZStd::string name);
}
}
For example, HelloWorldFunctions.cpp
#include "HelloWorldFunctions.h"
namespace MyGem
{
namespace HelloWorldFunctions
{
AZStd::string HelloWorld()
{
return "Hello World!";
}
AZStd::string HelloWorldTo(AZStd::string name)
{
return "Hello World to " + name;
}
}
}
Step 4: Add source files to CMake
Add the XML and function source files to one of Gem’s .cmake files, for example mygem_files.cmake
.
set(FILES
...
Include/HelloWorldFunctions.h
Include/HelloWorldFunctions.cpp
Include/HelloWorldFunctions.ScriptCanvasFunction.xml
...
)
Step 5: Register the new node
Note:This step is only required once for the first time custom free function node creation.
The final step is to register the new node. To do this, you need to modify your Gem’s Gem module or system component.
In your Gem’s module or system component, include the auto-generated registry header file, and invoke REGISTER_SCRIPTCANVAS_AUTOGEN_FUNCTION
with the sanitized Gem target name.
Note:Use the same auto-generated registry header file that you declared in Step 1 underAUTOGEN_RULES
in your Gem’sCode/CMakeLists.txt
. In our example, it isAutoGenFunctionRegistry.generated.h
.
Note:The sanitized Gem target name should contain letters and numbers only. In our example, it isMyGemStatic
which refers to theMyGem.Static
target.
For example, in MyGemSystemComponent.cpp
#include <AutoGenFunctionRegistry.generated.h>
REGISTER_SCRIPTCANVAS_AUTOGEN_FUNCTION(MyGemStatic)
...
Advanced ScriptCanvasFunction.xml Usage
This topic explores additional features we support in function XML file.
Basic usage
For example, we have Sum
function, the very basic usage
namespace MyGem
{
namespace HelloWorldFunctions
{
int Sum(int a, int b);
}
}
<?xml version="1.0" encoding="utf-8"?>
<ScriptCanvas>
<Library
Include="HelloWorldFunctions.h"
Namespace="MyGem::HelloWorldFunctions"
Category="Hello World">
<Function Name="Sum"/>
</Library>
</ScriptCanvas>
The basic node will look like following
Verbose usage
For the same Sum
function, we can provide more details in XML
<Function Name="Sum">
<Parameter Name="InputA" DefaultValue="1" Description="The input A of sum function."/>
<Parameter Name="InputB" DefaultValue="1" Description="The input B of sum function."/>
</Function>
The verbose node will look like following
Branch boolean function result
In general, node will only have single Out
execution slot. But we can create branch on node based on function result.
For example, we have IsPositive
function
bool IsPositive(int input);
Basic usage
- (Required)
Branch
attribute should be indicated asBoolean
when function result type is boolean. - (Optional)
BranchWithValue
attribute should be indicated asTrue
if you want to include result, default isFalse
<Function Name="IsPositive" Branch="Boolean" BranchWithValue="True"/>
Verbose usage
<Function Name="IsPositive" Branch="Boolean" BranchWithValue="True">
<Parameter Name="Input" DefaultValue="1" Description="The input of positive check function."/>
<Out Name="Input is Positive" Description="The out slot for true branch"/>
<Out Name="Input is not Positive" Description="The out slot for false branch"/>
</Function>
Branch non-boolean function result
We can also create branch even function result is not boolean, but it has to be coupled with a helper function, which should take non-boolean result as input and return a boolean result.
For example, we can use Sum
and IsPositive
functions together
Basic usage
- (Required)
Branch
attribute should be indicated with helper function name which isIsPositive
in our example. - (Optional)
BranchWithValue
attribute should be indicated asTrue
if you want to include result, default isFalse
<Function Name="IsPositive"/>
<Function Name="Sum" Branch="IsPositive" BranchWithValue="True"/>
Verbose usage
<Function Name="Sum" Branch="IsPositive" BranchWithValue="True">
<Parameter Name="Input A" DefaultValue="1" Description="The input A of sum function."/>
<Parameter Name="Input B" DefaultValue="1" Description="The input B of sum function."/>
<Out Name="Sum Is Positive" Description="The out slot for sum result is positive branch"/>
<Out Name="Sum Is not Positive" Description="The out slot for sum result is non-Positive branch"/>
</Function>
Note:For further node name, tooltip and category customization, please refer to Text Replacement
Migration instructions for generic function nodes
This topic guides you step-by-step through migrating from generic function nodes to custom free function nodes.
Step 1: Locate generic function node usage
Generic function nodes are deprecated. Support will be removed in a future release. To prepare, we recommend that you plan for migration as early as possible. Existing graphs will break once generic function nodes are removed.
In your Gem, locate generic function node code by searching for the following three macros:
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(NODE_NAME, CATEGORY, UUID, DESCRIPTION, ...)
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(NODE_NAME, DEFAULT_FUNC, CATEGORY, UUID, DESCRIPTION, ...)
SCRIPT_CANVAS_GENERIC_FUNCTION_MULTI_RESULTS_NODE(NODE_NAME, CATEGORY, UUID, DESCRIPTION, ...)
Note:The migration process assumes that the node signature and behavior stay the same. If replacement node signature and behavior are different, you should upgrade your graph manually.
Step 2: Create replacement custom free function nodes
For detailed instructions on how to create custom free function nodes, refer to the preceding topics.
As an example, here is part of the migration we have done for Math/Matrix3x3
nodes:
Example of replacing
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE
:AZ_INLINE Data::Vector3Type GetRow(const Data::Matrix3x3Type& source, Data::NumberType row) { ... } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(GetRow, k_categoryName, "{C4E00343-3642-4B09-8CFA-2D2F1CA6D595}", "returns vector from matrix corresponding to the Row index", "Source", "Row");
Replacement function and ScriptCanvasFunction.xml content:
Data::Vector3Type GetRow(const Data::Matrix3x3Type& source, Data::NumberType row) { ... }
<Function Name="GetRow"> <Parameter Name="Source"/> <Parameter Name="Row"/> </Function>
Example of replacing
SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS
:AZ_INLINE Data::BooleanType IsClose(const Data::Matrix3x3Type& lhs, const Data::Matrix3x3Type& rhs, const Data::NumberType tolerance) { ... } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(IsClose, MathNodeUtilities::DefaultToleranceSIMD<2>, k_categoryName, "{020C2517-F02F-4D7E-9FE9-B6E91E0D6D3F}", "returns true if each element of both Matrix are equal within some tolerance", "A", "B", "Tolerance");
Replacement function and ScriptCanvasFunction.xml content:
Data::BooleanType IsClose(const Data::Matrix3x3Type& lhs, const Data::Matrix3x3Type& rhs, const Data::NumberType tolerance) { ... }
<Function Name="IsClose"> <Parameter Name="A"/> <Parameter Name="B"/> <Parameter Name="Tolerance" DefaultValue="0.01"/> </Function>
Example of replacing
SCRIPT_CANVAS_GENERIC_FUNCTION_MULTI_RESULTS_NODE
:AZ_INLINE std::tuple<Data::Vector3Type, Data::Vector3Type, Data::Vector3Type> GetRows(const Data::Matrix3x3Type& source) { ... } SCRIPT_CANVAS_GENERIC_FUNCTION_MULTI_RESULTS_NODE(GetRows, k_categoryName, "{DDF76F4C-0C79-4856-B577-7DBA092CE59B}", "returns all rows from matrix", "Source", "Row1", "Row2", "Row3");
Replacement function and ScriptCanvasFunction.xml content:
AZStd::tuple<Data::Vector3Type, Data::Vector3Type, Data::Vector3Type> GetRows(const Data::Matrix3x3Type& source) { ... }
<Function Name="GetRows"> <Parameter Name="Source"/> </Function>
Step 3: Update generic function node macros
Replace each generic function node macro with the SCRIPT_CANVAS_GENERIC_FUNCTION_REPLACEMENT
macro, using a sanitized replacement node identifier that’s derived from ScriptCanvasFunction.xml. We use Namespace
and Function Name
to guarantee the uniqueness.
As an example, in Matrix3x3.ScriptCanvasFunction.xml
- Namespace:
ScriptCanvas::Matrix3x3Functions
- Function Name:
GetRow
,GetRows
,IsClose
<ScriptCanvas>
<Library
Include="Include/ScriptCanvas/Libraries/Math/Matrix3x3.h"
Namespace="ScriptCanvas::Matrix3x3Functions"
Category="Math/Matrix3x3">
...
<Function Name="GetRow">
<Function Name="GetRows">
<Function Name="IsClose">
...
<Library/>
<ScriptCanvas/>
The sanitized replacement node identifiers are: ScriptCanvas_Matrix3x3Functions_GetRow
, ScriptCanvas_Matrix3x3Functions_GetRows
and ScriptCanvas_Matrix3x3Functions_IsClose
...
SCRIPT_CANVAS_GENERIC_FUNCTION_REPLACEMENT(GetRow, k_categoryName, "{C4E00343-3642-4B09-8CFA-2D2F1CA6D595}", "ScriptCanvas_Matrix3x3Functions_GetRow");
SCRIPT_CANVAS_GENERIC_FUNCTION_REPLACEMENT(GetRows, k_categoryName, "{DDF76F4C-0C79-4856-B577-7DBA092CE59B}", "ScriptCanvas_Matrix3x3Functions_GetRows");
SCRIPT_CANVAS_GENERIC_FUNCTION_REPLACEMENT(IsClose, k_categoryName, "{020C2517-F02F-4D7E-9FE9-B6E91E0D6D3F}", "ScriptCanvas_Matrix3x3Functions_IsClose");
...
Step 4: Upgrade existing graphs that are now out of date
At this point, the system has all the required information to do the replacement. To upgrade your graphs, do one of the following:
- Batch Processing - For convenience you can use the version explorer tool found in Script Canvas Editor in the Tools / Upgrade Graphs menu. You can select multiple source graphs in the tool and upgrade them all to the latest version. The tool will save them in the new format, or report errors if the upgrade fails.
- Single Processing - You can open a specific graph in Script Canvas Editor. The graph will upgrade automatically. Save the graph to keep the new format.