IN THIS ARTICLE
Creating Custom Nodeable Nodes in Script Canvas
This topic guides you through how to create custom Script Canvas Nodeable Nodes step by step.
You’ll see the term nodeable used throughout the O3DE source code. A nodeable can refer to both the node that appears in the Node Palette as a result of the AzAutoGen processing, and the mechanism by which a compiled Script Canvas graph can invoke C++ functions.
Step 1: Adding support for custom nodeable nodes to a Gem
Note:This step is only required once for first time custom nodeable node creation.
In your Gem’s Code/CMakeLists.txt
, add a section for AUTOGEN_RULES
and declare Gem::ScriptCanvas
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 the StartingPointInput Gem’s Code/CMakeLists.txt
that supports Script Canvas custom nodes with following required changes:
Gem::ScriptCanvas
must be declared in theBUILD_DEPENDENCIES
of theSTATIC
libraryAdd an
AUTOGEN_RULES
section for custom free function under theSTATIC
libraryAUTOGEN_RULES *.ScriptCanvasNodeable.xml,ScriptCanvasNodeable_Header.jinja,$path/$fileprefix.generated.h *.ScriptCanvasNodeable.xml,ScriptCanvasNodeable_Source.jinja,$path/$fileprefix.generated.cpp *.ScriptCanvasNodeable.xml,ScriptCanvasNodeableRegistry_Header.jinja,AutoGenNodeableRegistry.generated.h *.ScriptCanvasNodeable.xml,ScriptCanvasNodeableRegistry_Source.jinja,AutoGenNodeableRegistry.generated.cpp
The
STATIC
library must be declared directly in theBUILD_DEPENDENCIES
of the Gem runtime module (and it should be included as part of editor module build dependencies hierarchy)StartingPointInput.Static
includes two .cmake file lists.- We include the common files and the platform specific files which are set in
startingpointinput_files.cmake
. - We include AzAutoGen ScriptCanvas free function required templates which are set in
startingpointinput_autogen_files.cmake
(We recommend to keep this file separately for clear scope)
Example contents of
startingpointinput_autogen_files.cmake
:set(FILES ... ${LY_ROOT_FOLDER}/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Header.jinja ${LY_ROOT_FOLDER}/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Source.jinja ${LY_ROOT_FOLDER}/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeableRegistry_Header.jinja ${LY_ROOT_FOLDER}/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeableRegistry_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 StartingPointInput.Static STATIC
NAMESPACE Gem
FILES_CMAKE
startingpointinput_files.cmake
startingpointinput_autogen_files.cmake # 4
...
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
AZ::AzFramework
CryCommon
Gem::ScriptCanvas # 1
AUTOGEN_RULES # 2
...
*.ScriptCanvasNodeable.xml,ScriptCanvasNodeable_Header.jinja,$path/$fileprefix.generated.h
*.ScriptCanvasNodeable.xml,ScriptCanvasNodeable_Source.jinja,$path/$fileprefix.generated.cpp
*.ScriptCanvasNodeable.xml,ScriptCanvasNodeableRegistry_Header.jinja,AutoGenNodeableRegistry.generated.h
*.ScriptCanvasNodeable.xml,ScriptCanvasNodeableRegistry_Source.jinja,AutoGenNodeableRegistry.generated.cpp
)
ly_add_target(
NAME StartingPointInput ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE Gem
FILES_CMAKE
startingpointinput_shared_files.cmake
...
BUILD_DEPENDENCIES
PRIVATE
AZ::AzFramework
Gem::StartingPointInput.Static # 3
)
...
Step 2: Create an XML file for code generation
Prepare for code generation by creating an XML file that contains information about the node’s class, input pins, output pins, and associated tooltip text. AzAutoGen uses this file to generate C++ code used by your node class when implementing your node’s functionality.Note:The exact schema to follow when creating your XML files can be found here: ScriptCanvasNodeable.xsd
We’ll use the following XML, copied from the O3DE source for the Input Handler node.
File: InputHandlerNodeable.ScriptCanvasNodeable.xml
<ScriptCanvas Include="Source/InputHandlerNodeable.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Class Name="InputHandlerNodeable" # 1
Namespace="StartingPointInput" # 2
QualifiedName="StartingPointInput::InputHandlerNodeable" # 3
PreferredClassName="Input Handler" # 4
Category="Input" # 5
Description="Handle processed input events found in input binding assets">
<Output Name="Pressed" Description="Signaled when the input event begins." > # 6
<Parameter Name="value" Type="float"/>
</Output>
<Output Name="Held" Description="Signaled while the input event is active." > # 6
<Parameter Name="value" Type="float"/>
</Output>
<Output Name="Released" Description="Signaled when the input event ends." > # 6
<Parameter Name="value" Type="float"/>
</Output>
<Input Name="Connect Event" Description="Connect to input event name as defined in an input binding asset."> # 7
<Parameter Name="Event Name" Type="AZStd::string" Description="Event name as defined in an input binding asset. Example 'Fireball'."/>
</Input>
</Class>
</ScriptCanvas>
Step 3: Create the node class files
The next step is to implement the C++ functions that will be invoked by the Script Canvas node. These source files reference the auto-generated source for your node, and use the ScriptCanvas::Nodeable
class as a base class.
There are three critical parts that every Script Canvas nodeable header file needs:
- It must derive from
ScriptCanvas::Nodeable
. - It must contain the node definition macro
SCRIPTCANVAS_NODE
. - It must include the generated header.
The following code fragment from the InputHandlerNodeable
header for the Input Handler node demonstrates these requirements:
File: InputHandlerNodeable.h
#include <ScriptCanvas/Core/Nodeable.h>
#include <ScriptCanvas/Core/NodeableNode.h>
#include <ScriptCanvas/CodeGen/NodeableCodegen.h>
#include <StartingPointInput/InputEventNotificationBus.h>
#include <Source/InputHandlerNodeable.generated.h> // (3)
namespace StartingPointInput
{
//////////////////////////////////////////////////////////////////////////
/// Input handles raw input from any source and outputs Pressed, Held, and Released input events
class InputHandlerNodeable
: public ScriptCanvas::Nodeable // (1)
, protected InputEventNotificationBus::Handler
{
SCRIPTCANVAS_NODE(InputHandlerNodeable) // (2)
...
};
}
Step 4: Add source files to CMake
Add the XML and class source files to one of Gem’s .cmake files.
For example, in InputHandlerNodeable
we must add the following lines:
File: startingpointinput_files.cmake
set(FILES
...
Source/InputHandlerNodeable.h
Source/InputHandlerNodeable.cpp
Source/InputHandlerNodeable.ScriptCanvasNodeable.xml
...
)
Step 5: Register the new node
Note:This step is only required once for first time nodeable 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. Use the StartingPointInput Gem from the O3DE source as a reference:
In your Gem’s module or system component, include the auto-generated registry header file, and invoke REGISTER_SCRIPTCANVAS_AUTOGEN_NODEABLE
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 the StartingPointInput example, it isAutoGenNodeableRegistry.generated.h
.
Note:The sanitized Gem target name should contain letters and numbers only. In the StartingPointInput example, it isStartingPointInputStatic
which refers to theStartingPointInput.Static
target.
For example, in
StartingPointInputGem.cpp
:
#include <AutoGenNodeableRegistry.generated.h>
...
REGISTER_SCRIPTCANVAS_AUTOGEN_NODEABLE(StartingPointInputStatic);
...
Advanced ScriptCanvasNodeable.xml usage
This topic explores additional features that we support in the nodeable XML file.
Presets
Presets are a way to pre configure default attributes across all XML tags for common types of nodeables. The currently implemented presets are shown below.
Compact
You can use the Compact preset for smaller compact style nodes that do not use execution slots.
As an example, here is the XML file for the += node:
File: CompactAddNodeable.ScriptCanvasNodeable.xml
<?xml version="1.0" encoding="utf-8"?>
<ScriptCanvas Include="Include/ScriptCanvas/Libraries/Compact/BasicOperators/CompactAddNodeable.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../AutoGen/ScriptCanvasNodeable.xsd">
<Class Name="CompactAddNodeable"
QualifiedName="Nodeables::CompactAddNodeable"
PreferredClassName="+="
Category="Compact/Basic Operators"
Namespace="ScriptCanvas"
Description="Adds the first input number to the second input number"
Preset="compact">
<Input Name="In" OutputName="Out">
<Parameter Name="a" Type="float"/>
<Parameter Name="b" Type="float"/>
<Return Name="Out" Type="float"/>
</Input>
</Class>
</ScriptCanvas>
Base and derived nodeable node
If you have shared logic across multiple nodeable nodes, you can create a base node and multiple derived nodes.
The following example uses the O3DE source for the Time Delay node:
File: TimeDelayNodeable.ScriptCanvasNodeable.xml
<ScriptCanvas Include="Include/ScriptCanvas/Libraries/Time/TimeDelayNodeable.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Class Name="TimeDelayNodeable"
QualifiedName="Nodeables::Time::TimeDelayNodeable"
PreferredClassName="Time Delay"
Base="Nodeables::Time::BaseTimer" # Declare base class as Nodeables::Time::BaseTimer
Category="Timing"
Namespace="ScriptCanvas"
Description="Delays all incoming execution for the specified number of ticks">
<Input Name="Start" Description="">
<Parameter Name="Delay" Type="Data::NumberType" DefaultValue="0.0" Description="The amount of time to delay before the Done is signalled."/>
</Input>
<Output Name="Done" Description="Signaled after waiting for the specified amount of times."/>
<PropertyInterface Property="m_timeUnitsInterface" Name="Units" Type="Input" Description="Units to represent the time in."/>
</Class>
</ScriptCanvas>
The TimeDelayNodeable
class implements a base class, called BaseTimer
. In the following base class XML, you can see that the base class defines the shared properties, “Units” and “TickOrder”:
File:
BaseTimer.ScriptCanvasNodeable.xml
<?xml version="1.0" encoding="utf-8"?>
<ScriptCanvas Include="Include/ScriptCanvas/Internal/Nodeables/BaseTimer.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Class Name="BaseTimer"
QualifiedName="ScriptCanvas::Nodeables::Time::BaseTimer"
PreferredClassName="BaseTimer"
Uuid="{64814C82-DAE5-9B04-B375-5E47D51ECD26}"
BaseClass="True" # Declare this nodeable as a base class, so it won't be reflected as a node
Category="Timing"
Description="Provides a basic interaciton layer for all time based nodes for users(handles swapping between ticks and seconds).">
<Property Name="m_timeUnits" Type="int" DefaultValue="0" Serialize="true">
<PropertyData Name="Units"
Description="Units to represent the time in."
Serialize="true"
UIHandler="AZ::Edit::UIHandlers::ComboBox">
<EditAttribute Key="AZ::Edit::Attributes::GenericValueList" Value="&BaseTimer::GetTimeUnitList"/>
<EditAttribute Key="AZ::Edit::Attributes::PostChangeNotify" Value="&BaseTimer::OnTimeUnitsChanged"/>
</PropertyData>
</Property>
<Property Name="m_tickOrder" Type="int" DefaultValue="static_cast<int>(AZ::TICK_DEFAULT)" Serialize="true">
<PropertyData Name="TickOrder" Description="When the tick for this time update should be handled."/>
</Property>
</Class>
</ScriptCanvas>
Note:For further node name, tooltip, and category customization, please refer to Text Replacement.