PlayerController
As stated in Controller Overview, the player controller class determines what the player sees, and how non-human players behave (it ties in AI functions and stuff). So lets start with a quick class hierarchy.
Engine.Actor +- Engine.Controller +- Engine.AIController | +- Gameplay.ScriptedController | +- UnrealGame.Bot (UT) | | +- xGame.xBot? | +- Gameplay.ScriptedTriggerController? | +- Engine.PlayerController +- UnrealGame.UnrealPlayer? +- xGame.xPlayer?
For the purposes of this discussion the non-player controller classes (Engine.AIController and below) will be ignored. They will be covered later.
A Quick Nod to the Controller Class
Here's what the class header of Controller has to say.
Controllers are non-physical actors that can be attached to a pawn to control its actions. PlayerControllers are used by human players to control pawns, while AI Controllers implement the artificial intelligence for the pawns they control. Controllers take control of a pawn using their Possess() method, and relinquish control of the pawn by calling UnPossess().
Controllers receive notifications for many of the events occuring for the Pawn they are controlling. This gives the controller the opportunity to implement the behavior in response to this event, intercepting the event and superceding the Pawn's default behavior.
Although a full discussion of the properties of Controller is beyond the scope of this page there are a couple of interesting (to me anyway) properties defined at this level.
- bGodMode - Set at this level it allows computer controlled players to enter god mode.
- FovAngle - The controller's field of view - allows bots to have wider FOV's. There's potential here for some bot customisation for the competative, as some players use a non standard FOV.
The PlayerController Class
The PlayerController class is where the real action happens as far as human players go. This class determines what the player sees when in first person view and in third person view and how the "camera" behaves. It handles things like view shaking and the display of fog. There are also handling functions in there for force-feedback events and support for more console commands than you can shake a stick at.
The most important thing to remember about the PlayerController is that it serves two purposes. The controller's ViewTarget is responsible for driving the location and rotation of the scene the player sees. The controller's Pawn is the pawn that is actually controlled by the PlayerController class. They do not have to reference the same Actor.
Mychaeel: What do you mean by the last sentence?
EntropicLqd: When playing in the game the ViewTarget is normally the same object as the controlled Pawn. This does not have to be the case though, and it would be quite possible to set the ViewTarget to an actor that's trailing the controller Pawn for example. I say that ... but I've not tested it yet - hopefully I'll have the chance tonight. I should be working on Customising the Player View tonight so I'll probably cover it off in there. Watch this space so to speak.
Mychaeel: Ah, that. Yes, it works.
The PlayerController and the controlled Pawn are tied explicitly to each other. The controller recieves many events to do with the current state and actions of the Pawn it's controlling. These event functions are actually invoked by the Pawn being controlled as it moves through the world and interacts with it's environment. The following code is fairly typical of the Pawn callbacks on offer. In the example below if the Pawn has a controller then it is up to the controller to return the view rotation. By default this will actually be the rotation of the Pawn.
simulated function rotator GetViewRotation() { if ( Controller == None ) return Rotation; return Controller.GetViewRotation(); }
How the views work
The view you see as a player is driven by an event defined at the PlayerController level. The function definition is shown below.
event PlayerCalcView(out actor ViewActor, out vector CameraLocation, out rotator CameraRotation )
The values passed in ViewActor, CameraLocation, and CameraRotation are derived from an alternate universe (I've not found where yet). In essence this function performs the following steps.
- Calls the SpecialCalcView() function on the Pawn (possibly the view target, possibly the currently possessed pawn). This gives the Pawn an opportunity to construct a custom view based upon what it sees. (used for remote control robots and stuff I guess - possibly even the 'deemer although I've not looked). If this function calculates the view the player sees then it should return true. NOTE: The Pawn's bSpecialCalcView property must be true for this function to be called.
- If a special view was not constructed by the pawn then the current view target of the controller is checked to make sure it still exists or is not about to be deleted from the world. If deletion is pending then a new view target is obtained.
- If the current view target is the current "Pawn" associated with the controller then either CalcBehindView() or CalcFirstPersonView() is called to produce the camera location and rotation required.
- If the current view target is the controller itself then the controller's rotation is used as the camera rotation.
- If the current view target is a projectile then the camera location is set to the view target's collision height, and the camera rotation is set to that of the projectile being followed.
- If none of the above conditions are met then the view target is checked to see if it is a Pawn. If this is the case then the following happens:
- If we are a client of a networked game and our view target is a player pawn then we use the location of the view target and the rotation of the controller (it reduces the perception of lag and allows you to look around in behind view).
- If are the server (or running a standalone game) then both the location and rotation of the view target is used.
A quick aside into the world of replication
Mychaeel: Player movement replication roughly works like this: Locally, the player moves freely, and the client sends information about that to the server. The server then tries to replay those moves when it receives them, and then sends information back to the client on whether the movements worked out or not (and, if not, where the player actually is right now). That way you don't experience any lag in your own movement when playing on a game server unless you interact with conflicting dynamic objects (for instance, other players or movers or projectiles).
So what about Spectators
Spectating is also handled by the PlayerController class. A state hierarchy is used to enabled and disabled various player functions and generally keep things tidy. The hierarchy is shown below.
BaseSpectating +- Spectating | +- AttractMode (also defined in xGame.xPlayer) +- PlayerWaiting +- WaitingForPawn
If you spend any time looking through the spectating code (which does more or less what you'd expect) you'll notice something really odd. A pathalogical desire to set the view target.
// Spectating - AltFire() calls ServerViewSelf() to return to your corpse (if you've been killed or whatever) // ServerViewSelf() does the following: SetViewTarget(self); ClientSetViewTarget(self); // ClientSetViewTarget() is defined as: function ClientSetViewTarget( Actor a ) { SetViewTarget( a ); }
In fact, every call to ClientSetViewTarget( target ) is preceded with a call to SetViewTarget( target ). Is this simply an example of redundant code within the code base or something more sinister? I'll leave it up to the reader to decide.
Movement and Control
Player movement and control can be split into two sections. The first is capturing input from the player and doing stuff with it. The second is ensuring that the player's position in the world is consistent between the client and the server.
Capturing Player Input
There are two classes shipped with UT2003 responsible for handling the player's input, and managing the input configuration. Both of these are in the Engine class. The two classes are shown below.
Engine.PlayerInput +- Engine.XBoxPlayerInput?
The PlayerInput class is defined as being within PlayerController. This means that the class has visibility of the attributes of the PlayerController class. These classes only exist on the client. The entry points into the PlayerInput class from a handling point of view are shown below.
// This function is called from PlayerController.PlayerTick( float DeltaTime ) // to figure out what the player is up to. event PlayerInput( float DeltaTime ) // This function is called from PlayerController.PlayerMove( float DeltaTime ) // it determines whether the player is dodging and if so, in which direction. function Actor.eDoubleClickDir CheckForDoubleClickMove(float DeltaTime)
It's pretty arcane in there. The logic within the double click move function is particularly tortuous. I'll need sleep before tackling that one.
Player Movement and Replication
Here's what the PlayerController class has to say about the player's movement replication. It's a good summary of exactly what is going on.
Here's how player movement prediction, replication and correction works in network games:
Every tick, the PlayerTick() function is called. It calls the PlayerMove() function (which is implemented in various states). PlayerMove() figures out the acceleration and rotation, and then calls ProcessMove() (for single player or listen servers), or ReplicateMove() (if its a network client).
ReplicateMove() saves the move (in the PendingMove list), calls ProcessMove(), and then replicates the move to the server by calling the replicated function ServerMove() - passing the movement parameters, the client's resultant position, and a timestamp.
ServerMove() is executed on the server. It decodes the movement parameters and causes the appropriate movement to occur. It then looks at the resulting position and if enough time has passed since the last response, or the position error is significant enough, the server calls ClientAdjustPosition(), a replicated function.
ClientAdjustPosition() is executed on the client. The client sets its position to the servers version of position, and sets the bUpdatePosition flag to true.
When PlayerTick() is called on the client again, if bUpdatePosition is true, the client will call ClientUpdatePosition() before calling PlayerMove(). ClientUpdatePosition() replays all the moves in the pending move list which occured after the timestamp of the move the server was adjusting.
I guess the above comments really beg the question, "How exactly do replicated functions work then?".
Related Topics
- Customising the Player View – How to put the the player view where you want it.
- [Customising the Player Input]? – How to get the control behaviour you want.
- Creating An Interaction From A PlayerController
Discussion
EntropicLqd: Anyone have any preference on what gets covered in PlayerController? I'll do the third person view and first person view functions. Might spend some time figuring out which events a player controller actually does recieve before the Pawn event. The other thing I've started doing is prefixing the class name with the package it's contained within. I know I find it useful, does anyone else? if not I'll drop it.
Chazums Other than as much as possible / relevant, not really . Putting in the package name is useful i think, makes it easier to see where the stuff being looked at is.
EntropicLqd: Hmm, I'll do what I can but I don't want to make the page too long. Thanks to Wormbo for fixing my markup ealier .
EntropicLqd: I wish Tim Sweeny would take a look at this and add a bit of in depth knowledge. So much of it feels like speculation and almost educated guesswork that it makes me uncomfortable. However, the cool thing about it is that this stuff occurs right at the engine level rather than the UT2K3 game level.
Trystan: "Page too long"? Can you give too much knowledge? Perhaps begin with as much knowledge as you can shove in here (because I'm looking forward to it as much as Chazums) and then one of us could refactor it for you? It'd be a good test of our understanding of what you're trying to convey, that's for certain. This is mostly a minor note to let you know that there's another person looking forward to this information. (I expect you to work all of Thanksgiving on this! )
EntropicLqd: It's a good job I'm working on something so dull that it makes my brain want to crawl out of my head and escape screaming then. Thanksgiving? They don't have that where I come from. Too much knowledge is a very dangerous thing. It's wisdom you can never have enough of.
Trystan: Damn global stuff. Sorry, I'm one of those asinine Americans who think that everyone is like them. Chaz is slowly but surely beating that out of me by refusing to be anything American like. I'll learn sooner or later.
Chazums Silly Trystan . nice work EntropicLqd you've certainly busy. In referance to the code at the end, could it be that it's used for when a bot replaces a client? My other thought is that its creating a duplicate so that the right stuff is displayed on the spectators screen.
EntropicLqd: If we were a bot taking over a player pawn we'd be in the AIController class. Still, nice theory.
EntropicLqd: Chazums & Trystan - is there anything on this page (or the Customising the Player View page) that doesn't make sense or needs further elaboration? I've kind of lost track a bit here.
Pingz: Shouldn't there be a class outline on this page like for all the other classes? You can then move all this content to a page like 'Explaining the PlayerController' or something.
Category Class (UT2003)
Category To Do – Move explainations to a different page and add standard class header and properties/methods descriptions.