服务端会给客户端发送一些数据,其中两大种类数据是 clientdata_t 和 entity_state_t 这里我们说说 entity_state_t 这个结构体。
你在丢在地上的枪、C4等等是服务端实体(edict_t),并且你能在客户端看到它们(废话),这些实体们是怎样发送到你的客户端的呢?
引擎不可能原原本本地把 edict_t 发送出去的,所以就有了 entity_state_t 这个结构体,它表示了一个可见实体所有必要的数据。
接上面:如果实体不可见,那何必发到客户端呢?:-)
所以 entity_state_t 只保存跟实体显示有关的数据,例如 origin、angles、model 这些,引擎只需要把这些数据发到客户端就行了。
引擎里有一个叫做 FullPack 的包(实际上就是数组),这个包里有全部需要发送给客户端的实体的 entity_state_t。
引擎会逐个检查服务端的所有实体,并且添加到包里,准备发送给客户端。
那引擎是不是默认就把能看见的实体都添加到包里了呢?并不是,因为引擎提供了一个接口让我们自己决定哪些实体可以被添加到包里!
你可以在 mp.dll 的源码里找到 AddToFullPack 这函数,可以看到这样的代码:
int AddToFullPack(struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet) { if ((ent->v.effects == EF_NODRAW) && ent != host) return 0; // 有EF_NODRAW这个标记的实体是不可见的,不添加到包里。 if (!ent->v.modelindex || !STRING(ent->v.model)) return 0; // 没有模型的实体是不可见的,不添加到包里。 if ((ent->v.flags & FL_SPECTATOR) && ent != host) return 0; // 观察者也是不可见的,不添加到包里。 // ... if (ent != host) { // 在可视范围(PVS)外的实体是看不到的,不添加到包里。 if (!CheckEntityRecentlyInPVS(hostindex, e, gpGlobals->time)) { if (!ENGINE_CHECK_VISIBILITY((const struct edict_s *)ent, pSet)) { MarkEntityInPVS(hostindex, e, gpGlobals->time, true); return 0; } MarkEntityInPVS(hostindex, e, gpGlobals->time); } } // ... }
参数 state 是将要添加到包里的 entity_state_t ,参数 ent 是正在处理的实体,参数 host 是包要发送到的那个玩家(的客户端)!
如果这个函数返回 0 (FALSE)引擎就不会把这个实体添加到包里,这个实体自然也就不会被发送到那个客户端(看不到)。
你甚至可以在这个函数里自定义需要发送的实体的数据!我们可以看到这样的代码:
int AddToFullPack(struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet) { // ... state->number = e; state->entityType = ENTITY_NORMAL; state->animtime = (int)(1000.0 * ent->v.animtime) / 1000.0; memcpy(state->origin, ent->v.origin, 3 * sizeof(float)); memcpy(state->angles, ent->v.angles, 3 * sizeof(float)); memcpy(state->mins, ent->v.mins, 3 * sizeof(float)); memcpy(state->maxs, ent->v.maxs, 3 * sizeof(float)); memcpy(state->startpos, ent->v.startpos, 3 * sizeof(float)); memcpy(state->endpos, ent->v.endpos, 3 * sizeof(float)); state->impacttime = ent->v.impacttime; state->starttime = ent->v.starttime; state->modelindex = ent->v.modelindex; state->frame = ent->v.frame; // ... }
你可以看到它从服务端实体(edict_t)抽出必要的数据填充到 entity_state_t 里!
最后返回 1(TRUE)引擎将会把我们填充好的 entity_state_t 添加到包里,发送给客户端。
这个函数真的很有用对吧,我们可以做一些有趣的功能,让一个实体可以被玩家A看到,玩家B却看不到!
我们只需要检查当前正在处理的实体(ent)是那个我们不让玩家B看到的实体,然后判断 host 是不是玩家B,如果是,就返回 0 不让这个实体发送给玩家B!
你甚至可以设置一个玩家(玩家也是实体)不让其它玩家看到!
注意,引擎里的包最多只能容纳 256 个实体!如果超出了这个数量,引擎将会忽略超出部分的实体!