Unreal Tournament — Создание простого мутатора на примере Vampire | Creating a simple mutator using the example of Vampire
Распечатать

Unreal Tournament — Создание простого мутатора на примере Vampire | Creating a simple mutator using the example of Vampire

Основная идея:
В данной статье нами будет рассмотрен процесс создания простого мутатора Vampire. Идея мутатора состоит в том, что игрок, который наносит урон другому игроку, получает количество единиц здоровья равное нанесённому урону. Таким образом мы имеем что-то вроде вампиризма.
 
Эта идея была выдвинута на одном из англоязычных сайтов по программированию на UnrealScript. Мы же пойдём дальше и введём также поддержку конфигурационных файлов, окон настройки мутатора и введём несложный графический эффект, показывающий, что игрок в данный момент крадёт жизнь у другого.
 
Подготовка к работе:
Компилятор UCC.exe для UnrealScript устроен таким образом, что принимает только определённую структуру расположения папок со скриптами. Дело в том, что для начала вам необходимо создать все условия для того что бы компилятор нашёл ваш исходный код и создал U-файл (такие файлы называются Packages, но для простоты далее мы будем употреблять слово пакаджи) с определённым именем. Делается это так:
  1. В папке UnrealTournament (там где находятся папки System, Maps, Textures и т.д.) создаём свою папку с названием нашего мутатора. Именно с таким названием будет создан наш U-файл или по-другому пакадж. В нашем случае имя папки будет, например, Vampire.
  2. В папке Vampire создаём папку Classes. В этой папке будут храниться наши файлы скриптов (UC-файлы).
  3. Теперь необходимо сообщить UCC.exe, что скрипты, лежащие в папке Vampire должны быть скомпилированы. Для этого в файле UnrealTournament.ini, находящемся в UnrealTournament/System, нужно дописать строчку
    EditPackages=Vampire
    после других строчек, начинающихся с EditPackages, в конце секции [Editor.EditorEngine].
 
В принципе больше ничего не нужно. Теперь достаточно запустить UCC.exe с параметром Make и все скрипты из папки Vampire будут скомпилированы. Как результат в папке UnrealTournament/System появится пакадж Vampire.u (если не возникнет никаких ошибок в процессе трейсинга и компиляции скриптов). Понятно, что папка Classes сейчас пуста и при компиляции у нас возникнет ошибка.
 
Предлагаю упростить процесс отладки и последующей компиляции нашего мутатора. Не всем удобно постоянно запускать UCC.exe из командной строки с каким-либо параметром. Также для проверки мутатора нам необходимо постоянно запускать игру, создавать новый сервер и выбирать из списка мутаторов наш Vampire. Что бы сократить время отладки сделайте следующее:
  1. В папке Vampire создайте папку General (это может быть любое название, но пусть это будет General - общая).
  2. В папке General создайте BAT-файл с именем compile.bat, откройте его и впишите такие строки:
    cd ..
    cd ..
    cd System
    del Vampire.u
    ucc make
    Это обеспечит нам переход в папку UnrealTournament/System, удаление старой версии нашего мутатора и запуск UCC.exe с параметром Make. Рекомендую в свойствах нашего BAT-файла убрать флажок «Закрывать окно по завершении сеанса работы», так как нам нужно видеть какие ошибки возникли в процессе компиляции.
  3. В папке Vampire создайте ярлык на наш BAT-файл. Это просто для удобства. Советую назвать ярлык COMPILE и поставить ему какую-нибудь приметную иконку.
  4. В папке Vampire создайте два ярлыка на UnrealTournament.exe и назовите их, например, DM-Deck16][ и CTF-Face.
  5. В свойствах ярлыка DM-Deck16][ в поле ввода “Обьект” после указанного пути к программе введите следующее:
    DM-Deck16][.unr?mutator=Vampire.Vampire
  6. Это может, например, выглядеть так:
    "C:\Program Files\GT Interactive\UnrealTournament\System\ UnrealTournament.exe" DM-Deck16][.unr?mutator=Vampire.Vampire
  7. Примерно также поступим с ярлыком CTF-Face. В свойствах ярлыка CTF-Face в поле ввода “Обьект” после указанного пути к программе введите следующее:
    CTF-Face.unr?game=Botpack.CTFGame?mutator=Vampire.Vampire
  8. Это может, например, выглядеть так:
    "C:\Program Files\GT Interactive\UnrealTournament\System\ UnrealTournament.exe" CTF-Face.unr?game=Botpack.CTFGame? mutator=Vampire.Vampire
  9. Данные операции дадут нам возможность быстро запускать игру на картах DM-Deck16][ и CTF-Face с нашим мутатором. Иногда необходимо проверять мутаторы на разных типах игры. Это два ярлыка позволяют это делать для DeathMatch и CaptureTheFlag.
  
Если вы проделали всё, что указано выше, то у вас должно выйти что-то подобное такому:
UnrealTournament (ПАПКА)
       |
       |- Vampire (ПАПКА)
                 |
                 |-COMPILE (ярлык на compile.bat)
                 |
                 |-DM-Deck16][ (ярлык)
                 |
                 |-CTF-Face (ярлык)
                 |
                 |-General (ПАПКА)
                 |      |
                 |      |-compile.bat (файл)
                 |
                 |-Classes (ПАПКА)
 
Теперь всё готово и мы можем переходить с самому процессу написания скриптов.
 
Основной код мутатора
Итак, приступим. Основой каждого мутатора, создаваемого пользователем, является класс, наследующий свойства и методы класса Mutator. Создайте в папке Vampire текстовый файл Vampire.uc и впишите в него следующие строки:
//===================================
// Vampire ; This is mutator script.
// Description: You gain life equal to the amount of damage you do to
// an enemy.
//===================================

class Vampire expands Mutator;

var bool Initialized;

var() float LifeStolenPerHit;
var() int MaxLife;
var() bool bInflictedDamageOnly;

function PostBeginPlay()
{
if (Initialized)
return;
Initialized = True;
Level.Game.RegisterDamageMutator( Self );
}

function MutatorTakeDamage( out int ActualDamage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation,
out Vector Momentum, name DamageType)
{
if ((InstigatedBy.IsA('Bot') || InstigatedBy.IsA('PlayerPawn')) && (Victim != InstigatedBy) && (InstigatedBy != None) && (Victim != None))
{
if ((Victim.Health < ActualDamage) && bInflictedDamageOnly)
ActualDamage = Victim.Health;
InstigatedBy.Health += ActualDamage * LifeStolenPerHit;

if ((InstigatedBy.Health > MaxLife) && (MaxLife != 0))
{
InstigatedBy.Health = MaxLife;
}
}

if ( NextDamageMutator != None )
NextDamageMutator.MutatorTakeDamage( ActualDamage, Victim, InstigatedBy, HitLocation, Momentum, DamageType );
}

defaultproperties
{
LifeStolenPerHit=1.000000
MaxLife=200
bInflictedDamageOnly=True
}
 
Теперь детально разберём этот текст.
Как видите в начале файла стоят строки:
//===================================
// Vampire ; This is mutator script.
// Description: You gain life equal to the amount of damage you do to
// an enemy.
//===================================
 
Всё, что стоит после символов //, считается комментарием. Этот комментарий является однострочным, то есть символами // можно закомментировать только тот участок текста, который стоит после этих символов и до конца строки. В UnrealScript есть также многострочный комментарий. Он начинается с символов /* и заканчивается соответственно символами */. Таким образом, весь текст, что стоит между этими символами будет закомментирован.
 
Комментарии не обязательны, но иногда в процессе отладки нужно временно убрать некоторый участок текста. Также комментарии служат для упрощения понимания кода скрипта. В нашем случае в комментарии указано название класса и краткое описание. Далее идёт описание класса мутатора:
class Vampire expands Mutator;
 
Описание начинается со слова class, после чего идёт название описываемого класса. В нашем случае это Vampire. После определения имени класса идёт слово expands, которое означает, что описываемый класс является наследником какого-либо другого класса. И, наконец, идёт имя родительского класса. Так как мы создаём мутатор, то родительским для нашего класса является класс Mutator. Иногда вы можете увидеть в описании других классов, что слово expands заменено на слово extends.
 
С самого начала UnrealScript использовал слово expands, но довольно давно оно было заменено на слово extends, которое используется в Java. Компилятор принимает их обоих и понимает одинаково, но в следующих версиях движка слово expands может не работать. Поэтому всегда используйте слово extends когда у вас есть выбор.
 
Пусть в нашем примере будет слово expands что бы вы видели, что бывают различные методы описания, но всё таки потом рекомендуется заменить его на слово extends. Как видите, в конце строчки ставится сивол ; (точка с запятой). Он является разделительным символом и ставится в конце строки после описания чего-либо или после выражений.
 
Точка с запятой не ставится перед символом { и после символа }. Заметьте также, что имя описываемого класса должно совпадать с именем файла, в котором этот класс определён. Например у нас класс Vampire описан в файле Vampire.uc (это необходимо и в случае отличия будет ошибка). Также после имени родительского класса могут идти модификаторы:
  • Abstract – данный класс не может быть вызван (spawned), так как является базовым;
  • Config – значения переменных данного класса сохраняются во внешнем конфигурационном файле. Если после этого ключевого слова стоят скобки с каким либо текстом в них, то значения переменных будут сохраняться в INI-файле с именем, стоящим в скобках. В противном случае (если в скобках ничего нет) значения переменных будут сохраняться в файле UnrealTournament.ini, который используется для этого по умолчанию;
  • NoUserCreate – данный класс не может быть помещён на уровень с помощью UnrealEd;
  • Native – некоторые фрагменты данного класса описаны с использованием внешнего программирования (импорт функций из библиотек DLL);
  • NativeReplication – переменные данного класса полностью описаны с использованием внешнего программирования;
  • SafeReplace – означает, что ссылка на этот класс может быть изменена на Null (пустая ссылка) или на значение по умолчанию, если данный класс не найден ни в одном из пакаджей. Пакаджи сами по себе не имеют свойства SafeReplace. Например вы в редакторе сделали свою карту с применением двух текстур MyTex1 и MyTex2 из пакаджа MyTextures.utx, а потом сохранили.
    • Если теперь вы удалите из пакаджа MyTextures.utx текстуру MyTex1, то на вашей карте те грани, на которые была наложена текстура MyTex1, покроются другой текстурой (той, которая выбрана по умолчанию) и карта всё равно будет запускаться. Но если теперь удалить сам пакадж MyTextures.utx и запустить карту, то игра выдаст сообщение об ошибке: «Пакадж MyTextures.utx не найден»;
  • Within <Класс> - обьекты данного класса могут быть созданы только внутри указанного Класса (это не работает для UnrealTournament v 420);
  • PerObjectConfig – информация о каждом обьекте данного класса должна храниться в конфигурационном файле отдельно под секцией с названием, зависящим от его (обьекта) имени. Смотри описание ключевого слова Config;
  • Transient – данный класс не включается при сохранении игрового соcтояния (в Unreal);
  • NoExport – не экспортировать в заголовок C++. Исполнение через командную строчку "UCC.exe Make -h" не будет генерировать автоматически заголовок C++ для функций и событий, описанных с помощью внешнего программирования.
 
После описания класса обычно идёт описание переменных (свойств, параметров) класса:
var bool Initialized;

var() float LifeStolenPerHit;
var() int MaxLife;
var() bool bInflictedDamageOnly;
  
Описание переменной начинается со слова var (от англ. Variable - величина), после чего идёт тип переменной (например, float,bool,int,string и т.д.), а за ним имя переменной. Если за словом var стоят скобки, то это означает, что этот параметр помещается в каком-нибудь разделе.
 
Пример: если в скобках указано название раздела Display, а переменная называется bUnlit, то в редакторе UnrealEd можно изменять значение данного параметра через окно Actor properties. В случае, когда в скобках не указано ничего, редактор UnrealEd не даёт возможности изменять значение параметра и единственный способ сделать это – редактировать раздел defaultproperties в скрипте класса (об этом ниже). Иногда вместо слова var вы можете встретить другие слова. Все они как и var обозначают характер описываемого свойства. Они могут быть следующие:
  • var – переменная, может принимать различные значения в зависимости от типа;
  • const – константа. Не может быть изменена ни из UnrealScript, ни из вне (с помощью внешнего программирования);
  • local – используется только внутри функций. Данная переменная существует на этапе исполнения функции, в которой она описана, а потом её значение будет утеряно.
 
Описываемые переменные могут быть следующих типов: 
  • bool – бинарная переменная для логических опираций. Значения: True/False (Правда/Ложь);
  • int – целое значение от –2147483648 до 2147483647 (включая пределы);
  • byte – целое значение от 0 до 255 (включая пределы). Значение, которое выходит за эти границы будет заворачиваться.
    Пример: число 256, введённое в переменную типа byte примет значение 0;
  • float – вещественное значение, десятичная дробь;
  • string – строка символов, заключённых в двойных кавычки;
  • name – идентификатор (имя), заключённый в одинарные кавычки. Имена могут содержать только буквы. Цифры и символ подчёркивания.

Регистр букв в именах не имеет значения.

 
Иногда вы можете встретить переменные типов vector, rotator, color и другие. Дело в том, что это не предопределённые типы, а структуры. UnrealScript даёт нам возможность создавать свои типы используя ключевые слова struct и enum. Переменные, описываемые после слова struct являются структурами. Описываются они так:
var struct <Имя_структуры> {
<Описание переменной 1>;
< Описание переменной 2>;
…
} <Имя_переменной 1>, <Имя_переменной 2>, …;
 
Таким образом структура представляет собой переменную, состоящую из набора других переменных (полей).
Служебное слово enum служит для определения перечисляемых типов. Описываются они подобно структурам:
var enum <Имя_типа> {
<Значение1>,
< Значение 2>,
…
} <Имя_переменной 1>, < Имя_переменной 2>, …;
 
Это как бы список значений, идущих по порядку. Если вам нужно узнать количество значений в этом списке, используйте функцию:
EnumCount(<Имя_типа>)
 
У нас описаны такие переменные класса:
  • Булевская переменная Initialized – служебная переменная для проверки корректной работы мутатора (в принципе не обязательна);
  • Вещественная переменная LifeStolenPerHit – указывает какой прцент от урона перейдёт в жизнь;
  • Целая переменная MaxLife – определяет максимальное количество жизни, которое может быть у игрока (что-бы был лимит вампиризма);
  • Булевская переменная bInflictedDamageOnly – если урон, нанесённый жертве больше её жизни и значение этого параметра равно True, то игрок, нанёсший урон получит количество жизни равное жизни жертвы, а если значение параметра равно False, то игрок, нанёсший урон получит количество жизни равное нанесённому урону как бы он ни был велик. В любом случае, после этого жертва умирает.
 
Теперь перейдём непосредственно к коду, описывающему методы нашего класса. Методы (функции) описываются так:
<Модификаторы_функции> function/event <Тип_функции> <Имя_функции> (<Модификаторы_параметра> <Тип_параметра> <Имя_параметра>, …)
{
…
}
 
Сначала идут модификаторы функции. Они могут быть такие:
  • final – данная функция не может быть перегружена (переопределена – override) в подклассах или состояниях (state) этого класса. Функции, определённые как статические (static) и финальные (final), автоматически считаются симулированными (simulated).
  • iterator – данная функция является итерационной функцией (итератором) для использования после ключевого слова foreach.
    • Итераторами могут быть только функции, определённые с помощью внешнего программирования (native).
  • latent – данная функция требует определённых затрат времени на выполнение. Латентные функции могут вызываться только из кода состояний (state) или из других латентных функций. Латентные функции должны быть внешними (native).
  • native (или intrinsic) – данная функция определена во внешнем С++ коде.
  • native(<Номер>) – данная функция находится под указанным номером в С++ коде.
  • simulated – данная функция вызывается на сервере и на клиенте при игре по сети. Функции, не определённые как симулированные игнорируются при работе скрипта у клиента.
  • singular – данная функция предотвращает самовызов.
  • static – данная функция может быть вызвана без экземпляра класса, в котором она описана.
  • exec – данная функция может быть вызвана через консоль.
 
После модификаторов функции идёт ключевое слово function или event. Слово event имеет такое же значение как и function, но при экспорте скриптов в С++ код имеет свои особенности.
 
Потом идёт тип функции, который определяет, данные какого типа возвращает функция. Типы функций могут быть такими же как и у переменных.
 
Далее идёт имя функции. Это просто идентификатор.
 
После имени между двух круглых скобок идёт список параметров, которые описываются подобно переменным, но без слова var. Описания параметров отделяются запятыми. Перед каждым из параметров может стоять модификатор параметра, определяющий его поведение.
Модификаторы параметров могут быть следующие:
  • out – значение переменной, которая будет подставлена в качестве данного параметра может быть изменено в теле функции. Если не ставить out перед описанием параметра и передать в качестве параметра переменную, то функция возьмёт из данной переменной значение, обработает его и возвратит некоторое значение в то место кода, где она (функция) стоит. Если же слово out стоит перед описанием параметра и в качестве параметра передаётся некая переменная, то функция возьмёт значение из этой переменной, запомнит имя переменной, обработает значение, а потом возвратит некоторое значение в то место кода, где она (функция) стоит, но также изменит значение переменной, подставленной в качестве параметра.
    Это можно использовать, когда требуется что бы функция возвращала более одного значения. Возможно моё объяснение путанное и непонятное, но это довольно сложно объяснить человеку, впервые столкнувшемуся с программированием. Для корректного объяснения требуется вводить такие понятия как параметры-переменные и параметры-значения, а также понятие указателя на область памяти. Желательно всё таки знать основы программирования на языках С++ или Pascal;
  • optional – данный параметр может быть пропущен при вызове процедуры. Если вы хотите пропустить данный параметр, то запятые всё равно нужны. Просто межлу ними ничего не будет стоять;
  • coerce – значение, которое передаётся функции через данный параметр, будет автоматически приведено к указанному типу.
 
После описания списка параметров идёт тело функции, заключённое в фигурные скобки.
Итак, перейдём к рассмотрению функций (методов), описанных в нашем мутаторе.
 
Первым идёт метод PostBeginPlay(), который у нас определяется так:
function PostBeginPlay()
{
if (Initialized)
return;
Initialized = True;
Level.Game.RegisterDamageMutator( Self );
}
 
Этот метод вызывается после создания экземпляра класса мутатора. Короче, почти в самом начале. Тут сначала с помощью служебной переменной Initialized мы проверяем мутатор на инициализацию. Если мутатор инициализирован, то выйти из данной функции (return – выйти из тела функции). В противном случае (мутатор не инициализирован) – функция выполняется дальше и вызывается метод RegisterDamageMutator с параметром Self, указывающим на данный мутатор.
 
Метод RegisterDamageMutator необходимо вызывать в тех мутаторах, где нужно что-либо делать, когда живым объектам (Pawn) наносится урон. Этот метод регистрирует наш мутатор как DamageMutator. Если мутатору не будет поставлен такой статус, то функция MutatorTakeDamage для обработки нанесённого урона вызываться не будет.
 
Далее описывается функция, которая и является костяком всего мутатора.
function MutatorTakeDamage( out int ActualDamage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation,
out Vector Momentum, name DamageType)
{
if ((InstigatedBy.IsA('Bot') || InstigatedBy.IsA('PlayerPawn')) && (Victim != InstigatedBy) && (InstigatedBy != None) && (Victim != None))
{
if ((Victim.Health < ActualDamage) && bInflictedDamageOnly)
ActualDamage = Victim.Health;
InstigatedBy.Health += ActualDamage * LifeStolenPerHit;

if ((InstigatedBy.Health > MaxLife) && (MaxLife != 0))
{
InstigatedBy.Health = MaxLife;
}
}

if ( NextDamageMutator != None )
NextDamageMutator.MutatorTakeDamage( ActualDamage, Victim, InstigatedBy, HitLocation, Momentum, DamageType );
}
 
Эта функция вызывается тогда, когда один живой обьект (Pawn) наносит урон другому. Она вызывается в методе TakeDamage живого обьекта, которому был нанесён урон, если мутатор зарегистрирован как DamageMutator. Если наш мутатор будет работать, то игра сначала определит какой урон наносит один игрок другому с помощью оружия и параметры попадания, затем идёт проверка инвентаря игрока на предмет наличия брони для уменьшения урона, а потом мы можем обработать это событие по-своему и изменить сам механизм нанесения урона. Как видите, процедура имеет некоторые параметры:
  • out int ActualDamage – параметр, определяющий величину нанесённого урона. Величина урона должна быть целым числом (int).Так как параметр описан со словом out, то это означает, что в процедуру через данный параметр будет передано некоторое значение, которое мы можем обработать и изменить, а затем уже оно будет возвращено обратно во внешний код;
  • Pawn Victim – параметр, указывающий на обьект, которому был нанесён урон. Это жертва;
  • Pawn InstigatedBy – параметр, указывающий на обьект, который нанёс урон жертве. Это подстрекатель;
  • out Vector HitLocation – параметр, содержащий вектор, координаты которого определяют координаты точки попадания. Именно в эту точку подстрекатель нанёс урон жертве. Так как параметр описан со словом out, то это означает, что в процедуру через данный параметр будет передано некоторое значение, которое мы можем обработать и изменить, а затем уже оно будет возвращено обратно во внешний код;
  • out Vector Momentum – параметр, определяющий вектор отдачи при получении урона жертвой. Так как параметр описан со словом out, то это означает, что в процедуру через данный параметр будет передано некоторое значение, которое мы можем обработать и изменить, а затем уже оно будет возвращено обратно во внешний код;
  • name DamageType – параметр, определяющий тип наносимого урона. В соответствии с различными типами урона можно описать различные методы его обработки.
 
Далее в теле функции идёт проверка:
if ((InstigatedBy.IsA('Bot') || InstigatedBy.IsA('PlayerPawn')) && (Victim != InstigatedBy) && (InstigatedBy != None) && (Victim != None))
 
Тут проверяется:
  • Является ли подстрекатель ботом;
  • Является ли подстрекатель игроком;
  • Не совершила ли жертва суицид;
  • Существует ли жертва как объект;
  • Существует ли подстрекатель как объект.

 

Эти проверки нужны для корректной работы мутатора. Если данные условия выполняются, то выполняется следующий отрезок кода:

if ((Victim.Health < ActualDamage) && bInflictedDamageOnly)
ActualDamage = Victim.Health;
InstigatedBy.Health += ActualDamage * LifeStolenPerHit;

if ((InstigatedBy.Health > MaxLife) && (MaxLife != 0))
{
InstigatedBy.Health = MaxLife;
}
 
Его можно разбить на две части.
Первая часть: 
if ((Victim.Health < ActualDamage) && bInflictedDamageOnly)
ActualDamage = Victim.Health;
InstigatedBy.Health += ActualDamage * LifeStolenPerHit;
 
Тут оценивается количество жизни у жертвы и в зависимости от параметров bInflictedDamageOnly и LifeStolenPerHit нашего мутатора начисляется определённое количество жизни подстрекателю.
Вторая часть:
if ((InstigatedBy.Health > MaxLife) && (MaxLife != 0))
{
InstigatedBy.Health = MaxLife;
}
 
Тут оценивается количество жизни подстрекателя после получения дополнительной жизни. В зависимости от параметра MaxLife нашего мутатора жизнь подстрекателя будет ограничена определённым числом или не будет ограничена вообще (кроме ограничения поставленного разработчиками в игре).
 
Таким мы определили свой механизм нанесения урона, который кроме стандартного уменьшения количества жизни жертвы также даёт определённое количество жизни подстрекателю.
 
После всего этого (в конце функции) идут стандартные для DamageMutator’ов строки:
if ( NextDamageMutator != None )
NextDamageMutator.MutatorTakeDamage( ActualDamage, Victim, InstigatedBy, HitLocation, Momentum, DamageType );
 
Они отвечают за корректную работу механизма нанесения урона, который описан в нашем мутаторе в том случае, когда кроме нашего мутатора подключены другие мутаторы, которые имеют статус DamageMutator и определяют функцию MutatorTakeDamage по-своему.
 
Тут идёт проверка на наличие следующего DamageMutator’а. Если такой мутатор есть, то выполняется его метод MutatorTakeDamage. Таким образом после обработки урона нашим мутатором его обрабатывает следующий DamageMutator и так далее пока все DamageMutator’ы не обработают наносимый урон.
 
В конце описания любого класса всегда (или почти всегда) идёт раздел defaultproperties. В этом разделе между фигурными скобками указываются имена переменых класса и значения, которые будут присвоены им по умолчанию. Когда создаётся обьект (экземпляр) какого-либо класса, то Unreal смотрит раздел defaultproperties и присваивает соответствующим переменным определённые разработчиком значения. Если этого раздела нет или в нём нет какой-либо переменной, то в качестве значений по умолчанию будут поставлены значения, указанные в разделе defaultproperties класса, который является родительским для нашего.
 
Вот и всё. Класс нашего мутатора описан. Теперь нужно скомпилировать текст скрипта в пакадж. Если вы всё делали в соответствии с действиями указанными в разделе «Подготовка к работе», то вам нужно просто зайти в папку UnrealTournament/Vampire и запустить файл COMPILE (ярлык на файл compile.bat). Если в процессе компиляции будут возникать ошибки, то ещё раз прочитайте и проделайте указанные в данной статье действия по написанию кода скрипта. В результате правильной компиляции мы должны получит файл Vampire.u в папке UnrealTournament/System.
 
Осталось только присоединить наш пакадж к движку Unreal Tournament. Игра должна «увидеть» наш мутатор и поместить его в список рядом с другими мутаторами. Это делается с помощью INT-файлов.
Создайте в папке System текстовый файл, назовите его Vampire.int и откройте в каком-либо текстовом редакторе (Wordpad например).
Добавьте следующие строки:
[Public]
	Object=(Name=Vampire.Vampire,Class=Class,MetaClass=
	Engine.Mutator,Description="Vampire,You gain life equal to the amount of damage you do to an enemy.")
 
Это и есть привязка. Тут указывается имя мутатора как класса, тип обьекта, родительский класс и описание. Имя состоит из имени пакаджа и имени класса в нём, разделяющихся точкой. Тип нашего обьекта – Class, а не текстура или звук. Родительским является класс Mutator, описанный в пакадже Engine. Описание состоит из двух предложений, разделяемых запятой. Первое предложение определяет имя мутатора в списке мутаторов, а второе – строчку, которая будет показываться в стоке состояния снизу экрана.
 
Сохраните и закройте этот файл.
Теперь запускайте UnrealTournament, выбирайте ваш мутатор в списке и начинайте новую игру.
 
Добавляем эффекты
Допустим, что нам захотелось украсить наш мутатор некоторым эффектом. Мы хотим, что бы при получении игроком жизни от его жертвы, вокруг него создавалась красная оболочка. Сделаем это.
 
Создайте в папке Vampire/Classes текстовый файл и назовите его VampireShell.uc – это и будет файл, описывающий класс оболочки.
Откройте этот файл и впишите в него следующие строки:
//===================================
// VampireShell ; This is effect script.
// Description: Red shell around damage instigator.
//===================================

class VampireShell expands Effects;

defaultproperties
{
bAnimByOwner=True
bOwnerNoSee=True
bNetTemporary=False
bTrailerSameRotation=True
Physics=PHYS_Trailer
RemoteRole=ROLE_SimulatedProxy
LODBias=0.500000
DrawType=DT_Mesh
Style=STY_Translucent
Texture=None
ScaleGlow=0.500000
AmbientGlow=64
Fatness=157
bUnlit=True
bMeshEnviroMap=True
LifeSpan=0.500000
}
 
Разберём скрипт данного класса. Как видите, в начале поставлены комментарии для облегчения разбора кода (как это было со скриптом мутатора).
 
После комментария идёт описание класса нашей оболочки, которая является классом-наследником класса Effects.
Никаких методов и переменных нам не требуется, поэтому сразу идёт раздел defaultproperties. Переменные, указанные в данном разделе требуют более детального разбора так как описаны не нами, а разработчиками в классах, которые являются родительскими для нашего (Effects, Actor). Итак, мы имеем следующие переменные:
  • bAnimByOwner=True – анимировать в соответствии с движениями владельца. Для того что бы наша оболочка повторяла движения модели игрока;
  • bOwnerNoSee=True – невидимый для владельца. Для того что бы игрок не видел оболочку перед глазами;
  • bNetTemporary=False – обьект и его переменные устанавливаются только в момент его создания и не подлежат дальнейшей обработке. Всё что обрабатывается клиентской частью не связано больше с сервером. Обычно это свойство устанавливается для классов, которые наследуются от класса Effects, а также других классов, которые существуют независимо (неуправляемые снаряды и т.п.). У нас оболочка как раз управляема (повторяет движения игрока), поэтому - False;
  • bTrailerSameRotation=True – поворот такой же как и у владельца. Для того что бы оболочка поворачивалась вместе с игроком;
  • Physics=PHYS_Trailer – тип физики. У нас оболочка прикреплена к игроку и её координаты меняются вместе с движением игрока (Trailer от англ. прицеп);
  • RemoteRole=ROLE_SimulatedProxy – роль данного обьекта в игровом мире. В данном случае мы ставим такое значение, которое применяется для обьектов, движение которых может быть предсказано;
  • LODBias=0.500000 – уровень детализации (LOD - Level of detail). По умолчанию в обьектах стоит 1;
  • DrawType=DT_Mesh – способ отрисовки обьекта. У нас трёхмерный обьёмный обьект;
  • Style=STY_Translucent – стиль наложения текстур. У нас прозрачная оболочка;
  • Texture=None – текстура обьекта. У нас ничего не стоит так как текстура выбирается в коде мутатора (смотрите далее);
  • ScaleGlow=0.500000 – в нашем случае прозрачной оболочки (Style=STY_Translucent) это степень прозрачности. Величина данного параметра меняется от 0 до 1. У нас 0.5 – полупрозрачная оболочка;
  • AmbientGlow=64 – яркость освещённости. Величина меняется от 0 до 255 (255 = пульсация);
  • Fatness=157 – ширина обьекта. По умолчанию 128 и меняется от 0 до 255.
  • bUnlit=True – не освещать обьект. У нас источники освещения не освещают, а тени не затеняют оболочку.
  • bMeshEnviroMap=True – хромовая поверхность. У нас оболочка с эффектом зеркальной поверхности.
  • LifeSpan=0.500000 – время жизни обьекта. Если это значение равно 0, то обьект существует до тех пор пока для него не будет вызван метод Destroy. Если значение отлично от нуля, то игра автоматически удалит обьект по истечении заданного времени в секундах. У нас стоит 0.5 – оболочка будет присутствовать на игроке пол-секунды, после чего исчезнет. 
 
Оболочка описана. Теперь следует внести некоторые изменения в код мутатора. После добавления эффекта текст скрипта в файле Vampire.uc должен выглядеть так:
//===================================
// Vampire ; This is mutator script.
// Description: You gain life equal to the amount of damage you do to
// an enemy.
//===================================

class Vampire expands Mutator;

var bool Initialized;
var vector InstFog;
var float InstFlash;
var VampireShell VampireShellEffect;
var texture ShellSkin;
var class<VampireShell> ShellType;

var() float LifeStolenPerHit;
var() int MaxLife;
var() bool bInflictedDamageOnly;
var() bool bIsRegenEffect;

function PostBeginPlay()
{
if (Initialized)
return;
Initialized = True;
Level.Game.RegisterDamageMutator( Self );
}

function MutatorTakeDamage( out int ActualDamage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation,
out Vector Momentum, name DamageType)
{
if ((InstigatedBy.IsA('Bot') || InstigatedBy.IsA('PlayerPawn')) && (Victim != InstigatedBy) && (InstigatedBy != None) && (Victim != None))
{
if ((Victim.Health < ActualDamage) && bInflictedDamageOnly)
ActualDamage = Victim.Health;
InstigatedBy.Health += ActualDamage * LifeStolenPerHit;

if (bIsRegenEffect && (MaxLife != 0 ))
{
FlashShell(InstigatedBy);
if ( InstigatedBy.IsA('PlayerPawn') )
PlayerPawn(InstigatedBy).ClientInstantFlash(InstFlash, InstFog);
}

if ((InstigatedBy.Health > MaxLife) && (MaxLife != 0))
{
InstigatedBy.Health = MaxLife;
}
}

if ( NextDamageMutator != None )
NextDamageMutator.MutatorTakeDamage( ActualDamage, Victim, InstigatedBy, HitLocation, Momentum, DamageType );
}

function FlashShell(pawn Other)
{
if (VampireShellEffect == None)
{
VampireShellEffect = Spawn(ShellType, Other,,Other.Location, Other.Rotation);
}
if ( VampireShellEffect != None )
{
VampireShellEffect.Mesh = Other.Mesh;
VampireShellEffect.DrawScale = Other.Drawscale;
VampireShellEffect.Texture = ShellSkin;
}
}

defaultproperties
{
LifeStolenPerHit=1.000000
MaxLife=200
bInflictedDamageOnly=True
bIsRegenEffect=True
InstFog=(X=475.000000,Y=325.000000,Z=145.000000)
InstFlash=-0.400000
ShellSkin=FireTexture'UnrealShare.Belt_fx.ShieldBelt.RedShield'
ShellType=Class'Vampire.VampireShell'
}
 
Как видите, в начале файла добавились описания новых переменных:
var vector InstFog;
var float InstFlash;
var VampireShell VampireShellEffect;
var texture ShellSkin;
var class<VampireShell> ShellType;
 
Эти переменные отвечают за вывод эффекта оболочки с заданной формой и текстурой, а также некоторых графических эффектов. Ещё целесообразно ввести новую переменную класса, которая будет служить для включения и выключения вывода эффекта оболочки. Это следует сделать так как некоторые игроки не любят излишеств графики, а у некоторых стоят маломощные компьютеры. Большое количество эффектов может понизить скорость отрисовки кадров. Так как эта переменная представляет из себя флаг (включить/выключить), то вполне естественно, что эта переменная имеет булевский тип:
var() bool bIsRegenEffect;
 
Вывод оболочки нужно производить в момент получения жизни игроком-подстрекателем. Это делается путём добавления нескольких строчек в тело функции MutatorTakeDamage, которая и вызывается в момент нанесения урона. Добавляются такие строки:
if (bIsRegenEffect && (MaxLife != 0 ))
{
FlashShell(InstigatedBy);
if ( InstigatedBy.IsA('PlayerPawn') )
PlayerPawn(InstigatedBy).ClientInstantFlash(InstFlash, InstFog);
}
 
Тут в условном операторе if проверяется состояние переменной bIsRegenEffect, а также оценивается текущие количество жизни игрока-подстрекателя. Далее следует вызов функции FlashShell, которая будет описана ниже. Ещё добавлены строки, отвечающие за вывод дополнительных графических эффектов.
Для создания оболочки вокруг игрока опишем функцию:
function FlashShell(pawn Other)
{
if (VampireShellEffect == None)
{
VampireShellEffect = Spawn(ShellType, Other,,Other.Location, Other.Rotation);
}
if ( VampireShellEffect != None )
{
VampireShellEffect.Mesh = Other.Mesh;
VampireShellEffect.DrawScale = Other.Drawscale;
VampireShellEffect.Texture = ShellSkin;
}
}
 
Эта функция принимает в качестве параметра объект класса pawn. Передавая в качестве значения параметра Other экземпляр класса pawn, соответствующий игроку, вокруг которого необходимо вывести оболочку, мы можем управлять нашим эффектом. В теле функции производятся действия, связанные с созданием, формированием и выводом оболочки. Сначала идёт несколько строчек, отвечающих за создание самого экземпляра класса VampireShell в игровом мире. Это делается с помощью функции Spawn, которая довольно часто используется и требует более детального рассмотрения. Она имеет такой синтаксис:
function actor Spawn
(
class<actor> SpawnClass,
optional actor SpawnOwner,
optional name SpawnTag,
optional vector SpawnLocation,
optional rotator SpawnRotation
);
 
Параметры:
  • SpawnClass – класс, экземпляр которого должен быть вызван;
  • SpawnOwner – владелец вызываемого класса. Это значение, которое будет подставлено в свойство Owner вызванного обьекта;
  • SpawnTag – значение, которое будет присвоено свойству Tag при создании обьекта;
  • SpawnLocation – вектор, определяющий координаты в пространстве, в которые должен быть вызван обьект;
  • SpawnRotation – ротатор, который определяет поворот обьекта. Подобно вектору указывает «направление взгляда» обьекта.
 
Таким образом в строчке:
VampireShellEffect = Spawn(ShellType, Other,,Other.Location, Other.Rotation);
 
мы создаём экземпляр класса, который определён в переменной ShellType. Для данного обьекта поле Owner содержит указатель на экземпляр класса actor, определяющего игрока, для которого создана оболочка. Значения полей Location и Rotation (координаты и поворот) оболочки совпадают со значениями аналогичных полей для игрока, для которого создана оболочка. Поле Tag не заполняется так как в этом нет необходимости.
 
После вызова объекта всегда следует проверять факт его существования. Если какие-либо из параметров функции Spawn заданы неправильно (например вы пытаетесь вызвать экземпляр класса не описанного нигде в UnrealScript), то объект просто не создастся. Поэтому перед работой с выбранным объектом следует проверять его на предмет существования. Делается у нас это следующим образом:
if ( VampireShellEffect != None )
{
VampireShellEffect.Mesh = Other.Mesh;
VampireShellEffect.DrawScale = Other.Drawscale;
VampireShellEffect.Texture = ShellSkin;
}
 
Тут проверяется создание оболочки и если она существует, то мы устанавливаем её форму (Mesh) подобно форме игрока, размер (Drawscale) подобно размеру игрока и устанавливается текстура, определённая в переменной ShellSkin.
 
Также в разделе defaultproperties добавились дополнительные строчки:
bIsRegenEffect=True
InstFog=(X=475.000000,Y=325.000000,Z=145.000000)
InstFlash=-0.400000
ShellSkin=FireTexture'UnrealShare.Belt_fx.ShieldBelt.RedShield'
ShellType=Class'Vampire.VampireShell'
 
Тут мы устанавливаем значение переменной bIsRegenEffect по-умолчанию равным True (показывать оболочку). Переменные InstFog и InstFlash отвечают за некоторые графические эффекты, а переменные ShellType и ShellSkin определяют тип класс оболочки и текстуру оболочки соответственно.
 
Подключаем конфигурационный файл
Иногда необходимо, чтобы ваши настройки для какого-либо мода сохранялись при выходе из Unreal Tournament в конфигурационном файле. Существует очень простой способ, позволяющий классам UnrealScript сохранять и загружать данные, используя при этом INI-файлы. Это очень удобно, так как вы можете конфигурировать ваш класс (например, менять значения некоторых полей и свойств) без повторной компиляции.
 
Такой механизм обращения к INI-файлам реализуется с помощью ключевого слова Config, которое и указывает, какую переменную и в каком файле сохранять. Для подключения конфигурационного файла к нашему мутатору достаточно добавить несколько дополнительных слов в файл Vampire.uc в разделе описания класса и переменных:
//===================================
// Vampire ; This is mutator script.
// Description: You gain life equal to the amount of damage you do to
// an enemy.
//===================================

class Vampire expands Mutator config(Vampire);

var bool Initialized;
var vector InstFog;
var float InstFlash;
var VampireShell VampireShellEffect;
var texture ShellSkin;
var class<VampireShell> ShellType;

var() config float LifeStolenPerHit;
var() config int MaxLife;
var() config bool bInflictedDamageOnly;
var() config bool bIsRegenEffect;
 
Как видите, в строке описания класса добавилось слово config. Это же слово появилось и в описании некоторых переменных.
Ключевое слово config в описании класса означает, что значения по умолчанию полей данного класса будут сохраняться и загружаться из INI-файла. Если после слова config, которое стоит в строке определения класса вставлены скобки с каким-либо именем в них, то значения полей будут сохраняться в INI-файл с таким именем (в папке System), а если скобок нет, то данные будут автоматически сохраняться в файле UnrealTournament.ini в секции с именем, состоящим из названия вашего пакаджа и имени класса:
[<Имя пакаджа>.<Имя класса>]
 
Слово config, стоящее в описании переменной означает, что данная переменная класса будет сохраняться в конфигурационном файле.
Вот и всё, что необходимо было сделать.
 
Добавляем интерфейс
Что бы наш мутатор принял профессиональный вид и мог легко конфигурироваться, можно ввести также поддержку интерфейса пользователя. UnrealScript позволяет довольно быстро создавать окна настроек мутаторов и модов. Эти окна настроек вызываются из меню Mod игры. Мы рассмотрим этот процесс на примере небольшого окна с малым количеством элементов управления.
 
Для того что бы окно корректно работало необходимо описать как минимум 3 класса:
  • новая единица в меню Mod;
  • фрейм окна;
  • клиентская область окна.
 
Для этого создайте в папке Vampire три текстовых файла и назовите их:
  • VampireModMenuItem.uc
  • VampireConfigWindow.uc
  • VampireClientWindow.uc
 
Откройте эти файлы поочерёдно и вставьте в них следующие строки:
 
1. Текст файла VampireModMenuItem.uc:
//===================================
// VampireModMenuItem ; This is script for menu item
// "Vampire Configuration" in the Mod menu.
//===================================

class VampireModMenuItem expands UMenuModMenuItem;

function Execute()
{
MenuItem.Owner.Root.CreateWindow(class'
VampireConfigWindow',10,10,320,175);
}
 
Описание:
Этот класс описывает пункт меню, при нажатии на который будет вызвано окно нашего мутатора. Функция Execute обрабатывает нажатие на данный пункт и создаёт окно настроек с помощью функции CreateWindow.
 
2. Текст файла VampireConfigWindow.uc:
//===================================
// VampireConfigWindow ; This is frame for
// Vampire's configuration window.
//===================================

class VampireConfigWindow expands UWindowFramedWindow;

function BeginPlay()
{
Super.BeginPlay();

WindowTitle = "Vampire Configuration";

ClientClass = class'Vampire.VampireClientWindow';

bSizable = False;
}

function Created()
{
Super.Created();
SetSize(320, 175);
WinLeft = (Root.WinWidth - WinWidth) / 2;
WinTop = (Root.WinHeight - WinHeight) / 2;
}
 
Описание:
Данный класс отвечает за создание окна (прямоугольной области с заголовком). Тут устанавливается заголовок окна, его размеры, положение на экране и т.д.
 
3. Текст файла VampireClientWindow.uc: 
//===================================
// VampireClientWindow ; This is client part of
// Vampire's configuration window.
//===================================

class VampireClientWindow expands UWindowDialogClientWindow;

var UWindowSmallCloseButton CloseButton;
var UWindowEditControl Edit[2];
var UWindowCheckbox OverDamage_Check,RegenEffect_Check;

var string EditBoxString[2];
var string OverDamageCheckString;
var string RegenEffectCheckString;
var string EditBoxHelpString[2];
var string OverDamageCheckHelpString;
var string RegenEffectCheckHelpString;

Var UWindowLabelControl TitleLabel;
Var UWindowLabelControl PermissionsLabel;
Var UWindowLabelControl CopyrightLabel;
Var UWindowLabelControl SplitLabel;

// called after object has been created...add content
function Created()
{
local int i,j,X,Y,Width;

EditBoxString[0] = "Life stolen per hit (%)";
EditBoxString[1] = "Maximum amount of hit points (0 = No limit)";
OverDamageCheckString = "Only inflicted damage comes to life";
RegenEffectCheckString = "Show regeneration effect";
EditBoxHelpString[0] = "Life stolen per hit (%)";
EditBoxHelpString[1] = "Max life";
OverDamageCheckHelpString = "Only inflicted damage
comes to life (Damage <= Victim's life)";
RegenEffectCheckHelpString = "Show regeneration effect";

CloseButton = UWindowSmallCloseButton(CreateControl(class
'UWindowSmallCloseButton', WinWidth-50, WinHeight-18, 48, 16));
CloseButton.SetHelpText("Close config window
and save your current settings!");

X = 10;
Y = 10;

for(i=0;i<2;i++)
{
Edit[i] = UWindowEditControl(CreateControl(class
'UWindowEditControl', X,Y, 300, 1));
Edit[i].SetText(EditBoxString[i]);
Edit[i].SetHelpText(EditBoxHelpString[i]);
Edit[i].SetFont(F_Normal);
Edit[i].SetNumericOnly(True);
Edit[i].SetMaxLength(3);
Edit[i].Align = TA_Left;
Edit[i].EditBoxWidth = 40;
Y += 20;
}
Edit[0].SetValue(string(int(100*class'Vampire'
.Default.LifeStolenPerHit)));
Edit[1].SetValue(string(class'Vampire'.Default.MaxLife));

OverDamage_Check = UWindowCheckbox(CreateControl
(class'UWindowCheckbox', X, Y, 300, 1));
if(class'Vampire'.default.bInflictedDamageOnly)
OverDamage_Check.bChecked = true; else OverDamage_Check.bChecked = false;
OverDamage_Check.SetText(OverDamageCheckString);
OverDamage_Check.SetHelpText(OverDamageCheckHelpString);
OverDamage_Check.SetFont(F_Normal);
OverDamage_Check.Align = TA_Left;
Y += 20;

RegenEffect_Check = UWindowCheckbox
(CreateControl(class'UWindowCheckbox', X, Y, 300, 1));
if(class'Vampire'.default.bIsRegenEffect)
RegenEffect_Check.bChecked = true; else
RegenEffect_Check.bChecked = false;
RegenEffect_Check.SetText(RegenEffectCheckString);
RegenEffect_Check.SetHelpText(RegenEffectCheckHelpString);
RegenEffect_Check.SetFont(F_Normal);
RegenEffect_Check.Align = TA_Left;
Y += 20;

SplitLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 10, Y, 300, 1));
SplitLabel.SetText("----------------------
-----------------------------------------------------");
SplitLabel.SetFont(F_Bold);
Y += 10;
TitleLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 20, Y, 300, 1));
TitleLabel.SetText("Vampire - Mutator V1.00");
TitleLabel.SetFont(F_Bold);
Y += 10;
PermissionsLabel = UWindowLabelControl
(CreateControl(class 'UWindowLabelControl', 20, Y, 300, 1));
PermissionsLabel.SetText("Free for noncommercial
use and distribution.");
Y += 10;
CopyrightLabel = UWindowLabelControl
(CreateControl(class 'UWindowLabelControl', 20, Y, 300, 1));
CopyrightLabel.SetText("Copyright 2002
by Your_Name 'Your_Nick' Your_SecondName.");
Y += 10;

Super.Created();
}

///////////////////////////////////////////////////
//when a control changes, Notify is called with the changed control
///////////////////////////////////////////////////
function Notify(UWindowDialogControl C, byte E)
{
local int i;
super.Notify(C, E);
if(E==DE_Change)
for (i=0;i < ArrayCount(Edit);i++)
{
if(C==Edit[i])
ApplyEdit(i);
}
switch(E)
{
case DE_Change:
switch(C)
{
case CloseButton: Close(); break;
case OverDamage_Check: ApplyOverDamage(); break;
case RegenEffect_Check: ApplyRegenEffect(); break;
}
}
}
function ApplyOverDamage()
{
class'Vampire'.default.bInflictedDamageOnly =
OverDamage_Check.bChecked;;
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
function ApplyRegenEffect()
{
class'Vampire'.default.bIsRegenEffect = RegenEffect_
Check.bChecked;;
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
function ApplyEdit(int i)
{
switch(i)
{
case 0: class'Vampire'.default.
LifeStolenPerHit = 0.01*float(int(Edit[i].GetValue())); break;
case 1: class'Vampire'.default.MaxLife = int
(Edit[i].GetValue()); break;
}
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
///////////////////////////////////////////////////
function Close(optional bool bByParent)
{
Super.Close(bByParent);
}
///////////////////////////////////////////////////

defaultproperties
{
}
 
Описание:
Этот класс определяет клиентскую область окна, то есть область, на которой создаются все элементы управления. Также тут обрабатываются все события, которые вызывает пользователь при работе с элементами интерфейса. Сначала все элементы должны быть описаны подобно переменным. В функции Created элементы интерфейса создаются на клиентской области и инициализируются (устанавливаются координаты, индивидуальные свойства и т.д.)
 
В функции Notify происходит обработка событий вызываемых при нажатии на кнопку или изменении числа в поле ввода. На вход в качестве параметров эта функция принимает имя элемента интерфейса и число, которое определяет тип вызванного события. Также необходимо что бы игра «знала» о новом пункте в меню Mod. Для этого открываем наш Vampire.int файл и добавляем туда строчку, отвечающую за пункт меню. После изменения файл должен выглядеть следующим образом:
[Public]
Object=(Name=Vampire.Vampire,Class=Class,MetaClass=
Engine.Mutator,Description="Vampire,You gain life equal to
the amount of damage you do to an enemy.")
Object=(Name=Vampire.VampireModMenuItem,Class=Class,
MetaClass=UMenu.UMenuModMenuItem,Description="Vampire Configuration")
 
Как видите была добавлена ещё одна строчка, которая начинается так же как и первая со слова Object, но описывает не мутатор, а пункт меню.
 
Конечный результат
В конце мы должны получить пять файлов в папке Vampire:
  1. Vampire.uc
  2. VampireShell.uc
  3. VampireModMenuItem.uc
  4. VampireConfigWindow.uc
  5. VampireClientWindow.uc
 
Эти файлы должны иметь такой вид:
 
1.) Текст файла Vampire.uc 
//===================================
// Vampire ; This is mutator script.
// Description: You gain life equal to the amount of damage you do to
// an enemy.
//===================================

class Vampire expands Mutator config(Vampire);

var bool Initialized;
var vector InstFog;
var float InstFlash;
var VampireShell VampireShellEffect;
var texture ShellSkin;
var class<VampireShell> ShellType;

var() config float LifeStolenPerHit;
var() config int MaxLife;
var() config bool bInflictedDamageOnly;
var() config bool bIsRegenEffect;

function PostBeginPlay()
{
if (Initialized)
return;
Initialized = True;
Level.Game.RegisterDamageMutator( Self );
}

function MutatorTakeDamage( out int ActualDamage,
Pawn Victim, Pawn InstigatedBy, out Vector HitLocation,
out Vector Momentum, name DamageType)
{
if ((InstigatedBy.IsA('Bot') || InstigatedBy.IsA('PlayerPawn'))
&& (Victim != InstigatedBy) && (InstigatedBy != None) && (Victim != None))
{
if ((Victim.Health < ActualDamage) && bInflictedDamageOnly)
ActualDamage = Victim.Health;
InstigatedBy.Health += ActualDamage * LifeStolenPerHit;

if (bIsRegenEffect && (MaxLife != 0 ))
{
FlashShell(InstigatedBy);
if ( InstigatedBy.IsA('PlayerPawn') )
PlayerPawn(InstigatedBy).ClientInstantFlash(InstFlash, InstFog);
}

if ((InstigatedBy.Health > MaxLife) && (MaxLife != 0))
{
InstigatedBy.Health = MaxLife;
}
}

if ( NextDamageMutator != None )
NextDamageMutator.MutatorTakeDamage( ActualDamage,
Victim, InstigatedBy, HitLocation, Momentum, DamageType );
}

function FlashShell(pawn Other)
{
if (VampireShellEffect == None)
{
VampireShellEffect = Spawn(ShellType, Other,,Other.Location, Other.Rotation);
}
if ( VampireShellEffect != None )
{
VampireShellEffect.Mesh = Other.Mesh;
VampireShellEffect.DrawScale = Other.Drawscale;
VampireShellEffect.Texture = ShellSkin;
}
}

defaultproperties
{
LifeStolenPerHit=1.000000
MaxLife=200
bInflictedDamageOnly=True
bIsRegenEffect=True
InstFog=(X=475.000000,Y=325.000000,Z=145.000000)
InstFlash=-0.400000
ShellSkin=FireTexture'UnrealShare.Belt_fx.ShieldBelt.RedShield'
ShellType=Class'Vampire.VampireShell'
}
 
2.) Текст файла VampireShell.uc 
//===================================
// VampireShell ; This is effect script.
// Description: Red shell around damage instigator.
//===================================

class VampireShell expands Effects;

defaultproperties
{
bAnimByOwner=True
bOwnerNoSee=True
bNetTemporary=False
bTrailerSameRotation=True
Physics=PHYS_Trailer
RemoteRole=ROLE_SimulatedProxy
LODBias=0.500000
DrawType=DT_Mesh
Style=STY_Translucent
Texture=None
ScaleGlow=0.500000
AmbientGlow=64
Fatness=157
bUnlit=True
bMeshEnviroMap=True
LifeSpan=0.500000
}
 
3.) Текст файла VampireModMenuItem.uc
//===================================
// VampireModMenuItem ; This is script for menu item
// "Vampire Configuration" in the Mod menu.
//===================================

class VampireModMenuItem expands UMenuModMenuItem;

function Execute()
{
MenuItem.Owner.Root.CreateWindow(class
'VampireConfigWindow',10,10,320,175);
}
 
4.) Текст файла VampireConfigWindow.uc
//===================================
// VampireConfigWindow ; This is frame for
// Vampire's configuration window.
//===================================

class VampireConfigWindow expands UWindowFramedWindow;

function BeginPlay()
{
Super.BeginPlay();

WindowTitle = "Vampire Configuration";

ClientClass = class'Vampire.VampireClientWindow';

bSizable = False;
}

function Created()
{
Super.Created();
SetSize(320, 175);
WinLeft = (Root.WinWidth - WinWidth) / 2;
WinTop = (Root.WinHeight - WinHeight) / 2;
 
5.) Текст файла VampireClientWindow.uc
//===================================
// VampireClientWindow ; This is client part of
// Vampire's configuration window.
//===================================

class VampireClientWindow expands UWindowDialogClientWindow;

var UWindowSmallCloseButton CloseButton;
var UWindowEditControl Edit[2];
var UWindowCheckbox OverDamage_Check,RegenEffect_Check;

var string EditBoxString[2];
var string OverDamageCheckString;
var string RegenEffectCheckString;
var string EditBoxHelpString[2];
var string OverDamageCheckHelpString;
var string RegenEffectCheckHelpString;

Var UWindowLabelControl TitleLabel;
Var UWindowLabelControl PermissionsLabel;
Var UWindowLabelControl CopyrightLabel;
Var UWindowLabelControl SplitLabel;

// called after object has been created...add content
function Created()
{
local int i,j,X,Y,Width;

EditBoxString[0] = "Life stolen per hit (%)";
EditBoxString[1] = "Maximum amount of hit points (0 = No limit)";
OverDamageCheckString = "Only inflicted damage comes to life";
RegenEffectCheckString = "Show regeneration effect";
EditBoxHelpString[0] = "Life stolen per hit (%)";
EditBoxHelpString[1] = "Max life";
OverDamageCheckHelpString = "Only inflicted damage comes to life
(Damage <= Victim's life)";
RegenEffectCheckHelpString = "Show regeneration effect";

CloseButton = UWindowSmallCloseButton
(CreateControl(class'UWindowSmallCloseButton',
WinWidth-50, WinHeight-18, 48, 16));
CloseButton.SetHelpText("Close config window
and save your current settings!");

X = 10;
Y = 10;

for(i=0;i<2;i++)
{
Edit[i] = UWindowEditControl(CreateControl
(class'UWindowEditControl', X,Y, 300, 1));
Edit[i].SetText(EditBoxString[i]);
Edit[i].SetHelpText(EditBoxHelpString[i]);
Edit[i].SetFont(F_Normal);
Edit[i].SetNumericOnly(True);
Edit[i].SetMaxLength(3);
Edit[i].Align = TA_Left;
Edit[i].EditBoxWidth = 40;
Y += 20;
}
Edit[0].SetValue(string(int(100*class'Vampire'
.Default.LifeStolenPerHit)));
Edit[1].SetValue(string(class'Vampire'.Default.MaxLife));

OverDamage_Check = UWindowCheckbox(CreateControl
(class'UWindowCheckbox', X, Y, 300, 1));
if(class'Vampire'.default.bInflictedDamageOnly)
OverDamage_Check.bChecked = true; else
OverDamage_Check.bChecked = false;
OverDamage_Check.SetText(OverDamageCheckString);
OverDamage_Check.SetHelpText(OverDamageCheckHelpString);
OverDamage_Check.SetFont(F_Normal);
OverDamage_Check.Align = TA_Left;
Y += 20;

RegenEffect_Check = UWindowCheckbox(CreateControl
(class'UWindowCheckbox', X, Y, 300, 1));
if(class'Vampire'.default.bIsRegenEffect)
RegenEffect_Check.bChecked = true;
else RegenEffect_Check.bChecked = false;
RegenEffect_Check.SetText(RegenEffectCheckString);
RegenEffect_Check.SetHelpText(RegenEffectCheckHelpString);
RegenEffect_Check.SetFont(F_Normal);
RegenEffect_Check.Align = TA_Left;
Y += 20;

SplitLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 10, Y, 300, 1));
SplitLabel.SetText
("--------------------------------------
-------------------------------------");
SplitLabel.SetFont(F_Bold);
Y += 10;
TitleLabel = UWindowLabelControl(CreateControl(class
'UWindowLabelControl', 20, Y, 300, 1));
TitleLabel.SetText("Vampire - Mutator V1.00");
TitleLabel.SetFont(F_Bold);
Y += 10;
PermissionsLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 20, Y, 300, 1));
PermissionsLabel.SetText("Free for noncommercial
use and distribution.");
Y += 10;
CopyrightLabel = UWindowLabelControl(CreateControl
(class 'UWindowLabelControl', 20, Y, 300, 1));
CopyrightLabel.SetText("Copyright 2002
by Your_Name 'Your_Nick' Your_SecondName.");
Y += 10;

Super.Created();
}

///////////////////////////////////////////////////
//when a control changes, Notify is called with the changed control
///////////////////////////////////////////////////
function Notify(UWindowDialogControl C, byte E)
{
local int i;
super.Notify(C, E);
if(E==DE_Change)
for (i=0;i < ArrayCount(Edit);i++)
{
if(C==Edit[i])
ApplyEdit(i);
}
switch(E)
{
case DE_Change:
switch(C)
{
case CloseButton: Close(); break;
case OverDamage_Check: ApplyOverDamage(); break;
case RegenEffect_Check: ApplyRegenEffect(); break;
}
}
}
function ApplyOverDamage()
{
class'Vampire'.default.bInflictedDamageOnly =
OverDamage_Check.bChecked;;
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
function ApplyRegenEffect()
{
class'Vampire'.default.bIsRegenEffect = RegenEffect_Check.bChecked;;
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
function ApplyEdit(int i)
{
switch(i)
{
case 0: class'Vampire'.default.LifeStolenPerHit = 0.01*float
(int(Edit[i].GetValue())); break;
case 1: class'Vampire'.default.MaxLife = int(Edit[i].GetValue());
break;
}
class'Vampire'.SaveConfig();
class'Vampire'.static.StaticSaveConfig();
}
///////////////////////////////////////////////////
function Close(optional bool bByParent)
{
Super.Close(bByParent);
}
///////////////////////////////////////////////////

defaultproperties
{
}
 
Также мы имеем файл Vampire.int в папке System, который должен выглядеть так:
[Public]
Object=(Name=Vampire.Vampire,Class=Class,MetaClass=Engine.
Mutator,Description="Vampire,You gain life equal to the
amount of damage you do to an enemy.")
Object=(Name=Vampire.VampireModMenuItem,Class=Class,
MetaClass=UMenu.UMenuModMenuItem,Description="Vampire Configuration")
 
Вот и всё – осталось только скомпилировать исходный код и начать играть.
 
Скачать уже готовый мутатор:
 
Надеюсь вы узнали из этого гайда что-нибудь интересное для себя.


Автор: Sandman

Unreal Tournament — Создание простого мутатора на примере Vampire | Creating a simple mutator using the example of Vampire Sandman" />
HTML-cсылка: BB-cсылка: Прямая ссылка:
КОММЕНТАРИИ
Имя*
E-Mail
Кликните на изображение чтобы обновить код, если он неразборчив

Переводчик

Перейти к статьям

ТОП 7 Недели