Announcement

Collapse
No announcement yet.

Multiplayer bugs becoming more prevalent - Harden & Improve Network / Multiplayer Support!

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • replied
    +1

    I'm having all sorts of trouble with being private, among other things listed in here.

    Maybe make it protected?

    Leave a comment:


  • replied
    +1 for these; having to write a huge amount of custom code.

    Leave a comment:


  • replied
    While not specifically Multiplayer, this is an issue with the Online Sub System:

    https://forums.unrealengine.com/show...or-Google-Play

    Leave a comment:


  • replied
    Due to recent discussions on a specific forum thread and the slack chat:

    Dedicated Servers don't work with the Lobby System of Steam. Using the presence bool (true) will crash the attempt to register the Server.
    Using a Dedicated Server without presence is still not working out of box. There were changes to be made in the Subsystem class and the
    socket class etc.

    There were issues with overflowing GameTags and GameData when pushing Settings to Steam and also missing conditions to ensure the usage
    of WinSock.

    Is there any chance that this will be addressed? I mean it's the interface between Steam and UE4, so i can understand if that is just totally backlogged,
    but on the other hand it's way to undocumented to solve this with the Engine Source on your own ):

    OR maybe there is a solution that has not been discovered? I don't know..

    Leave a comment:


  • replied
    Originally posted by Wrekk View Post
    Is there any progress on the 1024+ chars replication?
    Client to server?

    This works for us:

    MyPlayerState.h:
    Code:
        FString MyString;
        
        UFUNCTION()
        void SetMyString(FString NewMyString);
    
        UFUNCTION(Reliable, Server, WithValidation)
        virtual void ServerSetMyString();
        bool ServerSetMyString_Validate();
        void ServerSetMyString_Implementation();
    
        UFUNCTION(Reliable, Client)
        virtual void ClientRequestMyStringChunk(int32 ServerMyStringLen);
        void ClientRequestMyStringChunk_Implementation(int32 ServerMyStringLen);
        
        UFUNCTION()
        void RequestMyStringChunk();
        FTimerHandle TimerHandle_RequestMyStringChunk;
    
        UFUNCTION(Reliable, Server, WithValidation)
        virtual void ServerSendMyStringChunk(const FString& MyStringChunk);
        bool ServerSendMyStringChunk_Validate(const FString& MyStringChunk);
        void ServerSendMyStringChunk_Implementation(const FString& MyStringChunk);
    MyPlayerState.cpp:
    Code:
    void AMyPlayerState::SetMyString(FString NewMyString)
    {
        // Set my string.
        MyString = NewMyString;
    
        // If I'm the client & MyString isn't empty, tell the server I have it.
        if ((Role < ROLE_Authority) && !MyString.IsEmpty())
        {
            ServerSetMyString();
        }
    }
    
    bool AMyPlayerState::ServerSetMyString_Validate()
    {
        return true;
    }
    
    void AMyPlayerState::ServerSetMyString_Implementation()
    {
        // Client has told me it has set MyString.
        // Clear the server version and ask the client to start sending the first chunk.
        MyString.Empty();
        ClientRequestStringChunk(0);
    }
    
    void AMyPlayerState::ClientRequestMyStringChunk_Implementation(int32 ServerMyStringLen)
    {
        static int32 MaxChunkSize = 512; // NOTE: 512 could be anything < 1024
        FString MyStringChunk;
    
        // The server has a MyString that as long as ours - tell the server its done.
        if (ServerMyStringLen >= MyString.Len())
        {
            MyStringChunk = TEXT("END");
        }
        // Not enough string to send an entire chunk - send what we can.
        else if (ServerMyStringLen + MaxChunkSize >= MyString.Len())
        {
            MyStringChunk = MyString.Mid(ServerMyStringLen, (MyString.Len() - ServerMyStringLen));
        }
        // Send an entire chunk.
        else
        {
            MyStringChunk = MyString.Mid(ServerMyStringLen, MaxChunkSize);
        }
    
        ServerSendMyStringChunk(MyStringChunk);
    }
    
    void AMyPlayerState::RequestMyStringChunk()
    {
        // Ask the client to start sending another chunk
        ClientRequestStringChunk(MyString.Len());
    }
    
    bool AMyPlayerState::ServerSendMyStringChunk_Validate(const FString& MyStringChunk)
    {
        return true;
    }
    
    void AMyPlayerState::ServerSendMyStringChunk_Implementation(const FString& MyStringChunk)
    {
        // The client has told us we have the whole string.
        // Time to do something with it.
        if (MyStringChunk == TEXT("END"))
        {
        }
        else
        {
            // We have another chunk of string.
            // Add it to our version.
            MyString += MyStringChunk;
            // Delay request for next string chunk to prevent flooding.
            SETTIMERH(TimerHandle_RequestMyStringChunk, AMyPlayerState::RequestMyStringChunk, 0.05f, false);
        }
    }
    We send condensed JSON formatted strings from client to server using this method.
    Easy to send it back, since arrays replicate

    You don't have to use the player state - any owner actor will do.
    e.g PlayerController

    Hope that helps.

    Kris
    Last edited by Kris; 01-18-2016, 12:59 PM.

    Leave a comment:


  • replied
    Is there any progress on the 1024+ chars replication?

    Leave a comment:


  • replied
    we cant have our pawn locally controlled if we check the box "movement replication" because the server will correct our client position every time we try to move our pawn locally...

    but i read on answerhub that we can make the client authorative on his position, this still work? i read this:

    3) There is a config option for "ClientAuthorativePosition" [sic, I know it's misspelled] on the GameNetworkManager that changes the rules: the server will accept client movement as authoritative and not force a correction if it's within a squared distance (MAXPOSITIONERRORSQUARED) from where the server last saw it. So in this case, the server would warp the character to where the client tried to move, and the client's position is the authority. The server can still replicate movement to the client normally, this just handles rogue client movements.

    You set this in your game ini settings:

    [/Script/Engine.GameNetworkManager]
    MAXPOSITIONERRORSQUARED=625
    ClientAuthorativePosition=true
    but i cant found any game ini setting, i just found a DefaultGame.ini and i tried to put those line inside it, but this dont work, client isnt authorative, or i did something wrong but cant find what

    if someone know how to force client to be authorative, tell me pls, or where to find this game ini settings file?

    i dont care about players cheating, and i care about good and reactive control for players. i need to know if i can enable my pawn to be client authorative on his position

    Leave a comment:


  • replied
    Hi everyone,

    I'm sorry you have been running into issues with multiplayer. The networking team has actually been really busy helping out on Paragon, and in the meantime, we have been accumulating a backlog of engine networking bugs. Once the Paragon work settles down a bit, we are planning on spending more time on fixing these bugs, including the ones that have been mentioned in this thread.

    Originally posted by TheJamsh View Post
    It's also worth mentioning this; There were a fair few changes to Replication in 4.10 and yet not a single one of them was documented. For someone working on two multiplayer-focused titles this has proven to been an absolute nightmare. New options for FRepMovement for example weren't documented in the release notes despite being a pretty significant (and useful!) change. The only way to get around this was to compare relevant files on GitHub from version to version, which isn't foolproof - or hope you run into them at some point anyway.
    I'm the one who added the option and changed the default setting. This was actually changed in 4.9, and mentioned in the 4.9 release notes:

    • New: Reduced bandwidth usage by up to 15% or more by compressing replicated location, velocity, and rotation values more aggressively.
      • The amount of bandwidth savings you see in your project will vary depending on how much of it consisted of actor movement replication.
      • This does reduce the precision of these values slightly, but it should not cause noticeable artifacts in most cases. The level of compression is now configurable in the "Replication" section of the Class Defaults, in case higher precision is needed.
      • Using "Round Two Decimals" for the location and velocity, and "Short Components" for rotation will be equivalent to the precision that was used in 4.8.
    This was done as part of a bandwidth optimization push, and there was some debate internally on whether to change the default or not. UT was already using the lower precision for their replicated movement, and ultimately we decided on on the default that would use less bandwidth.

    If there were other changes you noticed that weren't documented, please mention them so that we can figure out what happened and try to make sure all future changes do get documented.

    Originally posted by TheJamsh View Post
    The ability to debug specific clients when using PIE and Visual Studio. Blueprint allows you to select which world you use for debugging / breakpoints etc. Visual Studio currently doesn't seem to have a way of doing this, and the only workaround I can think of is to package the game, start a session and attach to the client process. This is quite convoluted and difficult, whereas blueprint allows you to easily switch which world you're debugging in realtime.
    This could be difficult due to the fact that Visual Studio itself isn't aware of UE4's World/PIE setup. It might be possible to do something with a Visual Studio extension, but that's getting outside my area of expertise. I can offer some tips that might make debugging multiple PIE worlds a little easier in Visual Studio, though:
    • If you have access to a UWorld pointer at some level of the callstack which you're debugging, you can check it's value, or the world's name, to distinguish which world is currently being debugged (assuming there aren't world pointers pointing between PIE instances - this would be weird).
    • If you're debugging something that happens within, say, the UWorld::Tick call, you can do something like print a log message with the world's name at the beginning of the tick. Then when you hit a breakpoint, the log message should be near the bottom, indicating the world that's ticking.
    • If you need to distinguish between a server PIE world and a client PIE world, you can add a watch on the world's NetDriver->ServerConnection member. If the world is a server, it will be null. If it's a client, it will be non-null.

    Leave a comment:


  • replied
    I imagine that Origin Rebasing would require the server to be able to monitor multiple worlds at the same time, but I agree it would be a massive undertaking. To be fair, if you're making something on that scale and you don't have a full time multiplayer / network engineer on your dev team then it's time to advertise a job opportunity... It's a full-time project long job if you want to do multiplayer on that scale.

    I have just posted source code for my issue with the OnRep functionality on Answerhub, but I am also posting the example case here. The comments explain what's going on - but don't expect this to be easy to decipher out of context.

    Code:
    // This function is called both Server & Client-Side based on a timer. ONLY the Server Packs the data into the compressed byte.
    // This means, that the client is NEVER modifying the compressed byte, only the values that are derived from it. Therefore, OnRep should always fire, correct?
    // This is done so that although clients can display the 'heat ratio' of the current weapon, they cannot 'unlock' it when it cools down - only the server can.
    
    void ABZGame_Weapon::ApplyCool()
    {
    	HeatProgressRatio = FMath::Clamp(HeatProgressRatio - WeaponConfig.HeatRecoverySpeed * WEAPON_COOLDOWN_DELTA, 0.f, 1.f);
    
    	if (OwningGameObject) { OwningGameObject->UpdateFloatPropertyAllMaterials(OwningGameObject->HardPoints[WeaponIndex].MuzzleSocketName, HeatProgressRatio); }
    
    	// Only server can open the HeatLock
    	if (Role == ROLE_Authority && HeatProgressRatio <= WeaponConfig.EnableThreshold && bOverheatLocked == true)
    	{
    		bOverheatLocked = false;
    	}
    
    	if (Role == ROLE_Authority)
    	{
    		PackOverheatData();
    	}
    
    	if (HeatProgressRatio == 0.f)
    	{
    		GetWorldTimerManager().ClearTimer(TimerHandle_WeaponCooldown);
    	}
    }
    
    // This function is called whenever a projectile is fired, or whenever the client reaches the point where it would fire a projectile, IF it was the server.
    // This way, clients apply heat locally so that it appears responsive regardless of latency. We can likely trust the client here.
    // Like before, ONLY the Server can modify the Packed Data.
    // The client CAN lock their weapon, but they cannot UNLOCK it. This is fine, because the server will correct them, and we never want a client to be able to fire when the server says they can't.
    // For rapid-fire weapons, the time it takes to recieve the lock status will be too slow.
    
    void ABZGame_Weapon::ApplyHeat(const float HeatIncrease)
    {
    	HeatProgressRatio = FMath::Clamp(HeatProgressRatio + HeatIncrease, 0.f, 1.f);
    
    	if (OwningGameObject) { OwningGameObject->UpdateFloatPropertyAllMaterials(OwningGameObject->HardPoints[WeaponIndex].MuzzleSocketName, HeatProgressRatio); }
    
    	// If the timer hasn't already been set by ApplyCool, we can apply it here:
    	if (HeatProgressRatio != 0.f && !GetWorldTimerManager().TimerExists(TimerHandle_WeaponCooldown))
    	{
    		GetWorldTimerManager().SetTimer(TimerHandle_WeaponCooldown, this, &ABZGame_Weapon::ApplyCool, WEAPON_COOLDOWN_DELTA, true, WEAPON_COOLDOWN_DELTA);
    	}
    
    	// This may need to be IsNearlyEqual if this is called after Ticking the ApplyCool
    	if (HeatProgressRatio == 1.f)
    	{
    		bOverheatLocked = true;
    		StopFire();
    	}
    
     	if (Role == ROLE_Authority)
     	{
    		PackOverheatData();
    	}
    }
    
    // Packs the overheat data into a single byte.
    // IN THIS CASE, I have added '1' to the initial struct so that replication still occurs when the cooldown is recieved. Without adding '1', the data doesn't cause OnRep to fire when the value is zero.
    // TO circumvent this, remove the + 1 and use HeatProgressRatio * 126.0f instead.
    
    void ABZGame_Weapon::PackOverheatData()
    {
    	if (HeatProgressRatio > 1.f && HeatProgressRatio < 0.f)
    	{
    		UE_LOG(LogBZOnline, Warning, TEXT("Weapon %s heat ratio clamped for Network Packing. Prev Value %f"), *GetNameSafe(this), HeatProgressRatio);
    		HeatProgressRatio = FMath::Clamp(HeatProgressRatio, 0.f, 1.f);
    	}
    
    	// Add 1 to force replication even when Zero
    	OverheatData = (HeatProgressRatio * 125.f) + 1;
    	if (bOverheatLocked)
    	{
    		OverheatData = FMath::Clamp(OverheatData + 128, 0, 255);
    		const bool bCheck = true;
    	}
    }
    
    // Fired when the client recieves the overheat data, and they can determine from the values whether they are locked or not.
    // Remove "OverheatData -= 1" and change /125.f to /126.f to test the replication issue.
    
    void ABZGame_Weapon::OnRep_OverheatData()
    {
    	const bool RcvdLock = (OverheatData > 127) ? true : false;
    	if (bOverheatLocked != RcvdLock)
    	{
    		bOverheatLocked = RcvdLock;
    		if (bOverheatLocked == true)
    		{
    			// On Burst Finished - Shouldn't be required if this is done properly!
    		}
    	}
    
    	if (RcvdLock)
    	{
    		OverheatData = FMath::Clamp(OverheatData - 128, 0, 255);
    	}
    
    	OverheatData -= 1;
    	HeatProgressRatio = OverheatData / 125.f;
    	if (OwningGameObject) { OwningGameObject->UpdateFloatPropertyAllMaterials(OwningGameObject->HardPoints[WeaponIndex].MuzzleSocketName, HeatProgressRatio); }
    }
    
    // Called on the local firing client only.
    
    void ABZGame_Weapon::FireWeapon()
    {
    	if (WeaponConfig.SalvoCount > 1)
    	{
    		HandleSalvo();
    	}
    	else
    	{
    		// Simulation - FX, Audio & Heat
    		if (MyPawn && MyPawn->IsLocallyControlled())
    		{
    			if (GetNetMode() != NM_DedicatedServer) { SimulateWeaponFire(); }
    			if (WeaponConfig.HeatPerShot != 0.f) { ApplyHeat(WeaponConfig.HeatPerShot); }
    		}
    
    		ServerFireProjectile(GetMuzzleLocation(), GetAdjustedAim());
    	}
    }
    
    // Called on the Server Only (obviously)
    
    void ABZGame_Weapon::ServerFireProjectile_Implementation(FVector Origin, FVector_NetQuantizeNormal ShootDir)
    {
    	UseAmmo();
    
    	// The server runs the firing simulation for non-local pawns only, since it's already simulated for itself in FireWeapon or HandleFiring
    	if (MyPawn && !MyPawn->IsLocallyControlled())
    	{
    		if (GetNetMode() != NM_DedicatedServer) { SimulateWeaponFire(); }
    		if (WeaponConfig.HeatPerShot != 0.f && MyPawn && !MyPawn->IsLocallyControlled()) { ApplyHeat(WeaponConfig.HeatPerShot); }
    	}
    
    	const FTransform SpawmTM(ShootDir.Rotation(), Origin);
    	ABZGame_Ordnance* Projectile = bUseOrdnanceCaching ? FindFirstInactiveOrdnance() : Cast<ABZGame_Ordnance>(UGameplayStatics::BeginDeferredActorSpawnFromClass(this, WeaponConfig.OrdnanceClass, SpawmTM, ESpawnActorCollisionHandlingMethod::AlwaysSpawn, this));
    	if (Projectile)
    	{
    		if (!bUseOrdnanceCaching)
    		{
    			Projectile->SetOwningWeapon(this);
    			UGameplayStatics::FinishSpawningActor(Projectile, SpawmTM);
    		}
    
    		Projectile->InitializeProjectile(SpawmTM);
    	}
    }
    EDIT: Sod it, here's the source code to replicate the widget issue too. This is almost identical to ShooterGame's method - except it uses a User Widget instead of a Slate Widget. Swapping Pawn possession mid game is easy enough to try yourself.

    Code:
    void ABZGame_PlayerController::ClientGameStarted_Implementation()
    {
    	bAllowGameActions = true;
    
    	// Enable controls mode now the game has started
    	SetIgnoreMoveInput(false);
    
    	if (BZPlayerHUD.IsValid())
    	{
    		BZPlayerHUD->SetMatchState(EBZGameMatchState::Playing);
    		BZPlayerHUD->ShowScoreboard(false);
    		BZPlayerHUD->ShowUI(true);
    	}
    
    	bGameEndedFrame = false;
    
    	// Send round start event
    	const auto Events = Online::GetEventsInterface();
    	ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player);
    
    	if (LocalPlayer != nullptr && Events.IsValid())
    	{
    		auto UniqueId = LocalPlayer->GetPreferredUniqueNetId();
    
    		if (UniqueId.IsValid())
    		{
    			FOnlineEventParms Params;
    
    			// Generate a new session id
    			Events->SetPlayerSessionId(*UniqueId, FGuid::NewGuid());
    			Events->TriggerEvent(*UniqueId, TEXT("PlayerSessionStart"), Params);
    
    			bHasSentStartEvents = true;
    		}
    	}
    }
    
    bool ABZGame_InGameHUD::ShowUI(bool bEnable, bool bFocus /*= false*/)
    {
    	if (bIsUIVisible == bEnable)
    	{
    		// If the UI is already enabled, disable it in favor of the new request
    		if (bEnable)
    		{
    			ToggleUI();
    		}
    		else
    		{
    			return false;
    		}
    	}
    
    	if (bEnable)
    	{
    		ABZGame_PlayerController* MyPC = Cast<ABZGame_PlayerController>(PlayerOwner);
    		if (MyPC == NULL /*|| MyPC->IsGameMenuVisible()*/)
    		{
    			return false;
    		}
    	}
    
    	bIsUIVisible = bEnable;
    	if (bIsUIVisible && ScoreboardWidget)
    	{
    		ActiveGameWidget = CreateWidget<UBZGame_InGameWidget>(PlayerOwner, GameWidget);
    		if (ActiveGameWidget)
    		{
    			ActiveGameWidget->AddToViewport(0);
    
    			if (bFocus)
    			{
    				ActiveGameWidget->SetKeyboardFocus();
    			}
    		}
    	}
    	else if (ActiveGameWidget)
    	{
    		ActiveGameWidget->RemoveFromParent();
    
    		if (bFocus)
    		{
    			// Make Sure Viewport Has Focus
    			FSlateApplication::Get().SetAllUserFocusToGameViewport();
    		}
    	}
    
    	return true;
    }
    Last edited by TheJamsh; 01-01-2016, 08:48 AM.

    Leave a comment:


  • replied
    Originally posted by iniside View Post
    Origin rebasing will never work in multiplayer. Or at leastI dont see as readonable solution. Server have it oen version of world, how do you rebase origin to match all clients in the world ? You will run into orecision issues on server side.
    Even if you figurÄ™ it out, thereis still problem with client server coordinate translation.
    Segmemted world could be an option,but that would probably requiare how woldis treated in Unreal, which means heavy refactoring on all Engine layers.
    Segmented world of some kind is on trello backlog and I've tried asking if they have any plans on doing them sometime in the future, but nobody has responded. https://forums.unrealengine.com/show...ystem-question

    Leave a comment:


  • replied
    Originally posted by TheJamsh View Post
    Another request from the Slack group, native support for origin re-basing in Multiplayer games.
    Origin rebasing will never work in multiplayer. Or at least I dont see as reasonable solution. Server have it one version of world, how do you rebase origin to match all clients in the world ? You will run into precision issues on server side.
    Even if you figure it out, there is still problem with client server coordinate translation.
    Segmented world could be an option,but that would probably require how world treated in Unreal, which means heavy refactoring on all Engine layers.
    Last edited by iniside; 01-02-2016, 06:41 AM.

    Leave a comment:


  • replied
    Originally posted by Sadaleus View Post
    Totally agree! More attention to MP and Networking is needed also make Blueprints faster according to https://forums.unrealengine.com/show...ance-Benchmark which is very important when it comes to multiplayer games.
    It's important to remember that Blueprints were conceived to make programming more open to artists, so that programmers weren't always the bottleneck of a project. Being able to make a game with them entirely is somewhat of a side-effect. Having worked on three MP titles in UE4 now, I would say that if you're doing anything particularly hardcore or heavily Multiplayer orientated, C++ is king. You get a lot of fine control by doing so. BP is great, but MP support is very basic (and probably always will be).

    ---

    This will be a bit of a difficult thread to control since a lot of MP 'bugs' can often be placed back to the developers misunderstanding, since MP can be a complex beast. Always worth bearing that in mind when posting in this thread, but I'll try to help ppl out to the best of my knowledge I'm no expert though..
    Last edited by TheJamsh; 12-31-2015, 03:54 PM.

    Leave a comment:


  • replied
    Totally agree! More attention to MP and Networking is needed also make Blueprints faster according to https://forums.unrealengine.com/show...ance-Benchmark which is very important when it comes to multiplayer games.

    Leave a comment:


  • replied
    Amazing,
    Why I didn't see this before. I'm having troubles to start learning MP simply because I always face bugs even in simple stuff. For example, editing weapons in runtime would crash the game if client does it. Server can. Apparently, I had to untick replicate actor option, compile, tick the option again, then compile again :-o
    Also the widget just doesn't work for me. It never appears for clients.
    I agree with the previous posts, very little resources out there and most of them talking about almost the same things.
    Would be great if MP get more attention, more tutorials (real practical ones, I'm tired of seeing the word "simple" in tutorials). And people in this page you can always share what you've learned and help beginners (like me) to learn.

    Many thanks for the thread and bringing this topic :-)

    Happy new year to you all

    Leave a comment:


  • replied
    Another request from the Slack group, native support for origin re-basing in Multiplayer games.

    Anyone know if progress has been made towards world composition / origin rebasing for multiplayer? Seems like a doozy!

    Leave a comment:

Working...
X