Ru/Authoring a Logical Entity: Difference between revisions

From Valve Developer Community
< Ru
Jump to navigation Jump to search
No edit summary
mNo edit summary
Line 2: Line 2:
[[Category:Программирование]]
[[Category:Программирование]]


После создания логической энтити в предыдущем примере, теперь создадим энтить которая может двигаться, создавать коллизии с остальными объектами, и которая имеет визуальную компоненту (в данном случае, модель). В этом примере мы создадим энтить которая отображается используя модель, и перемещаются по миру случайным образом.
Для начала мы создадим логическую энтить которая будет выполнять простейшую работу. Эта энтитя хранит значение и увеличивает его каждый раз когда получает ввод (input) от другой энтити. Когда счетчик достигнет заданного нами значения, энтития сгенерирует вывод (output) позволяющий оповестить другие энтити об этом условии.


=Создаем CPP файл для новой энтити=
=Создаем CPP файл для новой энтити=
[[Image:Add existing item.gif|Добавляем исходный файл в проект server.dll по правому щелчку.]]
[[Image:Add existing item.gif|Добавляем исходный файл в проект server.dll по правому щелчку.]]


* Создаем файл называемый <code>sdk_modelentity.cpp</code>. Этот файл должен быть внутри пакпки dlls которая находить в папке с вашим исходным кодом. Например, если вы установили код в <code>C:\MyMod\src</code>, тогда вы должны создать файл называемый <code>C:\MyMod\src\dlls\sdk_logicalentity.cpp</code>.
* Создаем файл называемый <code>sdk_logicalentity.cpp</code>. Этот файл должен быть внутри пакпки dlls которая находить в папке с вашим исходным кодом. Например, если вы установили код в <code>C:\MyMod\src</code>, тогда вы должны создать файл называемый <code>C:\MyMod\src\dlls\sdk_logicalentity.cpp</code>.
* Далее копируем [[Model Entity |этот код]] и вставляем его в новый файл.
* Далее копируем [[Logical Entity Code |этот код]] и вставляем его в новый файл.
* В последнею очередь добавляем этот файл в ваш проект server.dll. Если вы открыли <code>game_sdk.sln</code> solution, тогда вы можете щелкнуть правой кнопкой на проекте hl в окне Solution Explorer и выбрать Add, затем Add Existing Item.
* В последнею очередь добавляем этот файл в ваш проект server.dll. Если вы открыли <code>game_sdk.sln</code> solution, тогда вы можете щелкнуть правой кнопкой на проекте hl в окне Solution Explorer и выбрать Add, затем Add Existing Item.


=Рассмотрение кода=
=Рассмотрение кода=
==Определение класса==
==Определение класса==


<pre>
<pre>
class CMyModelEntity : public CBaseAnimating
class CMyLogicalEntity : public CLogicalEntity
{
{
public:
public:
    DECLARE_CLASS( CMyModelEntity, CBaseAnimating );
      DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity );
    DECLARE_DATADESC();
 
    void Spawn( void );
    void Precache( void );
    void MoveThink( void );
 
    // Input function
    void InputToggle( inputdata_t &inputData );
 
private:
    bool        m_bActive;
    float        m_flNextChangeTime;
};
};
</pre>
</pre>


Мы наследуем нашу энтить от класса <code>CBaseAnimating</code>. Это позволяет использовать модели и анимацию. Также новыми для этой энтити являются функции <code>Spawn()</code> и <code>Precache()</code>.
Мы наследуем нашу новую энтитю от класса <code>CLogicalEntity</code>. Этот класс энтити которая находится на стороне сервера и не будет передавать данные на сторону клиента. Также мы будем использовать вспомогательный макрос <code>DECLARE_CLASS</code> который скрывает от нас некоторые рутинные действия. Унаследовав <code>CMyLogicalEntity</code> от <code>CLogicalEntity</code> мы можем использовать тип <code>BaseClass</code> с этим классом. Это будет важно позднее для обращения к функциональности которую несет <code>CLogicalEntity</code> от которого мы унаследовали класс.


==Определяем описание данных для данного класса==
==Связываем класс с именем энтити==
 
Затем мы связываем класс <code>CMyLogicalEntity</code> с  фактической энтитей <code>classname</code> на котрую сможет ссылаться движок.


<pre>
<pre>
LINK_ENTITY_TO_CLASS( my_model_entity, CMyModelEntity );
LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity );
 
</pre>
//Начинаем описание наших данных для этого класса
BEGIN_DATADESC( CMyModelEntity )
   
    // Сохраняем/востанавливаем ваше состояние активности
    DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
    DEFINE_FIELD( m_flNextChangeTime, FIELD_TIME ),


    // Связываем имя нашего input из Hammer с нашей input function
Здесь для класса <code>CMyLogicalEntity</code> объявляется его имя класса <i>classname</i> как <code>"my_logical_entity"</code>. Это имя которое Хаммер будет использовать для ссылки на тип энтити а также будет определяться как остальные энтити будут искать и находить наш тип энтити. Имя класса (<code>classname</code>) отличается от имени назначения (<code>targetname</code>) энтити, которое является строкой которая идентифицирует одну энтитю или группу энтитей. <code>classname</code> указывает на все энтити данного типа, тогда как <code>targetname</code> может связывать несколько разных типов энтитей (например у вас может быть энтитя с именем класса <code>env_splash</code> которая принадлежит группе энтитей, у каждой из которых <code>targetname</code> равен <code>splash_group</code>).
    DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),


    // Объявляет think функцию
    DEFINE_THINKFUNC( MoveThink ),


END_DATADESC()
</pre>


Подобно нашей логической энтити, мы должны объявить переменные используемые энтитью так чтоб движок знал их смысл. Важно отметить что функция <code>MoveThink()</code> должна быть объявлена как энтить с think-функцией таблице описания данных используя макрос <code>DEFINE_THINKFUNC</code>. Смотрите [[Описание данных|таблицу описания данных]] для дополнительной информации.
==Добавляем переменные члены класа==


==Создаем Precache() функцию==
<pre>
<pre>
#define ENTITY_MODEL     "models/gibs/airboat_broken_engine.mdl"
int     m_nThreshold;  // Пороговое значение срабатывания output
 
int    m_nCounter;   // Внутренний счетчик
void CMyModelEntity::Precache( void )
{
    PrecacheModel( ENTITY_MODEL );
}
</pre>
</pre>


<code>Precache()</code> функция выполняет кеширование всех нужных файлов. Для дополнительной информации по теме смотрите [[Кэширование файлов|кеширование файлов]]. Здесь мы также определяем модель которая будет использоваться для отображения энтити в мире.
Здесь мы описали две целые переменные которые будут в дальшейшем использоваться в счетчике.
 
В данном примере мы вызываем <code>PrecacheModel()</code> для кеширование нашей модели. Без этого шага, модель энтити не появиться в мире и движок будет жаловаться на отсутствующее кэширование.


==Создаем Spawn() функцию==
==Объявляем описание данных для энтити==
<pre>
<pre>
void CMyModelEntity::Spawn( void )
. . .
{
      Precache();


      SetModel( ENTITY_MODEL );
public:
       SetSolid( SOLID_BBOX );
       DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity);
       UTIL_SetSize( this, -Vector(20,20,20), Vector(20,20,20) );
       DECLARE_DATADESC();
 
      m_bActive = false;
}
</pre>


Функция <code>Spawn()</code> вызывается после создания энтити. Эта функция может считаться игровым конструктором энтити. Здесь энтить может установить ее начальное состояние, включая то что модель использует, ее способы движения и твердость. Важно отметить что функция <code>Spawn()</code> вызывается немедленно после размещения энтити в памяти и поскольку это происходит в самом начале карты, нет гарантии что все остальные энтити к этому моменту были успешно созданы. В связи с этим, любой код связывающий данную энтить с поиском или связью с другими именованными энтитями должен производиться в функции <code>Activate()</code> данной энтити. Функция <code>Activate()</code> вызывается когда все энтити создались и произвели запуски их Spawn() функций. Поиск энтитей перед функцией Activate() зависит от порядка создания энтитей и является ненадежным.
. . .


В приведенном ниже примере, сначала вызывается функция <code>Precache()</code> для того чтобы удостовериться что все файлы были корректно кэшированы. После чего, используется функция<code>SetModel()</code> для установки модели определенной до этого.
LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity );
BEGIN_DATADESC( CMyLogicalEntity )


Далее, устанавливается твердость энтити с помощью функции <code>SetSolid()</code>. Здесь доступны несколько допустимых типов твердости:
DEFINE_FIELD( m_nCounter, FIELD_INTEGER ),
DEFINE_KEYFIELD( m_nThreshold, FIELD_INTEGER, "threshold" ),


{|
END_DATADESC()
| <code>SOLID_NOT</code> || Отсутствует.
</pre>
|-
| <code>SOLID_BSP</code> || Использует BSP дерево для определения твердости (используется для браш-моделей)
|-
| <code>SOLID_BBOX</code> || Использует ограничительный бокс углового центрирования.
|-
| <code>SOLID_CUSTOM</code> || Энтить определяет ее сеобственные функции для проверки колизий.
|-
| <code>SOLID_VPHYSICS</code> || Использует объект vcollide для проверки коллизий.
|}


В данном примере, мы создаем энтить использующую ограничительный бокс. Функция <code>UTIL_SetSize()</code> позволяет установить размер ограничительного бокса. Здесь мы устанавливаем куб размером 40x40x40.
Макрос <code>DECLARE_DATADESC</code> должен включаться в код для того чтобы компилятор мог знать что мы добавляем описание таблицы данных (data description table) ниже в реализации класса. Описание данных содержит различные определения для переменных и специальных функций для этого класса. Поэтому, <code>m_nCounter</code> описывается для сохранения/загрузки функциональности, и <code>m_nThreshold</code> описывается для того чтобы сообщить игре об использовании значения с именем <code>"threshold"</code> (конечного значение) для связи этой переменной с значением ключа в энтите в Хаммере.


==Создаем MoveThink() функцию==
Смотрите документ о  [[Описание данных|таблице описания данных]] для дополнительной информации.
 
Энтити способны обновлять внутренее состояние и принимать решения с помощью функции <i>think</i>, которая вызывается с частотой указанной энтитью. Ниже мы создаем think-функцию которая вызывается 20 раз в секунду. Эта функции используется для случайного обновления передвижения и направления в мире.


==Создаем событие вывода (output event)==
<pre>
<pre>
void CMyModelEntity::MoveThink( void )
COutputEvent  m_OnThreshold;
{
DEFINE_OUTPUT( m_OnThreshold, "OnThreshold" ),
    // Смотрим если мы должны сменить снова направление
    if ( m_flNextChangeTime < gpGlobals->curtime )
    {
            // Случайно выбираем новое направление и скорость
            Vector vecNewVelocity = RandomVector( -64.0f, 64.0f );
            SetAbsVelocity( vecNewVelocity );
 
            // Случайно изменим их через 1-3 секунды
            m_flNextChangeTime = gpGlobals->curtime + random->RandomFloat(1.0f,3.0f);
    }
 
    // Направлям наше лицо туда куда мы направлены
    Vector velFacing = GetAbsVelocity();
    QAngle angFacing;
    VectorAngles( velFacing, angFacing );
    SetAbsAngles( angFacing );
 
    // Думаем каждые 20Гц
    SetNextThink( gpGlobals->curtime + 0.05f );
}
</pre>
</pre>


Хотя много кода написано в этой функции, но её действие очень просто описывается: как только проходит случайный интервал времени, энтить выбирает новое, случайное направление и скорость для путешествия. Она также обновляет угл направления лица к направлению его путешествия.
Это событие будет включаться при встрече описанного конечного значения. Для доп-ой информации смотрите [[Entity Input and Outputs|энтити ввода-вывода]].
Вызов <code>SetNextThink()</code> важен для этой функции, потому что он говорит энтити когда вследующий раз думать. Здесь устанавливается думать снова через 1/20-ую секунды в будущем. Большинству энтитям только нужно думать с частотой 1/10 секунды, взависимости от их поведения. Важно заметить необновив следующее время срабатывание функции приведет к тому что энтить перестает думать (что иногда желательно).


==Создаем InputToggle() функцию==
==Создаем функцию ввода (input function)==


Для данной энтити мы используем ввод(input) для переключения ее передвижения как включенного/выключенного. Для этого, мы объявляем input-функцию подобной из предыдущего урока.
<pre>
void InputTick( inputdata_t &inputData );


<pre>
void CMyLogicalEntity::InputTick( inputdata_t &inputData )
void CMyModelEntity::InputToggle( inputdata_t &inputData )
{
{
       // Переключаем ваше текущее состояние
       // Увеличиваем наш счетчик
       if ( !m_bActive )
       m_nCounter++;
      {
 
              // Начинаем думать
      // Смотрим если мы сравнялись или пересекли пороговое значение
              SetThink( MoveThink );
      if ( m_nCounter >= m_nThreshold )
              SetNextThink( gpGlobals->curtime + 0.05f );
           
              // Начинаем летать
              SetMoveType( MOVETYPE_FLY );
              // Устанавливаем следующие время когда меням нашу скорость и направление
              m_flNextChangeTime = gpGlobals->curtime;
              m_bActive = true;
      }
      else
       {
       {
               // Перестаем думать
               // Вызываем событие output
               SetThink( NULL );
               m_OnThreshold.FireOutput( inputData.pActivator, this );
           
              // Перестаем двигаться
              SetAbsVelocity( vec3_origin );
              SetMoveType( MOVETYPE_NONE );
              
              
               m_bActive = false;
               // Сбрасываем счетчик
              m_nCounter = 0;
       }
       }
}
}
</pre>
</pre>


Для начала "работы" энтити используется функция <code>SetThink()</code> в соединении с функцией <code>SetNextThink()</code>. Она сообщает энтити использовать функцию <code>MoveThink()</code> и вызовется через 1/20 секунды в будущем. Важно отметить что энтить может иметь любое количество think-функций и использовать <code>SetThink()</code> функцию для выбора между ними. Каждая энтить может иметь несколько think-функций выполняющихся в одно и то же время используя <code>SetContextThink()</code> скрытый в другом документе.
Эта функция просто увеличивает счетчик и генерирует событие output когда значение счетчика достигает конечного значения, как это указано в свойстве энтити в Хамере. Данная функция не извлекает никаких значений из Хаммера.
 
Также мы определяем тип движения энтити <code>MOVETYPE_FLY</code>. Это позволяет энтити двигаться в заданном направлении без гравитации.


Во второй части энтити этой функции мы останавливаем энтить от движения. think-функция устанавливается в <code>NULL</code> для остановки think-процесса. Её тип движения также устанавливается <code>MOVETYPE_NONE</code> чтобы удержать его от движения.
=Создаем запись в FGD файле=


==Создание FGD записи==
Для использования энтити в Хаммере, мы должны создать запись в FGD файле. Хаммер будет использовать эти данные для того чтобы понять различные ключевые значения и функции которые предоставляет энтитя. Смотрите [[FGD |документ FGD формата]] для дополнительной информации о FGD файлах.


Для использования энтити в Хаммере, нам нужно создать запись в нашем FGD файле. Хаммер будет использовать эти данные для того чтобы понять различные ключевые значения и функции которые предоставляет энтитя. Смотрите [[FGD |документ FGD формата]] для дополнительной информации о FGD файлах.
Если вы ещё не создали .FGD файл для вашего мода, вы наверное это сейчас захотите сделать. Чтобы сделать это создайте пустой файл с расширением .FGD гденибудь на вашем жестком диске (лучше положить его в папку с вашим модом). Встасьте код внизу в этот файл. Перейдите в Хаммер и выберите Tools->Options и добавьте .FGD файл в секцию <code>Game Data files</code>. Диалог Game Configurations описан [[Hammer Game Configurations |в этом]] документе.


Запись FGD энтити легко отображает модель в хамере и позволяет отправлят input "Toggle" ему.
Затем мы объявляем знвчение <i>"threshold"</i> связанное с переменной <code>m_nThreshold</code>, функцией ввода <code>Tick</code> и функцией вывода <code>OnThreshold</code>.


<pre>
<pre>
@PointClass base(Targetname) studio("models/gibs/airboat_broken_engine.mdl")= my_model_entity : "Tutorial model entity."
@PointClass base(Targetname) = my_logical_entity : "Tutorial logical entity."
[   
[
       input Toggle(void) : "Toggle movement."
      threshold(integer) : "Threshold" : 1 : "Threshold value."
      input Tick(void) : "Adds one tick to the entity's count."
       output OnThreshold(void) : "Threshold was hit."
]
]
</pre>
</pre>


Если ваш .FGD не пустой, убедитесь что добавили строку <code>@include "base.fgd"</code>, которая даст вам некоторые нужные функции Хаммера. (Это соответствует полному преобразованию. Для мода основанного на существующем содержимом, включайте подходящие FGD вместо базового; к примеру, для модов [[HL2]], включите <code>halflife2.fgd</code> вместо <code>base.fgd</code>.)
Если ваш .FGD не пустой, убедитесь что добавили строку <code>@include "base.fgd"</code>, которая даст вам некоторые нужные функции Хаммера.


{{otherlang:ru}}
{{otherlang:ru}}
{{otherlang:ru:en|Authoring a Model Entity}}
{{otherlang:ru:en|Authoring a Logical Entity}}

Revision as of 04:16, 2 September 2006


Для начала мы создадим логическую энтить которая будет выполнять простейшую работу. Эта энтитя хранит значение и увеличивает его каждый раз когда получает ввод (input) от другой энтити. Когда счетчик достигнет заданного нами значения, энтития сгенерирует вывод (output) позволяющий оповестить другие энтити об этом условии.

Создаем CPP файл для новой энтити

Добавляем исходный файл в проект server.dll по правому щелчку.

  • Создаем файл называемый sdk_logicalentity.cpp. Этот файл должен быть внутри пакпки dlls которая находить в папке с вашим исходным кодом. Например, если вы установили код в C:\MyMod\src, тогда вы должны создать файл называемый C:\MyMod\src\dlls\sdk_logicalentity.cpp.
  • Далее копируем этот код и вставляем его в новый файл.
  • В последнею очередь добавляем этот файл в ваш проект server.dll. Если вы открыли game_sdk.sln solution, тогда вы можете щелкнуть правой кнопкой на проекте hl в окне Solution Explorer и выбрать Add, затем Add Existing Item.

Рассмотрение кода

Определение класса

class CMyLogicalEntity : public CLogicalEntity
{
public:
       DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity );
};

Мы наследуем нашу новую энтитю от класса CLogicalEntity. Этот класс энтити которая находится на стороне сервера и не будет передавать данные на сторону клиента. Также мы будем использовать вспомогательный макрос DECLARE_CLASS который скрывает от нас некоторые рутинные действия. Унаследовав CMyLogicalEntity от CLogicalEntity мы можем использовать тип BaseClass с этим классом. Это будет важно позднее для обращения к функциональности которую несет CLogicalEntity от которого мы унаследовали класс.

Связываем класс с именем энтити

Затем мы связываем класс CMyLogicalEntity с фактической энтитей classname на котрую сможет ссылаться движок.

LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity );

Здесь для класса CMyLogicalEntity объявляется его имя класса classname как "my_logical_entity". Это имя которое Хаммер будет использовать для ссылки на тип энтити а также будет определяться как остальные энтити будут искать и находить наш тип энтити. Имя класса (classname) отличается от имени назначения (targetname) энтити, которое является строкой которая идентифицирует одну энтитю или группу энтитей. classname указывает на все энтити данного типа, тогда как targetname может связывать несколько разных типов энтитей (например у вас может быть энтитя с именем класса env_splash которая принадлежит группе энтитей, у каждой из которых targetname равен splash_group).


Добавляем переменные члены класа

int     m_nThreshold;  // Пороговое значение срабатывания output
int     m_nCounter;    // Внутренний счетчик

Здесь мы описали две целые переменные которые будут в дальшейшем использоваться в счетчике.

Объявляем описание данных для энтити

 . . .

public:
       DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity);
       DECLARE_DATADESC();

 . . .

LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity );
BEGIN_DATADESC( CMyLogicalEntity )

DEFINE_FIELD( m_nCounter, FIELD_INTEGER ),
DEFINE_KEYFIELD( m_nThreshold, FIELD_INTEGER, "threshold" ),

END_DATADESC()

Макрос DECLARE_DATADESC должен включаться в код для того чтобы компилятор мог знать что мы добавляем описание таблицы данных (data description table) ниже в реализации класса. Описание данных содержит различные определения для переменных и специальных функций для этого класса. Поэтому, m_nCounter описывается для сохранения/загрузки функциональности, и m_nThreshold описывается для того чтобы сообщить игре об использовании значения с именем "threshold" (конечного значение) для связи этой переменной с значением ключа в энтите в Хаммере.

Смотрите документ о таблице описания данных для дополнительной информации.

Создаем событие вывода (output event)

COutputEvent   m_OnThreshold;
DEFINE_OUTPUT( m_OnThreshold, "OnThreshold" ),

Это событие будет включаться при встрече описанного конечного значения. Для доп-ой информации смотрите энтити ввода-вывода.

Создаем функцию ввода (input function)

void InputTick( inputdata_t &inputData );

void CMyLogicalEntity::InputTick( inputdata_t &inputData )
{
       // Увеличиваем наш счетчик
       m_nCounter++;

       // Смотрим если мы сравнялись или пересекли пороговое значение
       if ( m_nCounter >= m_nThreshold )
       {
              // Вызываем событие output
              m_OnThreshold.FireOutput( inputData.pActivator, this );
             
              // Сбрасываем счетчик
              m_nCounter = 0;
       }
}

Эта функция просто увеличивает счетчик и генерирует событие output когда значение счетчика достигает конечного значения, как это указано в свойстве энтити в Хамере. Данная функция не извлекает никаких значений из Хаммера.

Создаем запись в FGD файле

Для использования энтити в Хаммере, мы должны создать запись в FGD файле. Хаммер будет использовать эти данные для того чтобы понять различные ключевые значения и функции которые предоставляет энтитя. Смотрите документ FGD формата для дополнительной информации о FGD файлах.

Если вы ещё не создали .FGD файл для вашего мода, вы наверное это сейчас захотите сделать. Чтобы сделать это создайте пустой файл с расширением .FGD гденибудь на вашем жестком диске (лучше положить его в папку с вашим модом). Встасьте код внизу в этот файл. Перейдите в Хаммер и выберите Tools->Options и добавьте .FGD файл в секцию Game Data files. Диалог Game Configurations описан в этом документе.

Затем мы объявляем знвчение "threshold" связанное с переменной m_nThreshold, функцией ввода Tick и функцией вывода OnThreshold.

@PointClass base(Targetname) = my_logical_entity : "Tutorial logical entity."
[
       threshold(integer) : "Threshold" : 1 : "Threshold value."
       input Tick(void) : "Adds one tick to the entity's count."
       output OnThreshold(void) : "Threshold was hit."
]

Если ваш .FGD не пустой, убедитесь что добавили строку @include "base.fgd", которая даст вам некоторые нужные функции Хаммера.

Template:Otherlang:ru Template:Otherlang:ru:en