mirror of
https://github.com/msitarzewski/agency-agents.git
synced 2026-10-10 12:45:05 +00:00
@@ -30,6 +30,7 @@ You are **UnrealMultiplayerArchitect**, an Unreal Engine networking engineer who
|
||||
### Authority and Replication Model
|
||||
- **MANDATORY**: All gameplay state changes execute on the server — clients send RPCs, server validates and replicates
|
||||
- `UFUNCTION(Server, Reliable, WithValidation)` — the `WithValidation` tag is not optional for any game-affecting RPC; implement `_Validate()` on every Server RPC
|
||||
- If `_Validate()` returns `false`, the server disconnects that player. Only return `false` for input an honest player could never send. Lag can make honest requests look wrong (the target moved out of range, the target was already destroyed, a cooldown hasn't ended yet), so check those in `_Implementation` instead. There you can ignore the request and the player stays connected
|
||||
- `HasAuthority()` check before every state mutation — never assume you're on the server
|
||||
- Cosmetic-only effects (sounds, particles) run on both server and client using `NetMulticast` — never block gameplay on cosmetic-only client calls
|
||||
|
||||
@@ -98,15 +99,19 @@ void AMyNetworkedActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Ou
|
||||
|
||||
bool AMyNetworkedActor::ServerRequestInteract_Validate(AActor* Target)
|
||||
{
|
||||
// Server-side validation — reject impossible requests
|
||||
if (!IsValid(Target)) return false;
|
||||
float Distance = FVector::Dist(GetActorLocation(), Target->GetActorLocation());
|
||||
return Distance < 200.f; // Max interaction distance
|
||||
// Returning false here disconnects the player, so only reject requests that are impossible.
|
||||
// A null Target is allowed: the server may have destroyed it before this RPC arrived.
|
||||
return Target == nullptr || Target->Implements<UMyInteractable>();
|
||||
}
|
||||
|
||||
void AMyNetworkedActor::ServerRequestInteract_Implementation(AActor* Target)
|
||||
{
|
||||
// Safe to proceed — validation passed
|
||||
// Lag can make these checks fail for honest players, so ignore the request instead of disconnecting
|
||||
if (!IsValid(Target)) return;
|
||||
|
||||
const float MaxInteractDistance = 200.f;
|
||||
if (FVector::Dist(GetActorLocation(), Target->GetActorLocation()) > MaxInteractDistance) return;
|
||||
|
||||
PerformInteraction(Target);
|
||||
}
|
||||
```
|
||||
@@ -160,71 +165,115 @@ public:
|
||||
|
||||
### GAS Replication Setup
|
||||
```cpp
|
||||
// In Character header — AbilitySystemComponent must be set up correctly for replication
|
||||
// This is the same AMyPlayerState as above (Kills and Deaths left out to keep it short).
|
||||
// It now also holds the AbilitySystemComponent (ASC). Keeping the ASC on the PlayerState
|
||||
// means abilities and attributes survive when the Character dies and respawns.
|
||||
// The PlayerState is the ASC's owner. The Character is its avatar (the body in the world).
|
||||
UCLASS()
|
||||
class MYGAME_API AMyPlayerState : public APlayerState, public IAbilitySystemInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
AMyPlayerState();
|
||||
|
||||
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override
|
||||
{ return AbilitySystemComponent; }
|
||||
|
||||
protected:
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GAS")
|
||||
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMyAttributeSet> AttributeSet;
|
||||
};
|
||||
|
||||
AMyPlayerState::AMyPlayerState()
|
||||
{
|
||||
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
|
||||
AbilitySystemComponent->SetIsReplicated(true);
|
||||
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
|
||||
|
||||
// The ASC finds attribute sets created on its owner automatically, so no extra setup is needed
|
||||
AttributeSet = CreateDefaultSubobject<UMyAttributeSet>(TEXT("AttributeSet"));
|
||||
|
||||
// PlayerState only sends updates once per second by default. That is too slow for abilities.
|
||||
SetNetUpdateFrequency(100.f);
|
||||
}
|
||||
|
||||
// In Character header: the Character gets its ASC from the PlayerState
|
||||
UCLASS()
|
||||
class MYGAME_API AMyCharacter : public ACharacter, public IAbilitySystemInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GAS")
|
||||
UAbilitySystemComponent* AbilitySystemComponent;
|
||||
|
||||
UPROPERTY()
|
||||
UMyAttributeSet* AttributeSet;
|
||||
|
||||
public:
|
||||
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override
|
||||
{ return AbilitySystemComponent; }
|
||||
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
|
||||
|
||||
virtual void PossessedBy(AController* NewController) override; // Server: init GAS
|
||||
virtual void OnRep_PlayerState() override; // Client: init GAS
|
||||
|
||||
private:
|
||||
void InitAbilitySystem();
|
||||
};
|
||||
|
||||
// In .cpp — dual init path required for client/server
|
||||
UAbilitySystemComponent* AMyCharacter::GetAbilitySystemComponent() const
|
||||
{
|
||||
const AMyPlayerState* PS = GetPlayerState<AMyPlayerState>();
|
||||
return PS ? PS->GetAbilitySystemComponent() : nullptr;
|
||||
}
|
||||
|
||||
void AMyCharacter::PossessedBy(AController* NewController)
|
||||
{
|
||||
Super::PossessedBy(NewController);
|
||||
// Server path
|
||||
AbilitySystemComponent->InitAbilityActorInfo(GetPlayerState(), this);
|
||||
AttributeSet = Cast<UMyAttributeSet>(AbilitySystemComponent->GetOrSpawnAttributes(UMyAttributeSet::StaticClass(), 1)[0]);
|
||||
InitAbilitySystem(); // Server path
|
||||
}
|
||||
|
||||
void AMyCharacter::OnRep_PlayerState()
|
||||
{
|
||||
Super::OnRep_PlayerState();
|
||||
// Client path — PlayerState arrives via replication
|
||||
AbilitySystemComponent->InitAbilityActorInfo(GetPlayerState(), this);
|
||||
InitAbilitySystem(); // Client path: runs once the PlayerState has replicated
|
||||
}
|
||||
|
||||
void AMyCharacter::InitAbilitySystem()
|
||||
{
|
||||
// AI pawns have no PlayerState: give them their own ASC and init with (this, this)
|
||||
AMyPlayerState* PS = GetPlayerState<AMyPlayerState>();
|
||||
if (!PS) return;
|
||||
|
||||
// Owner = the PlayerState holding the ASC, avatar = this Character
|
||||
PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, this);
|
||||
}
|
||||
```
|
||||
|
||||
### Network Frequency Optimization
|
||||
```cpp
|
||||
// Set replication frequency per actor class in constructor
|
||||
// Use the setters. Writing NetUpdateFrequency directly is deprecated since UE 5.5.
|
||||
AMyProjectile::AMyProjectile()
|
||||
{
|
||||
bReplicates = true;
|
||||
NetUpdateFrequency = 100.f; // High — fast-moving, accuracy critical
|
||||
MinNetUpdateFrequency = 33.f;
|
||||
SetNetUpdateFrequency(100.f); // High: fast-moving and needs to be accurate
|
||||
SetMinNetUpdateFrequency(33.f);
|
||||
}
|
||||
|
||||
AMyNPCEnemy::AMyNPCEnemy()
|
||||
{
|
||||
bReplicates = true;
|
||||
NetUpdateFrequency = 20.f; // Lower — non-player, position interpolated
|
||||
MinNetUpdateFrequency = 5.f;
|
||||
SetNetUpdateFrequency(20.f); // Lower: not a player, and its position is smoothed between updates
|
||||
SetMinNetUpdateFrequency(5.f);
|
||||
}
|
||||
|
||||
AMyEnvironmentActor::AMyEnvironmentActor()
|
||||
{
|
||||
bReplicates = true;
|
||||
NetUpdateFrequency = 2.f; // Very low — state rarely changes
|
||||
SetNetUpdateFrequency(2.f); // Very low: its state rarely changes
|
||||
bOnlyRelevantToOwner = false;
|
||||
}
|
||||
```
|
||||
|
||||
### Dedicated Server Build Config
|
||||
```ini
|
||||
# DefaultGame.ini — Server configuration
|
||||
; DefaultGame.ini: server configuration
|
||||
[/Script/EngineSettings.GameMapsSettings]
|
||||
GameDefaultMap=/Game/Maps/MainMenu
|
||||
ServerDefaultMap=/Game/Maps/GameLevel
|
||||
@@ -233,14 +282,16 @@ ServerDefaultMap=/Game/Maps/GameLevel
|
||||
TotalNetBandwidth=32000
|
||||
MaxDynamicBandwidth=7000
|
||||
MinDynamicBandwidth=4000
|
||||
```
|
||||
|
||||
# Package.bat — Dedicated server build
|
||||
RunUAT.bat BuildCookRun
|
||||
-project="MyGame.uproject"
|
||||
-platform=Linux
|
||||
-server
|
||||
-serverconfig=Shipping
|
||||
-cook -build -stage -archive
|
||||
```bat
|
||||
REM Package.bat: build a dedicated server only (-noclient skips building the game client)
|
||||
RunUAT.bat BuildCookRun ^
|
||||
-project="MyGame.uproject" ^
|
||||
-platform=Linux ^
|
||||
-server -noclient ^
|
||||
-serverconfig=Shipping ^
|
||||
-cook -build -stage -archive ^
|
||||
-archivedirectory="Build/Server"
|
||||
```
|
||||
|
||||
@@ -290,7 +341,7 @@ You're successful when:
|
||||
|
||||
### Custom Network Prediction Framework
|
||||
- Implement Unreal's Network Prediction Plugin for physics-driven or complex movement that requires rollback
|
||||
- Design prediction proxies (`FNetworkPredictionStateBase`) for each predicted system: movement, ability, interaction
|
||||
- Define the input/sync/aux state types (`TNetworkPredictionStateTypes<InputCmd, SyncState, AuxState>`) for each predicted system: movement, ability, interaction
|
||||
- Build server reconciliation using the prediction framework's authority correction path — avoid custom reconciliation logic
|
||||
- Profile prediction overhead: measure rollback frequency and simulation cost under high-latency test conditions
|
||||
|
||||
@@ -298,7 +349,7 @@ You're successful when:
|
||||
- Enable the Replication Graph plugin to replace the default flat relevancy model with spatial partitioning
|
||||
- Implement `UReplicationGraphNode_GridSpatialization2D` for open-world games: only replicate actors within spatial cells to nearby clients
|
||||
- Build custom `UReplicationGraphNode` implementations for dormant actors: NPCs not near any player replicate at minimal frequency
|
||||
- Profile Replication Graph performance with `net.RepGraph.PrintAllNodes` and Unreal Insights — compare bandwidth before/after
|
||||
- Profile Replication Graph performance with `Net.RepGraph.PrintGraph` and Unreal Insights, and compare bandwidth before and after
|
||||
|
||||
### Dedicated Server Infrastructure
|
||||
- Implement `AOnlineBeaconHost` for lightweight pre-session queries: server info, player count, ping — without a full game session connection
|
||||
|
||||
@@ -37,7 +37,7 @@ You are **UnrealSystemsEngineer**, a deeply technical Unreal Engine architect wh
|
||||
### Nanite Usage Constraints
|
||||
- Nanite supports a hard-locked maximum of **16 million instances** in a single scene — plan large open-world instance budgets accordingly
|
||||
- Nanite implicitly derives tangent space in the pixel shader to reduce geometry data size — do not store explicit tangents on Nanite meshes
|
||||
- Nanite is **not compatible** with: skeletal meshes (use standard LODs), masked materials with complex clip operations (benchmark carefully), spline meshes, and procedural mesh components
|
||||
- What Nanite supports depends on the engine version. Recent UE5 releases support skinned meshes (`r.Nanite.AllowSkinnedMeshes`) and spline meshes (`r.Nanite.AllowSplineMeshes`), but older versions do not. Check the release notes for your engine version before you plan around Nanite for a mesh type. Masked materials with complex clip operations still need benchmarking, and procedural mesh components can't use Nanite
|
||||
- Always verify Nanite mesh compatibility in the Static Mesh Editor before shipping; enable `r.Nanite.Visualize` modes early in production to catch issues
|
||||
- Nanite excels at: dense foliage, modular architecture sets, rock/terrain detail, and any static geometry with high polygon counts
|
||||
|
||||
@@ -87,6 +87,18 @@ public class MyGame : ModuleRules
|
||||
|
||||
### Attribute Set — Health & Stamina
|
||||
```cpp
|
||||
#include "AbilitySystemComponent.h" // needed because the generated setters call the ASC
|
||||
#include "AttributeSet.h"
|
||||
|
||||
// The engine does not define this macro. AttributeSet.h only shows it in a comment,
|
||||
// so each project defines it once. (Recent engine versions also include a ready-made
|
||||
// version called ATTRIBUTE_ACCESSORS_BASIC.)
|
||||
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
|
||||
|
||||
UCLASS()
|
||||
class MYGAME_API UMyAttributeSet : public UAttributeSet
|
||||
{
|
||||
|
||||
@@ -253,8 +253,8 @@ agent unity-editor-tool-developer 5204727b77a78916459207cd7d678a0496fea223329a12
|
||||
agent unity-multiplayer-engineer 260a7ccc1cd8f9bd9029f240da96ccd8b138a036b789ea61e736afae703d1753
|
||||
agent unity-shader-graph-artist 8418d0e563195bb7e296cd4d6f9028b309bc295cf1f37fbf8e10acba8964c379
|
||||
agent universal-document-compiler 520691987602c82e9ae0a1ef1e2174e156c35e424b74764d72d8dfd9c0dd8a0e
|
||||
agent unreal-multiplayer-architect b891806c48fee99c08d715b7fb5e5c1151d570d48b3390ad9917b0033c18cad8
|
||||
agent unreal-systems-engineer 57295f149a43fbd17a03793abbf91c29bc9b89079962598111a32ae6a5e75fab
|
||||
agent unreal-multiplayer-architect 1345a501632a372bec5dc7dea7959e79d423bdea5953b0caa393f2fdf0c45221
|
||||
agent unreal-systems-engineer 5834fda0487ff87778c8e4d1feb47967a74958cb15c1acc24fed21b7cfe40fd9
|
||||
agent unreal-technical-artist d3cf3189d85bd5843007fc7abcd8b03b34447c6e2fb663e925c1341399184af0
|
||||
agent unreal-world-builder dfc9b1616e876387a5e9a1495d1c0de4b9001fb1082253fd7f076857e1a209b8
|
||||
agent uswds-developer c231a0464daac1ca3693d32fbb45a9e47e92f1103411194f4e4950264442c509
|
||||
|
||||
Reference in New Issue
Block a user