I’ve been stuck on getting networking to function for some time now, and it turns out that my problems (currently) are down to Tick() and BeginPlay() not being called on clients - breakpoints in the aforementioned functions in the pawn owned by the client are never hit, whereas PostInitializeComponents() does work. Client RPCs sent from the server also work, so at first I just had the server send unreliable RPCs in its Tick() to have the client pawn update, but other components on the actor (namely a spring arm) aren’t updating either. This is obviously a massive bummer, and rather game breaking to say the least. I’ve found other people with the same issue, even dating many engine versions back, but no fix apart from starting a project from scratch, which I would rather avoid if at all possible. bCanEverTick is set to true in the constructor, and I even tried SetActorTickEnabled(true) in PostInitializeComponents(), to no avail.
I decided to bite the bullet and migrate everything to a fresh project. After much faffing about to get the copied code to compile again (it turns out that .Build.cs will only look at the dependency list after you compile successfully once first, i.e. without any extra items, and then add the dependencies back in) I faced the inevitable: it still doesn’t work. I went digging through some engine code after reading the bottom most answer here, and saw that BeginPlay() is only called in AWorldSettings::NotifyBeginPlay(), which in turn is triggered by the GameMode switching the state to InProgress. By default, a game mode will change to InProgress as soon as the level is loaded.
Let’s take the following scenario: the game starts on a lobby map, where the player can either choose to be the host (as a listen server), or connect to an existing session. A dummy game mode is active on this map. Once he starts a new session, the listen server loads the target map, and the map’s game mode is activated. The game mode switches the game state to InProgress once the map is fully loaded, and BeginPlay() is called on all existing actors. Now, a second player joins the session. The game is already InProgress - BeginPlay() isn’t fired for the actors from the client’s point of view. Even though the actors are set to replicate, they do not start ticking either, so they don’t know they were already set to begin play on the server. Only if I specify bAllowTickBeforeBeginPlay = true, Tick() is called on the client side. Sadly, this still doesn’t tick the spring arm component attached to the actor.
So how exactly is this done properly? I must be missing something very obvious.
The solution (read: hacky workaround) was surprisingly simple, but really shouldn’t be necessary. I’m still hoping to learn what I’m doing wrong exactly. At least now my actors are ticking and I can get on with the project.
void AOverviewPawn::PostInitializeComponents()
{
Super::PostInitializeComponents();
if (!HasAuthority())
{
BeginPlay();
}
}
Hard to tell. Is the Pawn placed in the level or spawned from the gamemode (or elsewhere)?
Actors spawned on clients via replication (DataChannel.cpp UActorChannel::ProcessBunch) should have PostNetInit() called on them which calls BeginPlay() given a few more conditions are met.
AWorldSettings::NotifyBeginPlay is called from the gamemode on the server when the match starts or transitions into the state “WaitingToStart” when ReadyToStartMatch() returns false. On clients it should be called from the gamestate when the state changes to “WaitingToStart”. The problem here is that the state may not actually be “WaitingToStart” ever (for example if you join a match that’s already in state “InProgress”).
So even on clients BeginPlay() should be called.
I always thought/assumed/expected BeginPlay to be called on clients as well but apparently not?
Have you considered debugging these functions (specifically AActor::PostNetInit, AWorldSettings::NotifyBeginPlay and AGameState::OnRep_MatchState) to see what happens on the clients?
Edit: Oh I’ve missed something regarding the state my never actually be “WaitingToStart” on the gamestate for clients.
What I’ve discovered leads to the conclusion that AWorldSettings::NotifyBeginPlay MUST always be called for clients. Whenever AGameState::OnRep_MatchState is called and the PreviousMatchState == “EnteringMap” (which is true for the first time the function is called because PreviousMatchState has “EnteringMap” assigned in the constructor), AGameState::HandleMatchIsWaitingToStart is called which in turn will call AWorldSettings::NotifyBeginPlay.
You may have a special setup that prevents or delays BeginPlay.
Yes, that’s it! I forgot to add a call to Super() in my GameState’s GetLifetimeReplicatedProps(). I could kiss you right now if it wasn’t socially awkward.
Actually, one more related question: at what point is the GameState “visible”? At PostLogin(), trying to find the game state in the player controller returns null, even though it persisted from the lobby map. Currently I use the very dirty method of having a second delay before querying the game state, but I’d very much like to replace it with a proper way of doing things.
Both work on clients for me by default, I haven’t had to do anything special.
PostInitializeComponents() is called locally, so there should never be an issue with that.
Tick(), again, will call locally (it has too - you’re clients are all running at different framerates!).
BeginPlay() is called by the World Settings - HOWEVER, I have noticed that Clients will call Begin Play at their own time, not neccesarilly when the Server decides to call it. They can in some cases call it before the Server, even.
The GameState is visible as soon as it is replicated, and you cannot garauntee replication order of anything. You have to code in such a way that everything can work around classes being valid at different times and in different orders. I ended up writing an ‘event manager’ for my online system, which fires a delegate when certain classes are first replicated, so that other dependent classes can set themselves up.
Don’t use delays either, not only do you not know what the order of replication is, you don’t know when it’ll happen either. Multiplayer stuff has to be event based.