ARTIKEL 1 - ENTITIES

von SumFuka


Das ist das erste Dokument in der Serie "Artikel", die verschiede Dinge in der Quake3 Welt erklären. Hast du dich jemals gefragt, wie verschiedene Dinge in der Spielewelt miteinander interagieren? Das ist die erste Frage, mit der wir uns befassen werden, in dem wir uns von unten heraufarbeiten werden. Das Design von Quake hat sich von Quake (I) bis Quake3 nicht sehr stark verändert. Die elementarste Sache in der Welt ist ein Entity!

EIN ENTITY?
Alles was du in der Quake3 Welt sehen kannst ist ein Entity. Eine Rakete? Das ist ein einfaches Beispiel für ein Entity. Ein Munitionspack? Ein Entity! Ein Spieler? Auch dieser ist ein Entity (aber ein spezielles). Nehmen wir ein Beispiel... mmhh.... mmhhhhhh... laß uns eine Rakete feuern! Hier ist die Funktionsweise:

  1. Du drückst den Feuerknopf
  2. Ein leerer Entity Slot (eine Art Steckplatz) wird ausgewählt
  3. Das Entity Rocket wird an dieser Stelle des Slots erzeugt
  4. Die Raketeneinstellungen werden zugewiesen (direkt vor dir positioniert, zielend in deine Blickrichtung, 900 Einheite/s schnell, ...)
  5. Die Rakete fliegt durch die Welt, bis eines der folgenden Ereignisse (events) eintritt:
    • wenn die Rakete etwas Solides (z.B. einen Spieler, eine Wand...) trifft, dann explodiert sie und das Entity wird entfernt
    • wenn die Rakete den Himmel berührt, dann wird das Entity ohne Explosion entfernt
    • wenn die Rakete in 10 Sekunden Flugzeit überhaupt nichts trifft, wird das Entity entfernt
  6. Nachdem das Entity entfernt wurde, wird der Slot als "frei" markiert, und kann wieder für neue Sachen verwendet werden.

Wie du siehst, ist die Lebensdauer einer Rakete relativ kurz. Vielleicht fragst du dich jetzt, ob es eine Grenze gibt, wieviele Raketen gleichzeitig in einer Map umherfliegen können? Ja, das gibt es...

WIE VIELE ENTITIES DARF ES GLEICHZEITIG GEBEN?
Es ist an der Zeit ein wenig Detektivarbeit zu betreiben. Was ist die Quake Welt? Sie besteht aus einem Haufen verschiedener Sachen, wie Maps, Spieler, und wie ... ENTITIES! Gehe in MSVC und führe eine globale Suche nach "g_entities" durch, bzw. Suche über Windows-F3 nach dem enthaltetenen Textstück. Du wirst dafür viele Treffer erhalten, uns interssiert hierbei aber speziell die Datei g_main.c, in der steht:

gentity_t g_entities[MAX_GENTITIES];

Diese Zeile verrät uns, dass es ein endliches Feld von Entities in der Welt gibt. Dieses Array trägt den Namen "g_entities" und ist MAX_GENTITIES groß. Aber was ist MAX_GENTITIES? Wir suchen wieder global nach dem Begriff, und finden in der Datei q_shared.h etwas sehr interesanntes:

#define GENTITYNUM_BITS 10 // don't need to send any more
#define MAX_GENTITIES (1<<GENTITYNUM_BITS)

Ok, Carmack zeigt uns hier ein wenig Guru-Level C Code. Glaube mir einfach, dass (x << y) bedeuted, dass x um y mal verdoppelt wird. Nachdem GENTITYNUM_BITS 10 ist, sind die MAX_GENTITIES 2 hoch 10, also 1024. In anderen Worten, es ist genug Platz in der Welt für z.B. 1024 Raketen, Spieler, Waffen, Rüstungen oder was auch sonst immer benötigt wird.

KÖNNEN DIE ENTITIES AUSGEHEN?
Was würde passieren, wenn du in dem einen Ende einer sehr großen Space-Map stehst und mit dem Raketenwerfer ständig feuerst? Unter der Annahme, dass deine Raketen die ganzen 10 Sekunden fliegen, kannst du 10 Raketen auf einmal in der Luft haben. (Du erinnerst dich sicherlich, ein Entity Slot kann wieder neu benutzt werden, nachdem das Entity daraus entfernt wurde). Wenn du 32 Spieler hast, und alle in der Ecke sitzen und ständig feuern, dann wird es etwa 320 Raketen gleichzeitig in der Luft geben. Noch immer haben wir das Limit nicht erreicht...

Es gibt eine gewisse Anzahl an Entities, die ständig in Gebrauch sind (z.B. Player, Waffen und Items). Alle anderen nicht-permanenten Entities haben den selben Lebenszyklus: Erzeugen, kurzes Leben, Entity Slot freigeben. Das ist etwas an das man immer denken sollte, wenn man einen Mod erstellt. Wenn du eine Clustergranate erzeugst, die sich bei Explosion in 100 Mini Granaten zerteilt, dann könnte jemand sehr schnell all deine Entity Slots aufbrauchen, in dem er einfach eine Salve davon feuert.

Was passiert, wenn unserer Welt die Entities ausgehen? Man sollte hier den schlimmsten Fall annehmen: Der Server stürzt ab. Wenn ein Entity eine relativ lange Zeit exisitiert, stelle sicher, dass es nicht möglich ist eine größere Anzahl von ihnen zur selben Zeit zu erstellen.

WAS IST EIN ENTITY?
Laß uns einen Blick in g_local.h werfen:

struct gentity_s {


entityState_t s; // communicated by server to clients
entityShared_t r; // shared by both the server system and game
// DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER
// EXPECTS THE FIELDS IN THAT ORDER!
//================================
struct gclient_s *client; // NULL if not a client
qboolean inuse;
char *classname; // set in QuakeEd
int spawnflags; // set in QuakeEd
qboolean neverFree; // if true, FreeEntity will only unlink
// bodyque uses this
int flags; // FL_* variables
char *model;
char *model2;
int freetime; // level.time when the object was freed

int eventTime; // events will be cleared
// EVENT_VALID_MSEC after set
qboolean freeAfterEvent;
qboolean unlinkAfterEvent;
qboolean physicsObject; // if true, it can be pushed by movers and fall
// off edges all game items are physicsObjects,
float physicsBounce; // 1.0 = continuous bounce, 0.0 = no bounce
int clipmask; // brushes with this content value will be collided
// against when moving. items and corpses
// do not collide against players, for instance
// movers
moverState_t moverState;
int soundPos1;
int sound1to2;
int sound2to1;
int soundPos2;
int soundLoop;
gentity_t *parent;
gentity_t *nextTrain;
gentity_t *prevTrain;
vec3_t pos1, pos2;
char *message;
int timestamp; // body queue sinking, etc
float angle; // set in editor, -1 = up, -2 = down
char *target;
char *targetname;
char *team;
gentity_t *target_ent;
float speed;
vec3_t movedir;
int nextthink;
void (*think)(gentity_t *self);
void (*reached)(gentity_t *self); // movers call this when
// hitting endpoint
void (*blocked)(gentity_t *self, gentity_t *other);
void (*touch)(gentity_t *self, gentity_t *other, trace_t *trace);
void (*use)(gentity_t *self, gentity_t *other, gentity_t *activator);
void (*pain)(gentity_t *self, gentity_t *attacker, int damage);
void (*die)(gentity_t *self, gentity_t *inflictor, gentity_t *attacker,
int damage, int mod);
int pain_debounce_time;
int fly_sound_debounce_time; // wind tunnel
int last_move_time;
int health;
qboolean takedamage;
int damage;
int splashDamage; // quad will increase this w/o increasing radius
int splashRadius;
int methodOfDeath;
int splashMethodOfDeath;
int count;

gentity_t *chain;
gentity_t *enemy;
gentity_t *activator;
gentity_t *teamchain; // next entity in team
gentity_t *teammaster; // master of the team

#ifdef MISSIONPACK
int kamikazeTime;
int kamikazeShockTime;
#endif

int watertype;
int waterlevel;
int noise_index;
// timing variables
float wait;
float random;
gitem_t *item; // for bonus items
qboolean botDelayBegin;


};

Ganz oben sind einige wichtige Dinge - ein entityState_t und entityShared_t - diese Bits beinhalten allgemeine Dinge, wie den Ort eines Entities, von welchem Typ es ist, die Größe der Boundin Box...

Danach kommt eine Struktur g_client_s *client; - Dies ist ein Pointer mit zusätzlichen Informationen, wenn das Entity ein "Client" (also Spieler oder Bot) ist. Wenn das Entity kein Client ist, so zeigt dieses auf NULL (also unbenutzt).

Weiter unten gibt es auch haufenweise interesannte Felder: Der Klassenname (classname), die Geschwindigkeit (speed), die Bewegunsrichtung (movedir), das Ziel (target), das Team... Es werden nicht alle Felder für ein Entity benötigt, z.B. ein roter Armor würd keine "damage" Zuweisung bekommen, wie es bei einer Rakete der Fall wäre. Die meisten Bereiche sind zum Glück relativ selbst erklärend.


"THINKING"...
Zudem finden wir in der Datei Funktionspointer. Die Namen davon sind: think, reached, blocked, touch, use, pain, die. Obwohl die Syntax sehr kryptisch aussieht (erinnere dich, Carmack ist ein Codeguru), ist es ziemlich einfach mit einem Beispiel zu erklären. Wir wollen, dass unsere Rakete nach 10 Sekunden explodiert. Siehe dazu g_missile.c:

bolt->nextthink = level.time + 10000;
bolt->think = G_ExplodeMissile;

Das bedeuted, nach 10 Sekunden "denkt" die Rakete wieder, und ruft dabei die Funktion G_ExplodeMissile auf. Analog dazu explodiert eine Granate nach 2,5 Sekunden. Kannst du den Code dafür selbst finden?

"Denken" ("thinking") ist ein netter Mechanismus, bei dem wir uns um wenig kümmern müssen. Wir erschaffen ein Entity, definieren, was es in Zukunft zu tun hat, und vergessen es einfach - die Engine kümmert sich von nun an darum.

10 mal die Sekunde überprüft der Server jedes Entity, ob etwas zum "denken" ansteht. Wenn ja, wird die "Denkfunktion" von diesem Entity aufgerufen. Analog dazu werden andere Entity Funktionen durch das Eintreten bestimmter Ereignisse ausgelöst. Wenn ein Player stirbt, so wird die player_die Funktion aufgerufen (siehe g_client.c).

PERMANENTE ENTITIES & CLIENTS
In q_shared.h sehen wir, dass die maximale Anzahl an Clients (Player oder Bots) - MAX_CLIENTS - auf 128 gesetzt wurde. Per definitionem werden die ersten MAX_CLIENTS Stück Entities in g_entities für Clients reserviert. Ein Feld in C wird von 0 ab gezählt, also g_entities[0] ist für den client 0, g_entities[1] für den client 1 ... bis zu g_entities[127] für den Spieler Nummer 127 reserviert.

So wie ein Feld von Entities in der Welt exisitiert, so gibt es auch ein Feld mit Informationen zum Client als structs. Siehe dazu g_main.c:

gclient_t g_clients[MAX_CLIENTS];

Hier haben wir ein Feld mit 128 gclient_t's (die Client Info Struktur). Und jedes dieser 128 g_entities zeigt auf den dazu korrespondierenden g_client[x]. Also z.B. g_entities[0]->client zeigt auf g_clients[0] usw. Wir werden erst später einen genaueren Blick auf diese Struktur werfen.

Du siehst, Entities haben einen großen Einfluß auf die Quake Welt (bzw. definieren diese erst). Ein anderes mal werden wir mehr über temporäre Entites (Railstreifen, Blut und ähnliche Effekte) reden.

<< zurück/back >>