Игровое программирование. Уроки скриптописания
Для многих начинающих игростроевцев, которые уже собирают свою команду, чтобы слепить на коленке очередной шедевр, программирование часто видится жутким монстром, с которым непонятно как бороться. Вроде и в 3D уже рисовать умеют, и в Photoshop кисточкой сноровисто работают, и другие полезные программы неплохо знают… Но как только дело доходит до кода, начинается паника и неразбериха: “ Кто спрограммирует? Кто напишет заветные строчки? А если и напишет, то как в них потом разобраться?! ”.
Данный материал открывает серию статей, в которых мы на простых примерах научим вас читать с листа и писать несложный программный код. Суперкрутыми программистами вы, разумеется, не станете (для этого надо учиться значительно дольше), но иероглифы кода перестанут быть для вас чем-то непонятным, от чего нужно держаться подальше.
Сегодня мы расскажем вам, как создать игровой мутатор оружия, превращающий заурядную шестистволку UT2004 в смертоносный плазмаган “Мегакиллер”. Пример, с одной стороны, несложный: разберутся даже самые начинающие. С другой стороны — ощутимый результат будет достигнут уже по факту прочтения вами статьи (и отработки ее на практике). Вы сами напишете мутатор для UT и опробуете его в игре.
Обратите внимание, что все упоминаемые в статье программы и файлы можно взять с нашего диска.
Установка среды разработки
В UT2004 встроен собственный компилятор UCC для игровых скриптов. Но, к сожалению, он является консольным приложением и компиляция в нем — весьма трудоемкий процесс. Поэтому компания Epic Games выпустила программный пакет Unreal Developer Environment (далее — UDE ), который может заметно упростить процесс написания игровых скриптов и их компиляцию. UDE распространяется в виде ut4mod -файла, то есть инсталлируемого игрового модуля.
При запуске установки модуля никаких особых манипуляций совершать не нужно — модуль сам определит наличие игры на вашем винчестере (через реестр) и путь к ней. Просто кликните кнопку Next. После установки на рабочем столе появится ярлык для UDE.
Классы игры и иерархия
Вся логическая составляющая UT представляет собой совокупность классов объектов, которые объединены в строгую иерархическую схему — так называемое дерево наследования. О наследовании и объектно-ориентированном программировании читайте на отдельном текстовом блоке.
Распаковка игровых скриптов
Изначально uc -файлы, содержащие классы объектов, упакованы в u -файлы. Но для работы с UDE нам придется их распаковать. Для этого в папке UT2004\System создаем bat -файл (или cmd ) со следующим содержимым: @for /f “tokens=*” %%a in (‘dir /b *.u’) do ( ucc batchexport %%a class uc …%%~na\Classes ). Затем стартуем этот файл, и через 2—3 минуты все требуемые скрипты будут распакованы.
Запускаем UDE , появится окно мастера настройки среды разработки (в дальнейшем это конфигурирование можно повторить по команде File/Wizards/The UDE Setup Wizard внутри программы). Настроек несколько, и все их можно использовать со значениями по умолчанию. Также необходимо проверить, чтобы пункт Create Class/Package Trees ( рис. 1. ) был активным (он будет выведен на шаге 3 — Step 3: Class/Package Tree ). Наконец, жмем Finish и ждем около минуты, пока среда разработки просканирует игровую папку на наличие файлов скриптов и объединит все найденные классы в дерево классов.
Настройка среды разработки
Перед нами среда разработки. Для организации рабочего пространства перемещаем открытые окна влево основного экрана с помощью соответствующей кнопки в правом верхнем углу каждого открытого окна. Теперь окна будут выезжать при наведении курсора на соответствующие значки панели слева. Для нас важны два окна: Classes ( рис. 3. ) и Packages ( рис. 4. ). В первом окне представлено иерархическое дерево всех игровых классов. Во втором — файлы скриптов в соответствии с их принадлежностью игровым архивам ( u -файлам).
Двойной клик на любом пункте этих окон автоматически открывает соответствующий uc -файл/класс. Заголовок текстового окна с кодом вынесен влево, что весьма необычно, поэтому для закрытия окна нужно кликнуть на крестик в левом верхнем углу (а не в правом, как это обычно делается в Windows).
Кстати, неоценимой заслугой UDE является возможность проследить источник любых данных. Для этого достаточно навести курсор на соответствующую лексическую единицу (например, имя класса или функции) и кликнуть на ней, удерживая нажатой клавишу Ctrl. При этом будет открыт файл, где интересующая нас единица была объявлена, а само объявление будет подсвечено. С помощью этой несложной функции очень просто исследовать происхождение тех или иных переменных, функций, классов.
Принципы объектно-ориентированного программирования
В ООП существуют три основных принципа, на которых строятся практически все логические манипуляции с объектами.
Инкапсуляция — объединение внутри объекта данных с методами для их обработки. Например, если рассматривается класс “Лампочка”, то у него должны быть методы “включить свет” и “выключить свет”.
Наследование — создание класса объектов на основе уже существующего класса. При этом создаваемый класс будет не только содержать все данные и методы базового класса, но и обладать своими собственными. Методы базового класса могут быть переопределены. Примеры наследования и переопределения: если класс “Пегас” наследуется от класса “Лошадь”, то у первого появляется новое свойство — “крылья” и соответствующий метод “махать крыльями”. Все остальное у этих двух классов одинаковое. Если мы рассмотрим класс “Русалка”, основанный на классе “Человек”, то в данном случае будет иметь место переопределение свойства “ноги” на “рыбий хвост”, а метод “двигаться” будет вместо движения ног отвечать за перемещение хвоста.
Полиморфизм — что это такое, проще всего рассмотреть на конкретном примере. Рассмотрим два класса объектов — “Пегас” и “Лошадь”. Полиморфизм заключается в том, что мы можем рассматривать любого пегаса как лошадь и, например, у обоих объектов выполнять метод “кушать траву” или “бежать галопом”.
Многие начинающие программисты не различают понятия “класс” и “объект”. Но при этом эти два понятия различаются примерно так же, как и, скажем, чертеж устройства и готовое устройство в магазине. В жизни их спутать трудно.
Создание проекта
К сожалению, в UDE кнопка “Создать новый проект” отсутствует. Поэтому создаем в корневой папке UT2004 файл CreateNewClass.bat с командами:
set pkg=Megakiller
md %pkg%
_ md %pkg%\Classes_
echo class %pkg% extends //base class >> %pkg%\Classes%pkg%.uc
Значение переменной pkg — это имя нашего проекта (в данном случае — Megakiller ). Остальные строки генерируют “пустой” скрипт.
После запуска пакетного файла в среде разработки выполняем команду UT2004 / Refresh Package/Class Tree. Теперь в списке архивов появился новый пункт Megakiller , а в нем скрипт Megakiller.uc. Этап подготовки завершен, переходим к программированию.
Идея модификации
Идея мутатора заключается в следующем: когда игрок стреляет в альтернативном режиме из “Мегакиллера”, стволы вместо пуль выпускают энергетические лучи, как у шокового ружья. При соприкосновении луча с любым препятствием происходит взрыв плазмы, который наносит здоровью противника заметный урон. При последовательном попадании нескольких лучей в одного и того же противника враг начинает гореть, а та часть тела, куда попал заряд, ампутируется.
Перед изучением нижеследующей информации настоятельно рекомендуется изучить текстовый блок “ Анатомия Unreal-класса ”.
Внимание! Читая нижеследующие абзацы, смотрите соответствующие строки в листинге кода, приведенном на отдельной странице (номера строк указаны слева от кода).
Класс оружия
Наше оружие будет объектом нового игрового класса Megakiller , основанном на уже существующем классе Minigun ( строка 1 ). “Мегакиллер” переопределяет некоторые свойства ( строка 2 ) родительского класса ( строки 2-7 ): альтернативную стрельбу ( FireModeClass ), событие при получении оружия ( PickupClass ), а также описание оружия ( Description ). Стоит заметить, что основным режимом стрельбы является FireModeClass(0) , а альтернативным — FireModeClass(1).
Класс альтернативной стрельбы
Класс альтернативной стрельбы для “Мегакиллера” представляет собой переработанный базовый класс MinigunAltFire. Для создания этого класса кликаем правой кнопкой мыши на нашем проекте Megakiller в окне Packages и в контекстном меню выбираем Create Sub Class. В открывшемся диалоговом окне ( рис. 3. ) в поле Parent Class вводим имя базового класса ( MinigunAltFire ), а в New Class Name — имя нашего нового класса ( MegakillerAltFire ). Обязательно ставим галочку в самом низу окна и жмем Оk. После этого в нашем проекте появляется новый файл MegakillerAltFire.uc.
В начале файла объявляем несколько новых свойств для объектов класса ( строки 2-5 ): визуальный тип взрыва ( ExplosionClass ), визуальный тип следа от взрыва, например на стенах ( ExplosionDecalClass ). Тип луча при альтернативной стрельбе ( BeamEffectClass ) и радиус поражения при взрыве ( DamageRadius ).
Методы должны объявляться в порядке, обратном порядку обращения к ним. То есть если метод X вызывает метод Y , то Y должен быть объявлен перед X. С учетом этого добавляем два новых метода Explode и BlowUp и переопределяем имеющийся SpawnBeamEffect. О последнем стоит упомянуть отдельно. Дело в том, что все оружие делится на две категории: оружие непосредственного поражения (пули, лучи) и оружие, поражающее посредством снарядов (ракет, осколков). В первом случае противник, на которого нацелено оружие, получает повреждение моментально, во втором случае решение о повреждении принимается после взрыва снаряда с учетом множества факторов (например, попал ли противник в радиус поражения). Рассмотрим эти методы детально.
Метод SpawnBeamEffect ( строки 23-30 ) имеет стандартный интерфейс, обеспечивающий успешный расчет попадания. Объявляем локальную переменную ShockBeamEffect (ключевое слово “local” ), значение которой задаем с помощью специальной функции Spawn с аргументами: какой эффект луча мы хотим получить, в каком направлении и откуда луч испускается. Наконец, в точке соприкосновения луча устраиваем взрыв, вызвав метод Explode.
В методе Explode ( строки 11-22 ) используется объект Instigator для уточнения, кто именно осуществил выстрел. После проверки, что взрыв возможен (луч не ушел в небо, например), воспроизводится звук взрыва и создаются спрайтовые визуальные эффекты для имитации взрывной волны и следа от взрыва на стенах. Затем проверяется, попадает ли игрок в зону поражения ( строка 19 ). Если попадает, ему наносится повреждение в размере DamageMin ( строка 41 ).
Для вычисления расстояния от игрока до центра взрыва используется функция VSize (длина вектора). В конце вызывается метод физического взрыва для противников — BlowUp ( строки 6-10 ). Этот метод задействует всего две функции объекта Instigator — HurtRadius и MakeNoise. Первая функция создает сферу поражения требуемых силы и радиуса в нужной точке пространства, вторая — информирует ИИ игры о том, что мы только что пошумели.
Свойства, задаваемые в соответствующем блоке ( строки 31-43 ): спецэффекты взрыва ( ExplosionClass ) и следы от него ( ExplosionDecalClass ), тип луча ( BeamEffectClass ), огонь из ствола при выстреле ( FlashEmitterClass ), радиус ( DamageRadius ) и некоторые параметры поражения при взрыве ( DamageMin , DamageMax , bSplashDamage и bRecommendedSplashDamage ). Плюс тип повреждений при взрыве ( DamageType ).
Классы повреждения
По уже известной нам схеме добавляем еще два игровых класса. Класс наносимых оружием повреждений при взрыве MegaBeamDamage наследуется от базового класса DamTypeShockCombo. В новом классе переопределяются значения параметров: горит ли тело убитого врага( bFlaming ), наносится ли урон дружественным игрокам ( bSuperWeapon ), наносятся ли повреждения при прямом попадании мгновенно ( bInstantHit ) и предохраняет ли броня от травм ( bArmorStops ).
Класс MegakillerPickup базируется на MinigunPickup и служит для одной цели: как только игроку в руки попадает “Мегакиллер”, ему выдается уведомление об этом, что проиллюстрировано в строках 4-5 листинга 4.
Класс игрового мутатора
Игровой класс MegakillerWeaponMod создаем на основе стандартного класса модов Mutator с параметром config(user). В классе переопределяется одна-единственная функция — CheckReplacement ( строки 2-33 ), задача которой состоит в подмене всех имеющихся экземпляров минигана “Мегакиллерами”. А располагаться оружие может в одном из трех мест:
— в специальных точках карты ( строка 7 ). Объект класса xWeaponBase позволяет узнать тип оружия с помощью обращения к полю WeaponType и при необходимости произвести замену ( строка 11 ).
— просто лежать на карте ( строка 15 ). Тут нам помогает класс WeaponPickup ( строка 15 ). Подмена осуществляется функцией ReplaceWith ( строка 19 ).
— оружие, которое есть у игрока в боекомплекте. Используемый класс объекта WeaponLocker дает возможность в цикле ( строка 26 ) по очереди перебрать все имеющиеся единицы оружия и заменить “миниган”, если таковой имеется ( строки 28-29 ).
В конце кода задаются значения параметров мутатора ( строки 34-40 ): тип оружия по умолчанию ( DefaultWeapon ) и описания мутатора ( GroupName , FriendlyName , Description ).
Исходный код модификатора
Компиляция и тестирование
Когда все скрипты готовы, остается только откомпилировать проект командой UT2004/Quick Compile Active Document (должен быть открыт любой скрипт проекта). Появится сообщение, что проект Megakiller не прописан внутри файла UT2004.ini (а это необходимо для компиляции). Поэтому кликаем Yes , а затем в следующем окне Ok.
Если все нормально, то через 10—20 секунд проект будет откомпилирован (редактор UnrealEd не должен быть запущен), а кнопка Ok в левом нижнем углу окна компиляции станет активной ( рис. 4. ). Если же надпись сменилась на Error , значит, во время компиляции произошла ошибка. Если кликнуть на этой кнопке, внизу экрана на вкладке Compile Results будет выведено, что именно вызвало ошибку, а в правом нижнем углу окна UDE появится небольшая справка о возможных способах устранения ошибки ( Рис. 5 ).
Если все прошло успешно, то запускаем игру, выбираем Instant Action в меню, ставим режим Deathmatch и на вкладке мутаторов добавляем мутатор Megakiller, после чего кликаем Play. Когда мод протестирован в деле, его можно выложить, например, в интернет. Дистрибутивными файлами являются Megakiller.u и Megakiller.ucl (автоматически генерируется при компиляции) из папки UT2004\System.
Для того чтобы вы сразу могли оценить действие мутатора и поиграть в него, мы выкладываем полную версию “Мегакиллера” на наш диск.
Анатомия Unreal-класса
Класс игры имеет унифицированную архитектуру. Структура файла следующая.
— Заголовок, содержащий объявление класса:
class ClassName extends BaseClass config(user); где ClassName — название нашего класса, BaseClass — базовый класс, на основе которого создается новый класс, config(user) — пример параметра в объявлении класса — указывает, что используются конфигурационные параметры из файла User.ini. Все игровые классы имеют в качестве родительского класс Object или любой из его классов-потомков. Лексемы class и extends являются зарезервированными обязательными атрибутами объявления игрового класса. Важно запомнить, что корректным является только тот uc -файл, имя которого совпадает с названием объявленного в нем класса. И в одном uc -файле должен быть описан только один класс.
— Объявления новых переменных (свойств класса) в следующем формате:
**var() class
— Объявления новых методов или переопределение унаследованных от базового класса имеют следующий вид:
**simulated function FunctionName( VariableType VariableName, … ) { … }**где FunctionName — имя нового метода (или переопределяемого), содержимое круглых скобок — аргументы метода — может отсутствовать, состоять из одного или нескольких значений. В этом случае VariableType — тип (класс) аргумента, VariableName — имя аргумента. Обратите внимание, что в случае переопределения метода родительского класса, заголовок (интерфейс) метода (строка, стоящая перед фигурными скобками) не должен модифицироваться! Лексема simulated — один из возможных параметров метода — говорит о том, что метод будет выполняться только на клиентской машине. Фигурные скобки обособляют в коде тело метода.
— Задание параметров (свойств) по умолчанию класса:
defaultproperties { Variable1Name=Class’Package.ClassName’; Variable2Name=Variable2Value; … } где defaultproperties — лексема, указывающая начало блока свойств, Variable1Name — название свойства, представляющего собой некоторый другой объект класса ClassName , содержащегося в u -файле с именем Package. Параметр Variable2Name имеет элементарный тип, поэтому и присваиваемое значение Variable2Value должно иметь тот же тип. Символ " = " означает операцию присвоения значения справа переменной слева. Фигурные скобки обособляют в коде блок присвоений.
* * *
Вот и подошел к концу наш первый экскурс в мир игрового программирования. Мы не только сделали интересный мутатор, но и научились читать несложный программный код. В одном из ближайших номеров “Мании” мы продолжим разбираться в азах программирования и, изучив более продвинутые строки кода, создадим более сложный мутатор.