When I was looking through the ShooterGame example…it seemed they used the PlayerState to keep up with Statistical type of information (Number of Kills ect). I believe the reason for this is, there is a PlayerState per PlayerController, and a Player Controller has the capability of possessing different characters or pawns. So for example, in your example, if you were to put health there, if your game had any capability of the PlayerController switching pawns…they would all share the same PlayerState. I think this is one of the more confusing aspects of Unreal…I think the documentation should do more to describe what kind of information should typically go where.
From your Character, you can get the PlayerController using the property ‘Controller’ (just cast it to your player controller type) that you are possessed by, and from there the PlayerState or you can access the PlayerState directly using the property of the same name, just keep in mind its pointing to the one associated with the controller that possesses your character. (which goes back to the problem of keeping something like ‘Health’ in playerstate)
I would definitely take a look at the ShooterGame example, as it helped me alot with this as it fully implements replication. You can find it in the ‘Learn’ section of the launcher to download.
Just to wrap things up, I think the ‘Character’ is the more appropriate place to put Health, as Health is usually specific to them. I believe PlayerState and PlayerController is more of representation of the Human player himself, so information related to that is more appropriate there.
Did you verify that you are getting the access violation on the PlayerState and not he Controller (Controller could be null if the Character is not possessed by one? Also, at hwat point are you printing that on the Screen?
// This is done from inside the character/ pawn class, but all you need is ether a Pawn or Controller to get the Player State
const AYourPlayerController* PlayerPC = Cast<AYourPlayerController>(GetController());
ATTSPlayerState* YourPlayerStateClass = Cast<ATTSPlayerState>(PlayerPC->PlayerState);
GEngine->AddOnScreenDebugMessage(21, 1.0f, FColor::Red, FString::SanitizeFloat(YourPlayerStateClass->SomeVariable);
Also as mentioned above Health belongs in the character/ pawn, and Player State is for information about that player relevant to other players.
e.g: Score, Deahs, Player Name and so 4th.
As others have said, you should guard your usage to make sure the variable is accessible, it will help ascertain what isn’t present at the time of the call. That being said there are lots of reasons why an APlayerState is NULL where you are trying to access it.
If this is a client, the server has to first give the player state to you via replication. This can take an indeterminate amount of time and you should wait for an “OnRep” notify. There are already two for this class, one on APlayerController and another on APawn. You may not have a pawn yet, but you should have a controller pretty quickly after login (again its not guaranteed based on network handshaking). APlayerController is pretty safe to work with after ReceivedPlayer or you could always have an RPC from the server in PostLogin/GenericPlayerInitialization tell the client that it is ok to do something (still may not have an APlayerState yet, but you should in most normal situations).
If this is a server, you should pretty much have one after the constructor for the AController is complete. If you need to do something, you should at least wait for InitPlayerState on the AController.
The APlayerState was meant to be a lightweight extension of the AController. Clients only have only their own controller, so in to get information on everyone else we have the array of APlayerState on the AGameState. Put anything you want there that you’d typically liked all clients to know. Server can write to the class and if the property is replicated, everyone will get it. Clients can write to the class and keep the data local to the client. Clients can make an RPC to the server and tell it to change the value which replicates to clients (including back to the client that made the call). It will be up to you to keep the flow of information straight, but the point is that it can be anything from server authoritative information to client scratchpad information. All caveats about availability of data apply, especially around new players joining and others leaving.
ACharacter derives from APawn, so any pawn function should be accessible. PlayerState is a public member of both APawn and AController.