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

Геннадий Гурьянов

В книге рассказывается о создании программного ядра однопользовательской 3д компьютерной игры в жанре приключения, квест-головоломки от первого лица в среде разработки Unity3d для персонального компьютера.

Представлен полный цикл создания с нуля до работоспособной версии игры, без графического наполнения. Книга адресована школьникам и начинающим.

Иллюстрация на обложке Геннадий Гурьянов

Иллюстратор Геннадий Гурьянов


© Геннадий Гурьянов, 2026

© Геннадий Гурьянов, иллюстрации, 2026


ISBN 978-5-0069-8512-4

Создано в интеллектуальной издательской системе Ridero

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


### О книге

В книге рассказывается о создании программного ядра однопользовательской 3д компьютерной игры в жанре приключения, квест-головоломки от первого лица в среде разработки Unity3d для персонального компьютера.

Представлен полный цикл создания с нуля до работоспособной версии игры, без графического наполнения. Книга адресована школьникам и начинающим.

Автор книги является разработчиком компьютерных игр с 2006 года, за его плечами большое количество выпущенных проектов, в том числе сделанных в одиночку, например, компьютерные игры в жанре приключения и квест AA-класса (double A) «Тайная Доктрина» и «Рыба». Все изображения представленные в книге являются собственными изображениями автора.


### От автора

Уважаемый читатель!

Современные компьютерные технологии открыли невиданную ранее возможность для реализации творческих идей с яркими художественными и смысловыми возможностями. У вас теперь есть возможность творить красоту, захватывающие добрые истории, фантастические красочные выкрутасы и туманную задумчивость!

Будучи создателем нескольких игр для персонального компьютера сделанных в одиночку – 4 маленьких и 2х очень больших, я, автор этой книги, покажу вам дорогу каждодневной радости творчества и вашего успеха. Создавайте ваши миры, дарите их людям, сейте в людские души трепет вашего сердца и добрые повествования…

Друзья! Предостерегаю вас от изготовления суррогата «ужастиков», «хорроров», насилия, мрака и тупости.

Берите ответственность смело за те идеи, которые подскажет вам сердце!

Добро пожаловать в мир творчества!

Научимся создавать компьютерную игру вместе!

### 1. Что делаем?

Структура этой книги сделана таким образом, что ее предлагается читать без пропускания глав. Главы в этой книге сделаны умозрительно и в определенной степени условны.

В книге мы будем делать основу компьютерной игры с головоломками, бродилку в трехмерном пространстве, игру для персонального компьютера, для одного играющего. То есть это игра будет не с плоскими объектами – «плоскатиками», а точно также как в настоящем мире – 3d – с трехмерными. В такой игре события развиваются путем решения головоломок, загадок. Сокращение 3d (или 3д) означает «3 dimensional’ на английском языке, что переводится как трехмерный, т.е. 3х мерное пространство. Это такое пространство, в котором есть 3 измерения: длина, высота и ширина, например то, в котором мы с вами живем в реальной жизни. Это также означает, что в такой игре можно ходить, бегать, прыгать и даже летать как в настоящей жизни. Все прочие игры, которые называют 2d означают, что их игровое пространство плоское или двумерное, в нем есть только длина и высота или наоборот: длина и ширина, или вообще высота и ширина. К примеру, плоское 2d пространство это листок бумаги.


Как работает компьютерная игра? Умозрительно ее можно разделить на две части: визуальная часть – все то, что видит игрок (картинки, художественное оформление, мультипликации, объекты, место нахождения игрока и т.д.) и программная часть (какие события есть в игре и как они происходят). Эти две части могут работать независимо друг от друга. Например, если у нас есть некоторое количество 3д моделей, то мы можем их расставить определенным образом – красиво, и такая трехмерная сцена уже может сама по себе радовать глаз. И по другому, имея программную часть, на нее можно, выражаясь весело – повесить практически любую визуальную часть, от стилизованной в виде кубиков до детально проработанных предметов и окружающей среды.

### 2. В чем «собака зарыта?»

Среда разработки или в простонародье «движок» в процессе создания игры выполняет роль кастрюли в которой варится суп. Эта «кастрюля» имеет определенные размеры, материал из которого она сделана, и она держит в себе весь суп. Имея разные продукты для супа (капуста, морковь, картофель, лук) их можно положить в разные по форме и размеру «кастрюли», и даже в очень маленькие, но суть «супа» от этого не поменяется. На сегодняшний день самыми популярными «движками-кастрюлями» у всех на слуху являются два: Unreal Engine и Unity3d Engine. Данная книга рассказывает о создании игры в среде Unity3d. Существует мнение, что среда разработки Unity3d упрощена и создана для разработки примитивных телефонных игр. Это не так. Если мы знаем, что хотим получить, то в Unity3d можно достичь высочайших графических результатов. Также широко распространено мнение о том, что только среда разработки Unreal Engine способна дать «фотореалистичный» результат игр и программ. Это ошибочное мнение основано на конкурентной рекламе, и не более. Что же касается самого главного аспекта – удобства создания игры, то Unity3d вне всяких сомнений это лучший выбор для творческого человека. Глубочайшая интуитивность Unity3d и ваша простая логика позволяет создавать то, что хочется практически сразу, просто на лету, даже не зная программы. Что означает эта интуитивность? Это значит, что если возникла идея: «а что если попробовать вот так сделать, подсоединить, повернуть?», то это можно сразу же сделать как подсказывает логика, идея, мысль, и «это» будет иметь статус – «можно». Никаких иных действий из разряда «чтобы повернуть это надо сначала добавить такой объект и настроить точку вращения» – не нужно. Например, если у вас есть 3д модель экскаватора с несколькими деталями, скажем, с кабиной, гусеницами, ковшом, то в Unity3d мы можем мгновенно, прямо в сцене, одним перетаскивание мыши привязать одну деталь к другой, в произвольном порядке, например кабину к ковшу, а ковш к гусеницам и, эта фантастическая конструкция с этого же момента готова к использованию в таком виде, сохраняя иерархию сделанных изменений. Пример, конечно, не очень практичен, однако он показывает, что в Unity3d у создателя игр есть практически полная творческая свобода и интуитивное понимание. А ведь дизайн, сборка на лету, возникшая идея, которую можно сразу же реализовать, все это фундамент творчества и радости. Напротив, в Unreal все вышеописанное и многое другое представляет из себя сплошную «головную боль» и ограничения: «сначала нужно сделать так, потом вот так, да, я плясать мастак…» как сказано в одном фильме. А об интуитивном создании дизайна и свободной сборке элементов прямо в сцене в Unreal и говорить не приходится. Не вдаваясь в дополнительные подробности можно сказать поэтически так: если работу на Unity3d можно сравнить с работой изящной кисточкой художника, то работу на Unreal можно сравнить с работой бревна в руках, которым вы пытаетесь что-то нарисовать. Утверждение о том, что среда разработки Unreal подходит для проектов созданных где-то во вне (во внешней программе 3д моделирования, например в 3ds Max), с учетом возможностей программ сегодняшнего дня не выдерживает критики. А уж о творчестве и интуитивности в таком случае говорить тем более невозможно.

### 3. Глубина погружения

Чтобы хорошо ориентироваться в том как будет работать создаваемая игра необходимо понять, что любая компьютерная программа (видеоигра это прежде всего программа) на любых компьютерах на Земле выполняется процессором сверху вниз последовательно, команда за командой (многопоточность и несколько ядер процессора мы рассматривать не будем). Программа это набор записей в блокноте, строчками сверху вниз, точно также как школьная тетрадь по русскому языку с сочинением на тему стихотворений Сергея Есенина. Только вместо художественного текста сочинения программист записывает в тетрадь команды для процессора на выбранном языке программирования. После запуска такой программы, процессор выполняет ее как я уже говорил сверху вниз, последовательно, команда за комнадой. В последние десятилется в программировании появились понятия: объекты, экземпляры, методы и т.д., однако, суть исполнения программы не изменилась – сверху вниз, последовательно.

Среда разработки Unity3d предоставляет создателю возможность делать его игру расставляя объекты и события как бы отдельно: здесь можно поставить персонажа с отдельной программой управления (в Unity3d это называется скрипт (от англ. script) или сценарий), здесь магазин продуктов с отдельной программой, а тут дверь ведущую в другой уровень со своей программой переноса в уровень. Необходимо запомнить, что это сделано только для удобства создателя игры, и на самом деле все эти «отдельные» программы выполняются процессором по порядку одна за другой, строка за строкой.

Как же происходит это выполнение? Микропроцессор компьютера выполняет программу циклами. Допустим, в программе есть 10 строк кода. Выполняется она сверху вниз точно также как вы читаете текст этой книги. Причем, до тех пор пока в недрах процессора и памяти компьютера не закончатся вычисления, например третьей строки кода, до тех пор все оставшиеся строки начиная с четвертой и до десятой никогда не выполнятся. Все эти как может показаться простые нюансы крайне важны как основа. Зная такие нюансы вы сможете сосредоточиться на творчестве, на придумывании «что можно воплотить в игре», а не думать «как же это сделать, не будет ли здесь ошибки и зависания игры?». Так вот, когда выполнение закончится после десятой строки цикл повторяется снова с первой, и так до бесконечности, пока не произойдут какие-то иные события, например, игрок просто выйдет из игры.

Продолжим. Описанные только что бесконечные циклы повторяются каждый кадр во время игры. Чем больше этих циклов успеет вычислить микропроцессор за одну секунду времени, тем лучше и быстрее работает игра. Если процессор успевает вычислить, например 60 таких циклов за 1 секунду, значит игра работает со скоростью 60 FPS (от англ. Frames Per Seconds – кадры в секунду) и на экране монитора игрока изображение игры меняется 60 раз за 1 секунду, т.е. 60 кадров в секунду. Такая игра воспринимается глазом играющего приятно, плавно, без рывков и притормаживаний. И наоборот, чем меньше циклов за 1 секунду выполняет процессор, тем ниже FPS игры и качество ее работы. Также необходимо иметь ввиду, что процессору в этих вычислениях помогает видеокарта. Можно сказать, что это второй процессор компьютера, но рассчитанный только на обработку графики, проще говоря, только на рисование всех картинок и 3д объектов игры.

Хочу отметить, что не нужно гнаться за высокими показателями FPS при разработке игры, так как на сегодняшний день процессоры компьютеров способны справляться с разными нагрузками одинаково успешно. Сосредоточтесь на творчестве и на создании того, что вы задумали, а не тратьте время на ненужные поиски «спасения» дополнительных 10 кадров в секунду. В конце концов, компьютерные технологии развиваются столь динамично, что в отрезке 2—3 года то, что сегодня работает несколько медленно, завтра будет работать совершенно нормально. Думайте о творчестве, а не о FPS.

### 4. Си шарп

Создавая код программы нашей игры мы будем использовать язык программирования C# (произносится си шарп). Это очень удобный и гибкий язык программирования. Его огромный плюс заключается в том, что он строго типизирован, т.е. в большинстве случаев он попросту не позволит вам сделать ошибку создания программы. Например, в этом языке нельзя просто так сказать, что строка текста это тоже самое что и число. В других языках программирования это возможно, что в процессе создания программы может повлечь возникновение неявных ошибок, которые приводят к непредсказуемым последствиям, и что еще хуже, место возникновения в коде такой ошибки очень трудно найти. Чтобы писать программы на языке C# нам понадобится Microsoft Visual Studio. Ее можно просто скачать из интернета. Перед загрузкой выберите раздел Community, этот вариант является бесплатным для скачивания.


Очень часто при написании кода игры необходимо много раз повторять одни и те же инструкции программы. Для этого существуют методы или другое название – функции. Метод это ничем не отличающийся от прочих строк кода набор команд, который может запускаться как отдельная подпрограмма. В Unity3d существует несколько фундаментальных методов, которые отвечают практически за всю работу игры. Первый из них это метод Update (). Именно этот метод бесконечно запускается каждый кадр игры. Чем чаще сможет выполниться этот метод процессором, тем выше будет FPS игры. Через этот метод будет выполняться почти весь код нашей будущей игры. Этот код принято называть логикой игры. Следующий фундаментальный метод называется Start (), который выполняется только один раз при запуске игры. Он нужен для того чтобы подготовить необходимые параметры к работе – инициализировать их. Причем, в нашей сцене игры может быть много методов Start () и Update () сразу: на двери перехода в уровень, на персонаже, на магазине продуктов и т. д. Как же они взаимодействуют друг с другом? Тут важно понять и запомнить, что при запуске игры сначала выполняются все объекты игры у которых есть метод Start () и после этого начинают выполняться все объекты с методом Update (). Например, если в запущенной сцене игры есть 100 объектов и на каждом есть метод Update (), то это означает, что за 1 кадр (упомянутый выше) процессор компьютера выполнит все эти 100 методов Update (). Для дополнительного понимания можно это представить так, что один метод Update () разделен на 100 методов, которые должны выполниться в течение одного кадра игры. Вообще, строго говоря, с точки зрения производительности такое большое количество методов Update () в одной сцене не очень хорошо. Но такое разбиение одного метода на разные объекты является одной из замечательных возможностей Unity3d. Благодаря такому разбиению процесс создания игры становится очень удобным и понятным: вот здесь напольный вентилятор, он появляется в начале игры и пока он включен его метод Update () запускается чтобы вращать лопасти его пропеллера.

### 5. «Вращать Медленно!»

Вводная часть закончена, теперь можно начать действовать. «Дальше действовать будем мы», как сказано в одной песне. Начнем с подготовки проекта. Проектом называется весь конгломерат кода, объектов, текстур, заготовок и т. д. лежащих в виде файлов в какой-то одной папке, которую использует Unity3d. Чтобы создать чистый новый проект необходимо запустить программу Unity Hub, она используется для организации проектов, создания и установки разных версий Unity3d. Создайте новый проект (кнопка New Project) используя любую версию Unity3d начиная с 6000. Чтобы скачать Hub введите в поиске Google слова Unity hub. Первая запись поисковика будет как раз то, что надо. При запуске Unity Hub активируйте лизцензию – Personal, она не требует оплаты за использование редактора Unity3d.

Для создания нашей игры мы будем использовать конвейер рендеринга называемый HDRP. В Unity3d существует 3 конвейера рендеринга: встроенный, называемый BIP или Built-In, универсальный URP и HDRP. Конвейер рендеринга это структура описывающая необходимые процедуры для преобразования вашей 3д сцены в изображение на экране компьютера. Можно сказать коротко и ясно, что различия между ними в их графических возможностях. BIP и URP конвейеры предоставляют посредственные возможности для графики и ориентированы на создание игр для мобильных платформ (для телефонов) и виртуальной реальности (VR). Самый классный из всех трех именно HDRP, он позволяет достичь самых высоких показателей графики, т.е. получить самую красивую картинку игры, самые реалистичные 3д сцены. Этот конвейер предназначен для создания игр запускаемых на персональном компьютере (ПК) и на консолях (приставках, наподобие «Sony PlayStation»). Берем его в работу.

Открыв созданный пустой проект нажмите Window> Package Manager. Откроется окно Диспетчера Пакетов (Package Manager). Необходимо в этом окне вверху переключить кнопку так, чтобы отображалась надпись Packages: Unity Registry. Это означает, что будут отображаться все пакеты доступные для скачивания от производителя Unity3d. Нужно отыскать в этом списке High Definition RP, нажать на эту надпись и в правой части окна нажать – Install (установить).

Уважаемый читатель, вы можете использовать HDRP шаблон (HDRP template) из Unity Hub или Мастер Настройки Конвейера (Render Pipeline Wizard) по вашему усмотрению, но я намеренно описываю как создать HDRP проект с нуля, так сказать на самом низком уровне. Главная причина заключается в том, что от версии к версии в Unity3d меняются стили, настройки, окна и прочее, беспрестанно переделываемое коллективом разработчиков Unity3d. Вся эта мишура может сбить с толку и запутать начинающего создателя игр. Кроме того, при использовании шаблонов и «мастеров настройки» очень часто пустой проект создается с ошибками «от производителя» (красные сообщения в консоли), что дополнительно сбивает с толку, раздражает и расстраивает. Описываемый мной «низкоуровневый» метод позволяет все установить правильно на любой версии программы без ошибок и – наверняка.

Только что мы установили сам конвейер в наш проект, т.е. налили для нашего «супа» воду в кастрюлю. Теперь надо конвейер включить, чтобы он мог начать действовать – «зажечь под кастрюлей газ». Для этого в окне Проект (Project) щелкните правой кнопкой мыши – Создать (Create)> Рендеринг (Rendering)> HDRP объект (HDRP Asset). Мы создали глобальный объект управляющий визуализацией нашего нового проекта. Теперь его нужно «включить», для этого нажмите Редактирование (Edit)> Настройки Проекта (Project Settings)> Графика (Graphics)> Конвейер по умолчанию (Default Render Pipeline) – перетащите в этот пустой слот только что созданный HDRP объект. Всё, теперь проект находится в состоянии HDRP и готов к работе. Последний шаг который необходимо сделать, это проверить, что Visual Studio, который мы скачали и установили ранее используется в нашем проекте по умолчанию. Для этого нажмите Редактирование (Edit)> Установки (Preferences)> Внешние Инструменты (External Tools)> (Внешний Редактор Сценариев) External Script Editor и установите в выпадающем списке Visual Studio.

### 6. Поехали!

Так, теперь займемся филигранно программами нашей будущей игры. Так как этих программ или как их еще называют «скриптов» будет много, то чтобы не путаться необходимо создать для них отдельную папку и назвать ее Scripts. Для этого в окне Проект (Project) на папке Assets щелкните правой кнопкой мыши Создать (Create)> Папка (Folder), назовем ее Scripts. Предлагаю взять вам на вооружение мысль о том, что удалять и переименовывать папки и файлы проекта необходимо в самом редакторе Unity3d, но не через Windows Проводник, так как все файлы проекта Unity3d связываются метаданными, которые в некоторых случаях очень капризны со всеми вытекающими отрицательными для радости творчества последствиями.

Старайтесь точь в точь делать так как описано все ниже по тексту с тем, чтобы не было путаницы и недопонимания.

Создадим игрока. Игрок это игровой объект с камерой, который является главным действующим лицом всей игры. Камера это объект, который показывает на экране все, что видит игрок. Вообще все, что есть в сцене или уровне Unity3d, все это игровые объекты – GameObject, причем все они расположены в виртуальном пространстве нашей сцены, поэтому любой объект в Unity3d всегда существует с компонентом Transform, который задает положение и размер объекта в пространстве. Логика подсказывает, что без этого компонента объект существовать не может, поэтому компонент Transform невозможно удалить из объекта.

Итак, игрок. Создадим программу для игрока, для этого нажмем правой кнопкой мыши в окне Проект (Project) в папке Scripts которую мы создали – Create> Scripting> Пустой C# Сценарий (Empty C# Script), назовем его FPCharacter. Двойной щелчок на этом файле чтобы открылся редактор Visual Studio. Мы увидим такие строчки кода:

using UnityEngine;

public class FPCharacter

{

}

Коротко разберем их. Строка using UnityEngine означает, что данная программа или сценарий или скрипт будет использовать (англ. using) базовые классы среды разработки Unity3d Engine.

Строка public class FPCharacter означает, что данный сценарий представляет собой класс (класс это основной тип данных языка программирования) с названием FPCharacter, и этот класс общедоступен благодря ключевому слову public, т.е. он может быть доступен для любого кода в проекте.

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

using UnityEngine;

public class FPCharacter: MonoBehaviour

{

}

Сейчас мы создали каркас нашей первой программы. Теперь необходимо наполнить его данными и операциями. Все наполнение класса или его «тело» должно быть записано между фигурными скобками.

Укажем или объявим переменные. Переменная это кусочек памяти вашего компьютера, которая имеет тип, т.е. кто она, имя – как ее зовут, и хранит в себе какие-то значения. Напишем:

public float speed = 2.0f;

public float speedfast = 50.0f;

public float gravity = -9.8f;

Первая строка означает, что объявлена переменная, которая называется speed, имеет значение равное 2.0, является общедоступной (public) и имеет тип – float, т.е. числовой тип с плавающей запятой. Для примера в C# есть тип int, т.е. целочисленный тип, в который можно внести значение 1, 2, 3 и т.д., но нельзя записать 1,4 или 2,3. Именно в тип float можно внести значения 2,3 или 1,4. Также по правилам языка си шарп при объявлении переменной типа float в указании значения нужно ставить букву f, чтобы точно отделить переменную от другого типа называемого double. Также в конце инструкции или объявления переменной всегда необходимо ставить точку с запятой. При передаче нашего кода в машинные инструкции для процессора (запуск нашей программы) точка с запятой указывает на то, что данная инструкция или объявление переменной закончено и можно переходить к следующим действиям.

Теперь созданные три переменные будут помогать нам управлять программой, т.е. игроком, которого мы делаем. Переменные скорость (speed), быстрая скорость (speedfast) и гравитация (gravity). Сейчас программа выглядит вот так:

using UnityEngine;

public class FPCharacter: MonoBehaviour

{

public float speed = 2.0f;

public float speedfast = 50.0f;

public float gravity = -9.8f;

}

Нажмем Ctrl + S, чтобы сохранить наш сценарий, и, чтобы двигаться последовательно и не путаться вернемся в редактор Unity3d в наш проект и создадим сам игровой объект игрока. Правой кнопкой в окне Иерархия (Hierarchy) нажмем> Создать Пустой (Create Empty). Назовем сразу этот игровой объект Player. А теперь просто набросим на него наш созданный сценарий FPCharacter (Можно набросить или перетащить сценарий на объект в окне Hierarchy или в окно (Инспектор) Inspector). Окно Inspector наиболее часто используемое окно Unity3d, в нем отображаются почти все свойства и параметры объектов будущей игры.

Теперь мы видим наш сценарий FPCharacter находящимся на объекте Player как его компонент, такой же как вышеописанный Transform. И мы видим все три переменные нашего сценария с их установленными значениями. Обратите внимание, что если убрать ключевые слова public, то переменные исчезнут из инспектора и их настройка в редакторе Unity3d станет невозможной.


### 7. Контроллер Персонажа

Продолжим, напишем такую строку кода:

CharacterController CharControl;

Это переменная типа CharacterController, которая называется CharControl. Объявление такой переменной происходит по аналогии того, что мы сделали выше. Чтобы лучше понять аналогию и запомнить принцип представьте так, что у нас была переменная типа float с названием speed. А сейчас у нас переменная типа CharacterController с названием CharControl. Закрепите в сознании, что все объявления в будущем будут происходить аналогично, тогда картина происходящего будет сразу ясна.

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 <CharacterController> ();

}

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

Теперь созданная нами переменная 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 <CharacterController> ();

}


}

Перед словом 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 <CharacterController> ();

}
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 <CharacterController> ();

}


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.

Теперь, в методе Update () внизу напишем такие строки:

RotatX = RotatX – Input.GetAxis («Mouse Y») * SensVert;

RotatX = Mathf.Clamp (RotatX, MinVert, MaxVert);

HeadTransf. localEulerAngles = new Vector3 (RotatX, HeadTransf. localEulerAngles. y, 0);

Первая из них считывает движение мыши по оси Y; вторая ограничивает вертикальные движения используя две переменные MinVert и MaxVert, имитируя реальные ограничения головы человека вверх-вниз; третья строка формирует вектор вращения нашей виртуальной головы, т.е. применяет сфомированное вращение.

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

using UnityEngine;

public class FPCharacter: MonoBehaviour

{

public float speed = 2.0f;

public float speedfast = 50.0f;

public float gravity = -9.8f;

public float SensHoriz = 2.0f;

public float SensVert = 2.0f;

public float MinVert = -70f;

public float MaxVert = 70f;


CharacterController CharControl;

float _speedfast;


Transform HeadTransf;

float RotatX;


void Start ()

{

CharControl = GetComponent <CharacterController> ();

HeadTransf = transform.GetChild (0);

}


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);


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


RotatX = RotatX – Input.GetAxis («Mouse Y») * SensVert;

RotatX = Mathf.Clamp (RotatX, MinVert, MaxVert);

HeadTransf. localEulerAngles = new Vector3 (RotatX, HeadTransf. localEulerAngles. y, 0);

}

}

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

Нажмем кнопку «Пуск» и теперь можно походить по плоскости и покрутить головой используя мышь. Основа нашего персонажа игрока готова.

### 12. Префаб

Создайте папку Prefabs в окне Project, выделите нашего игрока Player в окне Hierarchy и держа нажатой левую кнопку мыши перетащите нашего игрока в папку Prefabs. Таким образом, в окне Project создастся префаб Player. Префабы это сборки игровых объектов, сценариев, мультипликаций и пр., например, как наш объект Player, как шаблоны, которые могут использоваться в разных сценах. Префаб который мы создали теперь является источником для всех подобных объектов Player, которые где либо будут использоваться в проекте. Одним из главных преимуществ префаба является возможность вносить изменения или улучшения только в источник префаба так, что все эти изменения сразу же отразятся на всех экземплярах этого префаба во всех сценах где он используется.

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

### 13. LevelExecuter

Теперь займемся управлением нашими будущими сценами-уровнями игры. Создайте два сценария с названиями LevExecuter и Globals. Не забудьте добавить MonoBehaviour после двоеточия у обоих:

using UnityEngine;

public class LevelExecuter: MonoBehaviour

{

}

Сценарий LevelExecuter будет одним из главных во всем проекте, он предназначен для управления игрой. Globals будет содержать разнообразные переменные, которые должны существовать при переходе из одной сцены игры в другую. Дело в том, что все сценарии, которые мы будем писать и уже написали существуют в сцене как объекты, т.е. пока работает сцена работают и ее объекты, но как только мы переходим (загружаем) в другую сцену или другой уровень игры, то все объекты сцены из которой уходим, как и сама сцена, исчезают из оперативной памяти компьютера. Для сохранения информации в таких случаях мы будем пользоваться сценарием Globals.

В сценарии LevelExecuter, под строкой using UnityEngine; напишите такую строку:

using gb = Globals;

Чтобы не обращаться к классу Globals каждый раз полным его названием будем использвать короткое – gb.

В сценарии Globals запишите такую строчку кода:

//запускалась ли игра

public static bool GameCreated;

Две косые черты в языке C# означают комментарий программиста. Они нигде не используются в программе и служат только пометками для вас. В объявлении переменной GameCreated появились два новых слова: static и bool. Bool (boolean) означает тип при котором переменная может принимать только два значения – истина (true) и ложь (false). Ключевое слово static означает, что переменная не принадлежит какому-то конкретному объекту, она независима, что нам и нужно для сохранения определенных данных при переходе между сценами. Сама же переменная GameCreated будет хранить информацию о том создана игра или нет – true или false. Сейчас при объявлении переменной без явного указания значения, ее значение равно – false.