Ru/Authoring a Model Entity: Difference between revisions

From Valve Developer Community
< Ru
Jump to navigation Jump to search
m (avoid redir)
No edit summary
Line 1: Line 1:
{{translationupdate}}
''Предполагается что вы прочли и полностью поняли статью [[Authoring a Logical Entity:ru]].''


После [[Authoring_a_Logical_Entity:ru|создания логической энтити]] в предыдущем примере, теперь создадим энтить которая может двигаться, создавать коллизии с остальнымио бъектами, и которая имеет визуальную компоненту данном случае, модель). В этом примере мы создадим энтить которая отображается используя модель, и перемещаются по миру случайным образом.
В этой статье мы создадим ''объект'' (''объект'' = entity), который может двигаться, сталкиваться с другими объектами, и который отображается в игре виде модели). Мы заставим наш объект случайным образом перемещаться по миру.


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


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


=Рассмотрение кода=
Сейчас вы должны понимать следующий код:


==Создание определения класса==
<span style="color:blue;">#include</span> <span style="color:brown;">"cbase.h"</span>
<span style="color:blue;">class</span> CMyModelEntity : <span style="color:blue;">public</span> CBaseAnimating
{
<span style="color:blue;">public</span>:
DECLARE_CLASS( CMyModelEntity, CBaseAnimating );
DECLARE_DATADESC();
CMyModelEntity()
{
m_bActive = <span style="color:blue;">false</span>;
}
<span style="color:blue;">void</span> Spawn( <span style="color:blue;">void</span> );
<span style="color:blue;">void</span> Precache( <span style="color:blue;">void</span> );
<span style="color:blue;">void</span> MoveThink( <span style="color:blue;">void</span> );
<span style="color:green;">// Входная функция</span>
<span style="color:blue;">void</span> InputToggle( inputdata_t &inputData );
<span style="color:blue;">private</span>:
<span style="color:blue;">bool</span> m_bActive;
<span style="color:blue;">float</span> m_flNextChangeTime;
};


<pre>
Заметьте что на этот раз мы наследуем наш класс от <code>[[CBaseAnimating]]</code>, и на этот раз у нас появилось несколько новых функций. В закрытой (private) части класса содержатся две переменные: <code>bool</code> - это тип переменной которая может принимать только значения правда/ложь (true/false) (1/0), а переменные типа float могут хранить в себе числовые значения с десятичной точкой.
class CMyModelEntity : public CBaseAnimating
{
public:
    DECLARE_CLASS( CMyModelEntity, CBaseAnimating );
    DECLARE_DATADESC();


    void Spawn( void );
{{note|Наш ''объект'' не начнёт двигаться пока не получит соответствующую команду от <code>InputToggle()</code>. Заставить ''объект'' двигаться можно только вызовом функции <code>InputToggle()</code> (а не простой установкой значения <code>m_bActive</code> в единицу) - это станет неплохой практикой после того как вы прочтёте статью.}}
    void Precache( void );
    void MoveThink( void );


    // Функция ввода
== Имя ''объекта'' и таблица описания данных ==
    void InputToggle( inputdata_t &inputData );


private:
LINK_ENTITY_TO_CLASS( my_model_entity, CMyModelEntity );
    bool        m_bActive;
    float        m_flNextChangeTime;
<span style="color:green;">// Начало описания данных класса.</span>
};
BEGIN_DATADESC( CMyModelEntity )
</pre>
<span style="color:green;">// Сохранение/загрузка состояния ''объекта''.</span>
DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flNextChangeTime, FIELD_TIME ),
<span style="color:green;">// Присвоение нашей входной функции имя в Hammer'e.</span>
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
<span style="color:green;">// Объявление "мыслительной" (think) функции.</span>
DEFINE_THINKFUNC( MoveThink ),
END_DATADESC()


Мы наследуем нашу энтить от класса <code>CBaseAnimating</code>. Это позволяет использовать модели и анимацию. Также новыми для этой энтити являются функции <code>Spawn()</code> и <code>Precache()</code>.
Самое большое отличие от созданного нами ранее логического объекта в наличии макроса <code>DEFINE_THINKFUNC</code>, который даёт движку Source знать о, так называемой [[Think|"мыслительной"]] функции.


==Определяем описание данных==
== Определение модели ==


<pre>
<span style="color:green;">// Имя модели нашего объекта.</span>
LINK_ENTITY_TO_CLASS( my_model_entity, CMyModelEntity );
<span style="color:blue;">#define</span> ENTITY_MODEL <span style="color:brown;">"models/gibs/airboat_broken_engine.mdl"</span>


// Начинаем описание наших данных для класса
Этот код жёстко задаёт [[model|модель]], которую наш ''объект'' будет отображать в игре. Это статический кусок информации - не переменная: это значение не может меняться после компиляции кода. Путь к .mdl-файлу задаётся относительно дирректории игры (например <code>hl2/</code>).
BEGIN_DATADESC( CMyModelEntity )
   
    // Сохраняем/востанавливает наще активное состояние
    DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
    DEFINE_FIELD( m_flNextChangeTime, FIELD_TIME ),


    // Связываем наше имя input из Хаммера к нашей input функции
{{note|Мы создали эту строку только для того чтобы упростить поиск и замену этого значения в будущем. Она не влияет на работу нашего кода.}}
    DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),


    // Описываем нашу think функцию
== Функция Precache() ==
    DEFINE_THINKFUNC( MoveThink ),


END_DATADESC()
Мы добрались до первой функции. Функция <code>[[Precache()]]</code> вызывается при появлении объекта на карте и гарантирует загрузку всего, что в ней перечислено до того как появится игрок (т.е. до того как он увидит мир и получит контроль над игрой). Смотрите [[Precaching Assets|рекомендации по прекэшированию]] для получения дополнительной информации.
</pre>


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


==Создаем Precache() функцию==
<span style="color:green;">//-----------------------------------------------------------------------------
<pre>
// Назначение: Прекэширование необходимых объекту ресурсов.
#define ENTITY_MODEL     "models/gibs/airboat_broken_engine.mdl"
//-----------------------------------------------------------------------------</span>
<span style="color:blue;">void</span> CMyModelEntity::Precache( <span style="color:blue;">void</span> )
{
PrecacheModel( ENTITY_MODEL );
BaseClass::Precache();
}


void CMyModelEntity::Precache( void )
Для данного ''объекта'' мы прекэшируем модель, которую будем использовать, а затем вызываем функцию прекэширования нашего базового класса (которая в случае класса <code>CBaseAnimating</code> обеспечивает загрузку системы частиц для огня, т.к. каждый объект с моделью в игре может загореться). Другие команды прекэширования: <code>[[PrecacheParticleSystem()]]</code> и <code>[[PrecacheScriptSound()]]</code>
{
    PrecacheModel( ENTITY_MODEL );
}
</pre>


<code>Precache()</code> функция выполняет кеширование всех нужных файлов. Для дополнительной информации по теме, смотрите [[Precaching_Assets:ru|Кэширование файлов]]. Здесь мы также определяем модель которая представляет нашу энтить в мире.
== Функция Spawn() ==


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


==Создаем Spawn() функцию==
<span style="color:green;">//-----------------------------------------------------------------------------
<pre>
// Назначение: Настраивает начальное состояние объекта.
void CMyModelEntity::Spawn( void )
//-----------------------------------------------------------------------------</span>
{
<span style="color:blue;">void</span> CMyModelEntity::Spawn( <span style="color:blue;">void</span> )
      Precache();
{
Precache();
SetModel( ENTITY_MODEL );
SetSolid( SOLID_BBOX );
UTIL_SetSize( <span style="color:blue;">this</span>, -Vector(20,20,20), Vector(20,20,20) );
}


      SetModel( ENTITY_MODEL );
Сначала мы вызываем функцию <code>Precache()</code>, а затем идут вызовы различных функций класса <code>CBaseAnimating</code>. Функция <code>[[SetModel()]]</code> задаёт модель объекта. В скобках ей передаётся [[#Defining the model|определённый нами ранее]] путь к модели объекта. Функция <code>[[SetSolid()]]</code> требует более подробных разъяснений. Она определяет форму, которую наш объект будет использовать для проверки на столкновение с другими объектами. Использование формы самой модели было бы очень, очень [[expensive|дорогим]] по отношению к ресурсам компьютера, поэтому движок Source предлагает несколько компромисов:
      SetSolid( SOLID_BBOX );
      UTIL_SetSize( this, -Vector(20,20,20), Vector(20,20,20) );


      m_bActive = false;
;<code>SOLID_NONE</code>
}
:"Прозрачный" (не твердый) объект.
</pre>
;<code>SOLID_BBOX</code>
:Используется ориентированный по осям ограничивающий короб.
;<code>SOLID_BSP</code>
:Используется BSP-дерево для определения твёрдости (используется в [[brush|брашах]]).
;<code>SOLID_CUSTOM</code>
:''Объект'' использует свои собственные функции для проверки на столкновения.
;<code>SOLID_VPHYSICS</code>
:Для определения точных столкновений используется встроенная в модель [[collision model|модель столкновений]].


Функция <code>Spawn()</code> вызывается после создания энтити. Эта функция может считаться игровым конструктором энтити. Здесь энтить может установить ее начальное состояние, включая то что модель использует, ее способы движения и твердость. Важно отметить что функция <code>Spawn()</code> вызывается немедленно после размещения энтити в памяти и поскольку это происходит в самом начале карты, нет гарантии что все остальные энтити к этому моменту были успешно созданы. В связи с этим, любой код связывающий данную энтить с поиском или связью с другими именованными энтитями должен производиться в функции <code>Activate()</code> данной энтити. Функция Activate()</code> вызывается когда все энтити создались и произвели запуски их Spawn() функций. Поиск энтитей перед функцией Activate() зависит от порядка создания энтитей и является ненадежным.
Эти пункты выбираются на уровне движка и авторы модов не могут добавлять или менять их. Мы используем параметр <code>[[SOLID_BBOX]]</code>, который генерирует "[[bounding box|ограничивающий короб]]", размер которого движок определяет так, чтобы он охватывал всю модель. Более "дорогой" параметр <code>[[SOLID_VPHYSICS]]</code>, который использует встроенную в модель [[collision model|модель столкновений]], не поддерживает низкоуровневые функции перемещения, которые мы будем использовать для нашего объекта, поэтому мы его не применяем.


В приведенном ниже примере, сначала вызывается функция <code>Precache()</code> для того чтобы удостовериться что все файлы будут корректно кэшированы. После чего, используется функция<code>SetModel()</code> для установки модели определенной до этого.
Вызов <code>[[UTIL_SetSize()]]</code> нужен для того, чтобы сделать наш ограничивающий короб кубическим. Это сделанно потому, что каким бы странным это не казалось, ''ограничивающий короб не может вращаться''. Вам понадобится vphysics-обработка столкновений если вы захотите заставить модель вращаться. А по описанным выше причинам мы её не используем.


Далее, устанавливается твердость энтити с помощью функции <code>SetSolid()</code>. Здесь доступны несколько допустимых типов твердости:
{{warning|<code>Spawn()</code> вызывается '''незамедлительно''' после создания объекта. Если это случится сразу же после создания карты нет гарантии того, что на карте уже появились другие объекты. Поэтому любой код, который предполагает что объект будет связываться с другими игровыми объектами становится ненадежным. В таких случаях используйте функцию <code>[[Activate()]]</code>, которая всегда вызывается после появления всех объектов.}}


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


В данном примере, мы создаем энтить использующую ограничительный бокс. Функция <code>UTIL_SetSize()</code> позволяет установить размер ограничительного бокса. Здесь мы устанавливаем куб размером 40x40x40.
[[Think()|"Мыслительная" функция]] (to think - думать) позволяет ''объекту'' "принимать решения" без побуждения со стороны внешнего кода. Вспоните про наш ''логический объект'' - он делал что-нибудь только при задействовании его входной функции; это неподходит для ''объектов'', которые должны определенным образом перемещаться по миру. Мыслительная функция, если она присутствует, обычно является центральной функцией в коде ''объекта''.


==Создаем MoveThink() функцию==
Сейчас мы создаём мыслительную функцию, которая будет вызываться движком с периодичностью 20 раз в секунду. Это может показаться большим числом, однако современные процессоры очень быстрые, а наш ''объект'' очень простой. Вы сможете добавить очень большое кол-во объектов <code>CMyModelEntity</code> на карту без большой потери производительности.


Энтити способны обновлять внутренее состояние и принимать решения с помощью функции <i>think</i>, которая вызывается с частотой указанной энтитью. Ниже мы создаем think-функцию которая вызывается 20 раз в секунду. Эта функции используется для случайного обновления передвижения и направления в мире.
<span style="color:green;">//-----------------------------------------------------------------------------
// Назначение: Мыслительная функция для случайного перемещения объекта по карте.
//-----------------------------------------------------------------------------</span>
<span style="color:blue;">void</span> CMyModelEntity::MoveThink( <span style="color:blue;">void</span> )
{
<span style="color:green;">// Смотрим, должны ли мы снова поменять направление.</span>
<span style="color:blue;">if</span> ( m_flNextChangeTime < gpGlobals->curtime )
{
<span style="color:green;">// Задаём случайное направление и скорость.</span>
Vector vecNewVelocity = RandomVector( -64.0f, 64.0f );
SetAbsVelocity( vecNewVelocity );
<span style="color:green;">// Сменить направление сново через 1-3 секунды.</span>
m_flNextChangeTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
}
<span style="color:green;">// Поворот объекта туда, куда идет движение.</span>
Vector velFacing = GetAbsVelocity();
QAngle angFacing;
VectorAngles( velFacing, angFacing );
  SetAbsAngles( angFacing );
<span style="color:green;">// Запускать эту функцию с частотой 20Hz</span>
SetNextThink( gpGlobals->curtime + 0.05f );
}


<pre>
Несмотря на то, что в этой функции довольно много кода, она довольно простая. Когда случайный интервал времени подходит к концу объект выбирает новые случайные направление и скорость для продолжения движения. Также меняются углы положения объекта в пространстве, чтобы они соответствовали новому направлению движения. Это происходит для трех измерений.
void CMyModelEntity::MoveThink( void )
{
    // Смотрим если мы должны снова изменить направление
    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);
    }


    // Устанавливаем лицо по направлению движения
*<code>[[gpGlobals]]->curtime</code> возвращает время, в которое исполняется текущий код, как значение с плавающей точкой.
    Vector velFacing = GetAbsVelocity();
*<code>[[Wikipedia:Vector (spatial)|Вектор]]</code> - это набор переменных, используемых для определения движения, которые содержат в себе данные о направлении и скорости. Фунция <code>[[SetAbsVelocity()]]</code> задаёт абсолютную скорость,
    QAngle angFacing;
*<code>[[QAngle]]</code> это просто угол - т.е. то же самое что вектор только без данных о скорости. <code>[[QAngle]]</code> задаёт направление.
    VectorAngles( velFacing, angFacing );
*Функция <code>[[VectorAngles()]]</code> преобразует вектор (<code>velFacing</code>) в угол (<code>angFacing</code>). Не забывайте, что С++ очень строг в отношении типов: вам понядобятся утилитарные функции, такие как <code>VectorAngles()</code>, чтобы осуществлять преобразования между двумя типами.
    SetAbsAngles( angFacing );


    // Думаем каждые 20Гц
В конце мы вызываем функцию <code>[[SetNextThink()]]</code>, которая указывает когда в следующий раз необходимо вызвать мыслительную функцию. Здесь мы задаём новую обработку через 0.05 секунд (1/20 часть), однако это число может быть различным для разных объектов. Важно понимать, что если не вызвать функцию SetNextThink() объект перестанет "мыслить".
    SetNextThink( gpGlobals->curtime + 0.05f );
}
</pre>


Эта функция содрежит много кода, но смысл его довольно прост: после прохождения случайного интервала времени, энтити выбирает новое, случайное направление и скорость путешествия. Он также обновляет углы чтобы лицо было направлено по направлению движения.
Вы могли отметить, что здесь мы определяли новые переменные. Так же как и переменные, определенные внутри класса, переменные, определенные внутри функции, принадлежат данной функции. Они создаются каждый раз когда вызывается функция и уничтожаются по её завершении.


Вызов функции <code>SetNextThink()</code> важен, потому что это говорит энтити когда думать в следующий раз. Здесь устанавливается вызвать think через 1/20 секунды. Для большинства энтитей только нужно думать с частотой 1/10 секунды, взависимости от из поведения. Важно заметить что если не обновить next think time для энтити это приведет к тому что она престанет думать (иногда это удобно).
{{tip|На самом деле нам не очень то и нужна переменная <code>vecNewVelocity</code>. Попробуйте передать значение в функцию <code>SetAbsVelocity()</code> без создания лишней переменной. Вспомните почему мы добавяем <code>void</code> перед всеми нашими функциями.}}


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


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


<pre>
<span style="color:green;">//-----------------------------------------------------------------------------
void CMyModelEntity::InputToggle( inputdata_t &inputData )
// Назначение: Включать/выключать движение объекта.
{
//-----------------------------------------------------------------------------</span>
      // Переключаем наше состояние активности
<span style="color:blue;">void</span> CMyModelEntity::InputToggle( inputdata_t &inputData )
      if ( !m_bActive )
{
      {
<span style="color:green;">// Переключение состояния активности.</span>
              // Начинаем думать
<span style="color:blue;">if</span> ( !m_bActive )
              SetThink( MoveThink );
{
              SetNextThink( gpGlobals->curtime + 0.05f );
<span style="color:green;">// Начало мыслительного процесса.</span>
           
SetThink( &CMyModelEntity::MoveThink );
              // Начинаем летать
              SetMoveType( MOVETYPE_FLY );
SetNextThink( gpGlobals->curtime + 0.05f );
              // Устанавливаем следующее время когда меняем скорость и направление
              m_flNextChangeTime = gpGlobals->curtime;
<span style="color:green;">// Начать летать.</span>
              m_bActive = true;
SetMoveType( MOVETYPE_FLY );
      }
      else
<span style="color:green;">// Установка времени для смены направления и скорости.</span>
      {
m_flNextChangeTime = gpGlobals->curtime;
              // Перестаем думать
              SetThink( NULL );
<span style="color:green;">// Изменение переменной m_bActive для отображения текущего состояния.</span>
           
m_bActive = <span style="color:blue;">true</span>;
              // Перестаем двигаться
}
              SetAbsVelocity( vec3_origin );
<span style="color:blue;">else</span>
              SetMoveType( MOVETYPE_NONE );
{
           
<span style="color:green;">// Перестать мыслить.</span>
              m_bActive = false;
SetThink( NULL );
      }
}
<span style="color:green;">// Перестать двигаться.</span>
</pre>
SetAbsVelocity( vec3_origin );
  SetMoveType( MOVETYPE_NONE );
m_bActive = <span style="color:blue;">false</span>;
}
}


Для начала "работы" энтити используется функция <code>SetThink()</code> в соединении с функцией <code>SetNextThink()</code>. Она сообщает энтити использовать функцию <code>MoveThink()</code> и вызывается через 1/20 секунды. Важно отметить что энтить может иметь любое количество think-функций и использовать <code>SetThink()</code> функцию для выбора между ними. Каждая энтить может иметь несколько think-функций выполняющихся в одно и то же время используя <code>SetContextThink()</code> описанной в другом документе.
Здесь всё очень прямолинейно. Мы используем оператор <code>if</code> чтобы определить текущее состояние флага <code>m_bActive</code>. Знак восклицания означает "не": "Если <code>m_bActive</code> не равно true (истина), то сделать следующее...". Позже мы используем оператор <code>else</code> чтобы определить что мы будем делать в противоположном случае.


Также мы определяем тип движения энтити <code>MOVETYPE_FLY</code>. Это позволяет энтити двигаться в заданном направлении без гравитации.
При активировании объекта мы начинаем его мыслительный процесс указывая движку Source какую мыслительную функцию использовать (по умолчанию это функция <code>[[Think()]]</code>, однако она у нас отсутствует). Отметьте наличие знака & и пробелов около каждой скобки в аргументе. Затем мы сообщаем движку, что наш объект будет перемещаться по миру летая, хотя это скорее не полёт а "плавание", т.к. в движке Source отсутствует эмитация полёта (возможно вы сможете её добавить?). И, конечно, в конце мы устанавливаем <code>m_bActive</code> в true (истина) - само по себе это значение не поменяется!


Во второй части энтити этой функции мы останавливаем энтить от движения. think-функция устанавливается в <code>NULL</code> для остановки think-процесса. Её тип движения также устанавлтвается <code>MOVETYPE_NONE</code> чтобы предотвратить движение.
После команды <code>else</code> мы останавливаем объект. Мы сбрасываем мыслительную функцию в ноль (<code>NULL</code>) чтобы остановить мыслительный процесс. Функция <code>[[SetAbsVelocity()]]</code> устанавливает объект в его [[origin|начало]] - используется вектор с нулевой скоростью. Тип передвижения устанавливается в <code>[[MOVETYPE_NONE]]</code>, чтобы предотвратить любое перемещение, команда на которое может поступить извне. И в конце <code>m_bActive</code> устанавливается в false (ложь).


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


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


Запись FGD для энтити просто отображает модель в хаммере и позволяет отправлять input "Toggle" ей.
@PointClass base(Targetname) studio("models/gibs/airboat_broken_engine.mdl")= my_model_entity :  "Tutorial model entity."
[
input Toggle(void) : "Toggle movement."
]


<pre>
== The working entity ==
@PointClass base(Targetname) studio("models/gibs/airboat_broken_engine.mdl")= my_model_entity : "Tutorial model entity."
[   
      input Toggle(void) : "Toggle movement."
]
</pre>


Если ваш .FGD не пустой, убедитесь что добавили строку @include "base.fgd", которая даст вам некоторые нужные функции Хаммера. (При полном преобразование. Мод основанный на существующем содержимом, включайте соответствующие FGD файлы; на пример для [[HL2]] мода, включите <code>halflife2.fgd</code> вместо <code>base.fgd</code>.)
[[Image:My model entity.jpg|thumb|my_model_entity в игре.]]
 
Поэксперементируйте со своим объектом. Вы можете использовать консольную команду <code>ent_fire my_model_entity toggle</code> чтобы заставить его двигаться без вмешательства со стороны системы ввода/вывода карты. Вы можете отметить несколько особенностей:
 
;Объект не сталкивается с другими физическими объектами.
:Функции семейства <code>SetAbs*</code> не контактируют с физическими объектами. Если вы хотите симулировать физические столкновения вам придётся использовать другой метод перемещения.
;Объект удаляется от меня каждый раз, когда я к нему подхожу.
:Не совсем понятно почему это проиходит. Возможно <code>CBaseAnimating</code> считает что путь объекта блокирован?
 
== Смотрите также ==
 
*[[Authoring a Model Entity/Code|Полный код статьи]]
*[[Your First Entity|Ваш первый игровой объект]]


{{otherlang:ru}}
{{otherlang:ru}}

Revision as of 05:12, 18 October 2008

Предполагается что вы прочли и полностью поняли статью Authoring a Logical Entity:ru.

В этой статье мы создадим объект (объект = entity), который может двигаться, сталкиваться с другими объектами, и который отображается в игре (в виде модели). Мы заставим наш объект случайным образом перемещаться по миру.

Создайте файл sdk_modelentity.cpp в вашей персональной папке в серверном проекте и мы начнем.

Включение заголовочного файла и описание класса

Сейчас вы должны понимать следующий код:

#include "cbase.h"

class CMyModelEntity : public CBaseAnimating
{
public:
	DECLARE_CLASS( CMyModelEntity, CBaseAnimating );
	DECLARE_DATADESC();

	CMyModelEntity()
	{
		m_bActive = false;
	}

	void Spawn( void );
	void Precache( void );

	void MoveThink( void );

	// Входная функция
	void InputToggle( inputdata_t &inputData );

private:

	bool	m_bActive;
	float	m_flNextChangeTime;
};

Заметьте что на этот раз мы наследуем наш класс от CBaseAnimating, и на этот раз у нас появилось несколько новых функций. В закрытой (private) части класса содержатся две переменные: bool - это тип переменной которая может принимать только значения правда/ложь (true/false) (1/0), а переменные типа float могут хранить в себе числовые значения с десятичной точкой.

Note.pngПримечание:Наш объект не начнёт двигаться пока не получит соответствующую команду от InputToggle(). Заставить объект двигаться можно только вызовом функции InputToggle() (а не простой установкой значения m_bActive в единицу) - это станет неплохой практикой после того как вы прочтёте статью.

Имя объекта и таблица описания данных

LINK_ENTITY_TO_CLASS( my_model_entity, CMyModelEntity );

// Начало описания данных класса.
BEGIN_DATADESC( CMyModelEntity )
	
	// Сохранение/загрузка состояния объекта.
	DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flNextChangeTime, FIELD_TIME ),

	// Присвоение нашей входной функции имя в Hammer'e.
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),

	// Объявление "мыслительной" (think) функции.
	DEFINE_THINKFUNC( MoveThink ),

END_DATADESC()

Самое большое отличие от созданного нами ранее логического объекта в наличии макроса DEFINE_THINKFUNC, который даёт движку Source знать о, так называемой "мыслительной" функции.

Определение модели

// Имя модели нашего объекта.
#define	ENTITY_MODEL	"models/gibs/airboat_broken_engine.mdl"

Этот код жёстко задаёт модель, которую наш объект будет отображать в игре. Это статический кусок информации - не переменная: это значение не может меняться после компиляции кода. Путь к .mdl-файлу задаётся относительно дирректории игры (например hl2/).

Note.pngПримечание:Мы создали эту строку только для того чтобы упростить поиск и замену этого значения в будущем. Она не влияет на работу нашего кода.

Функция Precache()

Мы добрались до первой функции. Функция Precache() вызывается при появлении объекта на карте и гарантирует загрузку всего, что в ней перечислено до того как появится игрок (т.е. до того как он увидит мир и получит контроль над игрой). Смотрите рекомендации по прекэшированию для получения дополнительной информации.

Прекэширование получило отдельное имя потому что существуют другие типы "асинхронной" загрузки, которые могут выполняться после появления игрока.

//-----------------------------------------------------------------------------
// Назначение: Прекэширование необходимых объекту ресурсов.
//-----------------------------------------------------------------------------
void CMyModelEntity::Precache( void )
{
	PrecacheModel( ENTITY_MODEL );

	BaseClass::Precache();
}

Для данного объекта мы прекэшируем модель, которую будем использовать, а затем вызываем функцию прекэширования нашего базового класса (которая в случае класса CBaseAnimating обеспечивает загрузку системы частиц для огня, т.к. каждый объект с моделью в игре может загореться). Другие команды прекэширования: PrecacheParticleSystem() и PrecacheScriptSound()

Функция Spawn()

Функция Valve Spawn() вызывается всякий раз когда создаётся новый экземпляр объекта, примерно так же, как и конструктор. Вообще можно использовать функцию Spawn() вместо конструктора, но из соображений управляемости рекомендуется использовать и конструктор чтобы отделять инициализацию переменных от остального кода.

//-----------------------------------------------------------------------------
// Назначение: Настраивает начальное состояние объекта.
//-----------------------------------------------------------------------------
void CMyModelEntity::Spawn( void )
{
	Precache();

	SetModel( ENTITY_MODEL );
	SetSolid( SOLID_BBOX );
	UTIL_SetSize( this, -Vector(20,20,20), Vector(20,20,20) );
}

Сначала мы вызываем функцию Precache(), а затем идут вызовы различных функций класса CBaseAnimating. Функция SetModel() задаёт модель объекта. В скобках ей передаётся определённый нами ранее путь к модели объекта. Функция SetSolid() требует более подробных разъяснений. Она определяет форму, которую наш объект будет использовать для проверки на столкновение с другими объектами. Использование формы самой модели было бы очень, очень дорогим по отношению к ресурсам компьютера, поэтому движок Source предлагает несколько компромисов:

SOLID_NONE
"Прозрачный" (не твердый) объект.
SOLID_BBOX
Используется ориентированный по осям ограничивающий короб.
SOLID_BSP
Используется BSP-дерево для определения твёрдости (используется в брашах).
SOLID_CUSTOM
Объект использует свои собственные функции для проверки на столкновения.
SOLID_VPHYSICS
Для определения точных столкновений используется встроенная в модель модель столкновений.

Эти пункты выбираются на уровне движка и авторы модов не могут добавлять или менять их. Мы используем параметр SOLID_BBOX, который генерирует "ограничивающий короб", размер которого движок определяет так, чтобы он охватывал всю модель. Более "дорогой" параметр SOLID_VPHYSICS, который использует встроенную в модель модель столкновений, не поддерживает низкоуровневые функции перемещения, которые мы будем использовать для нашего объекта, поэтому мы его не применяем.

Вызов UTIL_SetSize() нужен для того, чтобы сделать наш ограничивающий короб кубическим. Это сделанно потому, что каким бы странным это не казалось, ограничивающий короб не может вращаться. Вам понадобится vphysics-обработка столкновений если вы захотите заставить модель вращаться. А по описанным выше причинам мы её не используем.

Warning.pngПредупреждение:Spawn() вызывается незамедлительно после создания объекта. Если это случится сразу же после создания карты нет гарантии того, что на карте уже появились другие объекты. Поэтому любой код, который предполагает что объект будет связываться с другими игровыми объектами становится ненадежным. В таких случаях используйте функцию Activate(), которая всегда вызывается после появления всех объектов.

Функция MoveThink()

"Мыслительная" функция (to think - думать) позволяет объекту "принимать решения" без побуждения со стороны внешнего кода. Вспоните про наш логический объект - он делал что-нибудь только при задействовании его входной функции; это неподходит для объектов, которые должны определенным образом перемещаться по миру. Мыслительная функция, если она присутствует, обычно является центральной функцией в коде объекта.

Сейчас мы создаём мыслительную функцию, которая будет вызываться движком с периодичностью 20 раз в секунду. Это может показаться большим числом, однако современные процессоры очень быстрые, а наш объект очень простой. Вы сможете добавить очень большое кол-во объектов CMyModelEntity на карту без большой потери производительности.

//-----------------------------------------------------------------------------
// Назначение: Мыслительная функция для случайного перемещения объекта по карте.
//-----------------------------------------------------------------------------
void CMyModelEntity::MoveThink( void )
{
	// Смотрим, должны ли мы снова поменять направление.
	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 );

	// Запускать эту функцию с частотой 20Hz
	SetNextThink( gpGlobals->curtime + 0.05f );
}

Несмотря на то, что в этой функции довольно много кода, она довольно простая. Когда случайный интервал времени подходит к концу объект выбирает новые случайные направление и скорость для продолжения движения. Также меняются углы положения объекта в пространстве, чтобы они соответствовали новому направлению движения. Это происходит для трех измерений.

Дополнительная информация:

  • gpGlobals->curtime возвращает время, в которое исполняется текущий код, как значение с плавающей точкой.
  • Вектор - это набор переменных, используемых для определения движения, которые содержат в себе данные о направлении и скорости. Фунция SetAbsVelocity() задаёт абсолютную скорость,
  • QAngle это просто угол - т.е. то же самое что вектор только без данных о скорости. QAngle задаёт направление.
  • Функция VectorAngles() преобразует вектор (velFacing) в угол (angFacing). Не забывайте, что С++ очень строг в отношении типов: вам понядобятся утилитарные функции, такие как VectorAngles(), чтобы осуществлять преобразования между двумя типами.

В конце мы вызываем функцию SetNextThink(), которая указывает когда в следующий раз необходимо вызвать мыслительную функцию. Здесь мы задаём новую обработку через 0.05 секунд (1/20 часть), однако это число может быть различным для разных объектов. Важно понимать, что если не вызвать функцию SetNextThink() объект перестанет "мыслить".

Вы могли отметить, что здесь мы определяли новые переменные. Так же как и переменные, определенные внутри класса, переменные, определенные внутри функции, принадлежат данной функции. Они создаются каждый раз когда вызывается функция и уничтожаются по её завершении.

Tip.pngСовет:На самом деле нам не очень то и нужна переменная vecNewVelocity. Попробуйте передать значение в функцию SetAbsVelocity() без создания лишней переменной. Вспомните почему мы добавяем void перед всеми нашими функциями.

InputToggle()

Мы добрались до нашей последней функции. Это входая функция, которая будет включать и выключать передвижение объекта.

//-----------------------------------------------------------------------------
// Назначение: Включать/выключать движение объекта.
//-----------------------------------------------------------------------------
void CMyModelEntity::InputToggle( inputdata_t &inputData )
{
	// Переключение состояния активности.
	if ( !m_bActive )
	{
		// Начало мыслительного процесса.
		SetThink( &CMyModelEntity::MoveThink );

		SetNextThink( gpGlobals->curtime + 0.05f );
		
		// Начать летать.
		SetMoveType( MOVETYPE_FLY );

		// Установка времени для смены направления и скорости.
		m_flNextChangeTime = gpGlobals->curtime;

		// Изменение переменной m_bActive для отображения текущего состояния.
		m_bActive = true;
	}
	else
	{
		// Перестать мыслить.
		SetThink( NULL );
		
		// Перестать двигаться.
		SetAbsVelocity( vec3_origin );
 		SetMoveType( MOVETYPE_NONE );
		
		m_bActive = false;
	}
}

Здесь всё очень прямолинейно. Мы используем оператор if чтобы определить текущее состояние флага m_bActive. Знак восклицания означает "не": "Если m_bActive не равно true (истина), то сделать следующее...". Позже мы используем оператор else чтобы определить что мы будем делать в противоположном случае.

При активировании объекта мы начинаем его мыслительный процесс указывая движку Source какую мыслительную функцию использовать (по умолчанию это функция Think(), однако она у нас отсутствует). Отметьте наличие знака & и пробелов около каждой скобки в аргументе. Затем мы сообщаем движку, что наш объект будет перемещаться по миру летая, хотя это скорее не полёт а "плавание", т.к. в движке Source отсутствует эмитация полёта (возможно вы сможете её добавить?). И, конечно, в конце мы устанавливаем m_bActive в true (истина) - само по себе это значение не поменяется!

После команды else мы останавливаем объект. Мы сбрасываем мыслительную функцию в ноль (NULL) чтобы остановить мыслительный процесс. Функция SetAbsVelocity() устанавливает объект в его начало - используется вектор с нулевой скоростью. Тип передвижения устанавливается в MOVETYPE_NONE, чтобы предотвратить любое перемещение, команда на которое может поступить извне. И в конце m_bActive устанавливается в false (ложь).

Запись FGD

Данная FGD запись позволяет Hammer'у отображать модель и позволяет вам задать имя объекта и послать ему входное сообщение "Toggle".

@PointClass base(Targetname) studio("models/gibs/airboat_broken_engine.mdl")= my_model_entity :  "Tutorial model entity."
[
	input Toggle(void) : "Toggle movement."
]

The working entity

my_model_entity в игре.

Поэксперементируйте со своим объектом. Вы можете использовать консольную команду ent_fire my_model_entity toggle чтобы заставить его двигаться без вмешательства со стороны системы ввода/вывода карты. Вы можете отметить несколько особенностей:

Объект не сталкивается с другими физическими объектами.
Функции семейства SetAbs* не контактируют с физическими объектами. Если вы хотите симулировать физические столкновения вам придётся использовать другой метод перемещения.
Объект удаляется от меня каждый раз, когда я к нему подхожу.
Не совсем понятно почему это проиходит. Возможно CBaseAnimating считает что путь объекта блокирован?

Смотрите также

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