Repository navigation
Generated Bindings Guide
Mirrored from
GENERATED_BINDINGS_GUIDE.mdin the repo root. Edit the source file and re-sync this page rather than editing only here.
This guide explains how to run the O3DESharp Binding Generator, compile the
generated .g.cs wrappers into a reusable DLL, and reference that DLL from your
game scripts.
- Overview — What Gets Generated
- Running the Binding Generator
- Understanding the Output
- Packaging Generated Bindings into a DLL
- Referencing the Bindings DLL from Game Scripts
- Automatic Generation via MSBuild (Design-Time)
- Registering Bindings on the C++ Side
- Worked Example: End-to-End Workflow
- Configuration Reference
- Troubleshooting
Which backend should I use? The Binding Generator has two backends, selected with
--source.--source reflectionis the default and the recommended choice for everything a C# script would actually want to call — it readsreflection_data.json(a dump of O3DE'sBehaviorContext, the same data Lua / ScriptCanvas / Python bindings consume) instead of parsing headers. Prerequisite: launch the O3DE Editor for your project once first, so it writes outreflection_data.json, then point--reflection-dataat that file. This section describes--source clang, which reads your C++ headers directly (using ClangSharp / libclang) instead — heavier, and only needed for types not yet reflected toBehaviorContext.
The --source clang backend reads your C++ headers (using ClangSharp / libclang) and
produces:
| Output File | Language | Purpose |
|---|---|---|
InternalCalls.g.cs |
C# |
delegate* unmanaged<> function-pointer fields — the raw native bridge |
{ClassName}.g.cs |
C# | Type-safe wrapper class with methods, properties, XML docs |
{EnumName}.g.cs |
C# | C# enum mirroring a C++ enum class
|
FluentExtensions.g.cs |
C# |
With*() builder methods for fluent chaining |
Metadata.g.cs + metadata.json
|
C# / JSON | Runtime reflection metadata (used by hot reload) |
{GemName}.csproj |
MSBuild | A pre-configured project that compiles all the above into a DLL |
BindingRegistration.g.cpp |
C++ | Coral AddInternalCall + UploadInternalCalls registration |
{GemName}_HotReload.g.h |
C++ | Re-registration callbacks for hot reload |
Key idea: the generated .g.cs files should be compiled into a DLL
(one per Gem) that your game scripts reference — just like you reference
O3DE.Core.dll today.
.NET 9.0 SDK (dotnet --list-sdks should show a 9.x)
ClangSharp 17.0.1 (pulled automatically by NuGet on first build)
cd Gems/O3DESharp/Code/Tools/BindingGenerator/O3DESharp.BindingGenerator
dotnet build -c Releasedotnet run -- init-config
# Creates binding_config.json in the current directory# All enabled gems, using the ClangSharp header-parser backend described
# in this guide:
dotnet run -- generate --project F:\o3de --source clang
# Specific gems only:
dotnet run -- generate --project F:\o3de --source clang --gems MyGem,PhysicsGem
# Verbose output + force full regeneration:
dotnet run -- generate --project F:\o3de --source clang --verbose --forceTo use the default reflection backend instead (recommended for normal gameplay-scripting use), launch the Editor once to produce
reflection_data.json, then run:dotnet run -- generate --project F:\o3de --source reflection --reflection-data F:\o3de\Generated\reflection_data.json
You can also drive the generator from the editor or from Python directly:
From the editor menu (recommended for interactive use):
Tools → C# Bindings → Generate Bindings
That action calls into csharp_editor_bootstrap.generate_bindings, which
discovers active gems, builds a BindingGeneratorConfig, and invokes the
ClangSharp tool with the same arguments as the CLI flow above.
From Python (e.g. an Editor Python Console snippet or a batch script):
# Editor Python Console
from Editor.Scripts.csharp_binding_generator import ClangSharpInvoker, BindingGeneratorConfig
invoker = ClangSharpInvoker() # auto-locates the tool csproj
config = BindingGeneratorConfig(
incremental_build=True,
require_export_attribute=False,
verbose=True,
)
result = invoker.generate_bindings(
project_path="C:/path/to/your/O3DE/project",
config=config,
)
print(f"success={result.success}, classes={result.total_classes}, "
f"files={len(result.generated_files)}")Note (Phase 9): earlier versions of this gem shipped a separate Python generator that consumed a
reflection_data.jsondump fromO3DESharpReflectionDataExportRequestBus, plus aBindingGenerationOrchestratorclass for chaining steps. That whole path is deprecated and removed. The classesBindingGenerationOrchestrator,load_reflection_data_from_json,TypeMapper, and the standaloneEditor/Scripts/generate_bindings.py --build-dllsCLI no longer exist. The only generator is now the C# ClangSharp tool above —ClangSharpInvokeris just a thin Python wrapper arounddotnet run --project .../O3DESharp.BindingGenerator.csproj.
[MultiGem] Generating bindings for gem 'PhysicsGem'
[CSharpGen] Generated: Assets/Scripts/PhysicsGem/InternalCalls.g.cs
[CSharpGen] Generated: Assets/Scripts/PhysicsGem/RigidBodyComponent.g.cs
[CSharpGen] Generated: Assets/Scripts/PhysicsGem/ColliderComponent.g.cs
[CSharpGen] Generated: Assets/Scripts/PhysicsGem/ExampleState.g.cs
[CSharpGen] Generated 3 wrapper classes and 1 enums
[CppGen] Generated: Code/Source/Scripting/Generated/BindingRegistration.g.cpp
[CppGen] Generated: Code/Source/Scripting/Generated/PhysicsGem_HotReload.g.h
[ExtGen] Generated: Assets/Scripts/PhysicsGem/FluentExtensions.g.cs (5 extension methods)
[MetaGen] Generated: Assets/Scripts/PhysicsGem/Metadata.g.cs
[ProjGen] Generated: Assets/Scripts/PhysicsGem/PhysicsGem.csproj
For a C++ header like:
// RigidBodyComponent.h
class RigidBodyComponent
{
public:
AZ::Vector3 GetLinearVelocity() const;
void SetLinearVelocity(const AZ::Vector3& velocity);
float GetMass() const;
void ApplyForce(const AZ::Vector3& force);
bool IsKinematic;
};The generator produces:
RigidBodyComponent.g.cs — the class you use in game code:
// AUTO-GENERATED FILE - DO NOT EDIT
using System;
using Coral.Managed.Interop;
namespace O3DE.PhysicsGem
{
/// <summary>
/// RigidBodyComponent
/// </summary>
public class RigidBodyComponent
{
public bool IsKinematic { get; set; }
/// <summary>
/// Get the linear velocity.
/// </summary>
public unsafe Vector3 GetLinearVelocity()
{
return InternalCalls.RigidBodyComponent_GetLinearVelocity(/* this pointer */);
}
/// <summary>
/// Set the linear velocity.
/// </summary>
public unsafe void SetLinearVelocity(Vector3 velocity)
{
InternalCalls.RigidBodyComponent_SetLinearVelocity(/* this pointer */, velocity);
}
public unsafe float GetMass()
{
return InternalCalls.RigidBodyComponent_GetMass(/* this pointer */);
}
public unsafe void ApplyForce(Vector3 force)
{
InternalCalls.RigidBodyComponent_ApplyForce(/* this pointer */, force);
}
}
}InternalCalls.g.cs — the raw function pointers (Coral binds these at load):
// AUTO-GENERATED FILE - DO NOT EDIT
using System;
using System.Runtime.InteropServices;
using Coral.Managed.Interop;
namespace O3DE.PhysicsGem
{
/// <summary>
/// Internal calls to native PhysicsGem C++ functions.
/// DO NOT call these directly - use the wrapper classes instead.
/// </summary>
internal static unsafe class InternalCalls
{
#pragma warning disable 0649
// RigidBodyComponent Functions
internal static delegate* unmanaged<IntPtr, Vector3> RigidBodyComponent_GetLinearVelocity;
internal static delegate* unmanaged<IntPtr, Vector3, void> RigidBodyComponent_SetLinearVelocity;
internal static delegate* unmanaged<IntPtr, float> RigidBodyComponent_GetMass;
internal static delegate* unmanaged<IntPtr, Vector3, void> RigidBodyComponent_ApplyForce;
#pragma warning restore 0649
}
}FluentExtensions.g.cs — fluent builder methods:
namespace O3DE.PhysicsGem
{
public static class FluentExtensions
{
/// <summary>
/// Fluent: Set linear velocity and return the same object for chaining.
/// </summary>
public static RigidBodyComponent WithLinearVelocity(
this RigidBodyComponent self, Vector3 velocity)
{
self.SetLinearVelocity(velocity);
return self;
}
}
}namespace O3DE.PhysicsGem
{
public enum ExampleState
{
Idle = 0,
Running = 1,
Paused = 2,
Stopped = 3
}
}<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>PhysicsGem</AssemblyName>
<RootNamespace>O3DE.PhysicsGem</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="O3DE.Core">
<HintPath>../../bin/O3DE.Core/O3DE.Core.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>If you used --build-dlls during generation (see Section 2), the DLLs are
already compiled. The orchestrator generates a .csproj per gem and runs
dotnet build -c Release on each one.
Each generated .csproj looks like:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>O3DE.Bindings.PhysicsGem</AssemblyName>
<RootNamespace>O3DE.PhysicsGem</RootNamespace>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableDefaultCompileItems>true</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<Reference Include="O3DE.Core">
<HintPath>...auto-detected.../O3DE.Core.dll</HintPath>
</Reference>
</ItemGroup>
</Project>The orchestrator automatically locates O3DE.Core.dll and adds a <Reference>
so the generated code can resolve the core API types.
The generated .csproj is also ready to build manually:
cd Assets/Scripts/PhysicsGem # or wherever the generated .csproj lives
dotnet build -c ReleaseOutput:
Assets/Scripts/PhysicsGem/
├── bin/
│ └── Release/
│ └── net9.0/
│ ├── PhysicsGem.dll ← this is your bindings DLL
│ └── PhysicsGem.xml ← XML docs for IntelliSense
├── InternalCalls.g.cs
├── RigidBodyComponent.g.cs
├── FluentExtensions.g.cs
├── Metadata.g.cs
├── metadata.json
└── PhysicsGem.csproj
Copy the built DLL into your project's scripting directory so the engine loads it:
copy bin\Release\net9.0\PhysicsGem.dll ..\..\Bin\Scripts\PhysicsGem.dllOr set the output path directly in the .csproj:
<PropertyGroup>
<OutputPath>..\..\Bin\Scripts\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>If you have bindings for multiple gems, create a solution:
dotnet new sln -n O3DEBindings
dotnet sln add Assets/Scripts/PhysicsGem/PhysicsGem.csproj
dotnet sln add Assets/Scripts/ScriptCanvasGem/ScriptCanvasGem.csproj
dotnet sln add Assets/Scripts/AudioGem/AudioGem.csproj
dotnet build O3DEBindings.sln -c ReleaseGem-to-gem dependencies are handled automatically: if PhysicsGem depends on
MathGem, the generated PhysicsGem.csproj already has
<ProjectReference Include="../MathGem/MathGem.csproj" />.
Now your game script project just adds a <Reference> (or <ProjectReference>)
to the bindings DLL:
<!-- MyGame.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<!-- The hand-written core scripting API -->
<Reference Include="O3DE.Core">
<HintPath>../../Bin/Scripts/O3DE.Core.dll</HintPath>
</Reference>
<!-- The generated binding DLLs -->
<Reference Include="PhysicsGem">
<HintPath>../../Bin/Scripts/PhysicsGem.dll</HintPath>
</Reference>
<Reference Include="AudioGem">
<HintPath>../../Bin/Scripts/AudioGem.dll</HintPath>
</Reference>
</ItemGroup>
</Project>If both projects live in the same solution (recommended during development):
<ItemGroup>
<Reference Include="O3DE.Core">
<HintPath>../../Bin/Scripts/O3DE.Core.dll</HintPath>
</Reference>
<ProjectReference Include="../PhysicsGem/PhysicsGem.csproj" />
</ItemGroup>This way, dotnet build on your game project automatically rebuilds the binding
DLL when headers change (if MSBuild task integration is enabled — see next section).
using O3DE;
using O3DE.PhysicsGem; // ← the generated namespace
namespace MyGame
{
public class PhysicsDemo : ScriptComponent
{
public override void OnCreate()
{
Debug.Log("PhysicsDemo starting");
}
public override void OnUpdate(float deltaTime)
{
// Use the generated wrapper class (type-safe, zero-overhead)
var body = new RigidBodyComponent();
Vector3 velocity = body.GetLinearVelocity();
if (velocity.Magnitude > 10f)
{
body.ApplyForce(-velocity.Normalized * 5f);
}
// Fluent builder pattern (from FluentExtensions.g.cs)
body.WithLinearVelocity(new Vector3(0, 5, 0));
// Enums work naturally
ExampleState state = ExampleState.Running;
if (state == ExampleState.Paused)
Debug.Log("Game is paused");
}
}
}| Generated Bindings (DLL) | Reflection API (NativeReflection) | |
|---|---|---|
| Setup | Run generator → build DLL → add reference | None — works out of the box |
| Type safety | Full compile-time checking | Runtime strings, object? returns |
| IntelliSense | Full (XML docs, parameter names) | None |
| Performance |
delegate* unmanaged<> — zero marshalling |
JSON serialization per call |
| When to use | Any component you use frequently | One-off queries, prototyping, dynamic access |
Rule of thumb: Use generated bindings for anything you call per-frame. Use the Reflection API for infrequent or exploratory access.
The generator ships with an MSBuild task package
(O3DESharp.BindingGenerator.Tasks) that can automatically re-generate bindings
every time you build — including during IntelliSense (design-time) builds.
- Build and pack the task NuGet:
cd Code/Tools/BindingGenerator/O3DESharp.BindingGenerator.Tasks
dotnet build -c Release
# Produces: bin/Release/O3DESharp.BindingGenerator.Tasks.{version}.nupkg- Add a local NuGet source:
dotnet nuget add source ./packages --name O3DELocal
copy bin\Release\*.nupkg .\packages\- Reference the package from your binding
.csproj:
<PackageReference Include="O3DESharp.BindingGenerator.Tasks" Version="1.0.0" />Or use the Import approach (already present in the generated .csproj):
<Import Project="$(O3DEBindingGeneratorTasksPath)"
Condition="Exists('$(O3DEBindingGeneratorTasksPath)')" />| Build Phase | MSBuild Target | What It Does |
|---|---|---|
| Before Compile | O3DEGenerateBindings |
Runs the generator; includes *.g.cs in <Compile>
|
| Design-Time | O3DEDesignTimeGenerate |
Same, but incremental-only — keeps IntelliSense fast |
| Clean | O3DECleanBindings |
Deletes all *.g.cs, *.g.cpp, *.g.h, metadata.json
|
Set these in your .csproj <PropertyGroup> to customize behavior:
| Property | Default | Description |
|---|---|---|
O3DEProjectPath |
../../project.json |
Path to project.json or gem.json |
O3DEBindingConfig |
../../binding_config.json |
Path to config file |
O3DEGeneratorPath |
(auto-detected) | Path to the generator executable |
O3DEOutputPath |
../../Assets/Scripts |
Where generated files go |
O3DEGenerateBindings |
true |
Master enable/disable |
O3DEDesignTimeGenerate |
true |
IntelliSense during editing |
O3DEIncremental |
true |
Skip unchanged files (hash-based) |
O3DEForceRegen |
false |
Force full regeneration |
O3DEVerbose |
false |
Detailed generator output |
O3DECleanBindings |
false |
Remove generated files on dotnet clean
|
<ItemGroup>
<O3DEGems Include="PhysicsGem" />
<O3DEGems Include="AudioGem" />
</ItemGroup>Leave empty to process all enabled gems in binding_config.json.
The generated C++ file (BindingRegistration.g.cpp) must be called from your
gem's module to wire up the function pointers that the C# InternalCalls expect.
In your gem's system component or module initialization:
#include "Scripting/Generated/BindingRegistration.g.cpp"
void MyGemSystemComponent::Activate()
{
// After Coral is initialized and the managed assembly is loaded:
Coral::ManagedAssembly* assembly = GetLoadedAssembly("PhysicsGem");
PhysicsGem::Generated::RegisterBindings(assembly);
}// Generated — BindingRegistration.g.cpp
namespace PhysicsGem::Generated
{
void RegisterBindings(Coral::ManagedAssembly* assembly)
{
assembly->AddInternalCall("PhysicsGem.InternalCalls",
"RigidBodyComponent_GetLinearVelocity",
reinterpret_cast<void*>(&RigidBodyComponent_GetLinearVelocity));
assembly->AddInternalCall("PhysicsGem.InternalCalls",
"RigidBodyComponent_SetLinearVelocity",
reinterpret_cast<void*>(&RigidBodyComponent_SetLinearVelocity));
// ... all other methods ...
assembly->UploadInternalCalls();
}
}Each AddInternalCall maps a fully-qualified C# field name
("PhysicsGem.InternalCalls" + "RigidBodyComponent_GetLinearVelocity") to the
address of a native C++ function. UploadInternalCalls() commits them all at once
to the Coral runtime.
The generated {GemName}_HotReload.g.h provides:
namespace PhysicsGem::Generated
{
void UnregisterBindings(Coral::ManagedAssembly* assembly);
bool HotReload(Coral::ManagedAssembly* oldAssembly, Coral::ManagedAssembly* newAssembly);
}Call HotReload(old, new) when the assembly is reloaded to re-register the
function pointers with the new assembly.
This section walks through generating bindings for a hypothetical "AudioGem" and using them in a game project.
Edit binding_config.json (or create one with dotnet run -- init-config):
{
"global": {
"cSharpNamespace": "O3DE",
"cSharpOutputPath": "Assets/Scripts/{GemName}",
"cppOutputPath": "Code/Source/Scripting/Generated",
"incrementalBuild": true,
"requireExportAttribute": false
},
"gems": {
"AudioGem": {
"enabled": true,
"headerPatterns": [
"Gems/AudioGem/Code/Include/**/*.h"
],
"excludePatterns": [
"**/Platform/**",
"**/Tests/**"
]
}
}
}cd F:\o3de\Gems\O3DESharp\Code\Tools\BindingGenerator\O3DESharp.BindingGenerator
dotnet run -- generate `
--project F:\o3de `
--config F:\o3de\binding_config.json `
--gems AudioGem `
--verboseOutput:
[Discover] Found gem: AudioGem (F:\o3de\Gems\AudioGem)
[Parser] Parsing: AudioTriggerComponent.h (3 classes, 1 enum)
[CSharpGen] Generated: Assets/Scripts/AudioGem/InternalCalls.g.cs
[CSharpGen] Generated: Assets/Scripts/AudioGem/AudioTriggerComponent.g.cs
[CSharpGen] Generated: Assets/Scripts/AudioGem/AudioEnvironmentComponent.g.cs
[CSharpGen] Generated: Assets/Scripts/AudioGem/AudioState.g.cs
[ExtGen] Generated: Assets/Scripts/AudioGem/FluentExtensions.g.cs
[ProjGen] Generated: Assets/Scripts/AudioGem/AudioGem.csproj
[CppGen] Generated: Code/Source/Scripting/Generated/BindingRegistration.g.cpp
cd F:\o3de\Assets\Scripts\AudioGem
dotnet build -c Release
# DLL is now at: bin/Release/net9.0/AudioGem.dllcopy bin\Release\net9.0\AudioGem.dll F:\MyProject\Bin\Scripts\AudioGem.dllMyGame.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputPath>..\..\Bin\Scripts\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="O3DE.Core">
<HintPath>..\..\Bin\Scripts\O3DE.Core.dll</HintPath>
</Reference>
<Reference Include="AudioGem">
<HintPath>..\..\Bin\Scripts\AudioGem.dll</HintPath>
</Reference>
<Reference Include="Coral.Managed">
<HintPath>..\..\Bin\Scripts\Coral\Coral.Managed.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
</Project>using O3DE;
using O3DE.AudioGem;
namespace MyGame
{
public class AudioPlayer : ScriptComponent
{
public override void OnCreate()
{
// Use the generated type-safe wrapper
var trigger = new AudioTriggerComponent();
trigger.ExecuteTrigger("Play_Ambient");
// Fluent chaining
trigger
.WithVolume(0.8f)
.WithPitch(1.0f);
// Generated enums
if (trigger.GetState() == AudioState.Playing)
Debug.Log("Audio is playing!");
}
public override void OnDestroy()
{
var trigger = new AudioTriggerComponent();
trigger.ExecuteTrigger("Stop_Ambient");
}
}
}cd F:\MyProject\Assets\Scripts\MyGame
dotnet build -c Debug
# → outputs Bin/Scripts/MyGame.dll (via OutputPath above)Launch the O3DE Editor, add a C# Script Component with class MyGame.AudioPlayer,
and enter Game Mode.
| C++ Type | C# Type |
|---|---|
bool |
bool |
int, int32_t
|
int |
uint32_t, unsigned int
|
uint |
int64_t |
long |
uint64_t |
ulong |
float |
float |
double |
double |
const char*, AZStd::string
|
string |
AZ::Vector2 |
Vector2 |
AZ::Vector3 |
Vector3 |
AZ::Vector4 |
Vector4 |
AZ::Quaternion |
Quaternion |
AZ::Color |
Color |
AZ::EntityId |
ulong |
AZ::Uuid |
Guid |
Coral::NativeString |
NativeString |
Coral::Bool32 |
Bool32 |
| Unknown pointer types | IntPtr |
If you set "requireExportAttribute": true, only C++ declarations marked with the
O3DE_EXPORT_CSHARP attribute are exported:
#include <Scripting/ExportAttributes.h>
class O3DE_EXPORT_CSHARP MyClass
{
public:
O3DE_EXPORT_CSHARP void ExportedMethod(); // ✓ exported
void AlsoExported(); // ✓ exported (class-level attr)
private:
void NotExported(); // ✗ private — never exported
};
class AnotherClass
{
public:
void Invisible(); // ✗ no attribute — skipped
O3DE_EXPORT_CSHARP void ButThisIs(); // ✓ method-level attr
};When requireExportAttribute is false (the default), all public
declarations are exported — no attribute needed.
The generator automatically skips C++ types that cannot be meaningfully wrapped in C#:
-
Template instantiations — any name containing
</>(e.g.,AZ::RHI::Handle<unsigned int>). -
STL / AZStd containers —
AZStd::unordered_map,AZStd::vector,AZStd::fixed_vector,AZStd::pair,AZStd::optional, etc. -
Internal iterator types —
Iterator_VM<...>.
These types are logged once at INFO level:
Skipped 312 unsupported template/container types
If you need a binding for a specific template specialization, create a typedef
in your C++ header and reflect that instead.
C++ class names like AZ::Render::MeshComponent are sanitized before being used
as filenames:
| C++ Name | Generated Filename |
|---|---|
AZ::Render::MeshComponent |
AZ.Render.MeshComponent.g.cs |
LmbrCentral::QuadShapeConfig |
LmbrCentral.QuadShapeConfig.g.cs |
ShaderSourceData::EntryPoint |
ShaderSourceData.EntryPoint.g.cs |
Namespace separators (::) become dots, illegal Windows characters
(< > : " / \ | ? *) are removed, and filenames are truncated to 120 characters.
The C# class name is similarly sanitized (last segment of the namespace; nested
parts joined with _).
- Check that your gem is listed in
binding_config.jsonundergemswith"enabled": true. - Verify
headerPatternsmatch your header locations — use--verboseto see what files the parser scans. - If using
--require-attribute, ensure headers include<Scripting/ExportAttributes.h>and useO3DE_EXPORT_CSHARP. - Run
dotnet run -- list-gems --project /pathto confirm gem discovery.
ClangSharp requires a native libclang. On Windows this is bundled via NuGet. On Linux, install LLVM:
sudo apt install libclang-17-dev
export LD_LIBRARY_PATH=/usr/lib/llvm-17/lib:$LD_LIBRARY_PATH- Include paths may be missing in
binding_config.json. - Preprocessor defines may not match your build configuration.
- Use
--verboseto see Clang diagnostics.
The C++ side must call RegisterBindings(assembly) after the managed assembly
is loaded. Check that:
-
BindingRegistration.g.cppis compiled into your gem's C++ target. - The registration function is called in your system component's
Activate(). - The
AddInternalCalltype names match the namespace in the generated C# ("O3DE.PhysicsGem.InternalCalls").
- Ensure the
.csprojhas a<Reference>or<ProjectReference>to the bindings DLL. - If using MSBuild task integration, check that
O3DEDesignTimeGenerateistrue(the default). - Rebuild the bindings project:
dotnet build.
- Delete
.binding_cache.jsonand regenerate, or pass--force. - If using MSBuild task, set
<O3DEForceRegen>true</O3DEForceRegen>for one build.
MyProject/
├── binding_config.json
├── Assets/
│ └── Scripts/
│ ├── O3DE.Core/ ← hand-written core API (ScriptComponent, etc.)
│ │ ├── O3DE.Core.csproj
│ │ └── *.cs
│ │
│ ├── PhysicsGem/ ← GENERATED binding DLL project
│ │ ├── PhysicsGem.csproj ← auto-generated .csproj
│ │ ├── InternalCalls.g.cs ← function pointers
│ │ ├── RigidBodyComponent.g.cs ← wrapper classes
│ │ ├── FluentExtensions.g.cs ← With*() methods
│ │ └── bin/Release/net9.0/
│ │ └── PhysicsGem.dll ← compiled bindings DLL
│ │
│ └── MyGame/ ← YOUR game scripts
│ ├── MyGame.csproj ← references O3DE.Core + PhysicsGem
│ ├── PlayerController.cs
│ └── AudioPlayer.cs
│
├── Bin/
│ └── Scripts/ ← deployment directory
│ ├── Coral/
│ │ └── Coral.Managed.dll
│ ├── O3DE.Core.dll
│ ├── PhysicsGem.dll ← deployed binding DLL
│ └── MyGame.dll ← deployed game scripts
│
└── Code/
└── Source/
└── Scripting/
└── Generated/ ← C++ registration (compiled into gem)
├── BindingRegistration.g.cpp
└── PhysicsGem_HotReload.g.h
Workflow:
-
Generate:
dotnet run -- generate --project .(or automatic via MSBuild) -
Build bindings DLL:
dotnet build Assets/Scripts/PhysicsGem/ -
Build game DLL:
dotnet build Assets/Scripts/MyGame/ -
Deploy: copy DLLs to
Bin/Scripts/ - Run: launch Editor, enter Game Mode
{ "global": { // Root C# namespace prefix. Generated code lives under O3DE.{GemName}. "cSharpNamespace": "O3DE", // Where generated .g.cs + .csproj go. {GemName} is replaced per-gem. "cSharpOutputPath": "Assets/Scripts/{GemName}", // Where generated .g.cpp + .g.h go (checked into source control). "cppOutputPath": "Code/Source/Scripting/Generated", // Extra -I paths passed to ClangSharp. "includePaths": [ "${O3DE_ENGINE_PATH}/Code", "${O3DE_ENGINE_PATH}/Gems" ], // Preprocessor defines passed to ClangSharp. "defines": [ "O3DE_EXPORT_CSHARP=__attribute__((annotate(\"export_csharp\")))", "AZ_COMPILER_CLANG=1" ], // Generate FluentExtensions.g.cs (With*() methods). "generateExtensionMethods": true, // Generate Metadata.g.cs + metadata.json. "generateMetadata": true, // Use SHA256 hashing to skip unchanged files. "incrementalBuild": true, // Only export declarations marked with O3DE_EXPORT_CSHARP. // false = export ALL public declarations. "requireExportAttribute": false, "verbose": false }, "gems": { "MyGem": { "enabled": true, // Glob patterns relative to the gem root. "headerPatterns": ["Code/Include/**/*.h"], "excludePatterns": ["**/Platform/**", "**/Tests/**"], // Per-gem overrides (same keys as global): "includePaths": [], "defines": [], "cSharpNamespace": "O3DE", "requireExportAttribute": false } } }