Two gamestate per client ?

Hey everyone !

I always thought there was only one gamestate per game, replicated from server to clients.

But when i put the following BeginPlay function in my gamestate cpp file, i get a surprise:


void AYagGameState::BeginPlay()
{
	Super::BeginPlay();

	if (Role == ROLE_Authority)
	{
		GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("Server game state"));
	}
	else
	{
		GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("Client game state"));
	}
}

When opening a server, i get what i expect (server screen):

a8a50a751d906c49afb04c5444e8bb50986f67d3.jpeg

But when connecting a client to the server, i get this (client screen):

gamestate_C.jpg

Whatever the number of clients connected, i always get two client messages on the new client.

Can anyone explain to me who’s this uninvited extra guy ?

Thanks !

Cedric

I experienced a bit of a weird thing when I was testing out some code that I think might be pertinent here. I think the debug messages are replicated. I’m not 100% sure, but some of the behavior I was seeing definitely suggested that. Also, be sure that you’re starting a dedicated server and a local server (i.e. one where a player is the server), because those are different cases too.

Hi,

I’m using a listen server (not dedicated), and i’m pretty sure it’s not only messages that are duplicated, because if i put some other instructions in this function, they are called twice as well.

I’m looking for a place where i can call a function that must exist only once on each client. I first thought about gamestate, then i got strange results with my wannabe unique function, which led me to test with debug messages, and here i am :slight_smile:

Cedric

Quote from the Docs:

So no, the GameState does not only exist on the server. But the GameMode should.

You have a GameState on every Client and if you call the BeginPlay function, which is replicated, it will call the Begin Function twice. Once for the server version and once for your ClientVersion of the GameState.
You see only two because the client itself sees only his own BeginPlay. The Second one is just the server version. It is still a Client though, because it is just being replicated, not really called on the server.

Telling us what you want to do would help us creating your function.

Hi eXi,

Thanks a lot for the details answer !

I am trying to share large pictures between all my players (the one on the server and the clients).

I managed to do it through a webservice (linux/apache/php server of my own) which works very well, but now i would like to get rid of the webservice so i don’t have to store every users pictures on my own online server.

So i try to have the ue4 server act as a file server (a sort of baby http server): the server can receive, store, and send files, and each client has a connection to it.

I have written a file server and a file client.

i now need to do the following:

  • when the ue4 (listen) server starts, it starts a (unique) file server
  • when a ue4 client connect to the ue4 server, it starts its own unique file client.

Here is my gamestate cpp current code in its full (not working^^) glory:


void AYagGameState::BeginPlay()
{
	Super::BeginPlay();

	if (Role == ROLE_Authority)
	{
		GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("Server game state"));
	}
	else
	{
		GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("Client game state"));
	}

	uint32 PCPort = 7778;
	// create and start the file server
	if (AYagActor::bIsUsingLocalFileServer && Role == ROLE_Authority)
	{
		GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Red, TEXT("New File Server"));
		YagFileServer = NewObject<UYagFileServer>(this);
		YagFileServer->FileServerGameState = this;
		YagFileServer->FileServerPC = GetWorld()->GetFirstPlayerController();
		YagFileServer->FileServerWorld = GetWorld();
		YagFileServer->Start(PCPort);
	}

	// create the file client and connect to file server
	if (AYagActor::bIsUsingLocalFileServer && Role < ROLE_Authority)
	{
		GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Red, TEXT("New File Client"));
		YagFileClient = NewObject<UYagFileClient>(this);
		YagFileClient->FileClientGameState = this;
		YagFileClient->FileClientPC = GetWorld()->GetFirstPlayerController();
		YagFileClient->FileClientWorld = GetWorld();
		YagFileClient->ConnectToFileServer(PCPort);
	}
}

As you can see, the server works fine (only one server socket listening on port 7778):

ServerOnly.jpg

but when i connect a client, i get 2 client sockets (one is lingering because they are not multithreaded yet so the second one disconnects the first one) when i would only expect one:

ServerWithOneClient.jpg

I hope it’s a bit more clear :slight_smile:

Cheers

Cedric

You could try the “PostLogin” Function of the GameMode.

This should be called when the Controller is initialized and the pointer isn’t null.
You will get a pointer to the connected controller.

I will try that and will update here as soon as i have news.

Thanks again :slight_smile:

Hi,

I made a few test with PostLogin (and Logout, which i didn’t know either).
That’s a great way to manage and distinguish every PC and i already started using it to simplify some other parts of my code !

But for this specific problem, it would involve calling a function on client from server, which, as far as i know, cannot be elegantly done: the only (reliable) way that i know is to use a replicated variable with a notification function. This is rather heavy and implies the use of a dummy variable, which i find unsatisfying.

So, knowing that the gamestate functions will be called twice on client, i ended simply adding a boolean flag:


void AYagGameState::BeginPlay()
{
	Super::BeginPlay();

	uint32 PCPort = 7779;
	// create and start the file server
	if (AYagActor::bIsUsingLocalFileServer && Role == ROLE_Authority)
	{
		GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Blue, TEXT("New File Server"));
		<code to start file server>
	}

	// create the file client and connect to file server
	if (AYagActor::bIsUsingLocalFileServer && Role < ROLE_Authority)
	{
		// because on client, BeginPlay is called twice due to replication
		if (!bIsFileClientStarted)
		{
			GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Blue, TEXT("New File Client"));
			<code to start file client>

			// mark file client as started
			bIsFileClientStarted = true;
		}
		else
		{
			GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("File client already started"));
		}
	}
}

Still, i’d like to add i am not fully happy with the way things work here.

Having to deal with the fact that every function (including initialization function) in gamestate will be called twice exactly the same way (with no way to even distinguish between server and client ? What’s the point ?) is kind of awkward.

Whatever the solution i choose (gamemode+OnRep or gamestate+flag), it feels like a workaround.

Of course, having said that, i cannot end this message without adding how much i love UE4 !

Again, thank you very much for all the help and explanations, very appreciated !

Cheers

Cedric

I know that you can differ Characters that are localy controlled and the copied once on other clients.

There is a function to call on characters that returns true or false if they are locally controlled or not.
This should make sure that only the local controlled character calls the functions. Maybe this helps you. You could call the function on the character
and check with this function :X

Hey !

Yes, that was indeed my first try: using playercontrollers and differentiating them with the help of ROLE_Authority combined with those functions you are refering to (IsLocalController() and IsLocalPlayerController()).

But this time i get the inverse problem that i got with the gamestate, the funtion are called only on the first PC, so they are all marked “first PC” or “Local PC”, i can only differentiate server from client, but can never reach the non localcontroller).

For example, with this code in the PC BeginPlayState() function:


if (Role == ROLE_Authority)
{
	GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("Server"));
}
else
{
	GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("Client"));
}

if (IsLocalPlayerController())
{
	GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("Local PC"));
}
else
{
	GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("not local PC"));
}
	
if (this == GetWorld()->GetFirstPlayerController())
{
	GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("first PC"));
}
else
{
	GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Cyan, TEXT("not first PC"));
}

I was expecting to get as many messages on each game instance as players connected (for exemple with 4 clients, i was expecting 5 messages on the server and 5 on each client). But i got only one message per machine. I remade the test quickly to get 2 screenshots (one message per machine, whatever the number of players: this the BeginPlayState() function is only called on the first player controller):

PC_server.jpg PC_client.jpg

So technically, although i don’t understand what’s going on, i can use that for my purpose, and i did at first, but then i thought the file server should be attached to the gamestate and not to the players, as there is only one gamestate per computer but several PC’s, and moreover the file service is more related to game mechanics than to players, which led me to that replication pb and the present thread.

So yeah, i know now 3 ways to accomplish what i want, i found one and you gave me 2 others, but as i said, none of them gives me the feeling that i fully understand what’s going on. Those “fundamental” actors, gamemode, gamestate and PC’s and how they behave and interact in a networking environment (and especially at initialization time) are very unclear to me.

For example, i can grasp the notion of replicated variables and RPC’s, but that replicated function thing (leading to a function being called twice identically on the same client), that’s a new surprise for my poor brain.

I am still experimenting (and currently struggling with multithreading), if i ever come up with an elegant (and understandable for me^^) solution, i will post it here of course.

Cheers !

Cedric

Yes i know that. I’m currently developing a Multiplayer TopDown Shooter and the RPCs are giving me headache one by one.
The thing that you need to remember is: The server is the only one that has the right to do something, so that i will be changed for everyone.

So if you want to jump with your character and you only call the function localy (or change the state etc), then the client has no right to tell the others that he jumped.
This is why you need the RPC. The server will now take care of the jumping part and since he is the authority, the others will update and jump too.

This is just one example.

For your problem:

I hope you know that the PlayerController only exist once on each client. A client does not have any other PC than the one he uses.

So if you do these 3 if/else inside the PC, you won’t get any information on the others. There can’t be a second message from the other client,
since there isn’t any other PC.

But the server knows every PC. So using “First Player Controller” will return the servers one. For clients there is no second PC.

oooow, i hadn’t got that one, i thought the PC’s were replicated everywhere, my mistake.

So ok, IsLocalPlayerController() only makes sense on the server, kind of obvious now^^

Thanks a lot for this clarification !

You are welcome. The multiplayer documentation still needs a lot of information. (: It’s normal that you don’t know everything yet.