Game Development Community

Ghosting... Killing Me

by Anthony Lovell · in Torque Game Engine · 01/04/2006 (2:55 pm) · 4 replies

I am having a bear of a time making ghosting work reliably.

In C++, on a server object, I create a NetObject-derived class and take the following steps which SHOULD be sufficient, I believe to ghost it to a client:

1. it is set with netflags Ghostable
2. I call myObj->registerObject();
3. I then pick the client NetConnection I want to ghost it to and call myClient->objectInScope(myObj);

And yet, update cycle after update cycle, my efforts to call

myClient->getGhostIndex(myObj) to find its ghostIndex, I get -1

What is likely to be wrong?

The game is fully up and interactive at the point this misbehavior occurs.

tone

#1
01/04/2006 (3:12 pm)
I suggest using NetConnection::objectLocalScopeAlways(myObj)--it is what the console method scopeToClient() uses, and itself calls objectInScope() for you.
#2
01/04/2006 (6:02 pm)
Ghosting always works reliably. What you're having problems with is scoping. =)

First thing to check is whether or not the object is actually being created. Any object derived from GameBase requires you to set the datablock before calling registerObject or it will simply error out. Generally the code I use for creating an object in C++ runs something along the lines of:

// Create the new object of class ObjectClass.
ObjectClass* pObject = new ObjectClass;

// If the object exists...
if (pObject)
{
    // Register it's datablock (pDataBlock is a pointer to a ObjectClassData datablock).
    pObject->onNewDataBlock(pDataBlock);

    // Attempt to register the object, and failing that...
    if (!pObject->registerObject())
    {
        // Delete the object and clear the pointer.
        delete pObject;
        pObject = 0;
    }
}


Of course, if it's not a GameBase-derived class you can simply skip the onNewDataBlock line. =)

If the object is fairly static in nature or is important to the overall gameplay, you can set the ScopeAlways flag. A ghostable object that is always in scope would have something like the code below in its constructor.

Object::Object()
{
    mNetFlags.set(Ghostable | ScopeAlways);
    .
    .
    .
}


Note that the ScopeAlways flag is different to the ScopeLocalAlways flag which only applies the scoping to a particular client.

I'd suggest you enable the ScopeAlways flag until you've successfully managed to determine the ghost index on your client. Once you've got the client looking up the index properly, then you can start fiddling with exactly how you want to scope things.

Now... Assuming you've got a valid ghostable, always-scoped object of some kind, you can send the ghost index to the client using something like what's below in the server's ::packUpdate (or equivalent) code.

// Assuming pObject is the pointer to your object and it has a non-null pointer...
if (pObject != 0)
{
    // Get the ghost index of the object.
    const S32 iIndex = connection->getGhostIndex(pObject);
    
    // If the ghost index is valid...
    if (stream->writeFlag(iIndex != -1))
    {
        // Write the index to the bitstream.
        stream->writeInt(iIndex, NetConnection::GhostIdBitSize);
    }
    else
    {
         // Otherwise, the object hasn't been ghosted yet, so adjust the return mask to try again.
         // Note that "MASK_OBJECTUPDATE" is the name of the bitmask used for flagging this data 
         // send in the first place.  If you don't re-flag this mask and an object hasn't been ghosted 
         // on the first pass, you'll never get the ghost index sent from the server.
         retMask |= MASK_OBJECTUPDATE;
    }
}
else
{
    // Otherwise, just write a false flag to the bitstream, since there's no object.
    stream->writeFlag(false);
}


Then, in the client's ::unpackUpdate (or equivalent) code, you want to have something like:

// Read in the flag for whether or not there's a valid object...
if (stream->readFlag())
{
    // Read in the ghost index of the object from the bitstream.
    const S32 iIndex = stream->readInt(NetConnection::GhostIdBitSize);
    
    // Resolve the ghost from its index and cast it to the appropriate class (assuming your object is 
    // of ObjectClass).
    pObject = dynamic_cast<ObjectClass*>(connection->resolveGhost(iIndex));
}
else
{
    // Otherwise, with no valid object, null the current pointer.
    pObject = 0;
}

If all of that doesn't work (though I can't see why it wouldn't), I'd suggest sticking some breakpoints throughout your code and stepping through things to see what's going on. Your best bet is on breaking at the beginning of the two network functions I've posted above

Good luck.

Edit: Stupid typing fingers they go bad bad.
#3
01/05/2006 (7:33 am)
So far, I have not worked with Datablocks at all, and am working with NetObject-derived items, not GameBase.

Quote:
I'd suggest you enable the ScopeAlways flag until you've successfully managed to determine the ghost index on your client. Once you've got the client looking up the index properly, then you can start fiddling with exactly how you want to scope things.

ScopeAlways was causing problems, as it does not robustly send initial updates as the packUpdate() return value is ignored. (a known issue) I am trying to employ the local scope functions for now, but fear I'm seeing some weirdness as well (still exploring).

edit: this is simply killing me. Just discovered that calling objectLocalScopeAlways() does not cause the NetObject to acquire a ghost index. Where is the how and why of this explained?

tone
#4
01/08/2006 (1:44 am)
Have you read the TNL docs yet?

Do note that all this ghost index stuff is delayed and it can take a while for objects to actually get IDs.