Game Development Community

What is the "proper" context for setControlObject?

by Mark Dynna · in Torque Game Engine Advanced · 09/26/2008 (9:15 am) · 6 replies

We've noticed a few inconsistencies in our game recently where some features that should work don't appear to function anymore after the player has mounted a vehicle. We have tracked the source of the problems (we believe) back to the setControlObject call. When I looked into it a bit further I came across a puzzling inconsistency.

ShapeBase has a virtual function setControlObject which inherited objects have their own implementation of, which makes sense to me. However, GameConnection also has a setControlObject function of its own. We have experimented with making the call one way and the other and each time, some things work and some things don't. Obviously, we have a few bugs to work out there, but I was wondering which is the proper way we should be calling setControlObject so that we can work on the problems from that perspective.

When a Player mounts a vehicle, should we be setting the control object on the Player, or on the client's GameConnection?

#1
09/26/2008 (9:46 am)
I'm pretty sure it should be called off of the client and not the player itself. Though I'm looking at the stock mounting scripts and it seems to be calling it off the player, while the spawning script is calling it off the client.
#2
09/26/2008 (11:50 am)
Adding confusion to the mix, stock Torque's mounting code bypasses the concept of setting the control object directly on the car--in effect, the Player class acts as a pass-through for control commands, but remains the control object.

The primary reason for this is to allow for things like keeping the camera's position at the eye of the Player object, and more importantly implementing things like freelook from within the vehicle.

Here's a summary of the control chain to hopefully clear it up a bit:

GameConnection (class)
--has a controlled object, set to be the Player class instantiation normally by GameConnection::setControlObject(--player--);
--move structs generated by the client are sent to this controlled object instantiation directly via networking (and client side prediction)

Player (class)
--receives move structs is it is the controlled object of a GameConnection.
--has an mControlObject, which is any object that should have (filtered) move structs passed to it if the Player is the true GameConnection controlled object. mControlObect is set with Player::setControlObject(ShapeBase * obj)
----if mControlObject is valid, the move struct (filtered) is delivered to the mControlObject after filtering.

Vehicle/etc. (class)
--gets passed a move struct at those times when a Player class has called Player::setControlObject(me) on that class.
--since it also derives from GameBase, it may also be controlled directly by a GameConnection if so desired.

The code that performs this is located near the very top of Player.cpp:

// Manage the control object and filter moves for the player
   Move pMove,cMove;
   if (mControlObject) {
      if (!move)
         mControlObject->processTick(0);
      else {
         pMove = NullMove;
         cMove = *move;
         if (isMounted()) {
            // Filter Jump trigger if mounted
            pMove.trigger[2] = move->trigger[2];
            cMove.trigger[2] = false;
         }
         if (move->freeLook) {
            // Filter yaw/picth/roll when freelooking.
            pMove.yaw = move->yaw;
            pMove.pitch = move->pitch;
            pMove.roll = move->roll;
            pMove.freeLook = true;
            cMove.freeLook = false;
            cMove.yaw = cMove.pitch = cMove.roll = 0.0f;
         }
         [b]mControlObject->processTick((mDamageState == Enabled)? &cMove: &NullMove);[/b]
         move = &pMove;
      }
   }


I've highlighted the specific line that takes the (filtered) move structure, and delivers it directly to the object the player (class) is "controlling".

So, the short answer is that you probably do not want to actually change the object the GameConnection is changing if you want to have freelook, correct camera positioning based on the Player's sitting eye position, etc.

However, if you truly want to bypass the player instantiation and directly control a vehicle then you would call GameConnection::setControlObject(vehicle).

Think transformers--the "player" would be transformation 1, and the "vehicle" would be transformation two--no concept of transformation 1 "sitting inside" of transformation 2, so we don't want to pass a move struct through the player class to the vehicle class--we want to control the vehicle directly.
#3
09/26/2008 (12:41 pm)
Excellent stuff, Stephen. Free look was one of the features that we have seen working "inconsistently" between these two methods and your explanation puts it in perfect perspective.

Bottom line: Because we want the player to be able to use Free Look and some other camera goodies when they are mounted to a vehicle, we should be using this method of "pass-through" control objects. We'll deal with the other oddities from this basis.
#4
09/26/2008 (12:58 pm)
Stephen, thanks for clarifying this. It explains why I can't pitch when I mount a vehicle, and it's now pretty easy to fix that.

I wonder though.. wouldn't it have been better to name the direct GameConnection control object and the control object to which the player class can pass the move structure to slightly differently? I'm thingking setControlObject and setControlledObject maybe.. I dunno.. it sounds more complicated now that I've written this down.
#5
09/26/2008 (1:53 pm)
Minor post edit:

The code blocked out above is from near the very top of Player::processTick(), not the top of the file itself.

Otherwise, sounds like you two got the message I was trying to communicate!
#6
10/09/2009 (9:27 am)
Sorry for reviving this ... but I think its a very important and aspect, and should be explained in the documentation more extensively!

A chart or anything, would be nice :)