Перегрузка функций

Добавлен , опубликован

Основы UnrealScript: Учимся на практике

Содержание:

Перегрузка функций

Так же, как и в случае с переменными, функции родительских классов также переходят новым скриптам. И, так же, как и переменные, эти функции в новых классах можно менять. Для примера, заглянем в Inventory\UTInventory\UTTimedPowerup. Там мы найдем два усилителя из UT3 - UTBerserk и UTUDamage. Каждый из них наследует от Inventory функцию GivenTo. Несмотря на то, что в обоих классах название функции одинаковое, в UTBerserk она влияет на скорость стрельбы подобравшего бонус игрока, а в UTUDamage увеличивает наносимый оружием урон. Также у каждого класса свое звуковое сопровождение для этой функции. В этом и заключается перегрузка функций - изменение или дополнение действия, которое совершает функция.
Для демонстрации, как же перегрузка функций может повлиять на игру, мы создадим собственные PlayerController и GameInfo

Создание собственных ​Game​Info и ​Player​Controller

GameInfo определяет правила игры. Примерами разных GameInfo являются Deathmatch и Capture The Flag. Это один из наиболее важных файлов в новой игре, и, чаще всего, именно он создается первым.
PlayerController - "мозг" нашего персонажа в игре. Именно он принимает сигналы, которые мы подаем через клавиатуру и мышку, он управляет нашим взглядом (сиречь, камерой), обменивается информацией с другими игроками, и много чего другого важного.
Создать начальную заготовку для GameInfo не так уж и сложно. Создайте новый класс с названием TestGame.uc
class TestGame extends UTDeathmatch;

defaultproperties
{
}
UTDeathmatch весьма неплохой класс для новой игры, даже если это будет одиночная игра, или игра без убийств. В нем и его родительских классах есть весь функционал для спавна игрока, расчета условий победы и т.д..
Теперь PlayerController. Создайте скрипт под названием TestPlayerController.uc и наберите там следующий код.
class TestPlayerController extends UTPlayerController;

simulated function PostBeginPlay()
{
     super.PostBeginPlay();
     `log("TestPlayerController spawned!");
}

defaultproperties
{
}
Помимо простой заготовки для контроллера, мы также добавили отладочный код, чтобы проверить, действительно ли создался нужный нам PC (буду сокращать, так как очень долго пишется).
Одной из обязанностей GameInfo является определение, какой же PC будет спавниться. Для того, чтобы сказать игре, чтобы она создавала именно наш контроллер, добавьте в defaultproperties в нашем TestGame.uc следующую строчку
PlayerControllerClass=class'TestGame.TestPlayerController'
Первая часть в кавычках после class является названием папки, где находятся наши скрипты. PlayerControllerClass - переменная, которая объявлена в GameInfo, и наследуется всеми его подклассами, включая TestGame.
Скомпилируйте код.
Для того, чтобы наша игра запускалась именно с нашим типом игры и именно на той карте, которую мы создали, и, в то же время, чтобы не приходилось каждый раз запускать ее с редактора, мы можем создать bat-файл.
Создайте на рабочем столе любой текстовый файл. В нем запишите следующее.
<путь к корневой папке UDK>\Binaries\Win32\UDK.exe USTutorialMap?GoalScore=0?TimeLimit=0?Game=TestGame.TestGame  -log
Эта строчка говорит нам запустить файл UDK.exe, загрузив сразу определенную карту (в нашем случае, USTutorialMap, которую я предоставил в первой подстатье), без лимита времени и очков (для игрового режима Deathmatch), и используя тип игры TestGame, который находится в папке TestGame. А -log обозначает, что мы хотим параллельно запустить окошко лога, которое будет изменяться в реальном времени (иногда бывает полезно туда заглядывать).
Напоследок переименуйте файл в, например, Test Game.bat. Само название значения не имеет, главное, чтобы расширение было bat. Теперь этот файл будет запускать нашу пробную игру.
Запустите игру, после чего проверьте лог. Там вы должны обнаружить
[0005.67] ScriptLog: TestPlayerController spawned!
Теперь мы знаем, что создается именно наш контроллер. Но что с ним делать дальше?

Изменение камеры с помощью ​Test​Player​Controller

Мы можем использовать функции PC, чтобы менять положение и направление нашей камеры. Давайте сделаем камеру с видом сверху, чтобы превратить нашу игру в эдакое подобие Crimsonland или Gauntlet.
За положение камеры отвечает функция GetPlayerViewPoint, которую мы и перегрузим.
С предыдущих подстатей мы должны знать уже, что с помощью векторов можно хранить расположение акторов в игровом мире. Чтобы наша камера всегда была в одном месте относительно игрока, мы можем использовать вектор положения игрока и вектор смещения. Для пущего понятия рисунок.
Мы могли бы просто добавить определенную величину в функции, но создать соответствующий вектор будет куда лучше, так как код будет более организованным. Внесите следующие изменения в наш PC
class TestPlayerController extends UTPlayerController;

var vector PlayerViewOffset;

defaultproperties
{
    PlayerViewOffset=(X=-64,Y=0,Z=1024)
}
Смещение по Z поднимет камеру вверх, а по X - сдвинет немного вперед. Числа можно менять по своему вкусу, но мне пока подходят и такие. Если хотите можете сдвинуть даже по Y.
Теперь сама функция. Объявлена она в Controller.uc, там мы и видим, как же она должна быть записана. Забыл сказать, при перегрузке функции мы обязательно должны объявлять ее абсолютно также, как она была объявлена изначально.
simulated event GetPlayerViewPoint(out vector out_Location, out Rotator out_Rotation)
Добавим ее в код нашего контроллера
simulated event GetPlayerViewPoint(out vector out_Location, out Rotator out_Rotation)
{
}
Первым делом, нужно вызвать родительскую версию этой функции, так как, не сделав этого, мы упустим много необходимых для корректной работы игры операций. Делается это через приписку super.
simulated event GetPlayerViewPoint(out vector out_Location, out Rotator out_Rotation)
{
     super.GetPlayerViewPoint(out_Location, out_Rotation);
}
Пока мы ничего не изменили. После компиляции и запуска это будет обычный шутер от первого лица. Для изменений нужно непосредственно применить наш сдвиг. Добавьте следующий код после супера
if(Pawn != none)
{
    out_Location = Pawn.Location + PlayerViewOffset;
    out_Rotation = rotator(Pawn.Location - out_Location);
}
Теперь объясняю по шагам.
Для начала мы проверяем, есть ли у нашего контроллера павн. Павн - это физическое воплощение нашего персонажа - его модель, здоровье, показатели, именно он вместо нас двигается, стреляет и т.д.. Контроллер - просто "интеллектуальная" часть персонажа, он даже не двигается. И для того, чтобы следовать за персонажем, мы должны смещать камеру именно относительно павна. Именно этому посвящена первая строчка после if - мы задаем положение камере, просто сдвинувшись от положения павна.
Вторая строчка посложнее. Нам нужно, чтобы камера всегда смотрела на павна. Для этого мы вычитаем положение камеры с положения павна, получая вектор, направленный от камеры до павна, и превращаем его в ротатор. Чтобы было лучше понятно, объяснение в картинках. Припустим, нам необходимо найти вектор С, имея вектор А и вектор В.
Чтобы получить С, мы можем просто развернуть А, и добавить к получившемуся -А вектор В
В итоге, мы получим С=-А+В, или можно переписать как С=В-А. В нашем случае, В - это положение павна, а А - положение камеры.
Скомпилируйте код и запустите игру.
"Что за нафиг?!" спросите вы. Мы стреляем только в землю, да и самого игрока не видно, только огромная пушка. Привыкайте, процентов в 80 всех случаев в программировании ваша программа не будет работать правильно сразу. Все программирование основано на принципе "пусть после того, как я решу эту проблему, возникнет новая, но я все равно ближе к тому, что задумал вначале". Главное - уметь исправлять текущие проблемы. А главный шаг к этому - знание, почему случилось именно так, а не иначе. Начнем с невидимого павна.
Дело в том, что по умолчанию модель павна скрыта для взгляда самого игрока. Если вы спросите "а почему же я вижу руки персонажа и оружие?", ответ предельно прост - это отдельные модели, которые видите только вы. Руки существуют только до локтей. Дело в том, что по умолчанию вы смотрите как бы из головы павна. И если бы павн не был невидимым, вы бы видели не пространство впереди, а кучу полигонов, которыми является поверхность головы павна изнутри. Именно поэтому его прячут.
Но ведь нам это не грозит. Поэтому это можно изменить. Решается это следующим образом. Добавьте в функцию строчку Pawn.Mesh.SetOwnerNoSee(false);, чтобы она выглядела так
simulated event GetPlayerViewPoint(out vector out_Location, out Rotator out_Rotation)
{
     super.GetPlayerViewPoint(out_Location, out_Rotation);

     if(Pawn != none)
     {
          Pawn.Mesh.SetOwnerNoSee(false);

          out_Location = Pawn.Location + PlayerViewOffset;
          out_Rotation = rotator(Pawn.Location - out_Location);
     }
}
Скомпилируйте и запустите.
Уже лучше? Немного мозолит глаза прицел, но нам пока не до него, у нас проблемы посерьезней. Если бы на нас напали, мы бы ничего не смогли сделать, так как стреляем в землю. Этим и займемся.
По умолчанию, в обычном шутере от первого лица, когда стреляет оружие, игра спрашивает у контроллера, куда направлять снаряд. Обычно контроллер приказывает направлять его по направлению камеры. Но нам это абсолютно не подходит, так как смотрит наша камера в землю под павном. Для решения проблемы существует функция GetAdjustedAimFor, которая изменяет направление стрельбы. Добавьте следующий код после нашей GetPlayerViewPoint
function Rotator GetAdjustedAimFor( Weapon W, vector StartFireLoc)
{
     return Pawn.Rotation;
}
Этим мы говорим контроллеру использовать направление нашего павна вместо направления камеры. Поскольку павн всегда находится в вертикальном положении, снаряды всегда будут лететь горизонтально. Для проверки, полный код нашего контроллера на этот момент
TestPlayerController.uc
class TestPlayerController extends UTPlayerController;

var vector PlayerViewOffset;

simulated event GetPlayerViewPoint(out vector out_Location, out Rotator out_Rotation)
{
    if(Pawn != none)
    {
        Pawn.Mesh.SetOwnerNoSee(false);

        out_Location = Pawn.Location + PlayerViewOffset;
        out_Rotation = rotator(Pawn.Location - out_Location);
    }
}

function Rotator GetAdjustedAimFor( Weapon W, vector StartFireLoc)
{
     return Pawn.Rotation;
}

defaultproperties
{
     PlayerViewOffset=(X=-64,Y=0,Z=1024)
}
Скомпилируйте и проверьте игру.
Еще лучше. Теперь можно взяться и за прицел.
Удалить прицел мы можем тремя способами. Первый - за это отвечает конфигурационная переменная в контроллере, и мы можем поменять ее по умолчанию, в соответственном INI файле, но в таком случае, игрок сможет вернуть ее в обратное состояние. Второй - мы можем удалить прицел из соответственного файла ScaleForm, но ScaleForm я в этой статье затрагивать не буду. Третий способ наиболее надежный - отключать прицел в начале каждой игры. Сделать это можно с помощью уже знакомой вам функции PostBeginPlay(). Добавьте ее перед самой первой функцией в контроллере:
simulated function PostBeginPlay()
{
    super.PostBeginPlay();
    bNoCrosshair = true;
}
Мы вызвали супер (наиболее важный шаг в перегрузке функций), после чего поменяли соответственную переменную. Скомпилируйте и проверьте.
Почти закончили. Осталась только гигантская пушка.
Помните, я говорил про вид от первого лица? Висящее в воздухе оружие как раз является моделью, которую мы видим от первого лица. Ее тоже можно спокойно отключить. Добавьте следующий код в блок if нашей функции GetPlayerViewPoint
if(Pawn.Weapon != none)
    Pawn.Weapon.SetHidden(true);
Таким образом, мы проверяем, есть ли у нашего павна оружие, и, в таком случае, мы прячем его большую версию.
Последняя проверка после компиляции:
Успех, товарищи!
Если для кого-то успеха нет, и компилятор ругается, вот вам окончательный вариант всего кода контроллера
TestPlayerController
class TestPlayerController extends UTPlayerController;

var vector PlayerViewOffset;

simulated function PostBeginPlay()
{
    super.PostBeginPlay();
    bNoCrosshair = true;
}

simulated event GetPlayerViewPoint(out vector out_Location, out Rotator out_Rotation)
{
    if(Pawn != none)
    {
        if(Pawn.Weapon != none)
            Pawn.Weapon.SetHidden(true);

        Pawn.Mesh.SetOwnerNoSee(false);

        out_Location = Pawn.Location + PlayerViewOffset;
        out_Rotation = rotator(Pawn.Location - out_Location);
    }
}

function Rotator GetAdjustedAimFor( Weapon W, vector StartFireLoc)
{
     return Pawn.Rotation;
}

defaultproperties
{
    PlayerViewOffset=(X=-64,Y=0,Z=1024)
}
Я знаю, что подобный тип управления ужасен, но пока придется довольствоваться ним, тем более, мы его немного упростим. После завершения полной статьи, я, возможно, проведу несколько экспериментов с управлением с помощью мыши.

Добавив немного кода в два скрипта мы серьезно поменяли нашу игру. Поэкспериментировав с перегрузкой функций, в частности, GetPlayerViewPoint, мы можем сделать стратегию, платформер или экшен от третьего лица.
В уроке мы воспользовались исключительно стандартными функциями. Подобных полезных функций полным полно, именно поэтому при создании нового скрипта советую часто заглядывать в исходники его родительских классов, вплоть до Actor и Object.
На этом все, в следующих уроках мы будем экспериментировать с акторами в качестве переменных.