Network Ghosts and Scoping网络复制和范围
The NetObject class is a derivative of SimObject that can replicate (ghost) itself across a network connection. All world object classes are subclassed from NetObject (the superclass of SceneObject). In order to best utilize the available bandwidth, the NetConnection attempts to
determine which objects are "interesting" to each client - and among those objects, which ones are most important. If an object is interesting to a client it is said to be "in scope" - for example, a visible enemy to a player in a first person shooter would be in scope.
NetObject类是一个从SimObject派生的类并能通过网络连接复制自身数据。所有世界的物体都从NetObject(SceneObject的超类)。为了最大利用带宽,NetConnection尝试确定哪些NetObject对象是对客户端“感兴趣”的-也就是确定在这些对象中间,哪些是最重要的。如果一个对象确定了是客户端“感兴趣”的那么就是说这个对象在“范围”里面,举例,在fps射击游戏中看到一个敌人进入视野范围。
Each NetConnection object maintains a scoping object - responsible for determining which objects are in scope for that client. Before the NetConnection writes ghost update information into each packet in NetConnection::ghostWritePacket, it calls the scope object's onCameraScopeQuery function which performs two services: first, it determines which objects are "in scope" for that client and calls NetConnection::objectInScope for each object on that client. Second, the onCameraScopeQuery call fills in the CameraScopeQuery structure which is then used to determine the priority of object updates.
每个NetConnection 对象保持了一个范围内的对象-负责检测哪些对象是在客户端需要的范围内。在NetConnection把对象更新信息通过NetConnection::ghostWritePacket写入网络包之前,它调用了范围内对象的onCameraScopeQuery函数来进行以下两个方面的工作:首先,它确定哪些对象在客户端的“范围”里面然后调用对象的NetConnection::objectInScope函数。第二,这个onCameraScopeQuery调用填充CameraScopeQuery结构,这个结构用来确定更新对象的优先级。
The default NetObject::onCameraScopeQuery function scopes everything in the world, but the V12 game example overrides this in ShapeBase::onCameraScopeQuery. ShapeBase calls the server SceneGraph::scopeScene function to traverse the scene from the client's point of view and scope all potentially visible objects. Each scoped object that needs to be updated is then prioritized based on the return value from the NetObject::getUpdatePriority function, which by default returns a constant value. This function is overridden in ShapeBase::getUpdatePriority to take into account the object's distance from the camera, its velocity perpendicular to the view vector, and other factors.
NetObject::onCameraScopeQuery函数缺省是把所有世界中的对象进入范围,但是V12游戏例子重载了ShapeBase::onCameraScopeQuery函数,它遍历客户端在视野以内的所有可见对象。每个范围里面的对象需要依靠函数NetObject::getUpdatePriority来确定自己的更新优先级,缺省是返回一个常量。这个函数被ShapeBase::getUpdatePriorty函数重载,并按照对象离相机的远近,以及它和视线的夹角以及其他因素来计算返回值。
Rather than always sending the full state of the object each time it is updated across the network, the V12 supports only sending portions of the object's state that have changed. To facilitate this, each NetObject can specify up to 32 independent sub-states that can be modified individually. For example, a player object might have a movement state, detailing its position and velocity, a damage state, detailing its damage level and hit locations, and an animation state, signifying what animation, if any, the player is performing. Each state data group is assigned a bit position in the class. When an object's state changes, the object notifies the network system with the NetObject::setMaskBits function. When the object is to be written into a packet in NetObject::packUpdate, the object's current state mask is passed in. The object's state mask is NOT written into the packet directly - it is the responsibility of the pack function to accurately encode which states are updated.
V12支持只发送对象的那些部分被更新的状态数据,而不是通过网络每个时间段发送所有的对象状态。为了达到这个目的,每个NetObject能指定最多32个能被分别单个改变的而不影响其他状态的状态数据。举例来说:一个玩家对象可以有移动状态数据,具体可以是位置和速度,一个伤害状态数据,具体可以是伤害等级和击打位置,和一个动画状态,表示怎样的动作,还可有玩家其他的状态等等。在对象中每个状态数据被赋予一个bit位置来标记。当对象的状态被改变了后,对象使用函数NetObject::setMaskBits通知网络系统。当对象通过函数NetObject::packUpdate打入网络数据包的时候,是按照当前的状态掩码来进行。这个对象的状态掩码并没有直接写入网络包,而只是负责pack函数能精确的得到需要更新的状态数据。
Initially an object's state mask is set to all 1's - signifying that all the object's states need to be updated.
初始化时对象的状态掩码都被置成1,表示所有的对象的状态都需要被更新。
An example NetObject:
class SimpleNetObject : public NetObject
{
public:
char message1[256];
char message2[256];
enum {
Message1Mask = (1 << 0),
Message2Mask = (1 << 1),
};
SimpleNetObject()
{
// in order for an object to be considered by the network system,
// the Ghostable net flag must be set.(Ghostable表示这个对象能被网络系统传输数据)
// the ScopeAlways flag indicates that the object is always scoped
// on all active connections.(ScopeAlways表示这个对象总是能被在所有的激活连接范围之内)
mNetFlags.set(ScopeAlways | Ghostable);
dStrcpy(message1, "Hello World 1!");
dStrcpy(message2, "Hello World 2!");
}
U32 packUpdate(NetConnection *, U32 mask, BitStream *stream)
{
// check which states need to be updated, and update them
if(stream->writeFlag(mask & Message1Mask))
stream->writeString(message1);
if(stream->writeFlag(mask & Message2Mask))
stream->writeString(message2);
// the return value from packUpdate can set which states still
// need to be updated for this object.
return 0;
}
void unpackUpdate(NetConnection *, BitStream *stream)
{
// the unpackUpdate function must be symmetrical to packUpdate
if(stream->readFlag())
{
stream->readString(message1);
Con::printf("Got message1: %s", message1);
}
if(stream->readFlag())
{
stream->readString(message2);
Con::printf("Got message2: %s", message2);
}
}
void setMessage1(const char *msg)
{
setMaskBits(Message1Mask);
dStrcpy(message1, msg);
}
void setMessage2(const char *msg)
{
setMaskBits(Message2Mask);
dStrcpy(message2, msg);
}
DECLARE_CONOBJECT(SimpleNetObject);
};
IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "obj.setMessage1(msg)")
{
((SimpleNetObject *) object)->setMessage1(argv[2]);
}
ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "obj.setMessage2(msg)")
{
((SimpleNetObject *) object)->setMessage2(argv[2]);
}
GameConnection, Moves and the Control Object游戏连接,移动和控制对象
The GameConnection class is the game-specific subclass of NetConnection. Applications can subclass NetConnection to directly write and read data from packets, as well as hook into the notify mechanism. The NetConnection::allocNotify function is called at the beginning of a packet write and is used to allocate a NetConnection::PacketNotify structure. This structure is used to store information about the data written into the network packet. When the packet is either acked or nacked, this notify structure is passed into the NetConnection::handleNotify
function. Subclasses of NetConnection can subclass the PacketNotify structure and override the allocNotify method to add custom data to the packet tracking record.
GameConnetion类是游戏特有的NetConnection子类。应用程序能子类化NetConnection来直接读写网络包,也可以通过挂到通告机制里面来读写。NetConnection::allocNotify函数在写入包之前被调用并分配了一个NetConnection::PacketNotify结构。这个结构用来存储数据写入的相关信息,当一个包被接受到(ACKED)和被丢失了(nacked),这个通告结构将会作为参数调用NetConnection::handleNotify函数。NetConnection的子类能继承PacketNotify结构并覆盖allocNotify方法来增加自定义的包跟踪数据。
The GameConnection in the V12 example introduces the concept of the control object. The control object is simply the object that the client associated with that network connection controls. By default in the example the control object is an instance of the Player class, but can also be an instance of Camera (when editing the mission, for example).
在V12中的GameConnetion对象介入了控制对象的概念。控制对象简单来说就是客户端与网络连接控制有关的对象。缺省模式下,在例子中是玩家类的一个实例,但是也能是一个相机的实例(当编辑一个任务的时候)
The V12 example uses a model in which the server is the authoritative master of the simulation. To prevent clients from cheating, the server simulates all player moves and then tells the client where his player is in the world. This model, while secure, can have problems - if the network latency is high, this round-trip time can give the player a very noticeable sense of movement lag. To correct this problem, the example uses a form of prediction - it simulates the movement of the control object on the client and on the server both. This way the client doesn't need to wait for round-trip verification of his moves - only in the case of a force acting on the control object on the server that doesn't exist on the client does the client's position need to be forcefully changed.
V12例子使用了服务器作为作为验证的模式,为了防止客户端作弊,服务器模拟了所有的玩家运动并告诉每个客户端他们的当前位置。这个模式虽然安全但是也有问题-如果网络延迟足够高,则会带给玩家非常显眼的运动延迟。为了纠正这个问题,例子使用了这样一种预测:它模拟了控制对象和服务器端一样的运动轨迹。这个方法让客户端不用等待服务器验证它的运动-仅仅在强制动作在服务器上发生而客户端没有发生时,这时客户端才需要强制改变自己的位置。
To support this, all control objects (derivative of ShapeBase) must supply a writePacketData and readPacketData function that send enough data to accurately simulate the object on the client. These functions are only called for the current control object, and only when the server can determine that the client's simulation is somehow out of sync with the server. This occurs usually if the client is affected by a force not present on the server (like an interpolating object) or if the server object is affected by a server only force (such as the impulse from an explosion).
为了达到这个目的,所有的控制对象(从ShapeBase派生)必须提供writePacketData和readPacketData函数来发送足够的数据正确的模拟客户端的对象,这两个函数仅仅为当前的控制对象调用,而且仅仅只有当服务器能够确定客户端的运动已经脱离了服务器的同步范围时调用。这种情况经常发生在客户端被一个服务器没有的行为所影响(比如运动插值)或者如果服务器对象受到了其他强制更新(比如被爆炸冲击)。
The Move structure is a 32 millisecond snapshot of player input, containing x, y, and z positional and rotational changes as well as trigger state changes. When time passes in the simulation moves are collected (depending on how much time passes), and applied to the current control object on the client. The same moves are then packed over to the server in GameConnection::writePacket, for processing on the server's version of the control object.
移动数据结构是玩家32毫秒一次的快照,包含x,y和z坐标位置和旋转量改变。当时间经过后移动数据就被收集到(依赖究竟多少时间经过了),然后把这些改变应用到当前客户端的控制对象上。这个同样的运动通过GameConnection::writePacket发送到服务器上,然后被服务器上的同样对象处理。