Ru/Authoring a Logical Entity: Difference between revisions
TomEdwards (talk | contribs) (ru templates) |
(update/correcting) |
||
Line 1: | Line 1: | ||
{{otherlang2|en=Authoring a Logical Entity}} | {{DISPLAYTITLE:Создание Логической Энтити}}{{otherlang2 | ||
|en=Authoring a Logical Entity}} | |||
Логические | Логические энтити, это самые простые энтити, потому что у них нет расположения в мире, нет визуального компонента, и они используются для работы с другими энтити через [[input]]. Для примера, <code>[[math_counter]]</code> хранит данные, и может их добавлять или вычетать; другие энтити на карте могут изменить данные через inputs или получать информацию через output. | ||
В | В этом уроке мы создадим логическую энтити, которая выполняет простую задачу хранения и увеличения значения всякий раз, когда приходит команда через input. Как только счетчик достигает определенного значения, энтити сообщит об этом через output. | ||
== Создание исходного файла == | == Создание исходного файла == | ||
Это руководство предполагает, что вы используете Visual Studio или Visual C++ Express. См. [[Compiler Choices:ru|Выбор Компилятора]]. | |||
[[Image:AddNewItem.png|center|Добавление нового .cpp | [[Image:AddNewItem.png|center|Добавление нового .cpp файла в личную директорию.]] | ||
Добавьте в проект <code>Server</code> | Добавьте <code>'''New Filter'''</code> в проект <code>Server</code> и назовите ее по своему усмотрению. Затем добавьте <code>'''New Item'''</code> - .cpp файл и назовите его '''sdk_logicalentity'''. Этот файл будет содержать весь код, упомянутый в этом руководстве. | ||
Давание каждой энтити собственного .cpp сокращает нагрузку, поскольку это позволяет компилятору разделять ваш код на более дискретные сегменты, и увеличивает скорость компиляции, поскольку требуется компилировать только измененные .cpp файлы. | |||
== Заголовочные | == Заголовочные Файлы == | ||
Чтобы | Каждый .cpp файл запускается на своей собственной, изолированной от остальной части проекта. Чтобы обойти это, мы можем включать "заголовочные файлы", которые объявляют компилятору, какие части проекта будут использованы. В нашем случае, очень простая энтити требует только одного файла: | ||
<span style="color:blue;">#include</span> <span style="color:brown;">"cbase.h"</span> | <span style="color:blue;">#include</span> <span style="color:brown;">"cbase.h"</span> | ||
<code>cbase.h</code> предоставляет доступ к основным наборам команд Valve для создания энтитей, и каждый написанный .cpp для Source должен его включать. Буква "c" является частью модели названий, используемых во всех Исходниках, и это означает, что код выполняется на стороне сервера (на стороне клиента будет "c_"). | |||
== Объявление классов == | |||
''Этот раздел довольно большой, но по окончанию, вы будите понимать, как работает C++.'' | |||
Почти вся обработка выполняется "объектами" (Objects). Объекты создаются, когда программа выполняется по шаблонам, называемыми "классами" (Classes). Создание энтити означает создание нового класса. | |||
Объекты создаются по шаблонам, | |||
<span style="color:blue;">class</span> CMyLogicalEntity : <span style="color:blue;">public</span> CLogicalEntity | <span style="color:blue;">class</span> CMyLogicalEntity : <span style="color:blue;">public</span> CLogicalEntity | ||
Line 38: | Line 34: | ||
<span style="color:blue;">public</span>: | <span style="color:blue;">public</span>: | ||
DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity ); | DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity ); | ||
... | ... <span style="color:green;">// Не печатайте эту строку!</span> | ||
}; | }; | ||
Это "объявление класса" (class declaration), в котором мы рассказываем C++ компилятору, какие объекты будут хранится в данном классе, и какие функции будут выполнятся. | |||
В первой строке кода командой class | В первой строке кода, мы начали с того, что объявили класс командой <code>class</code>. Затем дали ему имя <code>CMyLogicalEntity</code>, а затем, двоеточие говорит компилятору, что он "унаследовал" это от Valve класса <code>[[CLogicalEntity]]</code> (который доступен благодаря нашему #include <code>cbase.h</code>). Модификатор доступа <code>public</code> говорит, что открытые (и защищенные) члены родительского класса остаются открытыми (защищенными) и в дочернем классе. | ||
Наследование означает | [[Wikipedia:Inheritance (computer science)|Наследование]] означает использование существующего класса, а не создание своего собственного с нуля. <code>CLogicalEntity</code> сама по себе довольно проста, но это ''огромная'' часть кода в классах, наследуемая в свою очередь от <code>[[CBaseEntity]]</code> — не имя возможности наследовать все это, любой, пытающийся создать новую энтити, просто сойдет с ума. | ||
После написание команд класса, мы открываем фигурные скобки. Написание кода внутри скобок означает, что вы его группируете; в данном случае, группа это текущий класс. (Заметьте, что здесь нет точки с запятой, которая означает конец строки. Технически, мы продолжаем пишем ту же строку кода.) | |||
Далее идет "<code>public:</code>", который означает что следующие за ним "члены" класса доступны из других классов (мы вернемся к private объявлению позже). Вы должны делать члены публичными, только когда это строго необходимо: Этот принцип называется [[Wikipedia:Encapsulation (computer science)|Инкапсуляцией]] и он не является уникальным для C++. | |||
=== Первая команда === | === Первая команда === | ||
Теперь у нас есть каркас | Теперь, когда у нас есть каркас, мы может начать вводить команды. <code>DECLARE_CLASS</code> это '[[macro|макрос]]', созданный Valve для автоматизации большей части требуемого кода, необходимого для объявления новой энтити: при компиляции кода, макрос заменяется на различные функции и переменные. | ||
В круглых скобках мы | В круглых скобках мы передаем макросу "аргументы" (arguments), или "параметры" (parameters). Они передают информацию, необходимую для правильной работы. Вы, возможно, заметили, что мы посылаем ту же ключевую информацию, как в первой строке нашего объявления, но разделённую запятой. | ||
Линия, закрываемая точкой с запетой, говорит компилятору, что это конец команды, и вы собираетесь начать еще одну. Это необходимо, т.к. строки и пробелы игнорируются - компилятор видит все как непрерывный поток символов. | |||
=== | === Закрытие объявления (досрочно) === | ||
Не | Не вводите <code>...</code> это использовались, чтобы показать вам, куда пишется остальная часть кода объявления. Мы забежим немного вперед, и закроем команду <code>class</code> с помощью <code>};</code>. Это достаточно просто: закрывающая скобка противоположна открывающей скобки, которую мы использовали ранее, а точка с запятой выполняет ту же функцию, что и после <code>DECLARE_CLASS</code>. | ||
{{note:ru|Обычно | {{note:ru|Обычно, вам не нужны обе <code>}</code> и <code>;</code>, как это показано ранее. Команда <code>class</code> это особый случай.}} | ||
=== | === Объявление DATADESC, конструктор и функции === | ||
Теперь | Теперь вернемся обратно к многоточию, и добавим новые строки: | ||
DECLARE_DATADESC(); | DECLARE_DATADESC(); | ||
Line 77: | Line 73: | ||
} | } | ||
<span style="color:green;">// | <span style="color:green;">// Input функция</span> | ||
<span style="color:blue;">void</span> InputTick( inputdata_t &inputData ); | <span style="color:blue;">void</span> InputTick( inputdata_t &inputData ); | ||
Первая строка это | Первая строка, это другой макрос, который автоматизирует процесс объявления [[DATADESC]], таблицу мы добавим позже. Вторая строка, это комментарий (<code>//</code>), который игнорируется компилятором, но помогает людям читать ваш код (включая вас, когда вы вернетесь к нему через длительный промежуток времени). | ||
Третья строка это "конструктор" | Третья строка, это "конструктор". Это тип "функции": команда или несколько команд между <code>{</code> и <code>}</code>, которые "вызываются", или "выполняются" в одном потоке. Из-за того, что название такое же, как и у нашего класса, C++ знает, что она вызывается при каждом создании экземпляра класса. В этом примере мы используем его для "инициализации" m_nCounter нулем. Переменные (см. следующий подзаголовок) не имеют значения по умолчанию, и не обеспечивают их друг перед использованием, это может быть результатом странных и плохих результатов! | ||
Мы только что полностью "определили" конструктор, | Мы только что полностью "определили" конструктор, но большинство функций включая конструкторы для более объёмных классов) слишком большие, чтобы полностью определять их внутри классов. Поэтому мы объявляем и описываем их позже, в отдельном .cpp файле. | ||
Таким образом, последняя строка в этом коде является объявлением функции, которая будет вызываться при правильном сообщении на input, и, если вы помните нашу первоначальную цель - увеличит нашу переменную на 1. <code>void</code> означает, что функция не "возвращает" никаких данных в вызывающую функцию, так как вывод данных будет идти другим путём (конструктор это частный случай, которому никогда не требуется возвращать значение, поэтому тип конструктора всегда void). | |||
Мы называем эту функцию <code>InputTick</code> и в круглых скобках определяем требуемые "параметры". В нашем случае, это переменная <code>inputdata_t</code>, называемая <code>&inputData</code>. Это информация об input событиях, которые автоматически генерируются движком во время выполнения, включая I/O аргументы из карты (здесь отсутствуют), и энтити, от которого было получено сообщение. | |||
<code>&</code> означает, что вместо передачи всей информации о событии, мы передаем ''запись местонахождения <code>inputdata_t</code> например, в памяти системы''. Этот "указатель" позволяет нам получать доступ к необходимой информации без ее копирования в нашу текущую функцию. Это похоже на открытие файла через ярлык на рабочем столе, вместо прямого перемещения или копирования. | |||
<code>{}</code> для <code>InputTick()</code> отсутствуют: они появятся позже, когда мы напишем содержимое функции. Вместо них, необходимо использовать точку с запятой, чтобы обозначить конец команды. | |||
=== | === Private объявления === | ||
Сейчас мы объявив private члены. Они не будут доступны за пределами этого класса, как если бы мы делали public функцию (чего мы делать не будем). | |||
<span style="color:blue;">private</span>: | <span style="color:blue;">private</span>: | ||
<span style="color:blue;">int</span> m_nThreshold; <span style="color:green;">// Пороговое значение, по | <span style="color:blue;">int</span> m_nThreshold; <span style="color:green;">// Пороговое значение, по достижению которого активируется output</span> | ||
<span style="color:blue;">int</span> m_nCounter; <span style="color:green;">// Внутренний счётчик</span> | <span style="color:blue;">int</span> m_nCounter; <span style="color:green;">// Внутренний счётчик</span> | ||
COutputEvent m_OnThreshold; <span style="color:green;">// | COutputEvent m_OnThreshold; <span style="color:green;">// Output событие, когда счётчик достигает порогового значения</span> | ||
Первые две из них являются "переменными". Это "сосуды" в физической памяти, которые могут содержать значения, использоваться в вычислениях, записываться на диск, и обнуляться. Это "[[integer:ru|целое число]] (integer)" или просто "int" переменные, которые могут использоваться для хранения целых чисел, таких как -1, 0, 1, 2, 3, и т.д. Они не могут хранить слова или символы, ни числа с десятичной точкой. В C++ очень строго с такими вещами! | |||
<code> | <code>m_OnThreshold</code> является экземпляром <code>COutputEvent</code> класса, который Valve уже написал для вас в <code>cbase.h</code>. Вы будете использовать его в <code>InputTick</code> для передачи значения через I/O систему, когда будут выполнены все условия. На самом деле, это почти такая же переменная, как и <code>int</code>, но пользовательский контент не поставляется с компилятором. Единственной причиной, по которой этот тип не выделяется синим цветом, является то что Visual Studio не опознаёт его. | ||
Пару слов об именах в | Пару слов об именах в этом разделе. <code>m_</code> означает, что объекты, это члены текущего класса, когда <code>n</code> означает, что это числовое значение (Так же можно использовать <code>i</code> для 'integer'). Вы ''не должны'' следовать этим наименованиям, но они часто встречаются. | ||
=== | === Проверка === | ||
Это объявление завершено. Сверьтесь с [[Logical Entity Code:ru|эталонным кодом]]. Если он такой же, вы готовый уйти за <code>};</code> и добавить код в тело энтити. | |||
== Связывание класса с | == Связывание класса с энтити названием == | ||
Мы закончили | Мы закончили с объявлением класса. В Visual Studio, вы можете свернуть весь блок, нажатием на второй знак минуса на полях. Теперь, после <code>};</code>, введите это: | ||
Теперь после <code>};</code> | |||
LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity ); | LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity ); | ||
Это | Это другой макрос (об этом говорят заглавные буквы), связывающий C++ класс <code>CMyLogicalEntity</code> с движком Source [[classname]] my_logical_entity. Названия классов используются [[I/O]] системой в [[Hammer]], и [[Create()|иногда программистами]]. | ||
{{note:ru|Обычно, вам придется заключать my_logical_entity в кавычки, делая его типом [[string]], т.к. это не 'defined'; компилятор не поймет, что он означает, и выдаст ошибку. Этот макрос заключит его в кавычки для вас, но как вы увидите в следующем разделе, он используется не везде!}} | |||
== Таблица описания данных == | == Таблица описания данных == | ||
[[ | [[Data Descriptions|Таблица описания данных ]] это серия макросов, которые дают Source [[Wikipedia:Metadata|метаданные]] о членах класса, которые вы упомянули в нем. Комментарии в коде служат объяснением, что делает каждая из функций: <code>DEFINE_FIELD</code> гарантирует, что значение <code>m_nCounter</code> в сохраненной игре не обнулится до нуля при следующей загрузке. <code>DEFINE_KEYFIELD</code> делает такую же работу, а так же позволяет редактирование значения через Hammer. | ||
Этот код имеет | Этот код имеет примеры строковых значений, упомянутых в предыдущем разделе. В отличии от других команд, это простые данные, которые компилятор принимает, хранит, и слепо выполняет в движке. Между кавычками вы можете написать все, что угодно, код будет компилироваться, но когда дело дойдет до его выполнения, движок Source будет сбит с толку если значение окажется недопустимым. | ||
<span style="color:green;">// Начало | <span style="color:green;">// Начало описания наших данных для класса</span> | ||
BEGIN_DATADESC( CMyLogicalEntity ) | BEGIN_DATADESC( CMyLogicalEntity ) | ||
<span style="color:green;">// Для сохранения/загрузки | <span style="color:green;">// Для сохранения/загрузки</span> | ||
DEFINE_FIELD( m_nCounter, FIELD_INTEGER ), | DEFINE_FIELD( m_nCounter, FIELD_INTEGER ), | ||
<span style="color:green;">// Связывает | <span style="color:green;">// Связывает наши переменные с ключевыми значениями из Hammer</span> | ||
DEFINE_KEYFIELD( m_nThreshold, FIELD_INTEGER, <span style="color:brown;">"threshold"</span> ), | DEFINE_KEYFIELD( m_nThreshold, FIELD_INTEGER, <span style="color:brown;">"threshold"</span> ), | ||
<span style="color:green;">// | <span style="color:green;">// Связывает наше input название из Hammer с нашей input функцией</span> | ||
DEFINE_INPUTFUNC( FIELD_VOID, <span style="color:brown;">"Tick"</span>, InputTick ), | DEFINE_INPUTFUNC( FIELD_VOID, <span style="color:brown;">"Tick"</span>, InputTick ), | ||
<span style="color:green;">// | <span style="color:green;">// Связывает наш ouput с output названием, используемым Hammer</span> | ||
DEFINE_OUTPUT( m_OnThreshold, <span style="color:brown;">"OnThreshold"</span> ), | DEFINE_OUTPUT( m_OnThreshold, <span style="color:brown;">"OnThreshold"</span> ), | ||
END_DATADESC() | END_DATADESC() | ||
Заметьте, что наш | Заметьте, что наш input осуществляется через функцию, а наш output является лишь переменной <code>COutputEvent</code>, которую мы объявили ранее. Хотя input требует обработки, и, следовательно, функции, output будет уже обработан к моменту использования | ||
При написании собственных таблиц описания данных, не забудьте использовать запятую после каждой <code>DEFINE_*</code> команды. | |||
{{note:ru| | {{note:ru|Здесь нет запятых. Это весьма необычно!}} | ||
== Создание | == Создание input функции == | ||
Теперь мы " | Теперь мы намерены "определить" (define) нашу input функцию. Это последний шаг в C++. Обратите внимание, что первая строка идентична той, что мы ввели в объявлении класса, за исключением добавления <code>CMyLogicalEntity:</code>. Это гарантирует связь с нужным классом. В этом случае имеется только один класс в .cpp, но их может быть больше. | ||
<span style="color:green;">//----------------------------------------------------------------------------- | <span style="color:green;">//----------------------------------------------------------------------------- | ||
// Назначение: | // Назначение: Обработка входящих значений (tick input) от других энтити | ||
//-----------------------------------------------------------------------------</span> | //-----------------------------------------------------------------------------</span> | ||
<span style="color:blue;">void</span> CMyLogicalEntity::InputTick( inputdata_t &inputData ) | <span style="color:blue;">void</span> CMyLogicalEntity::InputTick( inputdata_t &inputData ) | ||
{ | { | ||
<span style="color:green;">// Увеличение нашего счетчика | <span style="color:green;">// Увеличение нашего счетчика</span> | ||
m_nCounter++; | m_nCounter++; | ||
<span style="color:green;">// Проверяем достиг ли счетчик порогового значения | <span style="color:green;">// Проверяем, достиг ли счетчик порогового значения</span> | ||
<span style="color:blue;">if</span> ( m_nCounter >= m_nThreshold ) | <span style="color:blue;">if</span> ( m_nCounter >= m_nThreshold ) | ||
{ | { | ||
<span style="color:green;">// Запуск события | <span style="color:green;">// Запуск output события</span> | ||
m_OnThreshold.FireOutput( inputData.pActivator, <span style="color:blue;">this</span> ); | m_OnThreshold.FireOutput( inputData.pActivator, <span style="color:blue;">this</span> ); | ||
<span style="color:green;">// | <span style="color:green;">// Сбрасывание нашего счетчика</span> | ||
m_nCounter = 0; | m_nCounter = 0; | ||
} | } | ||
} | } | ||
Вот что происходит | Вот что происходит внутри функции: | ||
;<code>m_nCounter++</code> | ;<code>m_nCounter++</code> | ||
: | :C++ сокращенная запись "увеличить на единицу". Полностью, это будет <code>m_nCounter = m_nCounter + 1;</code>. Другой способ написания - <code>m_nCounter += 1;</code>. | ||
;<code>if ( m_nCounter >= m_nThreshold )</code> | ;<code>if ( m_nCounter >= m_nThreshold )</code> | ||
: | :<code>if</code> определяет выполнены ли условия. <code>></code> означает больше, так что в сочетании с <code>=</code> мы имеем "оператор" "больше или равно". Если значение m_nCounter больше, или равно значению m_nThreshold, выполняется все, что внутри команды. | ||
;<code>{ | ;<code>{ }</code> | ||
:Это "вложенная" в функцию пара фигурных скобок. Она | :Это "вложенная" в функцию пара фигурных скобок. Она группирует все команды, выполняемые оператором <code>if</code> при выполнении всех условий. Там может быть много уровней вложенности, но будьте внимательны, чтобы отслеживать их все! | ||
;<code>m_OnThreshold.FireOutput</code> | ;<code>m_OnThreshold.FireOutput</code> | ||
: | :Возможно, вы просто скопировали код. Удалите его и напишите его полностью, и отметьте, что произойдет, когда вы достигните точки. Вы вызовите общие функции <code>m_OnThreshold</code> класса <code>COutputEvent</code>, и Visual Studio поможет вам предоставлением списка возможных вариантов. Продолжайте писать, чтобы отфильтровать нужное, или используйте стрелки вверх/вниз. | ||
:Если бы мы вызывали набираемую нами функцию из другого класса, | :Если бы мы вызывали набираемую нами функцию из другого класса, нам пришлось бы написать <code>MyLogicalEntity1.InputTick(<аргументы>)</code>, где <code>MyLogicalEntity1</code>, это название нужного нам экземпляра класса. (Это означает, что мы должны иметь указатель на экземпляр класса - мы не можем просто получить доступ к ''шаблону''!) | ||
;<code>( inputData.pActivator, this )</code> | ;<code>( inputData.pActivator, this )</code> | ||
: | :Эти аргументы мы передаем <code>COutputEvent</code>. Они будут автоматически сгенерированны через Source, и переданы в нашу функцию через параметры, которые указаны между <code>()</code>. | ||
:Мы послали: | |||
:# | :#Энтити, которая начинает всю эту цепь (в Source жаргоне "активатор"). | ||
:# | :#Энтити, которая вызывает функцию "caller", this - это указатель на объект, которому принадлежит функция, которую мы сейчас пишем. | ||
: | :Это требуется для [[Targetname#Keywords|targetname keywords]] (ключевых слов имен цели). | ||
;<code>m_nCounter = 0</code> | ;<code>m_nCounter = 0</code> | ||
:Если | :Если на сбрасывать счетчик, то он будет продолжать расти. | ||
Поздравляем, энтити готова для компиляции - нажмите {{key|F7}}, чтобы начать процесс. Если вы получаете сообщения об ошибках от Visual Studio, сверьтесь с [[Authoring a Logical Entity/Code:ru|эталоном кода]]! | |||
Мы не | Мы не можем использовать эту энтити напрямую из движка, однако - если вы когда-либо делали карту, вы знаете, что существует много I/O информации, которой не нашлось места в нашем коде. К счастью, наша DATADESC таблица позволяет Source подключить эту энтити в более широкую систему I/O автоматически; так что, единственное, что нам предстоит сделать, это рассказать об энтити [[Hammer]]у. | ||
== | == Добавление FGD записи == | ||
[[Image:Options Game Config.png|right|150px|Диалог настроек Hammer | [[Image:Options Game Config.png|right|150px|Диалог настроек Hammer]] | ||
[[FGD]] представляет собой текстовый файл, описывающий все энтити, используемые игрой, и делает их доступными. Мир не идеал и Hammer не будет читать код мода из него же, поэтому важно держать FDG файл в актуальном состоянии. | |||
Если у вас | Если у вас еще нет FGD, сохраните пустой текстовый файл в папке вашего мода с названием <code><modname>.fgd</code>. Затем запустите [[Hammer]] и перейдите к <code>Tools > Options</code>. Убедитесь, что ваш мод стоит в активной конфигурации, нажмите на кнопку "Add" и укажите путь к вашему FGD. | ||
FGD это другая тема, поэтому он не будет подробно расписываться именно здесь. Просто вставьте следующий код в ваш .FDG файл: | |||
@include "base.fgd" | @include "base.fgd" | ||
Line 220: | Line 216: | ||
] | ] | ||
И вот оно! Ваша энтити готова для использования. Используйте [[Hammer Entity Tool]] для добавления энтити на карту (вы можете использовать одну из Valve карт-примеров, если вам сложно создать свою), и настройте [[Inputs and Outputs|Inputs и Outputs]]. | |||
== | == См. Также == | ||
*[[Authoring a Logical Entity/Code| | *[[Authoring a Logical Entity/Code:ru|Полный код]] | ||
*[[Your First Entity| | *[[Your First Entity:ru|Ваша Первая Энтити]] | ||
*[[FGD| | *[[FGD|FGD файлы]] | ||
[[Category:Programming:ru]] | |||
[[Category:Tutorials:ru]] | |||
[[Category:Russian]] | [[Category:Russian]] | ||
Revision as of 08:53, 19 January 2010
Логические энтити, это самые простые энтити, потому что у них нет расположения в мире, нет визуального компонента, и они используются для работы с другими энтити через input. Для примера, math_counter
хранит данные, и может их добавлять или вычетать; другие энтити на карте могут изменить данные через inputs или получать информацию через output.
В этом уроке мы создадим логическую энтити, которая выполняет простую задачу хранения и увеличения значения всякий раз, когда приходит команда через input. Как только счетчик достигает определенного значения, энтити сообщит об этом через output.
Создание исходного файла
Это руководство предполагает, что вы используете Visual Studio или Visual C++ Express. См. Выбор Компилятора.
Добавьте New Filter
в проект Server
и назовите ее по своему усмотрению. Затем добавьте New Item
- .cpp файл и назовите его sdk_logicalentity. Этот файл будет содержать весь код, упомянутый в этом руководстве.
Давание каждой энтити собственного .cpp сокращает нагрузку, поскольку это позволяет компилятору разделять ваш код на более дискретные сегменты, и увеличивает скорость компиляции, поскольку требуется компилировать только измененные .cpp файлы.
Заголовочные Файлы
Каждый .cpp файл запускается на своей собственной, изолированной от остальной части проекта. Чтобы обойти это, мы можем включать "заголовочные файлы", которые объявляют компилятору, какие части проекта будут использованы. В нашем случае, очень простая энтити требует только одного файла:
#include "cbase.h"
cbase.h
предоставляет доступ к основным наборам команд Valve для создания энтитей, и каждый написанный .cpp для Source должен его включать. Буква "c" является частью модели названий, используемых во всех Исходниках, и это означает, что код выполняется на стороне сервера (на стороне клиента будет "c_").
Объявление классов
Этот раздел довольно большой, но по окончанию, вы будите понимать, как работает C++.
Почти вся обработка выполняется "объектами" (Objects). Объекты создаются, когда программа выполняется по шаблонам, называемыми "классами" (Classes). Создание энтити означает создание нового класса.
class CMyLogicalEntity : public CLogicalEntity { public: DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity ); ... // Не печатайте эту строку! };
Это "объявление класса" (class declaration), в котором мы рассказываем C++ компилятору, какие объекты будут хранится в данном классе, и какие функции будут выполнятся.
В первой строке кода, мы начали с того, что объявили класс командой class
. Затем дали ему имя CMyLogicalEntity
, а затем, двоеточие говорит компилятору, что он "унаследовал" это от Valve класса CLogicalEntity
(который доступен благодаря нашему #include cbase.h
). Модификатор доступа public
говорит, что открытые (и защищенные) члены родительского класса остаются открытыми (защищенными) и в дочернем классе.
Наследование означает использование существующего класса, а не создание своего собственного с нуля. CLogicalEntity
сама по себе довольно проста, но это огромная часть кода в классах, наследуемая в свою очередь от CBaseEntity
— не имя возможности наследовать все это, любой, пытающийся создать новую энтити, просто сойдет с ума.
После написание команд класса, мы открываем фигурные скобки. Написание кода внутри скобок означает, что вы его группируете; в данном случае, группа это текущий класс. (Заметьте, что здесь нет точки с запятой, которая означает конец строки. Технически, мы продолжаем пишем ту же строку кода.)
Далее идет "public:
", который означает что следующие за ним "члены" класса доступны из других классов (мы вернемся к private объявлению позже). Вы должны делать члены публичными, только когда это строго необходимо: Этот принцип называется Инкапсуляцией и он не является уникальным для C++.
Первая команда
Теперь, когда у нас есть каркас, мы может начать вводить команды. DECLARE_CLASS
это 'макрос', созданный Valve для автоматизации большей части требуемого кода, необходимого для объявления новой энтити: при компиляции кода, макрос заменяется на различные функции и переменные.
В круглых скобках мы передаем макросу "аргументы" (arguments), или "параметры" (parameters). Они передают информацию, необходимую для правильной работы. Вы, возможно, заметили, что мы посылаем ту же ключевую информацию, как в первой строке нашего объявления, но разделённую запятой.
Линия, закрываемая точкой с запетой, говорит компилятору, что это конец команды, и вы собираетесь начать еще одну. Это необходимо, т.к. строки и пробелы игнорируются - компилятор видит все как непрерывный поток символов.
Закрытие объявления (досрочно)
Не вводите ...
это использовались, чтобы показать вам, куда пишется остальная часть кода объявления. Мы забежим немного вперед, и закроем команду class
с помощью };
. Это достаточно просто: закрывающая скобка противоположна открывающей скобки, которую мы использовали ранее, а точка с запятой выполняет ту же функцию, что и после DECLARE_CLASS
.
Объявление DATADESC, конструктор и функции
Теперь вернемся обратно к многоточию, и добавим новые строки:
DECLARE_DATADESC(); // Конструктор CMyLogicalEntity () { m_nCounter = 0; } // Input функция void InputTick( inputdata_t &inputData );
Первая строка, это другой макрос, который автоматизирует процесс объявления DATADESC, таблицу мы добавим позже. Вторая строка, это комментарий (//
), который игнорируется компилятором, но помогает людям читать ваш код (включая вас, когда вы вернетесь к нему через длительный промежуток времени).
Третья строка, это "конструктор". Это тип "функции": команда или несколько команд между {
и }
, которые "вызываются", или "выполняются" в одном потоке. Из-за того, что название такое же, как и у нашего класса, C++ знает, что она вызывается при каждом создании экземпляра класса. В этом примере мы используем его для "инициализации" m_nCounter нулем. Переменные (см. следующий подзаголовок) не имеют значения по умолчанию, и не обеспечивают их друг перед использованием, это может быть результатом странных и плохих результатов!
Мы только что полностью "определили" конструктор, но большинство функций включая конструкторы для более объёмных классов) слишком большие, чтобы полностью определять их внутри классов. Поэтому мы объявляем и описываем их позже, в отдельном .cpp файле.
Таким образом, последняя строка в этом коде является объявлением функции, которая будет вызываться при правильном сообщении на input, и, если вы помните нашу первоначальную цель - увеличит нашу переменную на 1. void
означает, что функция не "возвращает" никаких данных в вызывающую функцию, так как вывод данных будет идти другим путём (конструктор это частный случай, которому никогда не требуется возвращать значение, поэтому тип конструктора всегда void).
Мы называем эту функцию InputTick
и в круглых скобках определяем требуемые "параметры". В нашем случае, это переменная inputdata_t
, называемая &inputData
. Это информация об input событиях, которые автоматически генерируются движком во время выполнения, включая I/O аргументы из карты (здесь отсутствуют), и энтити, от которого было получено сообщение.
&
означает, что вместо передачи всей информации о событии, мы передаем запись местонахождения inputdata_t
например, в памяти системы. Этот "указатель" позволяет нам получать доступ к необходимой информации без ее копирования в нашу текущую функцию. Это похоже на открытие файла через ярлык на рабочем столе, вместо прямого перемещения или копирования.
{}
для InputTick()
отсутствуют: они появятся позже, когда мы напишем содержимое функции. Вместо них, необходимо использовать точку с запятой, чтобы обозначить конец команды.
Private объявления
Сейчас мы объявив private члены. Они не будут доступны за пределами этого класса, как если бы мы делали public функцию (чего мы делать не будем).
private: int m_nThreshold; // Пороговое значение, по достижению которого активируется output int m_nCounter; // Внутренний счётчик COutputEvent m_OnThreshold; // Output событие, когда счётчик достигает порогового значения
Первые две из них являются "переменными". Это "сосуды" в физической памяти, которые могут содержать значения, использоваться в вычислениях, записываться на диск, и обнуляться. Это "целое число (integer)" или просто "int" переменные, которые могут использоваться для хранения целых чисел, таких как -1, 0, 1, 2, 3, и т.д. Они не могут хранить слова или символы, ни числа с десятичной точкой. В C++ очень строго с такими вещами!
m_OnThreshold
является экземпляром COutputEvent
класса, который Valve уже написал для вас в cbase.h
. Вы будете использовать его в InputTick
для передачи значения через I/O систему, когда будут выполнены все условия. На самом деле, это почти такая же переменная, как и int
, но пользовательский контент не поставляется с компилятором. Единственной причиной, по которой этот тип не выделяется синим цветом, является то что Visual Studio не опознаёт его.
Пару слов об именах в этом разделе. m_
означает, что объекты, это члены текущего класса, когда n
означает, что это числовое значение (Так же можно использовать i
для 'integer'). Вы не должны следовать этим наименованиям, но они часто встречаются.
Проверка
Это объявление завершено. Сверьтесь с эталонным кодом. Если он такой же, вы готовый уйти за };
и добавить код в тело энтити.
Связывание класса с энтити названием
Мы закончили с объявлением класса. В Visual Studio, вы можете свернуть весь блок, нажатием на второй знак минуса на полях. Теперь, после };
, введите это:
LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity );
Это другой макрос (об этом говорят заглавные буквы), связывающий C++ класс CMyLogicalEntity
с движком Source classname my_logical_entity. Названия классов используются I/O системой в Hammer, и иногда программистами.
Таблица описания данных
Таблица описания данных это серия макросов, которые дают Source метаданные о членах класса, которые вы упомянули в нем. Комментарии в коде служат объяснением, что делает каждая из функций: DEFINE_FIELD
гарантирует, что значение m_nCounter
в сохраненной игре не обнулится до нуля при следующей загрузке. DEFINE_KEYFIELD
делает такую же работу, а так же позволяет редактирование значения через Hammer.
Этот код имеет примеры строковых значений, упомянутых в предыдущем разделе. В отличии от других команд, это простые данные, которые компилятор принимает, хранит, и слепо выполняет в движке. Между кавычками вы можете написать все, что угодно, код будет компилироваться, но когда дело дойдет до его выполнения, движок Source будет сбит с толку если значение окажется недопустимым.
// Начало описания наших данных для класса BEGIN_DATADESC( CMyLogicalEntity ) // Для сохранения/загрузки DEFINE_FIELD( m_nCounter, FIELD_INTEGER ), // Связывает наши переменные с ключевыми значениями из Hammer DEFINE_KEYFIELD( m_nThreshold, FIELD_INTEGER, "threshold" ), // Связывает наше input название из Hammer с нашей input функцией DEFINE_INPUTFUNC( FIELD_VOID, "Tick", InputTick ), // Связывает наш ouput с output названием, используемым Hammer DEFINE_OUTPUT( m_OnThreshold, "OnThreshold" ), END_DATADESC()
Заметьте, что наш input осуществляется через функцию, а наш output является лишь переменной COutputEvent
, которую мы объявили ранее. Хотя input требует обработки, и, следовательно, функции, output будет уже обработан к моменту использования
При написании собственных таблиц описания данных, не забудьте использовать запятую после каждой DEFINE_*
команды.
Создание input функции
Теперь мы намерены "определить" (define) нашу input функцию. Это последний шаг в C++. Обратите внимание, что первая строка идентична той, что мы ввели в объявлении класса, за исключением добавления CMyLogicalEntity:
. Это гарантирует связь с нужным классом. В этом случае имеется только один класс в .cpp, но их может быть больше.
//----------------------------------------------------------------------------- // Назначение: Обработка входящих значений (tick input) от других энтити //----------------------------------------------------------------------------- void CMyLogicalEntity::InputTick( inputdata_t &inputData ) { // Увеличение нашего счетчика m_nCounter++; // Проверяем, достиг ли счетчик порогового значения if ( m_nCounter >= m_nThreshold ) { // Запуск output события m_OnThreshold.FireOutput( inputData.pActivator, this ); // Сбрасывание нашего счетчика m_nCounter = 0; } }
Вот что происходит внутри функции:
m_nCounter++
- C++ сокращенная запись "увеличить на единицу". Полностью, это будет
m_nCounter = m_nCounter + 1;
. Другой способ написания -m_nCounter += 1;
. if ( m_nCounter >= m_nThreshold )
if
определяет выполнены ли условия.>
означает больше, так что в сочетании с=
мы имеем "оператор" "больше или равно". Если значение m_nCounter больше, или равно значению m_nThreshold, выполняется все, что внутри команды.{ }
- Это "вложенная" в функцию пара фигурных скобок. Она группирует все команды, выполняемые оператором
if
при выполнении всех условий. Там может быть много уровней вложенности, но будьте внимательны, чтобы отслеживать их все! m_OnThreshold.FireOutput
- Возможно, вы просто скопировали код. Удалите его и напишите его полностью, и отметьте, что произойдет, когда вы достигните точки. Вы вызовите общие функции
m_OnThreshold
классаCOutputEvent
, и Visual Studio поможет вам предоставлением списка возможных вариантов. Продолжайте писать, чтобы отфильтровать нужное, или используйте стрелки вверх/вниз. - Если бы мы вызывали набираемую нами функцию из другого класса, нам пришлось бы написать
MyLogicalEntity1.InputTick(<аргументы>)
, гдеMyLogicalEntity1
, это название нужного нам экземпляра класса. (Это означает, что мы должны иметь указатель на экземпляр класса - мы не можем просто получить доступ к шаблону!) ( inputData.pActivator, this )
- Эти аргументы мы передаем
COutputEvent
. Они будут автоматически сгенерированны через Source, и переданы в нашу функцию через параметры, которые указаны между()
. - Мы послали:
- Энтити, которая начинает всю эту цепь (в Source жаргоне "активатор").
- Энтити, которая вызывает функцию "caller", this - это указатель на объект, которому принадлежит функция, которую мы сейчас пишем.
- Это требуется для targetname keywords (ключевых слов имен цели).
m_nCounter = 0
- Если на сбрасывать счетчик, то он будет продолжать расти.
Поздравляем, энтити готова для компиляции - нажмите F7, чтобы начать процесс. Если вы получаете сообщения об ошибках от Visual Studio, сверьтесь с эталоном кода!
Мы не можем использовать эту энтити напрямую из движка, однако - если вы когда-либо делали карту, вы знаете, что существует много I/O информации, которой не нашлось места в нашем коде. К счастью, наша DATADESC таблица позволяет Source подключить эту энтити в более широкую систему I/O автоматически; так что, единственное, что нам предстоит сделать, это рассказать об энтити Hammerу.
Добавление FGD записи
FGD представляет собой текстовый файл, описывающий все энтити, используемые игрой, и делает их доступными. Мир не идеал и Hammer не будет читать код мода из него же, поэтому важно держать FDG файл в актуальном состоянии.
Если у вас еще нет FGD, сохраните пустой текстовый файл в папке вашего мода с названием <modname>.fgd
. Затем запустите Hammer и перейдите к Tools > Options
. Убедитесь, что ваш мод стоит в активной конфигурации, нажмите на кнопку "Add" и укажите путь к вашему FGD.
FGD это другая тема, поэтому он не будет подробно расписываться именно здесь. Просто вставьте следующий код в ваш .FDG файл:
@include "base.fgd" @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." ]
И вот оно! Ваша энтити готова для использования. Используйте Hammer Entity Tool для добавления энтити на карту (вы можете использовать одну из Valve карт-примеров, если вам сложно создать свою), и настройте Inputs и Outputs.