Accessing Other Entities

From Valve Developer Community
< De
Jump to: navigation, search
English (en)Deutsch (de)中文 (zh)Translate (Translate)
Icon-broom.png
Dieser Artikel muss von der ersten in die dritte Person umgewandelt werden, um ihn an Wiki-Standards anzupassen.
Info content.png
This page has not been fully translated.

You can help by finishing the translation.

Also, please make sure the article tries to comply with the alternate languages guide.

In Hammer(en) kommunizieren Entities(en) nur über vordefinierte Inputs und Outputs(en). Die gute Nachricht ist, dass in C++ diese Limitation verschwindet und man auf alles zugreifen kann, was nicht explizit "privat" ist. Die schlechte Nachricht ist, dass die Arbeit dafür ein wenig schwerer ist, um es in Gang zu bringen.

Ein Beispiel

CBreakable* pWall = GetFuncBreakable(); // bestehende Funktion!
if (!pWall) return;

pWall->Break(this);

Im oberen Code wird:

  1. ein Pointer deklariert und die Entity zugewiesen (ein func_breakableEnglish)
  2. sichergegangen, dass dem Pointer erfolgreich ein Wert zugewiesen wurde
  3. auf eine Memberfunktion des Zielentity zugegriffen
Tip.pngTipp:'p' als Präfix von Pointernamen zu verwenden macht die Identifizierung später einfacher, aber es ist nicht notwendig.

Pointer

Ein Pointer (auch Zeiger) ist das C++ Äquivalent einer Desktopverknüpfung. Das aktuelle Ziel eines Pointers wird 'Zeigerende' genannt.

Ein Pointer hat 5 Hauptregeln:

  1. Pointer werden wie Variablen deklariert, abgesehen von dem Sternchen vor dem Namen (z. B. CBaseEntity* pOther). Verwende das Sternchen sonst nirgens - es ist nur für Deklarationen.
  2. Du musst eine Headerdatei einer jeden Klasse mit #include einbinden, für die ein Pointer erzeugt werden soll.
  3. Einem Pointer kann nur ein Objekt zugewiesen werden, welches eine passende klasse besitzt. Andernfalls wirft der Compiler einen Fehler.
  4. Beim Zugriff auf Member eines Zeigerendes muss ein -> statt eines Punkts verwendet werden.
  5. Jeder Versuch, auf einen nicht zugewiesenen ('null') Pointer zuzugreifen lässt die Modifikation sofort abstürzen. Willkommen zu C++!

Abgesehen davon ist die Syntax eines Pointer die gleiche, wie für jeden anderen Member der aktuellen Klasse.

Tip.pngTipp:Man kann einen Pointer für die Verwendung als Parameter schnell mit einem & erzeugen. SomeFunc(&MyVar) ist das gleiche, wie SomeType* pMyVar = MyVar; SomeFunc(pMyVar).

Casting

Zeigerenden müssen von der gleichen Klasse, wie der jeweilige Pointer sein. Aber was ist mit touch-Funktionen(en), welche die andere beteiligte Entity als CBaseEntity* liefert? Es gibt keine reinen CBaseEntity, oder?

Das ist tatsächlich so. Der Zeigerende ist kein CBaseEntity, aber der Pointer wurde "gecastet", um sich so zu verhalten, als wäre dort eins. Das ist möglich, wenn eine Klasse von einer anderen erbt; Da alles von CBaseEntity erbt, kann alles in ein CBaseEntity* gecastet werden. Grundlegendes Wissen, Wenn man nicht weiß, mit welchen Entities der eigene Code umgehen wird!

Ein CBaseEntity*-Pointer wenn man auf etwas in CBaseEntity definiertes zugreifen will. Wenn man aber auf etwas tiefer liegendes zugreifen will, dann muss der Pointer in eine Klasse gecastet werden, die Zugriff besitzt. Dies wird mit dem dynamic_cast erreicht.

CMyEntity::Touch( CBaseEntity* pOther )
{
	CBreakable* pWall = dynamic_cast<CBreakable*>(pOther);
	if (!pWall) return;

	pWall->Break(this);
}

pOther wird in einen CBreakable* gecastet, dessen Ergebnis in pWall gespeichert wird. Dann kann auf die CBreakable-Funktion Break() zugegriffen werden.

Es ist doppelt vierfach wichtig, dass der Pointer dieses Mal zugewiesen ist, denn der Cast wird versucht, wannimmer die Entity etwas berührt. Wenn pOther kein CBreakable ist, dann wird die Durchführung fehlschalgen, welches zu einem gefährlichen Nullpointer führt.

Note.pngHinweis:Das casten eines Pointers wird das Zeigerende nicht beeinflussen.
Tip.pngTipp:Wenn die Klassen beider Objekte bekannt sind, kann ein zeitsparender static_cast wie folgt durchgeführt werden: CBreakable* pWall = static_cast<CBreakable*>(pOther).

CHandle

Pointer repräsentieren physiache Positionen im Systemspeicher. Wenn ein Pointer zwischen dem Client und dem server transportiert werden oder in einem Spielstand gespeichert werden soll, muss ein CHandle(en) (aka EHANDLE) verwendet werden.

Entities finden

Es gibt keinen großartigen Zweck, zu wissen, wie ein Pointer erstellt wird, wenn man keine entity zum Bearbeiten hat. Aus gEntList bekommt man diese. Es ist ein globales Objekt, welches über cbase.h verfügbar ist.

gEntList bietet diverse Suchfunktionen, deren Namen alle mit Find oder Next beginnen. Jede wird jedoch nur 1 CBaseEntity* gleichzeitig liefern, also ist eine gewisse Polsterung notwendig, wenn man über alle Ergebnisse iterieren will:

CBaseEntity* pResult = gEntList.FindEntityByClassname(NULL,"npc_*");
while (pResult)
{
	CAI_BaseNPC* pNPC = dynamic_cast<CAI_BaseNPC*>(pResult);
	if (pNPC)
		pNPC->SetState(NPC_STATE_IDLE);

	pResult = gEntList.FindEntityByClassname(pResult,"npc_*");
}

Hier wird:

  1. gEntList.FindEntityByClassname() aufgerufen, um die erste Entity mit dem Hammer-Klassennamen "npc_*" zu erhalten.
    Note.pngHinweis:Das Sternchen ist in diesem Zusammenhang ein Suchplatzhalter.
  2. eine Schleife betreten, die endet, wenn pResult ungültig wird.
  3. unser CBaseEntity*, pResult, in einen CAI_BaseNPC* gecastet, damit Zugriff auf die KI-bezogenen Member besteht.
  4. sichergegangen, dass der Cast erfolgreich war.
  5. die gewünschte Operation durchgeführtPerform our desired operation.
  6. FindEntityByClassname() erneut für die suche nach "npc_*" aufgerufen, welche diese Mal die Suche in der Liste ab der Position von pResult's startet.
  7. zurück zum anfang der Schleife gesprungen.

Die gesamte Entity-Liste jedes Mal zu durchsuchen ist teuer(en), also sollte es nach Möglichkeit vermieden werden. Beispielsweise kann eine Entity in einem Memberpointer gespeichert werden, wenn ein späterer Zugriff erforderlich ist.

Siehe auch