RPC Events in C++
A CrowdyEvent is a function you call like a normal method. When you call it, the SDK sends it across the network and runs the body on the other clients.
You do not build a message, serialize a payload, or wire up a handler. You declare a receiver, generate a call site, and call it.
The headline is the parameter list. You pass real types directly: vectors, enums, structs, object references, arrays, and maps. You do not wrap your data in a payload struct first.
Declare a receiver and a call site
Include the event header, then declare two things on your class:
- A receiver UFUNCTION named
Name_Implementation, tagged with the CrowdyEvent meta keys. - The
CROWDY_EVENT(Name)macro, which generates theName(args...)call site.
#include "Replication/RPC/CrowdyEvent.h"
UCLASS()
class AMyEntity : public AActor
{
GENERATED_BODY()
public:
AMyEntity();
UFUNCTION(meta = (CrowdyEvent, CrowdyRecipient = "SpatialMulticast"))
void SetObjectLocation_Implementation(FVector NewLocation);
CROWDY_EVENT(SetObjectLocation)
private:
UPROPERTY(VisibleAnywhere)
TObjectPtr<UCrowdyEntityComponent> CrowdyEntity;
};
To send the event, call the name without the suffix:
SetObjectLocation(FVector(0.0f, 0.0f, 250.0f));
The receiving actor must carry a UCrowdyEntityComponent. Add it in the constructor.
AMyEntity::AMyEntity()
{
CrowdyEntity = CreateDefaultSubobject<UCrowdyEntityComponent>(TEXT("CrowdyEntity"));
}
For more on entities, see Entities and spawning.
Why the receiver must be a real UFUNCTION
The receiver is a literal UFUNCTION, not a function generated by the macro.
Unreal Header Tool only sees functions you write out. It cannot read a UFUNCTION that a macro emits. So the receiver, with its routing meta keys, is the part UHT processes and registers. The CROWDY_EVENT(Name) macro then generates only the call-site thunk that captures your arguments and sends them.
This split is the reason the meta keys live on Name_Implementation and the macro takes only the bare name.
Pass types directly, not a payload struct
You can pass any allowed type straight through the call. There is no payload struct to define, fill, and read back out.
Allowed parameter types:
bool,uint8,int32,int64,float,doubleFName,FString,FText- any
UENUM - any
USTRUCT - object references:
UObject*,UClass*,TSubclassOf<>,TSoftObjectPtr<>,TSoftClassPtr<> TArrayof any of the above, including objects- non-object
TSetandTMap
Not allowed:
- a return value
- a non-const output reference
- a
TSetorTMapof object references - delegates and interfaces
If a signature uses an unsupported type, the SDK logs an error and skips registration of that event, so the call will not replicate. See Allowed RPC parameter types for the full rules.
Example gallery
Each line below is a receiver signature. The body runs on the receiving clients; you call the bare name to send.
A path as an array of vectors, passed directly:
UFUNCTION(meta = (CrowdyEvent, CrowdyRecipient = "SpatialMulticast"))
void SetPatrolPath_Implementation(const TArray<FVector>& Path);
A class to spawn plus a location, both as plain parameters:
UFUNCTION(meta = (CrowdyEvent, CrowdyRecipient = "SpatialMulticast"))
void PlayEffect_Implementation(TSubclassOf<AActor> EffectClass, FVector At);
A soft reference to a material, resolved on the receiver:
UFUNCTION(meta = (CrowdyEvent, CrowdyRecipient = "SpatialMulticast"))
void SetSkin_Implementation(TSoftObjectPtr<UMaterialInterface> Skin);
A list of live actor references:
UFUNCTION(meta = (CrowdyEvent, CrowdyRecipient = "SpatialMulticast"))
void LinkTargets_Implementation(const TArray<AActor*>& Targets);
A string, an enum, and a color together, no wrapper:
UFUNCTION(meta = (CrowdyEvent, CrowdyRecipient = "SpatialMulticast"))
void Emote_Implementation(const FString& Tag, ESampleAnimState Anim, FLinearColor Color);
A scoreboard as a map, sent over a named channel to every member at any distance:
UFUNCTION(meta = (CrowdyEvent, CrowdyRecipient = "Multicast", CrowdyChannel = "Scores"))
void PushScores_Implementation(const TMap<FName, int32>& Scores);
Entity targeting by NetID
An event is aimed at the entity its sender object belongs to.
When you call SetObjectLocation(...) from an actor, the SDK reads the NetID from that actor's UCrowdyEntityComponent and tags the wire message with it. On every other client, the SDK finds the matching entity by that NetID and runs SetObjectLocation_Implementation on it.
This is why the receiving actor must carry an entity component. Without a NetID there is no entity to deliver to.
On a SpatialMulticast event, the owner runs the body locally as it sends, and the remote clients run the implementation when the message arrives. The owner does not receive a copy of its own event back.
Object-reference encoding
Object parameters do not send the object's data. They send a reference that each client resolves on its own side.
UObject*,UClass*, andTSubclassOf<>resolve by path on the receiver. Use them for assets and classes that exist on every client, or for actors the SDK can locate.TSoftObjectPtr<>andTSoftClassPtr<>carry a soft path. The receiver resolves and loads as needed.
Because references resolve per client, the asset or class has to be present on the receiver for the lookup to succeed.
Recipient, decay, and distance
The CrowdyRecipient key chooses who receives the event. CrowdyDecay and CrowdyDistance tune spatial delivery and apply only to SpatialMulticast.
UFUNCTION(meta = (CrowdyEvent,
CrowdyRecipient = "SpatialMulticast",
CrowdyDistance = "Four_Chunks",
CrowdyDecay = "Linear_50"))
void Footstep_Implementation(FVector At);
For the full set of recipients and the meaning of each decay and distance value, see Recipients and routing. For the complete list of meta keys and their values, see RPC meta keys.