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

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

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

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

Создание собственных GameInfo и PlayerController

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!
Теперь мы знаем, что создается именно наш контроллер. Но что с ним делать дальше?

Изменение камеры с помощью TestPlayerController

Мы можем использовать функции 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.
На этом все, в следующих уроках мы будем экспериментировать с акторами в качестве переменных.

Просмотров: 5 634

Montaro #1 - 7 лет назад 1
Хм... Написать Crimsonland на UDK оказывается не так трудно) а главное графика... Если поэкспериментировать с камерой (дрожь, небольшие наклоны) и добавить какой-нибудь блюр... недалеко и до зомбидривера))
Heroic_Pizza #2 - 7 лет назад 0
Не работает, начиная с камеры. Такое ощущение, что TestPlayerController не действует абсолютно. Зато всё отлично компилируется.
В логе изначально не было [0005.67] ScriptLog: TestPlayerController spawned!. Плюс какие-то ошибки. Скрин всего лога прилагается. Я вообще всё перепробовал, хотя уверен, что как обычно где-то не заметил маленький косяк.
Даже в DafaultGame.ini прописал стандартно:
DefaultGame=TestGame.TestGame
DefaultServerGame=TestGame.TestGame
PlayerControllerClassName=TestGame.TestPlayerController
Нашел, в чём была проблема.
У вас тут PlayerControllerClass=class'TestGame.TestPlayerController' чуть неправильно. Должен быть пробел между class и 'TestPlayerController'
прикреплены файлы
lentinant #3 - 7 лет назад 0
Heroic_Pizza, не знаю, в чем у вас была проблема, но в деф пропсах никаких пробелов быть не должно. Единственное, что тут может быть не так - необязательно писать папку со скриптами перед названием скрипта контроллера. В моем проекте спокойно работает
PlayerControllerClass=class'GFPlayerController'
Heroic_Pizza #4 - 7 лет назад 0
lentinant, у меня без пробела не работало.
Кстати, другой вопрос появился, раз вы заглянули.
Как от третьего лица сделать камеру, используя данный туториал? Если использовать только GetPlayerViewPoint, то что там нужно изменить?
lentinant #5 - 7 лет назад (отредактировано ) 0
Heroic_Pizza, можно сделать и от третьего лица, но тут нужно несколько серьезных правок, и не факт, что вы обойдетесь одним GetPlayerViewPoint. Поставить камеру за спину персонажу можно просто поменяв оффсет. А вот поворот камеры придется контролировать другим образом. В принципе, как организовать поворот влево-вправо, я представляю (но поскольку я пока бросил эту статью, сконцентрировавшись на собственном игровом проекте, вы этого тут не прочитаете), но вот с поворотом вверх-вниз придется помучиться.
Heroic_Pizza #6 - 7 лет назад 0
lentinant, в общем не всё так просто, да? Ладно, я просто не знаю, где найти туториал камеры от третьего лица с объяснениями и чтоб всё было понятно, а не просто код и куда его пихать. У вас вон объяснения почти к каждой строчке, всё ясно, хоть и сложно.
Спасибо, что уделили внимание.
lentinant #7 - 7 лет назад 1
Heroic_Pizza, могу дать название оригинала, это англоязычная книга. Конечно, перевожу я достаточно вольно, при необходимости дополняя текст собственными объяснениями, но, тем не менее, куски кода и рисунки перекочевали оттуда почти без изменений (разве что нужно будет изменить все Test в коде на Awesome). Она должна дать базовые знания и навыки в программировании на US. Нужно помнить, что самый важный навык программиста-амматора - умение находить необходимые элементы для своего кода в уже существующих исходниках. А туторов полно. В UDK есть полноценный сайт с инфой, где можно найти все - от редактора физики и инструкций по созданию контента до типов камер и различных жанровых элементов, правда, все также англоязычный.
Heroic_Pizza #8 - 7 лет назад 0
lentinant, к сожалению я не так хорошо знаю английский, чтоб во-всём этом разбираться. Честно говоря для меня и русские уроки доставляют немало трудностей в их понимании. Программирование и скриптинг для меня в новинку, но я твердо решил в этом разобраться, поэтому стараюсь, как могу.
Если честно, не понимаю таких людей, как вы и подобные, как Ogasoda, которые бескорыстно и терпеливо отвечают на вопросы, пишут подробные уроки и снимают видеотуториалы. Но огромное спасибо вам - без вас такие нубы, как я оставались бы в неведении и бросали всё на начальном этапе. )
lentinant #9 - 7 лет назад 0
Heroic_Pizza, честно говоря, как раз в этом я не силен. Мне очень трудно удержать свое внимание на проекте, в котором я что-то рассказываю другим, в то время, как я могу использовать эти знания в другом проекте (что, собственно, и случилось).