Accessing other entities:zn-cn

From Valve Developer Community
Jump to: navigation, search
English Deutsch

Hammer 中,实体 仅能通过已经定义好的 输入与输出 来交流。有一个好消息是,在C++里,这个限制没有了并且你可以访问一切没有明确说明是“私有”的东西。一个坏消息是你必须再费力一点来顺利开始。

一个例子

CBreakable* pWall = GetFuncBreakable(); // 预制函数!
if (!pWall) return;

pWall->Break(this);

在上面的代码中,我们:

  1. 我们声明了一个指针并且把一个实体赋值给它(一个 func_breakable 实体)
  2. 确认该指针已经成功地被赋值
  3. 访问一个目标实体的成员函数
Tip:给指针名加上前缀“p”使得后面识别它们更加容易,但并非必须这样做。

指针

一个指针是一个桌面快捷方式的C++等价物。一个指针的现有目标叫做“指向对象”

一个指针主要有五条规则:

  1. 像一个变量般声明,除了名字前有一个星号(比如说 CBaseEntity* pOther)。 不要在其他地方使用星号 —— 它(星号)仅仅用于声明
  2. 你必须 #include 类(你想要为这个或者这些类创建一个指针)的头文件。
  3. 一个指针仅能用符合它的类的一个object来赋值,否则编译器会抛出一个错误。
  4. 访问一个指向对象的成员时,你必须使用 -> 而不是通常的period character.
  5. 尝试访问一个未赋值的('null') 指针 会立刻使你的Mod崩溃。欢迎来到C++!

除此之外, the syntax surrounding a pointer is the same as any other member of the current class.

Tip:你可以快速创建一个指针for use as a function argument with &. SomeFunc(&MyVar) is the same as SomeType* pMyVar = MyVar; SomeFunc(pMyVar).

Casting

Pointees must be of the same class as the pointer. But what about touch functions, which provide the other entity involved as CBaseEntity*? There's no such thing as a pure CBaseEntity, right?

Indeed there isn't. The pointee isn't CBaseEntity, but the pointer has been "cast" to act as if it were one. This is possible when one class inherits from another; since everything inherits from CBaseEntity everything can be cast to CBaseEntity*. Essential knowledge if you don't know what entities your code will be dealing with!

A CBaseEntity* pointer is fine if you want to access something defined in CBaseEntity. But if you need to access something defined further downstream, then the pointer must be cast to a class that has access to it. This is achieved with dynamic_cast.

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

	pWall->Break(this);
}

We cast pOther to CBreakable*, storing the result in pWall. We can then access the CBreakable function Break().

It's doubly quadruply important to check that the pointer is assigned this time because the cast will be attempted whenever the entity touches something. If pOther isn't actually a CBreakable then the operation will fail, leading to a dangerous null pointer.

Note:Remember that casting a pointer does not affect the pointee itself.
Tip:If the classes of both objects are known, you can perform a time saving static cast like this: CBreakable* pWall = static_cast<CBreakable*>(pOther).

CHandle

Pointers represent physical locations in system memory. If you want to transfer a pointer between the client and server or store it in a saved game, you must use a CHandle (aka EHANDLE).

Finding entities

There isn't much point in knowing how to create pointers if you don't have an entity to work with in the first place. gEntList is the place to get one. It's a global object available through cbase.h.

gEntList offers various search functions, the names of which all start with Find or Next. Each one will only return one CBaseEntity* at a time however, so if you want to iterate through all the results a bit of padding is needed:

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_*");
}

Here we:

  1. Call gEntList.FindEntityByClassname() to get the first entity with the Hammer classname "npc_*".
    Note:The asterisk is a search wildcard in this context.
  2. Enter a loop that ends when pResult becomes invalid.
  3. Cast our CBaseEntity*, pResult, to CAI_BaseNPC* so that we can access its AI-related members.
  4. Confirm that the cast has been successful.
  5. Perform our desired operation.
  6. Call FindEntityByClassname() and search for "npc_*" again, this time starting at pResult's position in the list.
  7. Return to the start of the loop.

Searching the whole entity list all the time is expensive so try to avoid doing it if you can. For example if your class will need to access the other entity again later, keep it stored away in a member pointer.

See also