After some trial and error I managed to create a widget to display the error type.
I used address parameters when performing the client travel to populate the body.
There are several steps and musts to do this. Adding the widget to the viewport is not sufficient because widget is added before new level is completely loaded, and once it’s loaded, all widgets are removed.
WBP could use some work but it works for now I guess.
The first thing to do would be, implementing InitNewPlayer in your game mode class. Mine ended up looking like:
FString AMultiplayerPuzzleGameMode::InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal)
{
FString showError = UGameplayStatics::ParseOption(Options, TEXT("showError"));
FString errorTitle = UGameplayStatics::ParseOption(Options, TEXT("errorTitle"));
FString errorMessage = UGameplayStatics::ParseOption(Options, TEXT("errorMessage"));
UE_LOG(LogTemp, Warning, TEXT("OPTIONS: %s"), &Options);
if (showError == "true") {
if (ensure(ErrorDisplayClass != nullptr))
{
UErrorNotificationDisplay* ErrorWidget = CreateWidget<UErrorNotificationDisplay>(GetGameInstance(), ErrorDisplayClass);
if (ErrorWidget != nullptr) {
ErrorWidget->Setup(errorTitle, errorMessage);
ErrorWidget->AddToViewport(1000);
}
}
}
return FString();
}
Don’t forget to add the WBP class lookup in the constructor:
static ConstructorHelpers::FClassFinder<UUserWidget> ErrorDisplayBP(TEXT("/Game/UI/WBP_ErrorDisplay"));
if (!ensure(ErrorDisplayBP.Class != nullptr))
{
UE_LOG(LogTemp, Error, TEXT("ErrorDisplayBP not found!"));
return;
}
ErrorDisplayClass = ErrorDisplayBP.Class;
I had to modify the ZOrder for the widgets. I guess you could try doing that from Blueprints but I went another route. I created a custom implementation for the Main Menu level that you worked on during these lectures. The Menu level loads the WBP from Blueprint script. You need to implement an ALevelScriptActor in C++ and have your level blueprint inherit from this custom C++ class.
UCLASS()
class MULTIPLAYERPUZZLE_API AMainMenuLevelScript : public ALevelScriptActor
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
};
#include "MainMenuLevelScript.h"
#include "PuzzlePlatformGameInstance.h"
void AMainMenuLevelScript::BeginPlay()
{
auto GameInstance = Cast<UPuzzlePlatformGameInstance>(GetGameInstance());
if (GameInstance == nullptr) return;
GameInstance->LoadMenuWidget();
}
And in my UMenuWidget (where OpenMenu is implemented) I modified the value for AddToViewport to a lower value than the one used in my error widget.
void UMenuWidget::OpenMenu()
{
this->AddToViewport(900);
...
}
And this is what my OnNetworkFailure implementation ended up looking like:
void UPuzzlePlatformGameInstance::OnNetworkFailure(UWorld* World, UNetDriver* NetDriver, ENetworkFailure::Type FailureType, const FString& ErrorString)
{
APlayerController* PC = GetFirstLocalPlayerController();
if (!ensure(PC != nullptr)) return;
FString errorTitle = "Network Error";
TMap<ENetworkFailure::Type, FString> NetworkFailureMap;
// Populate the map with keys and values
NetworkFailureMap.Add(ENetworkFailure::NetDriverAlreadyExists, TEXT("A relevant net driver has already been created for this service"));
NetworkFailureMap.Add(ENetworkFailure::NetDriverCreateFailure, TEXT("The net driver creation failed"));
NetworkFailureMap.Add(ENetworkFailure::NetDriverListenFailure, TEXT("The net driver failed its Listen() call"));
NetworkFailureMap.Add(ENetworkFailure::ConnectionLost, TEXT("A connection to the net driver has been lost"));
NetworkFailureMap.Add(ENetworkFailure::ConnectionTimeout, TEXT("A connection to the net driver has timed out"));
NetworkFailureMap.Add(ENetworkFailure::FailureReceived, TEXT("The net driver received an NMT_Failure message"));
NetworkFailureMap.Add(ENetworkFailure::OutdatedClient, TEXT("The client needs to upgrade their game"));
NetworkFailureMap.Add(ENetworkFailure::OutdatedServer, TEXT("The server needs to upgrade their game"));
NetworkFailureMap.Add(ENetworkFailure::PendingConnectionFailure, TEXT("There was an error during connection to the game"));
NetworkFailureMap.Add(ENetworkFailure::NetGuidMismatch, TEXT("NetGuid mismatch"));
NetworkFailureMap.Add(ENetworkFailure::NetChecksumMismatch, TEXT("Network checksum mismatch"));
// Access values in the map by key
FString ErrorMessage = NetworkFailureMap[FailureType];
FString address = FString::Printf(TEXT("/Game/UI/MainMenu?showError=true?errorMessage=%s?errorTitle=%s"), *ErrorMessage, *errorTitle);
PC->ClientTravel(address, ETravelType::TRAVEL_Absolute);
UEngine* Engine = GetEngine();
if (!Engine) return;
Engine->AddOnScreenDebugMessage(0, 5, FColor::Red, TEXT("Network error. Returning to main menu."));
UE_LOG(LogTemp, Warning, TEXT("Network error.Returning to main menu."));
}
This could still use some work and probably generating the Map is not the most efficient way but for now I’ve put way too many hours on this, more than I intended. If anyone wants to improve this and provide corrections and suggestions please do so. Hopefully this works for someone.