Remote Calls
Replication handles syncing data automatically, but sometimes you need to explicitly tell the server or a client to do something. That's what Remote Procedure Calls (RPCs) are for -- they let you call a function on one machine and have it execute on another.
Three Types of RPCs
HELIX supports three RPC types, each for a different direction:
Server Functions
Called from a client, executed on the server. Use these when a player wants to do something that requires server authority -- like dealing damage, purchasing an item, or requesting a spawn.
- Blueprint
- Lua
- JavaScript
// Header: mark as Server RPC
UFUNCTION(Server, Reliable)
void Server_RequestAttack(AActor* Target);
// Implementation runs on the server
void AMyCharacter::Server_RequestAttack_Implementation(AActor* Target)
{
// Validate and apply damage on the server
if (Target && IsValid(Target))
{
UGameplayStatics::ApplyDamage(Target, 25.0f, GetController(), this, nullptr);
}
}
-- Client-side: request an action on the server
TriggerServerEvent("RequestAttack", target_actor)
-- Server-side: listen and handle (controller is passed automatically as first arg)
RegisterServerEvent("RequestAttack", function(controller, target)
if target and target:IsValid() then
target:ApplyDamage(25.0, controller)
end
end)
// Server-side: register an endpoint for the attack action
Helix.server(async () => {
Helix.endpoint("RequestAttack", async (helixId, targetId) => {
// Validate and apply damage on the server
const ok = await applyDamage(helixId, targetId, 25.0);
return { ok };
});
});
// Client-side: call the server endpoint
const id = Helix.Player.helixId();
await Helix.call("RequestAttack", id.toString(), targetId);
Client Functions
Called from the server, executed on a specific client. Perfect for sending personalized information -- like showing a notification, playing a sound only that player should hear, or updating their UI.
- Blueprint
- Lua
- JavaScript
// Header: mark as Client RPC
UFUNCTION(Client, Reliable)
void Client_ShowNotification(const FString& Message);
// Implementation runs on the owning client
void AMyCharacter::Client_ShowNotification_Implementation(const FString& Message)
{
// Display a UI notification for this player
ShowOnScreenMessage(Message);
}
-- Server: send a notification to a specific client
TriggerClientEvent(controller, "ShowNotification", "You found a rare item!")
-- Client: listen and display
RegisterClientEvent("ShowNotification", function(message)
print("Notification: " .. message)
end)
// Server: register an endpoint that clients can call
Helix.server(async () => {
Helix.endpoint("getNotifications", async (helixId) => {
const notifications = await loadNotifications(helixId);
return notifications;
});
});
// Client: call the endpoint to get notifications
const id = Helix.Player.helixId();
const notifications = await Helix.call("getNotifications", id.toString());
Multicast Functions
Called from the server, executed on all clients (and optionally the server too). Great for global events like explosions, weather changes, or announcements.
- Blueprint
- Lua
- JavaScript
// Header: mark as NetMulticast RPC
UFUNCTION(NetMulticast, Reliable)
void Multicast_PlayExplosion(FVector Location);
void AMyActor::Multicast_PlayExplosion_Implementation(FVector Location)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionFX, Location);
}
-- Server: broadcast an event to all connected clients
BroadcastEvent("PlayExplosion", explosion_location)
-- Client: all clients receive and play the effect
RegisterClientEvent("PlayExplosion", function(location)
print("Explosion at: " .. tostring(location))
end)
// JavaScript uses the endpoint/call pattern for server communication.
// For broadcast-style behavior, the server can push data to all clients
// through shared state or by having each client poll an endpoint.
Helix.server(async () => {
Helix.endpoint("getExplosions", async () => {
return recentExplosions;
});
});
Reliable vs. Unreliable
RPCs can be Reliable (guaranteed delivery, ordered) or Unreliable (faster, but might get dropped). Use Reliable for important gameplay events like damage or purchases. Use Unreliable for frequent, non-critical updates like cosmetic effects.
Best Practices
- Always validate on the server. Never trust data coming from a client RPC -- check that the action is legal before applying it.
- Keep payloads small. Send IDs or indices instead of full objects when possible.
- Don't spam RPCs. If you're calling an RPC every frame, you probably want replication instead.
- Use Multicast sparingly. Broadcasting to every client is expensive -- consider whether relevancy-based replication would work better.