02 августа 2005
Обновлено 17.05.2023

Игровое программирование. Уроки скриптописания

Игровое программирование. Уроки скриптописания - изображение обложка

Для многих начинающих игростроевцев, которые уже собирают свою команду, чтобы слепить на коленке очередной шедевр, программирование часто видится жутким монстром, с которым непонятно как бороться. Вроде и в 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 ). Этот метод задействует всего две функции объекта InstigatorHurtRadius и 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 PropertyName;**или **var() SimpleType PropertyName;**где PropertyClass — название класса создаваемого свойства, SimpleType — элементарный тип, такой как byte (байтовое значение от 0 до 255), int (знаковый целый тип), bool (логический тип), float (знаковый вещественный тип), string (символьная строка) и т.д. (полный список элементарных типов можно посмотреть в справке UDE ), PropertyName — имя нового свойства.

— Объявления новых методов или переопределение унаследованных от базового класса имеют следующий вид:

**simulated function FunctionName( VariableType VariableName, … ) { … }**где FunctionName — имя нового метода (или переопределяемого), содержимое круглых скобок — аргументы метода — может отсутствовать, состоять из одного или нескольких значений. В этом случае VariableType — тип (класс) аргумента, VariableName — имя аргумента. Обратите внимание, что в случае переопределения метода родительского класса, заголовок (интерфейс) метода (строка, стоящая перед фигурными скобками) не должен модифицироваться! Лексема simulated — один из возможных параметров метода — говорит о том, что метод будет выполняться только на клиентской машине. Фигурные скобки обособляют в коде тело метода.

— Задание параметров (свойств) по умолчанию класса:

defaultproperties { Variable1Name=Class’Package.ClassName’; Variable2Name=Variable2Value; … } где defaultproperties — лексема, указывающая начало блока свойств, Variable1Name — название свойства, представляющего собой некоторый другой объект класса ClassName , содержащегося в u -файле с именем Package. Параметр Variable2Name имеет элементарный тип, поэтому и присваиваемое значение Variable2Value должно иметь тот же тип. Символ " = " означает операцию присвоения значения справа переменной слева. Фигурные скобки обособляют в коде блок присвоений.

* * *

Вот и подошел к концу наш первый экскурс в мир игрового программирования. Мы не только сделали интересный мутатор, но и научились читать несложный программный код. В одном из ближайших номеров “Мании” мы продолжим разбираться в азах программирования и, изучив более продвинутые строки кода, создадим более сложный мутатор.

Комментарии
Чтобы оставить комментарий,Войдите или Зарегистрируйтесь