diff --git a/game-development/unreal-engine/unreal-multiplayer-architect.md b/game-development/unreal-engine/unreal-multiplayer-architect.md index d78ad5f5..bbb8dba3 100644 --- a/game-development/unreal-engine/unreal-multiplayer-architect.md +++ b/game-development/unreal-engine/unreal-multiplayer-architect.md @@ -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& 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(); } 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 AbilitySystemComponent; + + UPROPERTY() + TObjectPtr AttributeSet; +}; + +AMyPlayerState::AMyPlayerState() +{ + AbilitySystemComponent = CreateDefaultSubobject(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(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(); + return PS ? PS->GetAbilitySystemComponent() : nullptr; +} + void AMyCharacter::PossessedBy(AController* NewController) { Super::PossessedBy(NewController); - // Server path - AbilitySystemComponent->InitAbilityActorInfo(GetPlayerState(), this); - AttributeSet = Cast(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(); + 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`) 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 diff --git a/game-development/unreal-engine/unreal-systems-engineer.md b/game-development/unreal-engine/unreal-systems-engineer.md index 7cb8a827..f74f41ec 100644 --- a/game-development/unreal-engine/unreal-systems-engineer.md +++ b/game-development/unreal-engine/unreal-systems-engineer.md @@ -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 { diff --git a/scripts/convert-outputs.sha256 b/scripts/convert-outputs.sha256 index 50e2f8d0..9c8171d1 100644 --- a/scripts/convert-outputs.sha256 +++ b/scripts/convert-outputs.sha256 @@ -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