EventBeginPlay: Initialization Issues With Networking

The very short version of the question:
Is there any additional function called in C++ that acts as a post-initialization and is more reliable than EventBeginPlay in that is it always called after every other setup function?

The detailed version:
Our team is new to Unreal, and we’ve been focusing on R&D for a little while to get up to speed on the engine. Specifically, we are learning the ins and outs UE4 networking best practices.

All of the gameplay concepts at runtime seem perfectly feasible and intuitive to us so far. However, we have been hitting a lot of road blocks in regards to network map travel and player login. There have been “solutions” to these issues that we can envision right away, but not only are they messy, but they seem to expose framework flaws in the engine that we are simply not comfortable accepting just yet (unless we’re doing something wrong of course).

I’ll focus on one issue in particular for now. EventBeginPlay.

It is our understanding that EventBeginPlay is intended to be the last function called just before ticking. In terms of engine design, we read that as “Everything should be fully initialized, and all setup functions should be called at this point.” That sounds like the only way an event such as this is useful. If there is even one case in which BeginPlay is called before a crucial initialization function, it pretty much invalidates the usefulness of the event. Here is one example we have come across that is leading us to believe the EventBeginPlay function is not safe:

Example: When using seamless travel with CopyProperties.
Let’s say we already have multiple players logged in to a lobby level. One is acting as the listen server, the others as clients. Each player can make a selection of what character they want to play, and are also assigned a player number (used for player colors). This information is stored in a simple struct that lives in the PlayerState for each user.

Our Goal:
When moving into the actual gameplay level from the lobby, we need the player choices to be carried over to the next level. We do this by taking advantage of the CopyProperties function that the PlayerState provides. Quite simply, when leaving the lobby to head to gameplay map, CopyProperties is called and we simply copy the struct to the new PlayerState. Then, when we reach the new level, EventBeginPlay should be called on the PlayerController, and we should safely be able to use that copied struct to spawn the correct player and choose the correct colors for the mesh.

The Actual Outcome:
The listen server will work perfectly fine. When travelling to the gameplay map, CopyProperties is called, then all of the EventBeginPlay’s start firing off. Great. The player spawns the right character, and gets the right colors. However, when each client comes along, for some reason, the function order is reversed! First, EventBeginPlay is called, then CopyProperties. Thus, they spawn the default character, and all have the same color due to having the default struct values.

We have confirmed that using the first tick to perform the same spawning “solves” this problem. But honestly, we’re not comfortable with that solution. All the code-smell alarms go off. What we really need is one event that is always called last, just before the first tick.

Is there any additional function called in C++ that acts as a post-initialization and is more reliable than EventBeginPlay?

If anyone is interested, we also have another similar example that highlights the EventBeginPlay issue in regards to PostLogin. But I’ll save that for another time.

Hopefully someone has come across this before and has found a more elegant solution than “DoOnce” in the first tick. Thank you for any help!

I forgot to mention another solution is to use the Delay node with a time of zero, forcing the EventBeginPlay to wait another tick. But I still find that to be an issue. I’m still interested to see if there’s some other function hiding in C++ that I’m not aware of yet.

Hi,

I’m struggling with the same issue and know the problems that you descibed above very well. The only difference is that I’m using Blueprints instead of C++.

My logic looks like this:

  1. In the “Customize” section of the main menu you can modify your character (skin color, hair variations, etc.) and write the data into a savegame
  2. If you join a online session, you can join either the red or the blue team inside the lobby. The chosen/assigned team will be saved in your game instance as it’s the only persistent data storage class
  3. After a server travel to the play map, the game mode tells player controllers on post login to spawn their player pawns and this is the point where the problems begin…
  4. The player pawns get the stored data from the game instance on begin play and use this information to set their own name and to determine their team color (which will be set in a dynamic material instance depending on the assigned team; red or blue)

This works without any problems on the server (and without delay) but it does not work on the client immediately after spawning the player pawn because the casting of the game instance fails on begin play on the client side. I’ve built a macro that runs every frame on begin play till the cast will be successful once. This seems like a dirty workaround but I could not find another solution for this problem. Maybe it would be possible with event dispatchers but I have to try this out.

Did you manage to find a good solution so far?

Best regards,
Daniel

Another vote from me, with a similar issue.

I’m working through C++, and have noticed that the PlayerState isn’t fully initialized (and linked to the PlayerController) even by the time the PlayerState is hitting its BeginPlay() on the client side.

The work-around I’ve done is to tick on the PlayerController until it sees its PlayerState, then call my custom initialization code. Not ideal.

If anyone has instructions on how to do this cleanly, that’d be great.

Initializing things in a network environment is so messy in UE4… So many things going on I just wish there were a YouAreGoodToGo Event for initialization that is at AActor level :slight_smile: so any object could be initialized from there :smiley:
Anyway, regarding this specific issue, I’ve overridden OnRep_PlayerState().
I’m working on Blueprints as well so, so I added a blueprint event that is called within OnRep_PlayerState()

in .h

    	virtual void OnRep_PlayerState() override;
    	
    	UFUNCTION(BlueprintCallable, BlueprintImplementableEvent)
    	void OnClientInitialize();

in .cpp

    void ACustomPlayerController::OnRep_PlayerState()
    {
    	OnClientInitialize();
    }

in BP_PlayerController