Game Development Community

How does "Ghosting" work?

by St. · in Torque 3D Professional · 10/03/2009 (3:47 pm) · 10 replies

Hello and thanks for stopping by,

If you're reading this then the term "Ghosted Objects" is probably something you know very well. If it is, please help the desperate to understand the concept of ghosting objects.

As far as I understood, ghosting is synonymous to "sending object information over the network for the purpose of visual synchronization" (please correct me if I'm wrong at any point).

But how does it work in the code?

I've found the "activateGhosting" method in the engine code that seems to be sending the initial object information to the client at the moment of loading.
What I don't understand is...
1) mGhostArray - what kind of things exactly does this array store?
2) When is it populated and when do its contents change (if they do change at all)?
3) Is it possible to "start ghosting" new objects after the loading is complete, e.g. split the mission loading into two parts and have one part occur only when the player is at a certain mission stage?
I've tried adding things "on the fly" but that has brought no prolific results so far...

I know that in theory *everything* is possible having the source, but it's just the amount of Torque's code is really confusing... I don't know where to start "reading" it.

If someone could explain me the steps of how the objects are ghosted I'd be very grateful.

#1
10/03/2009 (4:27 pm)
Might do a bit of forum searching as there is a bit of material out there and probably better descriptions than I can give. For the most part ghost objects are client side copies of server objects that can be used for local reference purposes and can be updated by the server if needed. They work in conjuction with "scoping" to allow the server to update whatever the client is in need of so the client isn't receiving updates for the whole mission when the object is on the other side of the map. The object than can be updated by the server in conjuction with pack/unpack updates from the server.

#2
10/03/2009 (5:28 pm)
Yes, I understand that in general, but... I'm trying to understand in a more detailed way what bits of code and in what sequence are involved in ghosting objects.

I've tried searching the forum, there are tons of results most of which are very vague, and documentation is a little bit "too specific" in this area: the purpose of each class, method and variable is explained quite well, but there are just no examples of how this is being used in practice (e.g. in FPS Example). At least I could not find any. :/

Sorry if all that sounds as if I don't really know what I want, I was just hoping to understand the already existing implementation to be able to modify/rewrite it for my educational purposes... I feel that I understand the theory, but I don't know where to start practicing...

I guess I'll just get back to browsing the forums....
#3
10/03/2009 (7:19 pm)
If you take a look at the constructors for most NetObject derived classes you are likely to see something like this:
mNetFlags.set( Ghostable | ScopeAlways );

The bits in mNetFlags are enumerated in netObject.h
enum NetFlags
   {
      IsGhost           =  BIT(1),   ///< This is a ghost.
      ScopeAlways       =  BIT(6),  ///< Object always ghosts to clients.
      ScopeLocal        =  BIT(7),  ///< Ghost only to local client.
      Ghostable         =  BIT(8),  ///< Set if this object CAN ghost.

      MaxNetFlagBit     =  15
   };

Objects that are Ghostable and allocated on the server will be "ghosted" to clients that are within "scope" of it. If ScopeAlways is set then they are always in scope.

This does not apply to only on mission startup. As soon as you allocate a ghostable object on the server side it will be ghosted to clients within scope of it.

Note that "scope" is much less complex than you might think, it is literally just within a worldspace distance of the client's control object afaik.

Sooo, maybe the most correct way to do what you are asking would be to extend the object-scoping to do something more sophisticated, like, if the player is within this area of the mission - only objects in that half are in scope, etc.
#4
10/03/2009 (8:28 pm)
Hmm, I seeee... that gives me some food for thought, thank you.

But as I noticed all objects in the FPS example are always ghosted (so, they have the ScopeAlways flag, I believe). Where is the "relative scope" (the worldspace distance) set?
#5
10/03/2009 (8:37 pm)
I'm pretty sure that is hardcoded in C++ somewhere but have no idea... actually exposing that on a per-object basis might also accomplish what you want. But you'd need to do some more investigation to verify that how I described non-scope-always ghosting is actually correct...
#6
10/03/2009 (8:47 pm)
Ok, I'll take a look at the code and see if it reveals anything (after reading it for two days I'm beginning to feel that soon I will know it by heart...)

Thank you James, you've helped me a lot today. ;)

P.S. Still, if anyone else could confirm the fact that ghosting works as per Mr. Ford's description that would be very welcome.
#7
10/04/2009 (3:16 pm)
Yeah that's the way it works pretty much.

Anything which inherits from NetObject can be ghosted. James is spot on with the flags. I will go ahead and answer the next, inevitable questions :)

Ghosts only exist on clients, the master object only exists on servers. Usually, your gameplay code exists on the server (even if it's single-player, it is still, conceptually, the server). In order to control the ghosts from the server, you use netmasks, and packUpdate/unpackUpdate. There are many examples of this, but this is the rough idea about how to control the client ghosts, from the server.

The next thing you may want to do is find a ghosted object on the client. So, lets say you have an object that wants to know about another object on the client. You obtain the ghost index, and then call 'resolveGhost' on a Net connection. This usually takes place in pack/unpackUpdate. The server knows what ghost index an object is by calling 'getGhostIndex' on a net connection. So the server sends the ghost index to the client, the client resolves the ghost index. Note that a ghost-index is only valid for the connection it is determined on. You will typically only have one net connection to be concerned with, but just as a heads-up in case you plan anything crazy.
#8
10/05/2009 (7:26 am)
Some small additions:

To activate or not ghosting for an object is managed by the Ghost manager inside NetConnection class for the scoping. The class is looking for the scope object and then will kick the onCameraScopeQuery which will create a list of object to be scoped.

Here is an example of the one for the camera (found in ShapeBase)

void ShapeBase::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery * query)
{
   // update the camera query
   query->camera = this;
   // bool grabEye = true;
   if(GameConnection * con = dynamic_cast<GameConnection*>(cr))
   {
      // get the fov from the connection (in deg)
      F32 fov;
      if (con->getControlCameraFov(&fov))
      {
         query->fov = mDegToRad(fov/2);
         query->sinFov = mSin(query->fov);
         query->cosFov = mCos(query->fov);
      }
   }

   // use eye rather than camera transform (good enough and faster)
   MatrixF eyeTransform;
   getEyeTransform(&eyeTransform);
   eyeTransform.getColumn(3, &query->pos);
   eyeTransform.getColumn(1, &query->orientation);

   // Get the visible distance.
   query->visibleDistance = gServerSceneGraph->getVisibleDistance();

   // First, we are certainly in scope, and whatever we're riding is too...
   cr->objectInScope(this);
   if (isMounted())
      cr->objectInScope(mMount.object);

   if (mSceneManager == NULL)
   {
      // Scope everything...
      gServerContainer.findObjects(0xFFFFFFFF,scopeCallback,cr);
      return;
   }

   // update the scenemanager
   mSceneManager->scopeScene(query->pos, query->visibleDistance, cr);

   // let the (game)connection do some scoping of its own (commandermap...)
   cr->doneScopingScene();
}

This is the right function to change if you want to do some smart things with the ghosting. Some nice changes can be for example to manage visibility (in case of RTS)...
#9
10/05/2009 (9:53 am)
Extra info: the setHidden() SceneObject method actually works by sending a packet telling that ghost to be deleted and stoping the object's updates until it's unhidden. What you see rendered on your screen are always the ghosts (server objects never render), so when a ghost is deleted it goes hidden. It also prevents client hacks to see objects that should be hidden.

(It's also why calling setHidden() on a Player controlled by some client often crashes the game).
#10
10/05/2009 (11:59 am)
i talk a bit about ghosting in this tutorial.