Доступ к другим объектам
You can help by finishing the translation.
If this page cannot be translated for some reason, or is left untranslated for an extended period of time after this notice is posted, the page should be requested to be deleted.
Also, please make sure the article complies with the alternate languages guide.
В Hammer объекты могут взаимодействовать только через заданные входы и выходы. Хорошая новость в том, что в C++ это ограничение отсуствует, и можно получить доступ ко всему, что не является явно "ограниченным". Плохая новость в том, что придется немного потрудиться, чтобы добиться желаемого результата.
Как пример
CBreakable* pWall = GetFuncBreakable(); // Made-up function!
if (!pWall) return;
pWall->Break(this);
В приведенном выше коде:
- Объявляем указатель и присваиваем ему нужный объект (func_breakable)
- Проверяем, что указателю успешно присвоено значение
- Обращаемся к функции-члену целевого объекта
Указатели
В C++ указатель аналогичен ярлыку на рабочем столе компьютера. Текущая цель указателя называется 'указываемым объектом'.
Указатель имеет пять основных правил:
- Pointers are declared like a variable, except for an asterisk before the name (e.g.
CBaseEntity* pOther). Don't use the asterisk anywhere else - it's just for declarations. - You must
#includethe header file of any class you want to create a pointer for. - A pointer can only be assigned an object that matches its class. The compiler will throw an error otherwise.
- When accessing members of a pointee, you must use
->instead of the usual period character. - Any attempt to access an unassigned ('null') pointer will immediately crash your mod. Welcome to C++!
These aside, the syntax surrounding a pointer is the same as any other member of the current class.
&. SomeFunc(&MyVar) is the same as SomeType* pMyVar = MyVar; SomeFunc(pMyVar).
Приведение типов
Указываемые объекты должны принадлежать к тому же классу, что и указатель. Но как насчёт функций прикосновения, которые представляют другой задействованный объект в виде CBaseEntity*? Ведь исходного CBaseEntityв этом случае не существует, верно?
На самом деле это не так. Указатель не является CBaseEntity, но указатель был "приведен" таким образом, чтобы действовать как CBaseEntity. Это возможно, когда один класс наследуется от другого; поскольку всё наследуется от CBaseEntity, то всё может быть приведено к CBaseEntity*. Это важно, если неизвестно с какими объектами будет работать код!
Указатель типа CBaseEntity* подойдет, если нужно получить доступ к чему-либо, определенному в CBaseEntity. Но если нужно получить доступ к чему-либо, определенному ниже в классовой структуре, то указатель необходимо привести к классу, который имеет к этому доступ. Это достигается с помощью dynamic_cast.
CMyEntity::Touch( CBaseEntity* pOther )
{
CBreakable* pWall = dynamic_cast<CBreakable*>(pOther);
if (!pWall) return;
pWall->Break(this);
}
Мы привели pOther в тип CBreakable*, сохранив результат в pWall. Теперь можно получить доступ к функции Break() типа CBreakable.
В этом случае особенно важно проверить, что указатель присвоен, потому что приведение типов будет выполняться всякий раз, когда объект коснётся чего-либо. Если pOther на самом деле не является CBreakable, то произойдёт сбой выполнения операции и создание опасного нулевого указателя.
CBreakable* pWall = static_cast<CBreakable*>(pOther).
CHandle
Указатели представляют собой физический адрес в системной памяти. Если нужно передать указатель между клиентом и сервером или записать его в сохранении игры, необходимо использовать CHandle (он же EHANDLE).
Поиск объектов
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:
- Call
gEntList.FindEntityByClassname()to get the first entity with the Hammer classname "npc_*".
Примечание: В данном случае звёздочка является
символом подстановки для поиска.
- Enter a loop that ends when
pResultbecomes invalid. - Cast our
CBaseEntity*,pResult, toCAI_BaseNPC*so that we can access its AI-related members. - Confirm that the cast has been successful.
- Perform our desired operation.
- Call
FindEntityByClassname()and search for "npc_*" again, this time starting at pResult's position in the list. - 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.