-
Notifications
You must be signed in to change notification settings - Fork 2
Entity Networking
How entities are synchronized from server to clients in Q2RTXPerimental.
Q2RTXPerimental uses a client-server architecture where:
- Server runs authoritative game logic
- Clients render what the server tells them
Entity state is transmitted via delta compression - only changes are sent.
typedef struct entity_state_s {
int number; // Entity index
vec3_t origin; // Position
vec3_t angles; // Orientation
vec3_t old_origin; // For lerping
int modelindex; // Model to render
int modelindex2; // Weapon model
int modelindex3; // Custom models
int modelindex4;
int frame; // Animation frame
int skinnum; // Skin/texture
int effects; // Visual effects (EF_ROTATE, etc.)
int renderfx; // Rendering flags (RF_GLOW, etc.)
int solid; // Collision (for prediction)
int sound; // Looping sound
int event; // Entity event
} entity_state_t;What gets networked:
- Position, angles, velocity
- Model, frame, skin
- Visual effects
- Events
What DOESN'T get networked:
- Entity health (except player)
- AI state
- Think callbacks
- Server-only logic
Entities have different update priorities:
ET_PLAYER // Players - always sentET_MONSTER // Monsters
ET_ITEM // Items
ET_PUSHER // Moving platformsET_GENERAL // Static entitiesOnly changed fields are sent:
Frame 1: origin={100, 200, 50}, angles={0, 90, 0}, frame=5
Frame 2: origin={100, 200, 52} // Only origin changed, send 3 floats
Saves bandwidth vs. sending full state every frame!
Client receives entity snapshots from server:
// Client-side (clgame)
void CLG_ParseEntityUpdate(int entnum, entity_state_t *state) {
centity_t *cent = &cl_entities[entnum];
// Store previous state for interpolation
cent->prev = cent->current;
cent->current = *state;
// Interpolate between prev and current for smooth movement
}Clients interpolate between snapshots for smooth movement:
// Render position between snapshots
vec3_t lerped_origin;
LerpVector(prev_origin, current_origin, lerp_fraction, lerped_origin);
// Render at interpolated positionLerp fraction based on time between server updates (40 Hz = 25ms).
Clients predict their own movement locally for responsiveness:
// Client predicts player movement before server confirms
void CLG_PredictMovement() {
// Run same physics as server
PM_Move(&pmove);
// Use predicted position for rendering
// Server will correct if prediction was wrong
}Benefits:
- Instant response to input
- Smooth movement even with latency
Drawbacks:
- Must reconcile with server state
- Prediction errors cause "rubber-banding"
Some entities (projectiles) can be predicted:
// Client predicts projectile path
void CLG_PredictProjectile(centity_t *cent) {
// Use velocity and gravity to predict next position
predicted_origin = current_origin + velocity * dt;
predicted_origin[2] -= 0.5 * gravity * dt * dt;
}Server only sends entities potentially visible to each client:
// Server determines PVS for player
byte *pvs = gi.inPVS(player->s.origin);
// Only send entities in PVS
for (each entity) {
if (entity_in_pvs(entity, pvs)) {
Send_Entity_Update(player, entity);
}
}Benefits:
- Reduced bandwidth
- Better performance
- Prevents cheating (can't see through walls)
One-shot events synchronized with entity updates:
// Server sets event
player->s.event = EV_PLAYER_FOOTSTEP;
// Client processes event
void CLG_EntityEvent(int event) {
switch (event) {
case EV_PLAYER_FOOTSTEP:
Play_Footstep_Sound();
break;
}
}Events use incrementing bits to detect repeated identical events.
- Delta Compression: Only send changes
- PVS Culling: Only send visible entities
- Priority System: Important entities updated more often
- Event System: One-shot effects don't need continuous updates
// GOOD: Only change networked state when needed
if (new_frame != s.frame) {
s.frame = new_frame; // Will be sent
}
// BAD: Changing every frame unnecessarily
s.frame++; // Sent every frame even if same!// Check:
1. s.modelindex set? (gi.SetModel or gi.modelindex)
2. Entity linked? (gi.linkentity)
3. Entity in PVS? (clg_drawentities shows entities)
4. Correct entityType? (ET_GENERAL, ET_PLAYER, etc.)// Likely causes:
1. Not linking after position change
2. Prediction errors
3. Network packet loss
4. Incorrect interpolation// Check:
1. Server framerate (should be 40 Hz)
2. Network latency (ping command)
3. PVS updates (entity rapidly entering/leaving PVS)- Entity System Overview - Entity architecture
- Entity Lifecycle - Entity lifetime
- Client Game Module - Client-side processing
- Server Game Module - Server-side logic
- API - Entity Events - Entity event reference
Entity networking in Q2RTXPerimental:
- Server authoritative: Server runs game logic
- Delta compressed: Only changes sent to clients
- PVS culled: Only visible entities sent
- Interpolated: Clients smooth movement between updates
- Predicted: Players and some entities predicted client-side
- Event system: One-shot effects synchronized efficiently
Only entity_state_t fields are networked. Server-only data (health, AI, callbacks) stays on server. This architecture provides smooth gameplay while minimizing bandwidth usage.