Difference between revisions of "Authoring a Logical Entity:ru"

From Valve Developer Community
Jump to: navigation, search
(ru templates)
(update/correcting)
Line 1: Line 1:
{{otherlang2|en=Authoring a Logical Entity}}
+
{{DISPLAYTITLE:Создание Логической Энтити}}{{otherlang2
 +
|en=Authoring a Logical Entity}}
  
Логические объекты (объект = entity) это самые простые для создания игровые объекты т.к. они не имеют позиции в пространстве, не отображаются в игре и нужны только для того чтобы получать входные данные от других объектов. Например объект <code>[[math_counter]]</code> содержит число, к которому можно прибавлять или вычитать другое число; другие объекты на карте могут изменять значение этого числа через входы (inputs) или получать информацию от этого объекта через выход (output).
+
Логические энтити, это самые простые энтити, потому что у них нет расположения в мире, нет визуального компонента, и они используются для работы с другими энтити через [[input]]. Для примера, <code>[[math_counter]]</code> хранит данные, и может их добавлять или вычетать; другие энтити на карте могут изменить данные через inputs или получать информацию через output.
  
В этой статье мы создадим логический объект, который будет выполнять простую задачу хранения числа и увеличения его значения каждый раз когда объект будет получать на вход соответствующую команду. По достижении заданного нами значения наш объект будет генерировать вывод (output), чтобы уведомить другие объекты о достижении порогового значения счетчика.
+
В этом уроке мы создадим логическую энтити, которая выполняет простую задачу хранения и увеличения значения всякий раз, когда приходит команда через input. Как только счетчик достигает определенного значения, энтити сообщит об этом через output.
  
 
== Создание исходного файла ==
 
== Создание исходного файла ==
  
В этой статье предполагается что вы используете Visual Studio или Visual C++ Express. Смотрите статью про выбор компилятора: [[Compiler Choices:ru]].
+
Это руководство предполагает, что вы используете Visual Studio или Visual C++ Express. См. [[Compiler Choices:ru|Выбор Компилятора]].
  
[[Image:AddNewItem.png|center|Добавление нового .cpp-файла в собственную папку.]]
+
[[Image:AddNewItem.png|center|Добавление нового .cpp файла в личную директорию.]]
  
Добавьте в проект <code>Server</code> новую папку ("filter") и назовите её как вам угодно. Затем добавьте в эту папку .cpp файл и назовите его '''sdk_logicalentity'''. В этом файле будет весь код, который вы напишите при прочтении этой статьи.
+
Добавьте <code>'''New Filter'''</code> в проект <code>Server</code> и назовите ее по своему усмотрению. Затем добавьте <code>'''New Item'''</code> - .cpp файл и назовите его '''sdk_logicalentity'''. Этот файл будет содержать весь код, упомянутый в этом руководстве.
  
Хранение каждого объекта в своём собственном .cpp-файле снижает задержки, т.к. это позволяет компилятору разделять ваш код на более дискретные сегменты, и ускоряет компиляцию, т.к. только измененные .cpp файлы нуждаются в повторной компиляции.
+
Давание каждой энтити собственного .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 для создания объектов. Так как сейчас мы используем лишь простейшие команды нам нужна только одна базовая библиотека. В более сложных случаях на этом месте может быть очень много разных объявлений заголовочных файлов.
+
<code>cbase.h</code> предоставляет доступ к основным наборам команд Valve для создания энтитей, и каждый написанный .cpp для Source должен его включать. Буква "c" является частью модели названий, используемых во всех Исходниках, и это означает, что код выполняется на стороне сервера (на стороне клиента будет "c_").
  
Открывающая "c" означает, что это серверная библиотека, а закрывающее ".h" означает что это заголовочный, а не .cpp файл - но пока можете об этом не думать.
+
== Объявление классов ==
  
== Объявление класса ==
+
''Этот раздел довольно большой, но по окончанию, вы будите понимать, как работает 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>
 
  };
 
  };
  
Весь этот процесс (а не только строка <code>DECLARE_CLASS</code>, несмотря на название) называется "описание класса". Каждый экземпляр вашего объекта в игре это объект построенный по этому шаблону. Это справедливо для каждого объекта: каждый [[Combine Soldier]], который бегает в игре это независимый экземпляр соответствующего С++ класса.
+
Это "объявление класса" (class declaration), в котором мы рассказываем C++ компилятору, какие объекты будут хранится в данном классе, и какие функции будут выполнятся.
  
В первой строке кода командой class говорится о том что мы объявляем класс. Затем мы даём ему имя <code>CMyLogicalEntity</code>, а затем двоеточие говорит компилятору что мы наследуем наш класс от существующего класса Valve <code>CLogicalEntity</code> (за это можем поблагодарить включенный нами ранее файл <code>cbase.h</code>). Создаваемый нами класс называется дочерним, а класс <code>CLogicalEntity</code> - родительским. Модификатор <span style="color:blue;">public</span> говорит компилятору о том, что все открытые (и защищенные) члены родительского класса остаются открытыми (защищенными) и в дочернем классе.
+
В первой строке кода, мы начали с того, что объявили класс командой <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-членов).
+
Далее идет "<code>public:</code>", который означает что следующие за ним "члены" класса доступны из других классов (мы вернемся к private объявлению позже). Вы должны делать члены публичными, только когда это строго необходимо: Этот принцип называется [[Wikipedia:Encapsulation (computer science)|Инкапсуляцией]] и он не является уникальным для C++.
  
 
=== Первая команда ===
 
=== Первая команда ===
  
Теперь у нас есть каркас для того, чтобы добавлять в него команды. <code>DECLARE_CLASS</code> это 'макрос' созданный valve для автоматизации действий, которые необходимо проделать при объявлении нового класса.
+
Теперь, когда у нас есть каркас, мы может начать вводить команды. <code>DECLARE_CLASS</code> это '[[macro|макрос]]', созданный Valve для автоматизации большей части требуемого кода, необходимого для объявления новой энтити: при компиляции кода, макрос заменяется на различные функции и переменные.
  
В круглых скобках мы передаём макросу "аргументы", или "параметры": в продолжение нашей анологии мы говорим (в данном случае) какую книгу мы хотим добавить и к какому классу книг она относится. Как вы могли заметить мы передаем макросу ту же самую информацию, которая уже содержится в первой строке кода, но разделённую запятой.
+
В круглых скобках мы передаем макросу "аргументы" (arguments), или "параметры" (parameters). Они передают информацию, необходимую для правильной работы. Вы, возможно, заметили, что мы посылаем ту же ключевую информацию, как в первой строке нашего объявления, но разделённую запятой.
  
Макрос <code>DECLARE_CLASS</code> нужен для того чтобы когда ваш класс будет инициализироваться при запуске вашего мода, где-нибудь еще, в коде написанном Valve, для вас было выполнено изрядное кол-во невразумительной и скучной работы. Закрывающие точка с запятой говорит компилятору, что это конец команды и дальше последует уже другая команда (конец строки и пустое место в расчет не берутся - компилятор их просто пропускает).
+
Линия, закрываемая точкой с запетой, говорит компилятору, что это конец команды, и вы собираетесь начать еще одну. Это необходимо, т.к. строки и пробелы игнорируются - компилятор видит все как непрерывный поток символов.
  
=== Конец описания ===
+
=== Закрытие объявления (досрочно) ===
  
Не печатайте многоточие (<code>...</code>) - здесь оно поставлено только для того чтобы показать где будет находиться продолжение описания класса. Пропустим пока это описание, а вместо него завершим команду <code>class</code> напечатав <code>};</code>. Закрывающая скобка - это пара для открывающей скобки, напечатанной нами ранее, а точка с запятой выполняет ту же функцию, что и после команды <code>DECLARE_CLASS</code>.
+
Не вводите <code>...</code> это использовались, чтобы показать вам, куда пишется остальная часть кода объявления. Мы забежим немного вперед, и закроем команду <code>class</code> с помощью <code>};</code>. Это достаточно просто: закрывающая скобка противоположна открывающей скобки, которую мы использовали ранее, а точка с запятой выполняет ту же функцию, что и после <code>DECLARE_CLASS</code>.
  
{{note:ru|Обычно закрывающая фигурная скобка (<code>}</code>) и точка с запятой <code>;</code> не используются вместе. Команда <code>class</code> это особый случай.}}
+
{{note:ru|Обычно, вам не нужны обе <code>}</code> и <code>;</code>, как это показано ранее. Команда <code>class</code> это особый случай.}}
  
=== Описание DATADESC, конструктора и функции ===
+
=== Объявление DATADESC, конструктор и функции ===
  
Теперь вернитесь к многоточию и добавьте вместо него следующие строки:
+
Теперь вернемся обратно к многоточию, и добавим новые строки:
  
 
  DECLARE_DATADESC();
 
  DECLARE_DATADESC();
Line 77: Line 73:
 
  }
 
  }
 
   
 
   
  <span style="color:green;">// Входная функция</span>
+
  <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> говорят что вторая строка это комментарий, который игнорируется компилятором, и помогает другим людям (а так же вам самим, после того как вы вернетесь к работе после длительного перерыва) разобраться в вашем коде.
+
Первая строка, это другой макрос, который автоматизирует процесс объявления [[DATADESC]], таблицу мы добавим позже. Вторая строка, это комментарий (<code>//</code>), который игнорируется компилятором, но помогает людям читать ваш код (включая вас, когда вы вернетесь к нему через длительный промежуток времени).
  
Третья строка это "конструктор" класса. Конструктор - это разновидность функции: одна или несколько команд заключенных в фигурные скобки <code>{</code> и <code>}</code>, которые "вызываются" (или "выполняются") одним блоком. Из-за того, что конструктор имеет такое же имя, что и наш класс, компилятор знает, что эту функцию (конструктор) необходимо выполнить при создании нового экземпляра класса. В данном примере мы используем конструктор для того, чтобы "инициализировать" m_nCounter нулём. Переменные не имеют начального значения, и если не присвоить им его перед использованием, то это может привести к странным и непонятным результатам.
+
Третья строка, это "конструктор". Это тип "функции": команда или несколько команд между <code>{</code> и <code>}</code>, которые "вызываются", или "выполняются" в одном потоке. Из-за того, что название такое же, как и у нашего класса, C++ знает, что она вызывается при каждом создании экземпляра класса. В этом примере мы используем его для "инициализации" m_nCounter нулем. Переменные (см. следующий подзаголовок) не имеют значения по умолчанию, и не обеспечивают их друг перед использованием, это может быть результатом странных и плохих результатов!
  
Мы только что полностью "определили" конструктор, однако большинство функций (включая конструкторы для более объёмных классов) слишком большие, чтобы полностью определять их внутри классов, поэтому мы лишь описываем их, а определения позже записываем в отдельные .срр файлы.
+
Мы только что полностью "определили" конструктор, но большинство функций включая конструкторы для более объёмных классов) слишком большие, чтобы полностью определять их внутри классов. Поэтому мы объявляем и описываем их позже, в отдельном .cpp файле.
  
Соответственно последняя строка этого кода - это описание функции, которая будет вызвана при получении соответствующего входного параметра и, если вы всё ещё помните нашу первоначальную задачу, увеличит хранимое нами значение на единицу. <code>void</code> означает что функция не "возвращает" никаких данных в вызывающую функцию так как вывод данных будет идти другим путём (конструктор это частный случай функции, которому никогда не требуется возвращать значение, поэтому тип конструктора всегда <code>void</code>).
+
Таким образом, последняя строка в этом коде является объявлением функции, которая будет вызываться при правильном сообщении на input, и, если вы помните нашу первоначальную цель - увеличит нашу переменную на 1. <code>void</code> означает, что функция не "возвращает" никаких данных в вызывающую функцию, так как вывод данных будет идти другим путём (конструктор это частный случай, которому никогда не требуется возвращать значение, поэтому тип конструктора всегда void).
  
Назовем нашу функцию <code>InputTick</code> и в круглых скобках определим параметры, которые ей нужны - в данном случае это одно значение типа <code>inputdata_t</code>, которое называется <code>&inputData</code>; это информация о произошедшем событии, которая автоматически генерируется движком игры во время выполнения, и содержит, среди прочего, имя события, входные и выходные параметры с карты (здесь отсутствуют) и ссылку на объект от которого поступило сообщение.
+
Мы называем эту функцию <code>InputTick</code> и в круглых скобках определяем требуемые "параметры". В нашем случае, это переменная <code>inputdata_t</code>, называемая <code>&inputData</code>. Это информация об input событиях, которые автоматически генерируются движком во время выполнения, включая I/O аргументы из карты (здесь отсутствуют), и энтити, от которого было получено сообщение.
  
Знак <code>&</code> означает что мы, вместо того чтобы передавать функции абсолютно все детали о произошедшем событии, ''передаём в функцию адрес экземпляра <code>inputdata_t</code>, хранящегося в памяти''. Этот "указатель" позволяет нам обращаться к данным экземпляра не копируя его лишний раз. Это похоже на открытие файла непосредственно с чьего-нибудь компьютера вместо того чтобы сначала скопировать его на свой компьютер - только у нас нет сетевых задержек, т.к. С++ воспринимает всё как единую часть памяти произвольного доступа ([[Wikipedia:Random access memory|Random Access Memory]]).
+
<code>&</code> означает, что вместо передачи всей информации о событии, мы передаем ''запись местонахождения <code>inputdata_t</code> например, в памяти системы''. Этот "указатель" позволяет нам получать доступ к необходимой информации без ее копирования в нашу текущую функцию. Это похоже на открытие файла через ярлык на рабочем столе, вместо прямого перемещения или копирования.
  
Заметьте что в описании функции отсутствуют фигурные скобки <code>{}</code>: они появятся позже, когда мы будем писать содержимое функции. Точку с запятой необходимо использовать для того чтобы обозначить конец команды.
+
<code>{}</code> для <code>InputTick()</code> отсутствуют: они появятся позже, когда мы напишем содержимое функции. Вместо них, необходимо использовать точку с запятой, чтобы обозначить конец команды.
  
=== Описание закрытых (private) членов ===
+
=== 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>
+
  <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;">// Выходное событие когда счётчик достигает порогового значения</span>
+
  COutputEvent m_OnThreshold; <span style="color:green;">// Output событие, когда счётчик достигает порогового значения</span>
  
Первая строка это модификатор доступа - все следующие за ним описания будут закрытыми (private). Вторая и третья строка - это описания "переменных". Переменные - это некие сосуды в физической памяти компьютера, которые могут содержать в себе различные значения, которые, в свою очередь, могут использоваться в вычислениях, передаваться в функции, возможно записываться на диск или обнуляться. В нашем случае объявляется две переменные целого типа ("[[integer]]") то есть типа "int", которые могут использоваться для хранения целых чисел, таких как -1, 0, 1, 2, 3, и т.д., однако они не могут хранить слова или символы, а так же нецелочисленные значения. С++ очень строг в такого рода вопросах.
+
Первые две из них являются "переменными". Это "сосуды" в физической памяти, которые могут содержать значения, использоваться в вычислениях, записываться на диск, и обнуляться. Это "[[integer:ru|целое число]] (integer)" или просто "int" переменные, которые могут использоваться для хранения целых чисел, таких как -1, 0, 1, 2, 3, и т.д. Они не могут хранить слова или символы, ни числа с десятичной точкой. В C++ очень строго с такими вещами!
  
<code>m_OnThreshhold</code> это экземпляр <code>COutputEvent</code> - класса, который в Valve написали для вас в файле <code>cbase.h</code>. Вы будете использовать его в функции <code>InputTick</code> чтобы передать значение через систему ввода/вывода когда выполнятся соответствующие условия. Вообще <code>COutputEvent</code> это тоже тип переменной, как и <code>int</code>, но <code>COutputEvent</code> - это пользовательская переменная, которая не поставляется вместе с компилятором. Единственной причиной, по которой этот тип не выделяется синим цветом, является то что Visual Studio не опознаёт его.
+
<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>). Вы не ''обязаны'' придерживаться этих правил, но это строго рекомендуется.
+
Пару слов об именах в этом разделе. <code>m_</code> означает, что объекты, это члены текущего класса, когда <code>n</code> означает, что это числовое значение (Так же можно использовать <code>i</code> для 'integer'). Вы ''не должны'' следовать этим наименованиям, но они часто встречаются.
  
=== Итог ===
+
=== Проверка ===
  
Итак, описание класса завершено. Просмотрите то, что вы написали и убедитесь что всё соответствует началу этого [[Logical Entity Code|образца]]. Если так и есть, то вы готовы продвинуться дальше за <code>};</code> и начать писать тело нашего объекта.
+
Это объявление завершено. Сверьтесь с [[Logical Entity Code:ru|эталонным кодом]]. Если он такой же, вы готовый уйти за <code>};</code> и добавить код в тело энтити.
  
== Связывание класса с именем игрового объекта ==
+
== Связывание класса с энтити названием ==
  
Мы закончили описание класса. В Visual Studio вы можете свернуть всё это описание щёлкнув знак "-" слева от команды class.
+
Мы закончили с объявлением класса. В Visual Studio, вы можете свернуть весь блок, нажатием на второй знак минуса на полях. Теперь, после <code>};</code>, введите это:
Теперь после <code>};</code> добавьте эту строчку:
 
  
 
  LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity );
 
  LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity );
  
Это еще один макрос (об этом говорят заглавные буквы), который даёт классу "имя игрового объекта". Программный код обращается к игровым объектам не под тем же именем нежели система [[Inputs_and_Outputs:ru|ввода/вывода]] или [[Hammer]] по причинам, которые вы поймёте позже.
+
Это другой макрос (об этом говорят заглавные буквы), связывающий C++ класс <code>CMyLogicalEntity</code> с движком Source [[classname]] my_logical_entity. Названия классов используются [[I/O]] системой в [[Hammer]], и [[Create()|иногда программистами]].
 +
 
 +
{{note:ru|Обычно, вам придется заключать my_logical_entity в кавычки, делая его типом [[string]], т.к. это не 'defined'; компилятор не поймет, что он означает, и выдаст ошибку. Этот макрос заключит его в кавычки для вас, но как вы увидите в следующем разделе, он используется не везде!}}
  
 
== Таблица описания данных ==
 
== Таблица описания данных ==
  
[[Data_Descriptions:ru|Таблица описания данных]] - это макросы, которые обеспечивают движок Source [[Wikipedia:Metadata|метаданными]] о любом члене класса, который в нём содержится. Комментарии должны пояснить для чего нужен каждый макрос: например <code>DEFINE_FIELD</code> гарантирует, что значение <code>m_nCounter</code> будет храниться в сохранённой игре чтобы избежать его сбрасывания в ноль при перезагрузке игры. <code>DEFINE_KEYFIELD</code> проделывает ту же работу, а так же делает доступным редактирование этого параметра в Hammer.
+
[[Data Descriptions|Таблица описания данных ]] это серия макросов, которые дают Source [[Wikipedia:Metadata|метаданные]] о членах класса, которые вы упомянули в нем. Комментарии в коде служат объяснением, что делает каждая из функций: <code>DEFINE_FIELD</code> гарантирует, что значение <code>m_nCounter</code> в сохраненной игре не обнулится до нуля при следующей загрузке. <code>DEFINE_KEYFIELD</code> делает такую же работу, а так же позволяет редактирование значения через Hammer.
  
Этот код имеет пример коричневых "[[string|строковых]]" значений. В отличие от остальных значений они являются простыми данными, которые компилятор принимает, хранит и слепо передаёт движку. В ковычках вы можете написать всё что угодно и код будет компилироваться, но когда дело дойдёт до обращения к этой строковой переменной, движок Source будет сбит с толку если значение окажется недопустимым.
+
Этот код имеет примеры строковых значений, упомянутых в предыдущем разделе. В отличии от других команд, это простые данные, которые компилятор принимает, хранит, и слепо выполняет в движке. Между кавычками вы можете написать все, что угодно, код будет компилироваться, но когда дело дойдет до его выполнения, движок Source будет сбит с толку если значение окажется недопустимым.
  
  <span style="color:green;">// Начало нашего определения данных для класса CMyLogicalEntity.</span>
+
  <span style="color:green;">// Начало описания наших данных для класса</span>
 
  BEGIN_DATADESC( CMyLogicalEntity )
 
  BEGIN_DATADESC( CMyLogicalEntity )
 
 
 
 
  <span style="color:green;">// Для сохранения/загрузки.</span>
+
  <span style="color:green;">// Для сохранения/загрузки</span>
 
  DEFINE_FIELD( m_nCounter, FIELD_INTEGER ),
 
  DEFINE_FIELD( m_nCounter, FIELD_INTEGER ),
 
   
 
   
  <span style="color:green;">// Связывает нашу переменную с ключевым значением из Hammer'a.</span>
+
  <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;">// Даёт нашей функции ввода имя в Hammer'e</span>
+
  <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;">// Даёт нашей переменной вывода имя в Hammer</span>
+
  <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) через переменную, которую мы определили ранее.
+
Заметьте, что наш input осуществляется через функцию, а наш output является лишь переменной <code>COutputEvent</code>, которую мы объявили ранее. Хотя input требует обработки, и, следовательно, функции, output будет уже обработан к моменту использования
<!--Не могу понять смысла этой фразы; если кто-нибудь шарит, то переведите её пожалуйста - This is because, once generated, an output leaves the class it originates from immediately; any processing is done beforehand.-->
 
  
Когда вы пишите свои таблицы описания данных не забывайте ставить запятую после каждой команды <code>DEFINE_*</code>.
+
При написании собственных таблиц описания данных, не забудьте использовать запятую после каждой <code>DEFINE_*</code> команды.
  
{{note:ru|То, что здесь отсутствуют точки с запятой является довольно необычной практикой!}}
+
{{note:ru|Здесь нет запятых. Это весьма необычно!}}
  
== Создание входной функции ==
+
== Создание input функции ==
  
Теперь мы "определим" нашу входную функцию. Это последний шаг в написании С++ кода. Заметьте, что первая строка определения соответствует строке её описания в описании класса за исключением части <code>CMyLogicalEntity::</code>. Эта часть гарантирует, что мы описываем функцию нужного нам класса. В нашем случае в файле всего один класс, однако их может быть несколько.
+
Теперь мы намерены "определить" (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>
+
  <span style="color:green;">// Увеличение нашего счетчика</span>
 
  m_nCounter++;
 
  m_nCounter++;
 
   
 
   
  <span style="color:green;">// Проверяем достиг ли счетчик порогового значения.</span>
+
  <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>
+
  <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>
+
  <span style="color:green;">// Сбрасывание нашего счетчика</span>
 
  m_nCounter = 0;
 
  m_nCounter = 0;
 
  }
 
  }
 
  }
 
  }
  
Вот что происходит в этой функции:
+
Вот что происходит внутри функции:
  
 
;<code>m_nCounter++</code>
 
;<code>m_nCounter++</code>
:Это сокращенная запись выражения "увеличить на единицу". В полном варианте это выглядит так: <code>m_ncounter = m_ncounter + 1</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>m_nCounter</code> больше или равно значения <code>m_nThreshold</code>, то выполняются команды стоящие в фигурных скобках {}.
+
:<code>if</code> определяет выполнены ли условия. <code>></code> означает больше, так что в сочетании с <code>=</code> мы имеем "оператор" "больше или равно". Если значение m_nCounter больше, или равно значению m_nThreshold, выполняется все, что внутри команды.
;<code>{</code> и <code>}</code>
+
;<code>{ }</code>
:Это "вложенная" в функцию пара фигурных скобок. Она групирует команды, которые будут выполнены, когда выполнится условие оператора <code>if</code>. По мере необходимости может быть множество вложенных друг в друга фигурных скобок.
+
:Это "вложенная" в функцию пара фигурных скобок. Она группирует все команды, выполняемые оператором <code>if</code> при выполнении всех условий. Там может быть много уровней вложенности, но будьте внимательны, чтобы отслеживать их все!
 
;<code>m_OnThreshold.FireOutput</code>
 
;<code>m_OnThreshold.FireOutput</code>
:Если вы скопировали код из этой статьи и вставили его в Visual Studio, то лучше перепишите эту строку заново и отметьте что произойдёт когда вы напечатаете точку. Вам надо вызвать открытую (public) функцию экземпляра <code>m_OnThreshold</code> класса <code>COutputEvent</code> и Visual Studio помогает вам, показывая список доступных вариантов. Продолжайте печатать, пока не будет выделен нужный вам член класса, или выберите его с помощью стрелок клавиатуры и клавиши enter.
+
:Возможно, вы просто скопировали код. Удалите его и напишите его полностью, и отметьте, что произойдет, когда вы достигните точки. Вы вызовите общие функции <code>m_OnThreshold</code> класса <code>COutputEvent</code>, и Visual Studio поможет вам предоставлением списка возможных вариантов. Продолжайте писать, чтобы отфильтровать нужное, или используйте стрелки вверх/вниз.
:Если бы мы вызывали набираемую нами функцию из другого класса, то нам необходимо было бы напечатать <code>MyLogicalEntity1.InputTick(<аргументы>)</code>, где <code>MyLogicalEntity1</code> это имя нужного нам экземпляра класса. (Что означает, что нам понадобилось бы сначала создать этот экземпляр - нельзя обращаться к обычным функциям через название класса (''шаблона'')!).
+
:Если бы мы вызывали набираемую нами функцию из другого класса, нам пришлось бы написать <code>MyLogicalEntity1.InputTick(<аргументы>)</code>, где <code>MyLogicalEntity1</code>, это название нужного нам экземпляра класса. (Это означает, что мы должны иметь указатель на экземпляр класса - мы не можем просто получить доступ к ''шаблону''!)
 
;<code>( inputData.pActivator, this )</code>
 
;<code>( inputData.pActivator, this )</code>
:Это аргументы, которые мы передаём функции <code>FireOutput</code>. Они автоматически генерируются движком Source и передаются в нашу функцию через параметры, которые находятся в круглых скобках ().
+
:Эти аргументы мы передаем <code>COutputEvent</code>. Они будут автоматически сгенерированны через Source, и переданы в нашу функцию через параметры, которые указаны между <code>()</code>.
:В фукнцию <code>FireOutput</code> мы передаём:
+
:Мы послали:
:#Объект, который положил начало этой цепочке событий (на жаргоне Source это "активатор" (activator)).
+
:#Энтити, которая начинает всю эту цепь (в Source жаргоне "активатор").
:#Объект, который вызывает функцию <code>FireOutput</code> - "caller". <span style="color:blue;">this</span> - это указатель на объект, которому принадлежит функция, которую мы сейчас пишем.
+
:#Энтити, которая вызывает функцию "caller", this - это указатель на объект, которому принадлежит функция, которую мы сейчас пишем.
:Эти параметры нужны для "[[Targetname#Keywords|ключевых слов имён цели]]" (targetname keywords).
+
:Это требуется для [[Targetname#Keywords|targetname keywords]] (ключевых слов имен цели).
 
;<code>m_nCounter = 0</code>
 
;<code>m_nCounter = 0</code>
:Если мы не сбросим счётчик он будет продолжать увеличиваться выше порогового значения.
+
:Если на сбрасывать счетчик, то он будет продолжать расти.
  
Примите поздравления - объект готов к компиляции. Для её начала нажмите F7. Если вы получили уведомление об ошибке, то убедитесь, что ваш код соответствует [[Authoring a Logical Entity/Code|этому]]!
+
Поздравляем, энтити готова для компиляции - нажмите {{key|F7}}, чтобы начать процесс. Если вы получаете сообщения об ошибках от Visual Studio, сверьтесь с [[Authoring a Logical Entity/Code:ru|эталоном кода]]!
  
Мы не сможем использовать наш объект непосредственно из движка - если вы когда-нибудь создавали свою карту, то вы знаете что существует еще много информации ввода-вывода, которой не нашлось места в нашем коде. Однако наша таблица описания данных позволяет движку Source автоматически подключить наш объект к более широкой системе ввода/вывода; поэтому всё что нам осталось это дать [[Hammer]]'у знать о нашем объекте.
+
Мы не можем использовать эту энтити напрямую из движка, однако - если вы когда-либо делали карту, вы знаете, что существует много I/O информации, которой не нашлось места в нашем коде. К счастью, наша DATADESC таблица позволяет Source подключить эту энтити в более широкую систему I/O автоматически; так что, единственное, что нам предстоит сделать, это рассказать об энтити [[Hammer]]у.
  
== Создание записи в FGD-файле ==
+
== Добавление FGD записи ==
  
[[Image:Options Game Config.png|right|150px|Диалог настроек Hammer]]
+
[[Image:Options Game Config.png|right|150px|Диалог настроек Hammer]]
  
Файл [[FGD]] - это текстовый файл, который описывает какие объекты содержит ваш мод и что с ними можно делать. Мир не идеален и Hammer не умеет считывать эту информацию напрямую из кода мода, поэтому важно поддерживать FGD-файл в актуальном состоянии.
+
[[FGD]] представляет собой текстовый файл, описывающий все энтити, используемые игрой, и делает их доступными. Мир не идеал и Hammer не будет читать код мода из него же, поэтому важно держать FDG файл в актуальном состоянии.
  
Если у вас пока нет FGD-файла сохраните пустой текстовый файл в папке вашего мода и назовите его <code><имя_мода>.fgd</code>. Затем загрузите Hammer и зайдите в <code>Tools > Options</code>. Убедитесь что ваш мод стоит в "активной конфигурации" Hammer'a, нажмите кнопку "Add" и укажите путь к вашему файлу FGD.
+
Если у вас еще нет FGD, сохраните пустой текстовый файл в папке вашего мода с названием <code><modname>.fgd</code>. Затем запустите [[Hammer]] и перейдите к <code>Tools > Options</code>. Убедитесь, что ваш мод стоит в активной конфигурации, нажмите на кнопку "Add" и укажите путь к вашему FGD.
  
Файлы 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|FGD файлы]]
  
 +
[[Category:Programming:ru]]
 +
[[Category:Tutorials:ru]]
 
[[Category:Russian]]
 
[[Category:Russian]]
[[Category:Programming:ru]]
 

Revision as of 08:53, 19 January 2010

English

Логические энтити, это самые простые энтити, потому что у них нет расположения в мире, нет визуального компонента, и они используются для работы с другими энтити через input. Для примера, math_counter хранит данные, и может их добавлять или вычетать; другие энтити на карте могут изменить данные через inputs или получать информацию через output.

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

Создание исходного файла

Это руководство предполагает, что вы используете Visual Studio или Visual C++ Express. См. Выбор Компилятора.

Добавление нового .cpp файла в личную директорию.

Добавьте 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.

Note.png Примечание: Обычно, вам не нужны обе } и ;, как это показано ранее. Команда 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, и иногда программистами.

Note.png Примечание: Обычно, вам придется заключать my_logical_entity в кавычки, делая его типом string, т.к. это не 'defined'; компилятор не поймет, что он означает, и выдаст ошибку. Этот макрос заключит его в кавычки для вас, но как вы увидите в следующем разделе, он используется не везде!

Таблица описания данных

Таблица описания данных это серия макросов, которые дают 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_* команды.

Note.png Примечание: Здесь нет запятых. Это весьма необычно!

Создание 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, и переданы в нашу функцию через параметры, которые указаны между ().
Мы послали:
  1. Энтити, которая начинает всю эту цепь (в Source жаргоне "активатор").
  2. Энтити, которая вызывает функцию "caller", this - это указатель на объект, которому принадлежит функция, которую мы сейчас пишем.
Это требуется для targetname keywords (ключевых слов имен цели).
m_nCounter = 0
Если на сбрасывать счетчик, то он будет продолжать расти.

Поздравляем, энтити готова для компиляции - нажмите F7, чтобы начать процесс. Если вы получаете сообщения об ошибках от Visual Studio, сверьтесь с эталоном кода!

Мы не можем использовать эту энтити напрямую из движка, однако - если вы когда-либо делали карту, вы знаете, что существует много I/O информации, которой не нашлось места в нашем коде. К счастью, наша DATADESC таблица позволяет Source подключить эту энтити в более широкую систему I/O автоматически; так что, единственное, что нам предстоит сделать, это рассказать об энтити Hammerу.

Добавление FGD записи

Диалог настроек Hammer

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.

См. Также