C++ Integration

Module Setup and Dependencies

The PhotonFusion.uplugin ships two modules. PhotonFusion is Type: Runtime, available on every supported platform — this is the module customer game modules link against. PhotonFusionEditor is Type: UncookedOnly and only loads in desktop editor builds; it hosts the custom K2 nodes and the debug tooling and is stripped from cooked client builds.

C#

// MyGameModule.Build.cs
public MyGameModule(ReadOnlyTargetRules Target) : base(Target)
{
    PublicDependencyModuleNames.AddRange(new[]
    {
        "Core", "CoreUObject", "Engine", "InputCore",
        "PhotonFusion", // brings in Fusion runtime + re-exported engine modules
    });
}

PhotonFusion.Build.cs re-exports the engine modules it depends on — Core, Engine, DeveloperSettings, PhysicsCore — so a downstream module that lists PhotonFusion does not need to repeat them.

Customer code includes Fusion headers — FusionActorComponent.h, FusionOnlineSubsystem.h, FusionHelpers.h, FusionMacros.h — directly with #include. The PhotonFusionUbtPlugin (the UHT exporter described below) is auto-discovered by UBT when the plugin is present in Plugins/. It does not need to be referenced from Build.cs.

The FUSION_BODY Macro

FUSION_BODY() is declared in FusionMacros.h and placed in a UCLASS or USTRUCT body immediately after GENERATED_BODY(). It mirrors the way Unreal's own generated-body macros work — a per-class anchor that the code generator hangs the generated body off.

C++

#include "MyActor.fusion.h"

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
    FUSION_BODY()

    UPROPERTY(Replicated)
    float Health;
};

The macro expands via FUSION_BODY_MACRO_COMBINE(CURRENT_FILE_ID, _, __LINE__, _FUSION_BODY) to a per-file-per-line identifier that the generated .fusion.h defines. A missing #include "<HeaderName>.fusion.h" or a missed FUSION_BODY() produces a compile error pointing at the offending class — the diagnostic is precise because every macro instance is uniquely numbered.

The .fusion.h Generated Header and Build-Tool Integration

The PhotonFusionUbtPlugin is a C# UnrealHeaderTool exporter, declared as FusionUhtCodeGen with [UhtExporter(Name = "PhotonFusionCodeGen", ModuleName = "PhotonFusion")]. It runs alongside UHT during the standard Unreal build and emits two files per header file: <Name>.fusion.h (which FUSION_BODY() expands into) and <Name>.fusion.gen.cpp (which carries the dispatcher implementations).

The exporter registers two new UHT keywords via FusionUhtKeywords. FUSION_BODY records the macro line number per class. SEND_FUSIONRPC parses the RPC function declaration that follows it, synthesises a <Name>_Receive companion declaration plus a hidden __FUSIONRPCEVENT_<Name> UPROPERTY, and generates the dispatch glue in the .fusion.gen.cpp.

For every header that uses FUSION_BODY() or SEND_FUSIONRPC, two generators emit four files:

Input Generator Output Role
MyClass.h UHT (stock) MyClass.generated.h Standard Unreal reflection — what GENERATED_BODY() expands into.
PhotonFusionUbtPlugin (Fusion UHT exporter) MyClass.fusion.h + MyClass.fusion.gen.cpp The body FUSION_BODY() expands into, plus the SEND_FUSIONRPC dispatcher implementations.

The SEND_FUSIONRPC Macro

SEND_FUSIONRPC(...) is a sentinel keyword. In C++ it expands to nothing — FusionMacros.h defines it as #define SEND_FUSIONRPC(...); — so the compiler sees an unannotated function declaration. The UBT plugin, however, parses the macro as an RPC declaration and generates the dispatch glue. The compiler and the code generator effectively see two different things, which is how the plugin avoids adding any compile-time overhead to call sites.

C++

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
    FUSION_BODY()

    SEND_FUSIONRPC(EFusionRPCTarget::SendToObjectOwner)
    void MyRpc(int32 Damage, FVector Location);

    // Developer implements this on the receive side:
    void MyRpc_Receive(int32 Damage, FVector Location);
};

For the higher-level RPC semantics, target modes (EFusionRPCTarget), and reliability/ordering guarantees, see RPC Basics.

Custom Parameter Marshalling

Blueprint-callable custom RPCs route through UFusionHelpers::AddParamToBuffer. The function is declared CustomThunk with CustomStructureParam="Value", so a wildcard BP pin can carry any UPROPERTY type into the marshaller. DEFINE_FUNCTION(UFusionHelpers::execAddParamToBuffer) is the thunk implementation: it walks the live FProperty and serializes the value into a TArray<uint8>.

The matching dispatch entry points are UFusionHelpers::GetFunctionDescriptor(Source, EventName) to look up the UFusionFunctionDescriptor from the event name, and UFusionHelpers::InvokeCustomRPC(Source, EventName, RPCId, Target, Buffer) to send the assembled buffer with an EFusionRPCTarget.

C++

// Pattern that BP nodes lower to:
TArray<uint8> Buffer;
UFusionHelpers::AddParamToBuffer(Buffer, Damage);
UFusionHelpers::AddParamToBuffer(Buffer, HitLocation);
UFusionHelpers::InvokeCustomRPC(
    SourceActor, EventName, RPCId,
    EFusionRPCTarget::SendToObjectOwner, Buffer);

SEND_FUSIONRPC is the path for native C++ RPCs. The AddParamToBuffer / InvokeCustomRPC path is what Blueprint nodes lower to — useful to know if writing custom K2 nodes or hand-rolling dispatch from C++. See RPC Basics for the customer-facing API.

Type Descriptors

UFusionTypeDescriptor (in Types/FusionTypeDescriptor.h) is the runtime metadata Fusion builds for each replicated class or struct. It caches the source UStruct*, a WordCount for the wire-format size, a TypeHash for cross-peer agreement on the type identity, and a TArray<Property*> describing every replicated field. Per-class instances live on UFusionTypeLookup in ClassDescriptors and StructDescriptors.

UFusionFunctionDescriptor is the RPC sibling. It holds the source UFunction*, a ParametersSize, and a TArray<FFusionFunctionProperty> with per-parameter WordOffset / WordCount. The wire-format hash for an incoming RPC is resolved back to the function through UFusionTypeDescriptor::EventFunctions plus the EventHashToName / EventNameToHash maps.

EFusionDataTypes Byte size (from EFusionDataTypeByteSize)
Byte1
Int / UInt4
Int16 / UInt162
Int64 / UInt648
Float4
Double8
Vector12 or 24 (depending on precision)
Quat16 or 32
StringVariable (heap-backed)
ObjectId4 or 8 (depending on the encoded reference type)
ArrayVariable (per-element)
CustomDefined by the custom descriptor builder

Custom Type Descriptor Builders

UFusionCustomTypeDescriptorBuilder (in FusionCustomTypeDescriptorBuilder.h) is the override point for type descriptors. Subclass it and override CreateDescriptor(UFusionTypeLookup*, UStruct*, FProperty*, FPropertyBuildOptions) to return a hand-built UFusionTypeDescriptor instead of the reflection-driven default. This is how Fusion implements its own special-cases — FFusionNetworkedArray and AGameStateBase both ship custom builders.

Register the builder with UFusionTypeLookup::RegisterTypeBuilder(UStruct* Target, UClass* Builder). The plugin itself does this in UFusionOnlineSubsystem startup for FFusionNetworkedArray (via UFusionNetworkedArrayBuilder) and AGameStateBase (via UFusionGameStateDescriptorBuilder).

C++

UCLASS()
class UMyDescriptorBuilder : public UFusionCustomTypeDescriptorBuilder
{
    GENERATED_BODY()
public:
    virtual UFusionTypeDescriptor* CreateDescriptor(
        UFusionTypeLookup* Lookup,
        UStruct* Target,
        FProperty* SourceProperty,
        FPropertyBuildOptions Options) override
    {
        // Build a custom descriptor and return it.
    }
};

// In module startup:
UFusionTypeLookup::RegisterTypeBuilder(
    FMyData::StaticStruct(),
    UMyDescriptorBuilder::StaticClass());

Making a USTRUCT Fusion-Serializable

Mark the USTRUCT with FUSION_BODY() and include the matching .fusion.h. The descriptor is then built automatically by UFusionTypeLookup::CreateTypeStructDescriptor, which dispatches to the right UFusionTypeBase::AddProperty overload per FProperty kind.

Control which fields are considered via FPropertyBuildOptions and the EFusionBuildStructOptions flag set. SkipNotReplicated honours CPF_RepSkip. AddDefaultProperties decides whether the default builder runs. AddStringProperties and AddNameProperties opt FString and FName properties into the descriptor — without them, strings and names are skipped even when marked Replicated (the trade-off is documented in Compatibility with Built-in Netcode).

C++

#include "MyData.fusion.h"

USTRUCT()
struct FMyData
{
    GENERATED_BODY()
    FUSION_BODY()

    UPROPERTY()
    int32 Value;

    UPROPERTY()
    FVector Position;
};

For dynamic-array members, use the FusionArraySize UPROPERTY metadata string from FusionMeta to bound capacity. FusionMeta::FusionArrayMaxSize (64) is the hard cap. GetPreAllocationSize reads the per-property value via FusionMeta::GetArraySizeRegistry() at descriptor-build time, so the slot is allocated up front and never grows past the configured size.

Async Utilities

FusionTimerAwaitable (in FusionTimerAwaitable.h) is a C++20 coroutine awaitable. It suspends the coroutine until a UWorld timer fires, implementing await_ready, await_suspend, and await_resume. The awaitable resumes immediately if its TWeakObjectPtr<UWorld> has gone stale, so the coroutine does not deadlock when the world is destroyed mid-wait.

UFusionRoomActionBase (in Actions/FusionRoomActionBase.h) is the Blueprint-async base class. It derives from UBlueprintAsyncActionBase, exposes OnSuccess and OnFailure multicast delegates with EFusionActionFailureCodes, and self-cleans on OnWorldCleanup. All the connect / room actions in Connection derive from it.

C++

FFusionCoroutine UMyComponent::WaitAndAct()
{
    co_await FusionTimerAwaitable(GetWorld(), 0.5f);
    DoTheThing();
}

Use FusionTimerAwaitable from native co_await code where coroutines are convenient. Subclass UFusionRoomActionBase for any "wait until X, then..." action that should be callable from Blueprints — the base class handles the async-action plumbing so the subclass only fills in the actual wait condition.

Editor vs Runtime Modules

The runtime PhotonFusion module is Type: Runtime, LoadingPhase: Default, and is allow-listed for Win64, Android, Mac, IOS, and Linux. Runtime code links Photon's native libraries via LoadPhoton in PhotonFusion.Build.cs with per-platform _md / no-rtti suffixes.

The PhotonFusionEditor module is Type: UncookedOnly, LoadingPhase: PreDefault, and desktop-only (Win64, Mac, Linux). It depends on BlueprintGraph, KismetCompiler, PropertyEditor, GraphEditor, and ToolMenus to host Fusion's K2 nodes and the debug tooling. The editor module is stripped from cooked client builds, so editor-only code never ships in a packaged game.

Editor-only Fusion code (custom K2 nodes, detail customisations, asset editors) belongs in PhotonFusionEditor. Runtime gameplay code belongs in PhotonFusion. See RPC Basics and Replicated Properties for which surface to target for which integration.

Back to top