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:
-
Du drückst den Feuerknopf
- Ein leerer Entity Slot (eine Art Steckplatz) wird ausgewählt
- Das Entity Rocket wird an dieser Stelle des Slots erzeugt
- Die Raketeneinstellungen werden zugewiesen (direkt vor dir positioniert,
zielend in deine Blickrichtung, 900 Einheite/s schnell, ...)
- 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
-
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:
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 >>
|
|