Делаем PC игру вместе - читать онлайн бесплатно, автор Геннадий Гурьянов, ЛитПортал
Делаем PC игру вместе
Добавить В библиотеку
Оценить:

Рейтинг: 5

Поделиться
Купить и скачать

Делаем PC игру вместе

Год написания книги: 2026
На страницу:
2 из 3
Настройки чтения
Размер шрифта
Высота строк
Поля

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

Прежде чем продолжить нужно обязательно уяснить что в C# типы переменных могут быть с конкретным значением или только ссылки на другие переменные. Например, описанные выше переменные float являются типами значений, т.е. в них можно указать конкретно, что speed = 2.0f; Ссылочные же типы, такие как CharacterController являются только указателем (ссылкой) на соответствующий класс. Логика подсказывает, что если это ссылка, то нужно получить что-то на что можно ссылаться, т.е. получить конкретный класс или объект. Для этого используем один из главных методов, который я описывал ранее, он называется Start (). Напишем строчку метода Start () в «тело» нашего сценария FPCharacter:


using UnityEngine;

public class FPCharacter: MonoBehaviour

{

public float speed = 2.0f;

public float speedfast = 50.0f;

public float gravity = -9.8f;


CharacterController CharControl;


void Start ()

{

}

}

Запишем в этот метод строчку кода получения конкретного класса CharacterController:

void Start ()

{

CharControl = GetComponent  ();

}

Можно заметить, что объявление класса и метода выглядит похоже – между фигурными скобками находится основной код, так называемое «тело» структуры.

Теперь созданная нами переменная CharControl используя функцию GetComponent <> () (получить компонет) становится ссылкой на компонент CharacterController. Но хочется спросить, а где же этот компонент, который мы получили? Если функция получения начинается так как у нас сразу со слов GetComponent после знака равенства, значит получение чего либо происходит прямо «здесь», в этом игровом объекте на котором находится сценарий кода.

Общий вид программы теперь такой:

using UnityEngine;

public class FPCharacter: MonoBehaviour

{

public float speed = 2.0f;

public float speedfast = 50.0f;

public float gravity = -9.8f;


CharacterController CharControl;


void Start ()

{

CharControl = GetComponent  ();

}


}

Перед словом Start появилось новое слово void, оно означает «пустой», это наиболее часто используемый тип при указании метода. Дело в том, что метод в языке программирования C# может выдавать некоторый результат своих вычислений. В таком случае вместо void ставится тот тип, который и должен быть результатом. Например, если результатом вычислений будет число с запятой, то вместо void ставится float.

Как я рассказывал ранее метод Start () выполняется один раз. Теперь при запуске программы ссылка на компонет CharacterController будет храниться все время пока работает программа.

Переключаемся снова в редактор Unity3d, выделяем наш объект Player в окне Hierarchy и в окне Inspector нажимаем кнопку Добавить Компонент (Add Component). В открывшемся меню в строке поиска вводим несколько первых букв, например: char и мы сразу можем видеть, что в результатах поиска уже виден искомый компонент CharacterController – добавляем его. Теперь мы видим 3 компонента на нашем объекте Player: Transform, FPCharacter и CharacterController. Введите положение объекта (в компоненте Transform) равным 0,0,0, таким образом объект переместится в нулевое положение пространства сцены, осей координат. Возьмите за правило при создании и настройке нового объекта всегда ставить его в позицию 0,0,0 и вращение 0,0,0. Это избавит вас от множества путаниц связанных с расстановкой объектов «там где нужно». Чтобы приблизиться зрением к нашему объекту в окне Scene можно всегда нажимать клавишу F на клавиатуре, она сфокусирует изображение на выделенном объекте.

Мы видим наш объект как зеленый проволочный каркас, это границы компонента CharacterController, т.е. фактически размер и рост человека игрока. Нужно изменить несколько параметров, выставьте значения MinMoveDistance = 0, Radius = 0.35, Height = 1.8. Так у нас получается персонаж игры ростом 1.8 метра. Единица измерения равная 1 в Unity3d равна 1 метру. Соблюдение приблизительно точных размеров таких же как в действительности очень важно при создании игры, так как слишком завышенные размеры, например, если игрок будет размером не 1.8, а 18 единиц Unity3d, может негативно повлиять на множество разнообразных аспектов. Это же касается и избыточного уменьшения. Поэтому, если например, вы будете создавать какие-то 3д модели в программе 3д моделирования 3ds Max, то в ней нужно выставить System Unit Scale: 1 Unit = Meters, Display Unit Scale: Metric, Millimeters, точку вращения повернуть (зеленый маркер) вверх на 90 градусов и эскпортировать 3д модель в формате. FBX с установленным принудительно Units = Meters. При таком способе отправки моделей в среду разработки Unity3d ваши модели всегда будут соответствовать реальным размерам и всегда будут повернуты правильно. Разумеется, необходимо будет соблюдать правильные размеры при моделировании в программе 3ds Max по ее правилам.

8. Оператор условия if ()

Возвращаемся к коду. Настало время операторов. Оператор это символ или текстовое слово, которое обозначает какие-либо действия с данными. Уясним один из самых главных операторов всего языка программирования C#, он называется if (если) – условие. Его можно охарактеризовать как один из самых фундаментальных наряду с оператором циклического повторения for (для) – цикл. Умозрительно можно сказать, что все программы на планете Земля представляют собой вариации кодов основанных на операторах условия if и циклического повторения for, независимо от конкретного языка программирования. Напишем такие строчки кода в «тело» нашего сценария FPCharacter, прямо под методом Start ():

void Update ()

{

if (Input.GetKey (KeyCode. LeftShift))

{

_speedfast = speedfast;

}

}

Во-первых, у нас появился описанный ранее метод Update (), который запускается каждый кадр на протяжении работы программы игры. Во-вторых, мы теперь можем видеть два вложенных друг в друга «тела» структур: одно из них является содержимым метода Update (), т.е. все то, что находится между его фигурными скобками, а второе «тело» принадлежит оператору if () – находится, так сказать, в его пределах воздействия. Тут есть интересная особенность, о которой обязательно нужно упомянуть: если «в теле» оператора if () объявить какую-либо переменную, то она будет доступна только «в теле» оператора if (). Напротив, если объявить переменную «в теле» метода Update (), она будет доступна и в Update () и в if ().

Теперь оператор if (). Это оператор, который необходим чтобы просто напросто описать условие: «если – то». В нашем случае, сейчас, это описание действия «если нажата такая-то клавиша, то сделать такое то действие». В коде Input.GetKey (KeyCode. LeftShift) используются класс и метод определения нажата ли пользователем определенная клавиша на клавиатуре, в данном случае Левый Шифт (LeftShift). Все это выражение является условием оператора if (). Если клавиша нажата, то выполняется код находящийся «в теле» оператора if ().

Причем, что интересно, проверка «нажата ли клавиша» произойдет, например, 60 раз за одну секунду, если игра работает со скоростью 60 FPS – это как раз к тем сведениям, которые описывались в начале книги.

Продолжим. «В теле» оператора if () появилась одна новая переменная _speedfast. Сначала объявим ее, «в теле» нашего класса FPCharacter, сразу под объявленными ранее переменными скоростей запишем такую строчку кода:

float _speedfast;

Сейчас мы объявили переменную типа float без указания ключевого слова public. Это делается для того, чтобы она не появилась в окне Inspector редактора, так как не требует установки вручную и кроме этого сценария (FPCharacter) нигде больше не используется, т.е. она внутренняя или локальная. Также мы не задали ее значение, поэтому в данном случае эта переменная равна 0.

Общий вид кода нашего сценария теперь такой:

using UnityEngine;

public class FPCharacter: MonoBehaviour

{

public float speed = 2.0f;

public float speedfast = 50.0f;

public float gravity = -9.8f;


float _speedfast;


CharacterController CharControl;


void Start ()

{

CharControl = GetComponent  ();

}void Update (){if (Input.GetKey (KeyCode. LeftShift)){_speedfast = speedfast;}}}

Теперь нажимая клавишу левый шифт на клавиатуре, переменная _speedfast будет принимать значение переменной speedfast. Но сейчас такое присвоение значений будет иметь мало смысла так как не имеет никакого обратного действия, поэтому продолжим рассмотрение оператора if (). Переменная speedfast нужна чтобы ускорить движение нашего персонажа держа клавишу левый шифт, т.е. когда клавиша левый шифт не будет нажата скорость персонажа должна быть обычной. Для реализации этого пишем такие строчки кода, прямо после фигурной скобки «тела» оператора if ():

else{_speedfast = 1;}

У нас появилась новая часть оператора if (), которая называется else (еще), это ветвление, т.е. если условие заданное кодом в операторе if () не выполняется по каким-то причинам, то выполнение условия можно разветвить – выполнить кусок кода находящийся «в теле» оператора else. Окончательно оператор if () в нашем случае будет выглядеть следующим образом:

if (Input.GetKey (KeyCode. LeftShift)){_speedfast = speedfast;}else{_speedfast = 1;

}

Таким образом, выполняясь каждый кадр множество раз в методе Update () будет производиться проверка нажатия клавиши левого шифта: если он нажат, то внутренняя (локальная) переменная скорости будет равна заданной вручную переменной speedfast, или если левый шифт не нажат, то локальная переменная будет равна 1. Нужно запомнить, что оператор if () сам по себе использовать можно, но оператор else без if () нельзя. Также бывают прочие ветвители условий когда к оператору else добаваляются еще данные для проверки. Это будет рассмотрено в книге далее.

Продолжим наш сценарий игрока. Напишем далее «в теле» метода Update () такие строчки кода:

float offsetX = Input.GetAxis («Horizontal») * speed * _speedfast;

float offsetZ = Input.GetAxis («Vertical») * speed * _speedfast;

Мы объявляем две локальные переменные offsetX и offsetZ, которые считывают нажатие клавиш ориентации в пространстве. Снова мы видим класс Unity3d под названием Input и получение осей координат Horizontal (горизонтальных) и Vertical (вкртикальных). Два слова Horizontal и Vertical являются, так сказать, собирательными для всех кнопок клавиатуры, джойстика и т. д. поделенных на 2 категории: горизонтальные и вертикальные. Вертикальные в данном случае понимается как противопоставление горизонтальным осям, потому что движение персонажа представялет собой влево-вправо и вперед-назад, но не влево-вправо, вверх-вниз.

Получая данные через класс Input от клавиатуры играющего, также мы умножаем данные на скорость и на быструю скорость (например, бег), которую только что разобрали. Знак звездочка означает обыкновенное умножение.

9. Движенине

Добавим следующую строку кода, которая формирует локальную переменную вектора в пространстве, в трех координатах – X, Y, Z:

Vector3 movement = new Vector3 (offsetX, 0, offsetZ);

Vector3 (вектор 3) в Unity3d фактически представляет собой просто 3 собранные вместе переменные типа float описанные ранее, но используется для описания положения в пространстве и направления движения.

Мы видим новый оператор под названием new (новый), этот оператор нужен для создания нового экземпляра типа данных. В данном случае мы создаем новый вектор с указанием двух переменных offsetX и offsetZ. Отсутствует переменная Y, которая описывает движение вверх-вниз, и сейчас не нужна.

Правильные направления по осям в Unity3d можно легко запомнить используя ассоциативное запоминание. Например, чтобы запомнить, что координата Z (зэт) это движение вперед, то можно представить себе, что Z похоже на зигзагообразное движение лодки по воде уходящей вдаль, т.е. плывем вперед (вперед-назад). Y (игрек) похожа на песочные часы, которые сыпятся только вниз (вверх-вниз), а для X (икс) остается только влево-вправо.

Сейчас мы сделали вектор движения movement (движение), который будет ответственным за движение нашего персонажа вперед-назад и влево-право, в зависимости от того какие клавиши на клавиатуре будет нажимать играющий человек. Но сейчас при текущем нашем коде движение вперед-назад и влево-право будут происходить медленнее, чем движения по диагонали. Это будет происходить потому, что при движении по диагонали будут учитываться значения смещений и offsetX и offsetZ вместе. Чтобы сделать движение по всем направлениям равномерным нужно добавить такой код следующей строкой:

movement = Vector3.ClampMagnitude (movement, speedfast);

ClampMagnitude это внутренний метод Unity3d, который ограничит величину нашего вектора движения так, что скорость перемещения будет равномерной по всем направлениям.

Добавим такую строчку кода:

movement. y = gravity;

Чуть выше мы не использовали переменную Y, которая нужна для движения вверх и вниз. Сейчас мы добаваляем и ее. В нашем векторе movement есть 3 переменные и мы можем получить или задать каждую из них в отдельности, сейчас это выглядит как movement. y, т.е. мы задаем эту переменную равной gravity (гравитация) для того чтобы наш будущий игрок мог свободно падать вниз если у него не будет опоры под ногами.

Следующий важный нюанс состоит в том чтобы наш будущий игрок мог двигаться одинаково на разных по мощности компьютерах, при разной скорости работы игры, при разных FPS игры и т. п. Для этого дальше напишем такую строчку кода:

movement = movement * Time.deltaTime;

В данной строке кода мы увязываем наш вектор движения movement через класс Time (многозадачный класс категории игрового времени) со временем рендеринга (визуализации) предыдущего кадра – deltaTime. Таким образом, любое движение в игре сможет рассчитываться вне зависимости от конкретного компьютера и его мощности. Используйте умножение на Time.deltaTime всегда в любом коде основанном на каком-то изменении значений во времени.

Также в языке C# предусмотрена возможность записи последней строки кода, так сказать, укороченно, что мы и сделаем:

movement *= Time.deltaTime;

В таком виде строка кода полностью соответсвтует предыдущей, так мы ее и оставим.

Теперь нужно «подковать еще одну ногу», а именно, наш персонаж скоро сможет двигаться, но как только он повернется его движение нарушится, т.е. персонаж будет смотреть куда-нибудь в сторону, а двигаться будет продолжать строго вперед-назад, влево-вправо. Чтобы такого не было нужно преобразовать вычисления нашего вектора movement из локальных координат в глобальные. Запишем далее такую строку:

movement = transform.TransformDirection (movement);

Слово transform это ссылка на соответствующий компонент, который находится на нашем игровом объекте Player. Здесь такая же технология как и с GetComponent <> () описанным ранее. Если после знака равенства сразу пишется transform или GetComponent <> (), это означает, что операции производятся с текущим игровым объектом на котором находится наш сценарий кода. TransformDirection () это метода Unity3d преобразующий наш вектор в мировые координаты (глобальные). Коротко поясню, что мировые координаты это основа всей 3д сцены среды разработки Unity3d, и они неизменны. А локальные координаты принадлежат какому-либо объекту сцены и постоянно изменяют свои направления, в зависимости от вращения этого объекта.

Теперь движения нашего персонажа вперед-назад, влево-вправо будут происходить в соответствии с его поворотом.

Программа движения персонажа готова и нужно ее исполнить, вернее применить к нашему игровому объекту Player. Для этого сообщим наш вектор движения компоненту CharacterController используя его внутренний метод Move (Двигать), добавим строчку кода:

CharControl.Move (movement);

Таким образом, наши строчки кода запускаясь бесконечно каждый кадр в методе Update () будут считывать нажимаемые игроком клавиши, формировать вектор направления и скорости передвижения игрока и двигать его. Теперь весь написанный код выглядит так:

using UnityEngine;

public class FPCharacter: MonoBehaviour

{

public float speed = 2.0f;

public float speedfast = 50.0f;

public float gravity = -9.8f;


CharacterController CharControl;

float _speedfast;


void Start ()

{

CharControl = GetComponent  ();

}


void Update ()

{

if (Input.GetKey (KeyCode. LeftShift))

{

_speedfast = speedfast;

}

else

{

_speedfast = 1;

}


float offsetX = Input.GetAxis («Horizontal») * speed * _speedfast;

float offsetZ = Input.GetAxis («Vertical») * speed * _speedfast;

Vector3 movement = new Vector3 (offsetX, 0, offsetZ);

movement = Vector3.ClampMagnitude (movement, speedfast);

movement. y = gravity;

movement *= Time.deltaTime;

movement = transform.TransformDirection (movement);

CharControl.Move (movement);

}

}

Сохраним наш сценарий и переключимся в редактор Unity3d.

10. Запуск игрока

Испытаем нашего игрока, посмотрим как он двигается. Для этого прежде всего необходимо переключить все окна редактора в удобную компоновку так, чтобы видеть сразу и окно Game (Игра) и окно Scene (Сцена). В правом верхнем углу редактора нажмите кнопку Select editor layout (Выбрать макет) и выберите из списка – «2 by 3», это то, что надо.

Теперь сделаем нечто наподобие земли с тем чтобы наш игрок не падал вниз в безду. Нажмите вверху вкладку GameObject> 3D Object> Plane. В сцене появится игровой объект Plane (Плоскость) с коллайдером. В связи с тем, что расчет столкновений и физических взаимодействий даже для компьютеров сегодняшнего дня задача с большой нагрузкой, поэтому в среде резработки Unity3d есть компоненты Colliders (Коллайдеры, от англ. collide – сталкиваться), которые выполняют функции виртуальных поверхностей используемых для расчетов столкновений и симуляции физического поведения объектов. Например, сложный (т.е. много полигональный) объект сферу можно заключить в коллайдер в форме простого куба. Тогда такая сфера станет препятствием для прохождения сквозь нее и может уже быть использована в физических расчетах. Главное преимущество в данном случае будет заключаться в том, что компьютеру потребуется рассчитывать только 6 поверхностей виртуального куба для столкновений, а не сотни или более поверхностей самой сферы.

Так вот, на созданной только что плоскости уже есть коллайдер, т.е. она может сразу использоваться как земля или препятствие для падения вниз. Поставьте нашего игрока Player в окне Scene (зеленый проволочный каркас) чуть выше этой плоскости чтобы при запуске программы он не «провалился под землю» (если вдруг маркеры движения или лучше сказать стрелки пропадают, то можно нажимать клавишу W чтобы снова активировать их. Также необходимо чтобы объект Player был выбран в окне Hierarchy).

Нажмем кнопку Play (Запуск) вверху посередине (не забудем кликнуть один раз в окне Game с тем чтобы фокус воспроизведения был как в готовой игре, иначе игровое управление работать не будет). Сейчас можно нажимать клавиши WASD и стрелки, и наш игровой персонаж начнет двигаться и может даже упасть если зайдет за границы плоскости. Если двигаясь нажмать левй шифт, то скорость движения игрока увеличится. Но как мы понимаем не хватает вращения виртуальной головы используя компьютерную мышь, поэтому добавим эту возможность. Нажмем стоп (вверху посередине) и вернемся к нашему коду FPCharacter.

Запишем следующую строчку кода «в теле» метода Update:

transform.Rotate (0, Input.GetAxis («Mouse X») * SensHoriz, 0);

И «в теле» класса FPCharacter объявим переменную:

public float SensHoriz = 2.0f;

Строка кода, которую мы написали будет вращать наш игровой объект Player используя метод Rotate () по оси Y считывая движения мыши. Переменная SensHoriz нужна для настройки чувствительности или скорости вращения головы. Но это вращение будет происходить только в стороны, поэтому пришло время добавить «саму голову» и оставшееся вращение вверх-вниз.


11. Голова

Вернемся в редактор, выделим наш объект Player в окне Hierarchy, нажмем на нем правой кнопкой мыши Create Empty (Создать Пустой). Таким образом, у нашего объекта Player появится дочерний объект, назовем его Head (Чтобы переименовать любой объект в Unity3d можно нажать клавишу F2). Этот объект Head и будет нашей головой. Нужно выставить значение Y = 0.7 для параметра Position его компонента Transform, так как мы понимаем, что голова должна быть выше уровня «живота». Теперь добавим глаза голове нашего игрока, для этого на только что созданном объекте Head в окне Inspector нажмем Add Component и введем в поиске Camera, добавим ее. Если в вашей сцене присутствует (смотрите в окне Hierarchy) камера, которая называется «Main Camera’, то удалите ее, так как она больше не нужна. Возвращаемся к коду.

Чтобы наша программа, наш сценарий, мог управлять только что созданной головой с глазами, нужно программно взаимодействовать с ней, т.е. нужно получить на нее ссылку, ссылку на объект голова. Вообще в Unity3d все является объектами и на всех них можно получить ссылку и, соответственно, управлять ими. Так запомните.

Сначала, «в теле» класса FPCharacter объявим ссылочную переменную на компонент Transform и 4 цифровых переменных:

Transform HeadTransf;

float RotatX;

public float SensVert = 2.0f;

public float MinVert = -70f;

public float MaxVert = 70f;

А теперь запишем строку кода получения ссылки на объект в методе Start ():

HeadTransf = transform.GetChild (0);

Метод GetChild () нужен для создания ссылки на дочерний объект. Когда мы создавали объект Head, то вы видели, что он появился как бы под объектом Player, т.е. является его дочерним объектом, и до поры до времени повторяет все движения и вращения своего «родителя», все это простая иерархия. Такая иерархия может быть многоуровневой – дочерний объект, дочерний объект дочернего объекта и т.д., и является фундаментальной особенностью Unity3d, крайне простой в создани и редактировании прямо на лету, и очень полезной почти во всех видах работ в среде разработки Unity3d, как программистских, так и в художественных.

GetChild (0) означает, что мы получаем дочерний объект с индексом 0, т.е. просто первый сверху вниз в иерархии. В программировании всегда начало отсчета происходит от нуля, т.е. если у вас есть 10 объектов, то получить каждый из них можно по порядку начиная от 0 и заканчивая 9. Чтобы привыкнуть к такой манере считать и быстро ориентироваться в ней, ведь в природе такое невозможно, в природе ничего нельзя назвать нулевым номером, потому что если у вас в руках 10 яблок, то любое первое яблоко, это номер 1, а десятое – 10, так вот, чтобы быстро ориентироваться в такой системе отсчета когда у вас есть указанное количество элементов или объектов, то просто отнимайте единицу от их общего количества и никаких сложностей не будет. Если у вас есть 23 яблока, значит в программировании обращаться к ним нужно как 0—22.

На страницу:
2 из 3