#Lobby Player Names
1 messages · Page 1 of 1 (latest)
Do you have Splitscreen Players in your game?
If the answer is "no", then here are a few things:
- In your OnRep_PlayerList, you don't need to call "GetAllActorsOfClass", since you only have 1 Player, so GetPlayerController0 would be enough.
- In your "UpdateLobbyUI", why are you getting the first PlayerState of the PlayerArray? Are you trying to get the local Player's PlayerState? Because the order of the PlayerArray is not a given.
- In "OnPostLogin", you don't need to check "IsServer", because the GameMode only exists on the Server.
- In "OnPostLogin", why do you add a 1 Second Delay? Delays are not meant to bridge any time gaps where something isn't valid. You are also completely ignoring the fact, that OnPostLogin could call again during that 1 second, which would change the Player pin to a new value before the delay runs out, which would break a lot of stuff.
- I don't think you need to change the GameState to NetLoadOnClient. I've never done that and I don't see why that would be needed.
Ultimately, if you want to react to PlayerStates becoming valid on Clients and you want to update the UI, you should just hook into the PlayerState directly.
- Use BeginPlay of your PlayerState to know when a given PlayerState is valid on anyone.
- From there, use GetPlayerController0 and call "AddPlayerToLobbyUI" or something like this on your Widget, passing the PlayerState along
- In addition, in your UI Widget, in EventConstruct, get the PlayerArray from the GameState and call AddPlayerToLobbyUI on each entry. This is to catch all the PlayerStates that already called BeginPlay before your UI was added.
- In the AddPlayerToLobbyUI function, you may want to check that there is no Entry with the given PlayerState already (just loop the Entry Widgets and compare the PlayerState variable you supposedly set on them when creating them).
- You may want to use EndPlay of the PlayerState and a "RemovePlayerFromLobbyUI" to clean the UI up again.
The whole OnRep_PlayerList is somewhat redundant and error-prone.
tysm yeah i was trying to get it so that when anyone joins eitehr via host creation or joining off invite their steam name is grabbed and put into 1 of the 2 slots and replicated
but i was having issues replicating the names so i just started trying stuff
but yeah no splitscreen players its just gonna be a co-op 2 player story game
hmm so should i even use playerlist variable for this then? @sullen tangle
Probably redundant then
ugh still cant get playernames to show up, it shows instance name in PIE but not when I test packaged
Instance Name?
o i see
That's normal in PIE
But that means it's working in PIE
What do you see in packaged?
And what do you package as? Dev? Shipping?
So Dev(elopment)
yea
So you don't see anything? Can you try if you see something in Standalone?
E.g. start Standalone via the Editor, or rightlick your .uproject file and hit Launch Game.
Hm
which is weird because i didnt even get to create the session by clicking play it just launches the level straightup
Yeah but this is the Standalone Player
They also have a PlayerState that calls BeginPlay
So if this is the MainMenu that also holds your Lobby UI, then it wouldn't stop from doing that.
this is what it looks like in packaged
i made a seperate level for lobby
i ahve a main menu level and lobby level
Right, did you play through Editor -> Standalone?
yeah
uhh i have 0 clue about that 1st but current map is lobby yeah
oh
no its set to play standalone
Okay but that's fine
should i try play as listen
It started in the Lobby ause you have the lobby open
ah true
if you do it from MainMenu or if you use the .uproject file it will be the correct flow
gotcha
Now about Packaged, not sure atm. Do you get the proper Steam overlay popping up when starting?
yeah
I would suggest you add an ugly, fixed size background to that Name Entry Widget, so that it shows even if the retrieved name is empty.
Just for debugging of course
kk
Then you can at least check if the Widget flow works
If that doesn't show then something earlier breaks
Which is strange caues this is just hte ListenServer
There is no ping or replication going on yet
for the name im just updating the text thats in my lobby ui i didnt create a seperate widget for it could that be why?
I mean, usually one cuts the UI into multiple widgets
The center of your UI would be a BP_Lobby_PlayerList
And each entry would be a BP_Lobby_PlayerList_Entry
BP_Lobby_PlayerList would get all PlayerStates from the GameState on EventConstruct and loop over them, creating and adding BP_Lobby_PlayerList_Entry to, e.g., a VerticalBox for each PlayerState, passing the PlayerState along to it.
Rest of the logic with BeginPlay of PlayerState I already explained
BP_Lobby_PlayerList is also what would have the "AddPlayerToLobbyUI" function I mentioned earlier.
And checking if an entry already exists would be looping over the Children of the VerticalBox, casting them to BP_Lobby_PlayerList_Entry and comparing the PlayerState in that with the one you want to add a new entry for and if you find a match you don't add the entry.
That all said, it shouldn't stop your game from showing the name in that textbox regardless
So maybe add some PrintStrings to log the parts of your code that should call
ah I see, yeah I just set that up as a function within my lobby ui this i what it is, kinda messy but it just loops through All Player Names array which is just an array of the 2 text boxes andc checks to see if they are empty and then if they are empty it grabs steam name and adds the text
And check the Saved/Logs folder afterwards
will do the print strings and see where it stops at
Okay 2 t hings
- You don't need to cast here, cause the Pin is already of type BP_CustomPlayerState I assume
- Just call "GetPlayerName" on the PlayerState. You don't need the NetID stuff for this.
The problem with not properly checking if an entry exists is that if you loop over the PlayerArray and add entries and there is a PlayerState that hasn't called BeginPlay yet, you'd be adding the entry twice.
Cause BeginPlay would also do so.
wouldnt the loop checking if string is empty be checking if the entry exists?
or is that wrong
All you do is find the next empty one
That doesn't confirm that any of the non-empty ones already hold this player's name
ohh
true
hmm so do you think I should just scrap this and setup the individual widgets then?
In the future, yes
For now, please add PrintStrings every where from PlayerState BeginPlay, over to the lobby event construct, throughout the functions that add the names
And check the logs of the dev build to see what is not calling properly
kk
ok done gonna launch and see
uhh
its working now?
wtf
gonna try with virtual machine to see if player 2 replicates
ah i see what i did wrong i think
i had all player names set once the loop in event construct that got player array from gamestate
i had all player names set only after loop was completed by accident
So everything good now?
ok soo this is weird
on virtual nmachine i can see the otehr player name like this
but on my end they arent there
this branch false breaking loops like a million times
whenever the 2nd plyer joined
player 2's console
branch false, breaking is the print string i put after the branch was set to be false
and the nit seems like the loop started a bunch of times until player 2 fully loaded or sometihng
wonder why i cant see them on my end though?
its also worth noting that my game started literally devouring my cpu
@sullen tangle in pie logs it says infinite loop
It's because on "false" you are going back into the ForLoop
The code doesn't seem to find an empty entry for the player
You might want to double check, with print strings, why that is
Check what the UI actually found and how often it looped
oh wait shouldnt false break it actually
if the text it gets isnt equal to empty it should break
No
oh
False should actually do nothing
If it breaks it would stop at the first found non-empty string and never add your player to an empty one
o
maybe thats what was breaking the game on my end and not showing palyer 2
let me test again
@limpid eagle How many TextBlocks do you actually have prepared there?
If it's exacly 2, then there is a good chance that the other one is filled by something you don't expect
So please print whatever your function there finds in the array
This is probably a hint at what I said before that you gotta double check in your code that the playerState name isn't added twice
kk
will have to test it in packaged as pie sees 2
but did you mean to have it print hte text it finds?
being added twice
yeah it is
for player 1 not player 2 but still same thing
Then what you can do for now is
When your Branch for === empty returns false, add another Branch
The condition for that is if GetText(Text) === GetPlayerName
And if that is true, then you can simply break the loop, or just call return
That will stop filling the name in twice for now I would assume
In the future it would be better to have a proper Widget for the Entries and to have the actual PlayerState pointer as a varaible on it (exposed on spawn and passed in via the CreateWidget node).
Or fwiw, if you hand place the entry widgets, simply set on the widget.
And then you can compare the PlayerStates instead of the names.
Lobby Player Names
gonna implement it now
but yeah didnt know i should make sepearte widgets and such
just thought you add some text to whatever ui and update it
will it be the same for the huds and objkecitve markers and such?
hmm still getting my name twice wth
maybe how im getting the playername is wrong
Ok so i figured it out actually
for some reason update lobby ui (my original repnotify ffunction) is being called whenever teh 2nd player joins but only for the 2nd player
so this add player to lobby ui isnt the thing actually replicating both players
its my original rep notify function wtf
but its not being called on host machine
i think youre asleep/away but any thoughts? @sullen tangle
maybe thats wrong actually i have 0 clue
all ik is original repnotify is being called for 2nd player
nvm it isnt that since i just made PlayerList replicated instead of repnotify and it still works on player 2 not player
1
I would need to sepnt a bit of time actually coding this part myself again to give you better insight, cause right now it's all just from memory.
It seems to perfectly work in the Editor after all, right?
And also Standalone fwiw.
You should probably get rid of your old logic.
I can write you some fake code that you can try to translate into Blueprints. (cause I don't have UE on this Laptop and can't make pictures atm.).
I will try to use as little "actual coding language" as possible, so that you can simply translate the text to what you Blueprints might do.
I actually question now if it wouldn't have been better to simply do it in Blueprints and screenshot those. Hm.
I hope this helps you either way. I didn't test this setup, but that's a write up in some fake language that fits how Blueprints work.
Now I really want to have a coding language than can translate into Blueprints like this.
Something people can write pseudo code with that then can be copy pasted and results in Blueprints.
Obviously nothing that can add logic that doesn't exist. That would require C++ again. Hmhmhm. So much one could do, so little time.
Ooo gonna try this tysm
jhust woke up lol
just read through all of that though, ty for writing it that way
Since im trying to have the 2 names be in these 2 different white spots would i still use vertical box?
No, if you have two specified slots then you can alter it
I updated the link with 2 Alternative Snippets.
Bottom most ones
For LobbyUI and PlayerListEntry
kk will try the one with two player list entry
/// Returns param is of type BP_LobbyPlayerState
BP_LobbyPlayerState WB_PlayerListEntry::GetPlayerState()
{
return PlayerState;
}
WB_PlayerListEntry::SetPlayerState(PlayerState)
{
PlayerState = PlayerState;
IsValid(PlayerState) => True
{
TB_PlayerName.SetText(PlayerState.GetPlayerName());
}
=> False
{
TB_PlayerName.SetText("");
}
}
a little confused on this part,
more specifically BP_LobbyPlayerState WB_PlayerListEntry::GetPlayerState()
{
return PlayerState;
}
am I casting to bp_lobbyplayerstate to retrieve the playerstate? @sullen tangle
What exactly do you want to cast
im not sure if casting is what i need to do but what is
/// Returns param is of type BP_LobbyPlayerState
BP_LobbyPlayerState WB_PlayerListEntry::GetPlayerState()
{
return PlayerState;
doing
i have the isvalid part down i believe, just unsure of the rest
actually think i got it
@sullen tangle i believe this is what you detailed in the psuedo code?
Correct
ok and for tryaddplayertolobbyui this is what i came up with but im unsure of how to connect the playertwo one?
or do I have both going into the 1st get player state one
and if the 1st one is valid then check the 2nd one?
Use a "Sequence" to mimic the call order from the pseudo code
It's not 100 percent the same. It first checks if either of the Entries has the PlayerState
And then it checks them for not valid
In the pseudo code that is
Your code could, in theory, lead to the player being added to 2 and then to 1 if 1 got clearer at some point. Unrealistic but at least that's why I didn't do it like that
ah i see
well how my logic is going to be setup is if host disconnects the lobby closes and everyone is sent to main menu
but if player 2 leaves they are just removed from the name
Yeah that's why it's probably unrealistic to expect
gotcha
gonna finish it up and test it out :)
do i need to replicate the variables in playerlist?
or should be fine
Widgets don't support replication
So no
The main point of this setup is, that the whole Replication part is handled by the PlayerStates replicating and calling BeginPlay on the individual Players.
Or for you, it's just the Second Player fwiw
Server also calls BeginPlay on the PlayerStatess of course, but not cause the replicated to it.
Yeah that can all go
kk
hmm its not working but i believe its because the player state keeps returning as invalid
i added break points
the set text breakpoint in set player state never gets reached
kk
lobby player controller calls this function and then this function checks to see if the player one entry state and the palyerstate received from lobbyplayer controller are equal
and then if not then it checks to see if the playerstate it got from player one entry is valid
and if its not valid it calls set playerstate
and then set playerstate receives the playerstate from the lobby ui fnction
and sets the variable playerstate to that and then checks if its valid
which is always returning as invalid
should i be plugging in the playerstate received from lobby playercontroller rather than the one received from get playerstate into the set playerstate one?
yeah that was it
oops lol
hmm now only working in editor and standalone
Hm, that's the exact thing you had before, right?
Feels like something is wrong with your packaged project.
Shouldn't be ping related. You could try to see if it works without Steam enabled.
kk
samje thing
adding print strings
ok so add player to lobby ui is getting called
but not going any further
neither of the print strings are firing
is it possible that its getting called before lobby ui is constructed? since the message comes after
Yeah
Actually, I fully forgot a step I think
Gimme a minute
Alright I updated the Snippet
Specifically the WB_LobbyUI
It has the EventConstruct now to query the PlayerStates that already called BeginPlay before the UI existed.
@limpid eagle
gotcha ty will add
still cant see player 2 as player 1
i tested it in PEI
PIE too
pie gets both for 2nd client
only 1 for 1st
so weird
@sullen tangle is it possible that this requires a seperate function that calls for an update? maybe
ok adding a 1 second delay fixed it in pie
seems like it was trying to grab the palyerstate before player 2's playerstate was fully initialized so it was just using player 1
or something
gonna try packaged
packaged worked
is there any other way otehr than a delay to wait until playerstate is initialized
i also had to add a delay to the end play calling the remove logic as it was trying to access the lobbyuireference too fast and returning none before
before calling the remove logic
also remove logic isnt working for some reason
think its coming up with an empty playerstate when trying to get, only logical answer maybe
Hm. Delays are usually not needed. That's why it's set up to use BeginPlay of the PlayerState.
The logs read like they updated everything properly
PIE only getting one Player for first is strange.
That's the server not getting who's PlayerState? Their own or the client's?
I mean that's why we use BeginPlay to know when the PlayerState is valid.
it wasnt getting the clients playerstate
seems like it was trying to grab its playerstate too quick
so it couldnt find anything
or smthn idek
If the LobbyUi is invalid on EndPlay then that's fine. Just check it with an IsValid. If it's invalid then it either is already destroyed or not yet created and in both cases there is no need to remove the player from it.
There is no "too quick" if one relies on BeginPlay 🤔
Yeah I'm gonna think a bit about it. Just woke up. Gotta get to the office first.
np
yeah looking at it again is super strange
because beginplay is literally in the playerstate
lmao
It's not like the code is super complex either
Hm. Maybe the PlayerState is pending kill at that point and some IsValid check stops it, but I doubt that. We could also listen to the Destroyed delegate in the Widget itself and clean it up that way
testing some things out atm
Do I need to put open level here?
er kinda hard to screenshot all
but whenever an invite is accepted do I need open level? its already transitionin to lobby fine without open level but im not sure
for the host creating the session it opens level
If you use JoinSession then no
yeah i use join sessio
That will perform a client travel
You should call anything after the join session call anymore fwiw
But that has nothing to do with the PlayerState
adding some print strings to see how far it gets shouldve done that already lmao
I will probably eventually code this myself to debug it. I don't really get why it's still relying on delays.
yeah me either
doesnt make any sense since the logic originates in the playerstate
is it possible lobby ui event construct is running before playerstate starts?
reading the strings it eems like thats the case
Yeah it's to catch ones that already called BeginPlay
Maybe gotta check Is Valid on the loop array elements of the PlayerArray
Not sure atm
Was thinking of event tick maybe but not sure
some type of event tick
Ok its the same for remove where the lobby ui is deconstructed first so I tried an event destruct and jhust had event destruct call remove from lobby ui and it works
i dont understand how lobby ui can be constructed before a playerstate can be initialized
hmm it also seems like its only the case for when the 2nd player joins that that happens where lobby ui is constructed 1st as testing with only 1, the console shows lobby playerstate first
actually scratch that i have 0 clue since in editor lps is initialized after lobby construction, but in packaged playerstate is first so idk why adding a delay fixed it
but this i believe is still true
Behavior is very different between PIE, Standalone and Packaged in Networking.
There is also a big difference in ListenServer (who doesn't get any Replicated Data and has things "Instantly") and Clients, who have no guaranteed of receiving replicated data in any order.
It can totally be that they join, get the PlayerController and then have to wait a bit on the PlayerState of the other Player and their own.
But that's why I designed it to only have 2 points of "attack":
- PlayerState
- LobbyUI
The LobbyUI grabs all PlayerStates from the PlayerArray when it gets created to ensure it grabs all of the ones that were available before the UI was added.
The PlayerState tells the LobbyUI that it is valid from BeginPlay to add itself.
I will need to debug this myself, cause I'm pretty convinced that this should work.
I know there are some extra steps where a Client that leaves might leave behind a PlayerState that is "Idling", so they can reconnect and get their stats back.
But that shouldn't matter for the connection part.
Gotcha, I'm gonna stop yapping and sleep for a few hours lmk if you find a anything and thanks again :)
So I programmed it on my end, and the reason is quite strange.
I mean, not strange, it's somewhat understandable, but also stupid :D
I will give you some background info so you learn something from this.
The AController class is the one that spawns the APlayerState. AController is the parent class if APlayerController.
There are two ways to spawn an Actor in C++, one is simply calling SpawnActor, the other is using SpawnActorDeferred, which is followed by a SpawnedActor->FinishSpawning().
You usually use Deferred if you want to init the Actor before it is read for play (BeginPlay fwiw).
Now the APlayerState is spawned non-deferred, so after the code-line that calls SpawnActor, it will instantly call BeginPlay.
However, after that, Epic sets the PlayerName:
PlayerState = World->SpawnActor<APlayerState>(PlayerStateClassToSpawn, SpawnInfo);
// force a default player name if necessary
if (PlayerState && PlayerState->GetPlayerName().IsEmpty())
{
// don't call SetPlayerName() as that will broadcast entry messages but the GameMode hasn't had a chance
// to potentially apply a player/bot name yet
PlayerState->SetPlayerNameInternal(GameMode->DefaultPlayerName.ToString());
}
Now, that however isn't even the PlayerName that we need. That's just the DefaultValue that comes from the GameMode (you can set that in BPs too, but it's irrelevant atm).
The AGameModeBase further calls InitNewPlayer, which does this:
// Init player's name
FString InName = UGameplayStatics::ParseOption(Options, TEXT("Name")).Left(20);
if (InName.IsEmpty())
{
InName = FString::Printf(TEXT("%s%i"), *DefaultPlayerName.ToString(), NewPlayerController->PlayerState->GetPlayerId());
}
ChangeName(NewPlayerController, InName, false);
That uses the ?Name=XYZ that can be passed along the Connection URL to tell the Server the Player's Name. Of course, not in Blueprints, as JoinSession does all the Connection stuff for you and you never get a chance to inject any options.
ULocalPlayer has a function that can be used to supply more, but that's beyond the scope of what this is about.
I would assume that there is another call somewhere for accessing the PlayerName from the Subsystem (so you get Steam's name), but maybe that already happens locally and the name is what is being sent.
You should be able to, in your Log, to see this sort of line when the Client connects:
LogNet: Login request: ?Name=SPS-Desktop-01-FC27210D42B7BABCAB4CA9A9424FAA83 userId: NULL:SPS-Desktop-01-FC27210D42B7BABCAB4CA9A9424FAA83 platform: NULL
This is in PIE, but should be similar to Packaged etc.
The tl;dr of this all is that the BeginPlay function of a given APlayerState (so also your Lobby one), calls before the PlayerName is valid.
I noticed that cause I added a default name to the Widget which gets set to an Empty text when the Client joins (meaning the Name is being set, but empty).
Now the reason why your dealy works is, well cause it delays the action and the Name is set. It might literally just be 1 Frame Delay that is needed to get this working (Delay node with 0 in duration).
However, to make it easier and non-dealy relying, you can do these two changes:
- Change the SetPlayerState function in the WB_PlayerListEntry back to this:
So that its not setting the name. And now we gonna do the ugly thing and use a Binding, which is the Tick part you asked for, but not directly tick.
- You add a Binding to the Text Property of the
TB_PlayerName. If you don't know how that works, please google a bit.
And the function you bind, after renaming it to something saner like GetPlayerNameText does then this:
That fixes it for me in PIE. I won't Test package, cause I have no time for it atm.
Server hosted:
Client joined:
Dedicated Server with 1 Client, in PIE, started from the Lobby Level (just to show that it works fine with any constellation):
Listen Server with 2 Players (1 Client), in PIE, started from Lobby Level (again to show it works):
2 Standalone Games, started via right-clicking .uproject file:
This is probably as much support as I can give you at this point. (theoretically way too much haha, but Lobbys and Multiplayer is exactly my thing and I didn't want to accept defeat).
If you still run into Packaged Problems, please debug it a bit yourself and come back if you really can't progress.
Also, in case you wonder how one would solve this without Binding/Tick/Delay0:
In theory, in C++, the PlayerState's PlayerName variables is marked as RepNotify.
/** Player name, or blank if none. */
UPROPERTY(ReplicatedUsing = OnRep_PlayerName)
FString PlayerNamePrivate;
UFUNCTION()
ENGINE_API virtual void OnRep_PlayerName();
and one would probably override the OnRep_PlayerName function to add some Delegate/EventDispatcher to it.
Then in the PlayerListEntry Widget, when setting a Valid PlayerState, one would simply bind to the Delegate to be notified when the name changes.
There is an event in the GameMode called OnChangeName, which calls for every player that changes name, but given that is only calling on the Server and would need another RPC to tell Clients about it, it's not useful.
I was going to go do the binding you showed
and thjen i saw this
.
its like a preset binding or whattever
and it works
but seriously ty for all of your help I really appreciate it
The Binding system can locate matching Functions and Properties on Member Variables. That's what you see there.
It does remove your ability to do anything custom though, but if that's fine then go for it.
If you need to alter the name (shorten it) or want to show some default value when the PlayerState is invalid, you'll need to supply your own function instead of the ones in the drop down.
ah i see
ill just use yours just to be safe
just thought it was weird never saw those before
and also the remove is now working since we have it on a binding now
binding + destruct combination i believe is whats getting it to work awesome :D
I didn't add that part to mine. It worked fine without, but maybe that's a ping issue that I didn't notice in PIE.
seems like its because the lobby ui reference is pending kill
er no the lobby player controller is pending kill
so we cant grab the lobby ui ref
Blueprint Runtime Error: "Attempted to access LobbyPlayerController_C_0 via property K2Node_DynamicCast_AsLobby_Player_Controller, but LobbyPlayerController_C_0 is not valid (pending kill or garbage)". Node: Remove Player from Lobby UI Graph: EventGraph Function: Execute Ubergraph Lobby Player State Blueprint: LobbyPlayerState
so destruct runs before player controller is pending kill
Very strange that the PlayerState calls EndPlay with the PlayerController being already pending kill. But I guess that is a replication thing.
Bindign to the Destroyed Callback of the PlayerState in the UI is better anyway.
In theory, the BeginPlay call can also be replaced by a binding (would need to do that binding), but then we start overengineering stuff
yeah I fixed it by just having begin play set a ui reference in playerstat
and just using tha tref
@light crater Have a look at this thread.
There is a lot of back and forth in here as I already helped a user once with Lobby Names.
Rock and roll, thanks. It's pretty beefy to read but might be worth while
In theory it's enough to start here
#1246489335344402503 message
#1246489335344402503 message
https://dev.epicgames.com/community/snippets/1W12/unreal-engine-lobbyui-example-pseudo-code
@light crater That last link was some fake code I wrote for them
You might be able to follow that
It's written as a "Text to BP" thing with explanation
Aye, thank you. especially towards blueprints
Fwiw, this was to fill a list with PlayerEntry Widgets
Which might not be exactly what you do, but you should still learn a lot from it
I'm just deleting old code right now.
What I'm having issues with is when to ask the right questions and to whom.
I understand the owning actor can call the RPC, I understand that the game mode is the server, no need to replicate on it. I know the player controllers own the widgets, but I'm having a pain of a time in practice. Sometimes I take one step forward and two steps back in this haha