готовые коды для unity
Игра на Unity, с открытым кодом
Суть игры
Идея пришла внезапно, и пока она не улетела решил записать а потом и воплотить ее. Представьте холодную, темную камеру в некой тюрьме. В ней сидит, неизвестно как и неизвестно кем, прикованный волшебник, которого каждую ночь мучает всякая нечисть. Из последних сил он создает небольшой файрбол и вдыхает в него подобие жизни.
Познакомьтесь, это Кальцифер, ваш аватар в этой игре.
Именно им вы будете уничтожать вся гадость, которая ползет в сторону нашего закованного бедняги.
Пояснения насчет кода
Начнем с GameManager. Он всему голова, именно в нем меняется состоянии игры — Initialization->GameLoop->Win или Lose. Одинок и един, ибо синглтон. Так как игра не сетевая, простая, и без сложных переходов, то было принято решение использовать этот паттерн. Здесь же идет обработка попаданий по игроку (см. ниже Известные проблемы), учет хитпоинтов и проверка на выигрыш\проигрыш. Был бы GodObject, да слишком мало у нас классов, поэтому знает не всё обо всех. На этапе инициализации, создается пул объектов отображающих анимацию урона по игроку и смерти врагов. Для отслеживания состояния хитпоинтов, можно подписаться на UpdateHpWizardDelegate или UpdateHpCalciferDelegate. В нашем случае это делает GUIManager для отображения текущего хп на экране.
К этому времени SpawnManager уже составил список точек спавна врагов
a WaveManager загрузил порядок волн создания врагов. Волны можно настроить двумя способами: прописать в коде игры или загрузить с Json файла. Для редактирования этого Json написан кастомный editor:GameDataEditor
Можно прописать точный номер спавна или указать что можно создаться на любом свободном.
Создание волн сделанно с помощью хитрой корутины:
Вначале проходит firstWaveDelay секунд до начала запуска 1 первой волны. После этого в цикле прогоняют все волны по очереди, вставляя нужную задержку step.delay между шагами волны. Почему в корутине а не например в Update? Да собственно можно и так и эдак, просто тут более наглядно, видно где задержка ( yield return new WaitForSeconds) и не надо городить лишние циклы и проверки.
Давай те глянем что же представляют из себя SpawnPoint. Это MonoBehavior c 2 компонентами: SpawnPoint и CircleCollider2D. В первом, с помощью второго, определяется занят ли спавн каким то врагом. OnDrawGizmos отображает в редакторе Unity расположение спавнов.
Все враги происходят от базового класса BasicEnemy в котором есть несколько виртуальных методов:
Кстати, врагов и многие другие объекты (много и часто создаваемых на сцене) мы не удаляем с помощью Destroy(this), а отправляем обратно в пул объектов — ObjectPool.Recycle(this). Таким макаром мы неплохо экономим на создании объектов, которое как известно достаточно затратное дело.
Так например анимации заканчиваются вызовом SelfDestroy(), который и возвращает объект анимации обратно в пул.
Движутся же враги с помощью силы пафоса компонента BasicEnemyMoving. В нем нас интересуют два метода: OnEnable()и Move(). OnEnable () вызывается после вытаскивания врага с пула и нужен для поворота врага (если необходимо) в сторону цели.
Move() же является виртуальным методом, который и движет врага к цели. Его можно переопределить в потомках и сделать особенное движение (с рывками, синусоидальное и т.п.)
Структура кода в Unity3d — личное мнение и пара трюков
Хотелось бы поделиться личными впечатлениями о разработке мобильных игр на основе Unity3d. Изначально думал уместить в одном посте все мелкие «Tip&Trick» с которыми столкнулся при работе с Unity3d за последнее время. Но их оказалось черезчур много. Так что в этом посте будут только те, которые касаются непосредственно написания кода.
Главная тема поста — разделение классов по «слоям», связывание их через события и чуть-чуть о том, как наладить взаимодействие объектов на сцене.
Кому интересно — добро пожаловать под кат!
Структура приложения
Когда эти слои как-то сформировались у меня в голове, встал вопрос — как наладить взаимодействие между ними? Т.е. я понимал, что класс, который отвечает за загрузку следующего уровня, никак не должен быть завязан на соответствующую кнопку в меню. Но как это сделать?
Проблема была решена через статические классы и события.
Общая идея — объекты каждого слоя не взаимодействуют с объектами другого слоя. Они только вызывают статические события на своем слое, а на них уже подписываются все, кому нужно.
Почему события статические?
Дело в том, что чтобы подписаться на события конкретного объекта в сцене нужно иметь ссылку на него. А это добавляет кучу лишних связей и идея разделения «слоев» теряется.
В ряде случаев решение оказалось еще проще — достаточно было добавить синглтон для общения со слоем, и взаимодействие сильно упрощалось.
Далее постараюсь на примерах описать, как это работает в каждом слое.
Интерфейс
Туториалы по Unity полны вот таких примеров:
Этот, кстати, из официальной документации.
Что здесь не так?
Во время разработки и отладки в Unity пользоваться кнопками очень удобно. Но при переходе на мобильное устройство они заменяются на элементы интерфейса. Если завязаться на кнопки — придется потом редактировать класс игрового объекта. К тому же, что делать, если на нажатие кнопки должны реагировать несколько объектов?
Оказалось удобнее создать отдельный класс, например, UserInterface, в нем объявить глобальное событие OnFireButtonClick, и подписываться на него везде, где нужно.
Основная идея по интерфейсу такая: не делать обработку Input в игровых объектах. Сделать отдельные объекты интерфейса, которые будут принимать все команды от пользователя, а в игровых объектах подписываться на события. События лучше порождать в синглтоне, т. к. конкретные объекты интерфейса, могут меняться, и это не должно влиять на игровые объекты.
Бррр… Как-то сумбурно получилось, но, надеюсь общую идею я донес.
Кстати, если подписываетесь на события, не забудьте добавить «отписку»:
А то будет забавная ситуация, когда сцену закрыли, а объект живет и обрабатывает event’ы. Да и сборщик мусора корректно не отработает. Не отписываться можно только в том случае, если объект порождающий событие находится в той же сцене, что и обработчик и будет удален вместе с ним.
Тут непонятная ситуация — я предполагал, что закрытие сцены означает уничтожение всех ее объектов и автоматическую «отписку» от всего. Оказалось, что это не так. Больше похоже на то, что приложение просто выходит из блока, отвечающего за сцену. Предполагается, что на объекты больше нет ссылок и, их удалит сборщик мусора. Не понял, баг это или фича. Если кто понимает суть этой ситуации — поделитесь, плизз, знаниями. Очень любопытно.
Игровые объекты
Это все, что бегает, прыгает, крутится, стреляем и взаимодействует между собой на нашей сцене.
Для их общения Unity предлагает несколько подходов.
1. Связать их через public свойства. Выглядит это примерно так:
Минус в том, что нам нужно связывать объекты между собой. А если мы их динамически генерируем?
Тут нам на помощь приходит второй подход:
2. Найти объект на сцене по имени или тегу. Например:
Минус этого способа виден в самом примере — нужного объекта в сцене может и не оказаться. Хорошо, если его там вообще нет, и мы можем ничего не делать, а что, если его туда добавили, но с другим именем?
Тут опять можем обойти через статическое событие. Если каждый инстанс Car будет пробрасывать свое столкновение через статическое событие класса (CarManager), нам не придется искать объект или привязываться к нему.
Получится что-то вроде:
У такого подхода есть свои минусы и подводные камни, так что пользоваться им нужно осторожно и не забывать отписываться от событий. Но иногда он сильно помогает.
Состояние игры
Состояние игры — это обобщенное название классов, отвечающих за фиксацию прохождения уровней, загрузку следующих, набранные очки и т. п.
Главное, что тут нужно учесть — не нужно делать Application.LoadLevel( ); из игровых объектов — рано или поздно, вам потребуется ввести дополнительные проверки, подсчеты, промежуточные экраны и т. п. Лучше вынести все это в отдельный класс. Туда же можно вынести команды паузы и восстановления игры и т. п.
Т.е. в сцене уже не придется заботиться о том, что нужно делать при завершении уровня и какое имя было у предыдущей сцены, если игрок вдруг захочет ее переиграть. Просто вызываем класс-менеджер и все.
Аналогично, если нам нужно в сцене реагировать на приостановку игры, например, проставить Time.timeScale = 0; достаточно подписаться на соответствующее событие.
Все эти мелочи сильно упрощают разработку.
Взаимодействие с сервисами Google
Google расщедрился на замечательные сервисы, которые можно вызывать из мобильной игрушки. Теперь не нужно писать собственные лидерборды, ачивки и многое другое. К тому же, эти сервисы развиваются и обещают стать какой-то супер-пупер-вундервафлей.
Учитывая все это, выглядит логичным не размазывать их вызов тонким слоем по всему приложению, а сконцентрировать в одном-двух класс, связав с остальными частями приложения через события. Это позволит развивать «социальную» составляющую игры не сильно влияя на геимплей.
Реклама
Каждый провайдер рекламы предоставляет свои инструменты для показа. Разбираться в игровых классах со всем этим зоопарком нет никакого желания. Я для себя сделал простенький статически класс AdsController, с одним публичным методом: ShowBanner() и вызываю его там, где хочу показать рекламу. Вся работа с AdMob спрятана внутри и, если я решу перейти на что-то другое, лазить по коду и отлавливать вызовы не придется. Мелочь, а приятно.
Завязаться так же можно через события, но в моем случае больше подошел синглтон, т. к. интерфейс вызова рекламного блока я вряд ли буду менять, а вот игровые события могут измениться.
Т.е. вводим ограничение на частоту успешных показов. Актуально для показа баннеров. Например, я показываю их при завершении уровня или гибели. Но первые уровни проходятся за десяток секунд. Чтобы не нервировать игрока, добавил такое ограничение.
Главное — учитывать именно успешные показы. А то при глюках с соединением игрок рекламу не увидит никогда и будет огорчен тем, что не отблагодарил разработчика своим вниманием.
Социальные сервисы
Этот «слой» по своей роли очень похож на рекламный, но суть его еще проще — все команды для социальных сетей собрать в кучу, наружу выставить один статический класс с методами: PostToFacebook, PostToVk и т. п. Все, что можно спрятать — спрятать внутри классов. Через параметры передавать минимум. У меня он свелся к одному параметру — количеству набранных очков. В принципе, использование этого класса сводится просто к вызову нужного метода, когда пользователь жмет кнопку «Поделиться».
В таком виде его можно таскать из проекта в проект практически без изменений.
Заключение
Надеюсь, что этот поток мыслей оказался кому-нибудь полезен. Знаю, что ничего особенного я не рассказал и что люди, работающие с Unity давно и толково знают все это и еще 100 более подходящих способов сделать тоже самое. Надеюсь, что я помог новичкам, а знатоки поделятся своим опытом в комментариях.
Если есть интерес, могу рассказать про шишки, которые я набил при импорте 3d объектов и выстраивании структуры проекта.
Топ 15 бесплатных Unity ассетов для начинающего 2D разработчика
Введение
Unity3D – безумно удобная среда, которая многое делает за нас. Именно поэтому она сейчас так востребована: разработка игр при правильном подходе становится не сложнее сборки конструктора.
Сама платформа Unity предоставляет широкий набор инструментов, но иногда не хватает и их. Тогда в дело вступает Asset Store с миллионами ассетов, моделей и примеров проектов.
Если у вас еще мало опыта в разработке приложений, имейте в виду: Asset Store – это огромный склад велосипедов, которые уже изобрели до вас. При этом там можно найти очень много бесплатного контента, или же контента по цене одной чашки кофе (сэкономьте на латте!).
Почему мы посвятили статью именно 2D играм? Посмотрите на рынок: 2D игры сейчас переживают ренессанс. Чтобы в этом убедиться, достаточно взглянуть на инди-сектор в Steam, Ketchapp и Zeptolab в мобильной разработке. 2D игры давно превратились из отжившего свое сектора в отдельную процветающую нишу рынка. Поэтому если вы решили делать 2D игру на Unity, сначала убедитесь, что у вас в арсенале есть все нужные инструменты, чтобы обеспечить должное качество продукта.
Немного про Asset Store
Если вы читаете эту статью, то скорее всего что-то слышали про Asset Store. Там хранятся несколько тысяч моделей, плагинов, систем частиц, скриптов и многое другое. Часть этого добра распространяется за деньги, а часть совершенно бесплатно! И мы настоятельно рекомендуем пользоваться этим обстоятельством. Прежде чем реализовывать A* для ваших юнитов, подумайте: может быть, кто-то уже сделал это до вас и выложил в общий доступ в удобной форме.
Ассеты с точки зрения 2D разработки
Так как мы рассматриваем 2D направление, модели, скрипты для камеры, скайбоксы и подобное отсекается сразу. Естественно, найдется разработчик, который сможет применить эти вещи в 2D игре, но статья предназначена в первую очередь для начинающих, которым в таких проектах лишние сложности не нужны.
Что же нам может понадобиться? Вот список:
Непосредственно ТОП
Пак стандартных ассетов от самой Unity. Это самые необходимые и проверенные в деле ассеты. Все они относятся к следующим категориям: 2D, Cameras, Characters, CrossPlatformInput, Effects, Environment, ParticleSystems, Prototyping, Utility, Vehicles. Эти ассеты можно подключить при установке Unity.
Великолепная библиотека твинов. Содержит все необходимые виды твинов (move, scale, rotate, alpha, color). Ее отличает очень удобный вызов методов, поддержка easing type и многое другое. Комбинируя простые твины, можно создавать очень интересные эффекты.
Очень простой для начинающего разработчика паковщик спрайтов. Необходим для создания атласов, которые экономят вам место и улучшают производительность.
Очень мощный инструмент для работы с полигон-коллайдерами. Позволяет более тонко настраивать количество вершин, работать с ними в рантайме.
Более сложный пример 2D платформера от самой Unity. В отличии от 2D Platformer Controller здесь добавлена анимация, стрельба и еще множество функций и объектов.
Ассет, который содержит в себе все необходимое для создания платформера. Здесь есть скрипты, управление персонажем, работа с камерой, вся необходимая физика и механика различных платформ. Очень рекомендуем посмотреть на этот ассет, прежде чем приступать к своему проекту.
Небольшое улучшение для ваших полигон-коллайдеров. С этим ассетом они становятся более плавными и… более тяжелыми с точки зрения производительности. Но если вам нужны точные формы коллайдеров, то этот ассет для вас.
Удобный ассет-редактор для создания тайловых уровней; может разбивать атласы по сетке, рисовать уровни в редакторе Unity, добавлять коллайдеры на блоки и настраивать слои отрисовки.
Достаточно удобная и простая система локализации. Не подойдет для больших игр, но в маленьких проектах отлично справляется с заменой текста на разных языках.
AutoSave – ассет, который не ценишь, пока он не сработает. Суть в том, что при обычном положении вещей если Unity зависает или вылетает, а вы не сохранили сцену, то все внесенные изменения пропадут. То есть вы можете час делать уровнь, забыть сохраниться и потерять его из-за критической ошибке в скрипте (например, бесконечного цикла). AutoSave сохраняет сцену при каждом нажатии на кнопку Play. Это очень удобно.
Хороший бесплатный пак частиц с готовыми префабами. Содержит взрывы, вспышки, эффекты огня и так далее. Для начального проекта самое то, особенно если вы будете менять шейдеры у частиц и получать свои, новые эффекты.
Мощный шаблон «рогалика». Содержит в себе управление героем, врагов, этажи уровней, звуки и эффекты.
Расширенный и усиленный вариант Simple Sprite Packer. Огромное количество функционала для работы с атласами.
Интересный ассет для создания красивой 2D системы освещения с контрастными тенями. Хорошо подойдет если вы делаете тактическую игру, стэлс платформер или какой-нибудь хоррор. Подобный ассет добавит в вашу игру атмосферы.
🏓 Создаем 2D-игру на Unity: инструкция для новичка
Двумерные игры сравнительно просты: для них не требуется сложных 3D-моделей, программный код по сравнению с 3D-проектами выглядит понятнее. Такие игры популярны как на десктопах, так и на мобильных устройствах. Unity также позволяет разрабатывать игры и для браузеров.
За последние годы вышло много популярных двумерных игр:
Программная реализация 2D-игр проще не только из-за отсутствия третьего измерения: на самой сцене меньше объектов, вместо трехмерных моделей плоские спрайты, вместо скелетной анимации – покадровая. А еще 2D-игры проще портировать на другие платформы – легче найти новую аудиторию.
Особенности создания 2D-игр на Unity
Предварительно рассмотрим основные понятия Unity, без понимания которых будет проблематично создать игру:
Пошаговый процесс создания 2D-игры на Unity
Предполагаем, что вы уже установили редактор и создали аккаунт на портале Unity.
В первую очередь создадим новый проект и откроем его настройки (Edit → Project Settings). Во вкладке Editor установим параметр Default Behaviour Mode в значение 2D
Настройка проекта
Детальная настройка проекта
Следующим шагом сохраним текущую активную сцену, назвав ее, например, Scene1. Теперь создадим основные игровые объекты: ракетку, мяч и менеджер игры, в котором будет храниться основная логика игры.
1. Создаем пустой объект, переименовываем в GameManager.
Создаем пустой объект
2. Создаем C#-скрипт с названием GameManager. Ассоциируем скрипт с объектом GameManager, перетащив мышкой скрипт на объект.
Создаем C# скрипт
3. Создаем квадратный спрайт, называем его Pad (Assets → Create → Sprites → Square). Аналогично создаем круглый спрайт Ball (Assets → Create → Sprites → Circle). Масштабируем спрайт Pad со следующими параметрами – x:0.5, y:2.5, z:1.
Создаем спрайты
4. Создаем префабы для Pad и Ball, после чего добавляем к ним компонент Box Collider 2D (включаем параметр Is Trigger) и компонент Rigidbody 2D (выставляем параметр Body Type в значение Kinematic).
5. Создаем C#-скрипты Ball и Pad. Ассоциируем их с префабами.
6. Заполняем скрипты следующим кодом.
GameManager.cs Ball.cs Pad.cs
6. Добавляем к префабу Ball и Pad теги с аналогичными именами. Выделив префабы, в инспекторе мы можем видеть выпадающий список тегов. Там же расположены и кнопки для добавления и редактирования тегов.
7. В настройках камеры выставляем параметр Projection в значение Orthographic, а параметр Clear Flag – в значение Solid Color.
Настройка камеры
8. Настраиваем кнопки, как показано на следующих скриншотах (Edit → Project Settings → Input Manager).
Настройка ввода, основное
Настройка ввода, первый игрок
Настройка ввода, второй игрок
Вот и всё, игра готова!
Пинг-понг, итоговый результат
Билд для платформы Windows
Дополнительные туториалы
1. Официальный туториал от Unity, где детально рассмотрен процесс создания roguelike RPG.
3. Youtube-канал N3K EN содержит множество уроков как по отдельным компонентам Unity, так и полноценные серии уроков по созданию игр с нуля.
В числе прочего вы разработаете 2D-платформер с физическими загадками и динамическим освещением, научитесь портировать его на мобильные устройства. Кроме того, разработаете полноценную браузерную стратегию, а также игру в жанре двухмерных гонок.
По окончании обучения вы будете иметь портфолио из 4 игр, которое можно показать на собеседовании. Если же какая-то часть материала будет непонятна, вы всегда можете обратиться к персональному преподавателю.
Создание простого ИИ на C# в Unity
Почти любой игре необходим взаимодействующий с пользователем искусственный интеллект (AI), чаще всего в виде враждебной игроку силы. В некоторых случаях ИИ должен помогать игроку, в других — бороться с ним, но у всех управляемых компьютером персонажей существует некоторое сходство. В зависимости от требований проекта ИИ может использовать простые или сложные поведения. Такими требованиями могут быть дипломатия с другим игроком или простое блуждание вперёд-назад по платформе. Как бы то ни было, необходимо сделать так, чтобы ИИ качественно выполнял свою работу.
В этом проекте я продемонстрирую очень простой искусственный интеллект. Допустим, мы создаём игру, в которой игрок должен тайком пробраться рядом с вражеским штабом. Когда игрока замечает камера слежения, рядом создаются враги и в течение короткого промежутка времени преследуют игрока. Именно это мы и реализуем в проекте на простейшем уровне. Завершив проект, вы получите управляемый объект игрока, круг, используемый в качестве камеры врага, и объект врага, который будет преследовать игрока, когда о его присутствии сообщит объект камеры.
Подготовка
Для начала нам нужно создать 3D-проект. Нажмите на кнопку New в верхней части окна после запуска Unity, как это показано на рисунке 1.
Рисунок 1: создание нового проекта
Назовите свой проект AI и убедитесь, что он является 3D-проектом. Выбрав место на компьютере для хранения проекта, нажмите на кнопку Create Project внизу, показанную на рисунке 2.
Рисунок 2: экран настройки проекта
После создания проекта нам первым делом нужно настроить папки в окне Assets, чтобы упорядочить свою работу. Нажмите правой кнопкой на окне Assets и выберите Create → Folder для создания новой папки. Назовите эту папку Materials. Затем создайте вторую папку и назовите её Scripts. На рисунке 3 показано, как это должно выглядеть.
Рисунок 3: создание новой папки
После всего этого окно Assets должно выглядеть так, как показано на рисунке 4.
Далее создадим пол, на котором будут стоять все объекты. В окне Hierarchy выберите Create → 3D Object → Plane, чтобы создать объект-плоскость, которая будет использоваться в качестве пола.
Рисунок 5: создание объекта Plane.
Назовите этот объект Floor и измените его значение X Scale на 7, а значение Z Scale — на 3. После этого окно Inspector с выбранным объектом Floor должно выглядеть так, как показано на рисунке 6.
Рисунок 6: задание свойств объекта Floor.
Теперь нам нужно создать новый материал для Floor, чтобы отличать его от остальных объектов, которые будут размещены в сцене. В папке Materials окна Assets создайте новый материал, нажав правой кнопкой на окно Assets и выбрав Create → Material.
Рисунок 7: создание нового материала
После завершения назовите материал Floor.
Рисунок 8: материал Floor.
В верхней части окна Inspector с выбранным материалом Floor выберите color picker.
Рисунок 9: выбор color picker.
Разумеется, вы можете выбрать для пола любой цвет, но в этом примере я выбрал красно-коричневый цвет, как показано на рисунке 10.
Рисунок 10: color picker.
Выберите объект Floor в окне Hierarchy, и в компоненте Mesh Renderer выберите маленькую стрелку рядом с Materials.
Рисунок 11: подготовка к изменению материала.
Перетащите материал Floor из окна Assets в поле Element 0 компонента Mesh Renderer в окне Inspector.
Рисунок 12: задание материала Floor в качестве материала объекта Floor.
Закончив с объектом Floor, мы дожны создать вокруг области стены, чтобы игрок не мог свалиться с края. Снова заходим в Create → 3D Object → Plane для создания новой плоскости. Назовём эту плоскость Wall и выставим ей те же размеры, что и у Floor, то есть X Scale со значением 7 и Z Scale со значением 3. Затем создадим ещё три стены, выбрав объект и трижды нажав Ctrl + D. После этого разместим стены вокруг пола в соответствии с данными из таблицы.
Название | Position X | Position Y | Position Z | Rotation X | Rotation Z |
Wall | -35 | 21 | 0 | 0 | -90 |
Wall (1) | -1 | 11 | -15 | 90 | 0 |
Wall (2) | -1 | 11 | 13.5 | -90 | 0 |
Wall (3) | 34 | 21 | 0 | 0 | 90 |
Таблица 1: позиции и повороты всех объектов Wall.
Завершив всё это, нужно изменить положение камеры, чтобы она смотрела на пол сверху. Выберите объект Main Camera и задайте для Y Position значение 30, для Z Position значение 0, а X Rotation — значение 80.
Рисунок 13: настройка объекта камеры.
Сцена подготовлена, поэтому настало время создания персонажа игрока. В окне Hierarchy нажмите на Create → 3D Object → Sphere, чтобы создать объект-сферу. Назовите этот объект Player, а затем нажмите на кнопку Add Component в нижней части окна Inspector.
Рисунок 14: добавление нового компонента.
Теперь найдите Rigidbody. После этого выберите из списка компонент Rigidbody и добавьте Rigidbody к объекту Player.
Рисунок 15: добавление компонента Rigidbody.
Далее нужно присвоить игроку тэг, который позже пригодится нам в коде. Нажмите на раскрывающееся меню Tag в левом верхнем углу окна Inspector и выберите тэг Player.
Рисунок 16: задание нового тэга.
Рисунок 17: размещение игрока.
Чтобы наш будущий код работал правильно, нам, разумеется, нужно прикрепить его к объекту. Снова заходим в окно Hierarchy и на этот раз выбираем Create → 3D Object → Cube. Назовём этот куб Guard, добавим к нему компонент Rigidbody и компонент NavMesh Agent с помощью кнопки Add Component в окне Inspector. Далее поместим его где-нибудь в верхнем левом углу сцены. После этого окно Inspector объекта Guard будет выглядеть следующим образом:
Рисунок 18: объект Guard в окне Inspector.
И этот объект должен быть расположен так:
Рисунок 19: Размещение объекта Guard.
Наконец, нам потребуется объект, используемый в качестве «глаз» объекта Guard, который будет уведомлять Guard о том, что его касается игрок. В последний раз перейдите в окно Hierarchy и выберите Create → 3D Object → Sphere для создания ещё одного объекта-сферы. Назовите этот объект Looker. На этот раз нам не нужно добавлять к нему никаких других компонентов. Однако мы изменим размер объекта. Выбрав Looker, измените следующие переменные компонента Transform в окне Inspector.
Рисунок 20: размещение объекта Looker.
Настало подходящее время для того, чтобы придать Looker уникальный материал, чтобы было заметно, что его стоит избегать. В папке Materials окна Assets нажмите правой клавишей мыши и создайте новый материал. Назовите его Looker и задайте ему ярко-красный цвет. После этого назначьте этот материал в качестве материала объекта Looker, чтобы изменить его цвет. После этого сцена должна выглядеть следующим образом:
Рисунок 21: объект Looker с новым материалом.
Единственное, что нам осталось — создать навигационный меш для Guard, по которому он сможет перемещаться. В верхней части редактора Unity есть меню Window. Выберите Window → Navigation, чтобы открыть окно Navigation, показанное на рисунке 22.
Рисунок 22: окно Navigation.
Выберите объект Floor в Hierarchy, а затем в окне Navigation поставьте флажок Navigation Static.
Рисунок 23: Navigation Static.
Далее выберем опцию Bake в верхней части окна.
Рисунок 24: переключение на меню Bake.
Откроется меню Bake, в котором можно изменять свойства навигационного меша, который мы собираемся создать. В нашем примере ничего изменять не требуется. Достаточно нажать на кнопку Bake в правой нижней части.
Рисунок 25: создание нового навигационного меша.
На этом этапе Unity попросит сохранить сцену. Сохраните её, после чего будет создан навигационный меш. Теперь сцена будет выглядеть так:
Рисунок 26: текущая сцена с добавленным навигационным мешем.
Теперь всё в Unity настроено, поэтому настало время для создания скриптов, необходимых для работы проекта. В окне Assets нажмите правой клавишей мыши и выберите Create → C# Script. Назовите этот скрипт Player. Повторите эту операцию ещё два раза, создав скрипты с названиями Guard и Looker.
Рисунок 27: создание нового скрипта.
После этого папка Scripts в окне Assets будет выглядеть так:
Рисунок 28: папка Scripts.
Первым мы начнём писать код скрипта Player. Дважды щёлкните по скрипту Player в окне Assets, чтобы открыть Visual Studio и приступить к созданию кода.
Скрипт Player достаточно прост, всё что он делает — позволяет пользователю перемещать объект-мяч. Под объявлением класса нам нужно получить ссылку на компонент Rigidbody, который мы ранее создали в проекте.
Сразу после этого в функции Start мы прикажем Unity сделать текущий компонент Rigidbody объекта Player значением rb.
После этого скрипт Player будет выглядеть так:
Рисунок 29: скрипт Player на текущий момент.
Теперь, когда значение rb присвоено, нам нужно позволить объекту Player двигаться при нажатии клавиш со стрелками. Для перемещения объекта мы будем использовать физику, применяя силу к объекту при нажатии пользователем клавиш со стрелками. Для этого достаточно добавить в функцию Update следующий код:
На этом мы завершили скрипт Player. Готовый скрипт будет выглядеть следующим образом:
Рисунок 30: готовый скрипт Player.
Сохраните свою работу и вернитесь в Unity. На этот раз выберите в окне Assets скрипт Guard. Чтобы заставить код для Guard работать, нужно добавить в верхнюю часть скрипта конструкцию using.
Далее объявим следующие переменные сразу после объявления класса.
В качестве значения переменной player объекта Guard используется объект Player. Она пригодится нам позже, когда мы прикажем объекту Guard преследовать игрока. Затем объявляется переменная navmesh для получения компонента NavMeshAgent объекта. Её мы используем позже, когда Guard начнёт преследовать игрока после того, как узнает о том, что игрок касается объекта Looker. В функции Start нам нужно задать в качестве значения переменной navmesh компонент NavMesh Agent объекта:
Затем в функции Update мы добавим единственную строку кода:
Эта строка задаёт точку назначения для объекта Guard. В нашем случае она будет брать текущую позицию объекта Player и перемещаться к этой точке. После срабатывания объект будет постоянно преследовать игрока. Вопрос в том, как выполняется процесс срабатывания? Он будет закодирован не в скрипте Guard, а в скрипте Looker. Прежде чем переходить к скрипту Looker, посмотрите на рисунок 31, чтобы сверить свой код скрипта Guard.
Рисунок 31: готовый скрипт Guard.
Внутри Looker нам снова нужно объявить следующие переменные:
После этого закомментируем функцию Start, которая в этом скрипте нам не нужна. Перейдём к функции Update и добавим следующий код:
Именно здесь происходят основные действия проекта, поэтому давайте проанализируем код. Во-первых, в зависимости от значения булевой переменной movingDown, объект, к которому прикреплён этот скрипт, будет двигаться вверх или вниз. Как только он достигнет определённой точки, то изменит направление. Далее Looker снизит значение сброса на основании реального времени. Как только таймер станет меньше нуля, он возьмём скрипт Guard из объекта Guard и отключит его, после чего объект Guard начнёт перемещаться к последней известной до этого моменрта позиции игрока, а затем остановится. Looker также снова включает его коллайдер, чтобы весь процесс мог начаться заново. Теперь наш скрипт выглядит следующим образом:
Рисунок 32: скрипт Looker.
Кстати о коллайдерах: настало время создать код коллизии, сбрасывающий таймер Looker и включающий скрипт Guard. В функции Update создайте следующий код:
OnCollisionEnter Unity автоматически распознаёт как код коллизии, а поэтому выполняет его при возникновении коллизии с другим объектом. В нашем случае он сначала проверяет, имеет ли столкнувшийся объект тэг Player. Если нет, то он игнорирует остальную часть кода. В противном случае он включает скрипт Guard, задаёт таймеру reset значение 5 (то есть пять секунд), и отключает его коллайдер, чтобы игрок по-прежнему мог двигаться сквозь объект и случайно не застрял в объекте Looker. Функция показана на рисунке 33.
Рисунок 33: код коллизии для Looker.
На этом весь код проекта готов! Можно сделать ещё пару вещей, прежде чем закончить проект. Сохраните всю работу и вернитесь в Unity.
Завершение проекта
Для завершения проекта нам достаточно прикрепить скрипты к соответствующим объектам и задать несколько переменных. Во-первых, перейдите из окна Navigation в окно Inspector:
Рисунок 34: переход в окно Inspector.
После этого начнём с объекта Player. Выберите его в окне Hierarchy, а затем в нижней части окна Inspector нажмите на кнопку Add Component и добавьте скрипт Player. На этом объект Player завершён.
Рисунок 35: компонент скрипта Player.
Далее выберите объект Guard. Как и раньше, прикрепим скрипт Guard к объекту. На этот раз нам понадобится сообщить Guard, кто является игроком. Для этого перетащите объект Player из Hierarchy в поле Player компонента скрипта Guard, как показано на рисунке 36.
Рисунок 36: делаем объект Player значением поля Player.
Также нам нужно отключить скрипт Guard. В нашем проекте Guard будет преследовать игрока после включения его скрипта. Этот скрипт Guard должен включаться только после того, как игрок коснётся объекта Looker. Всё, что нужно сделать — снять флажок рядом с текстом Guard (Script) в компоненте:
Рисунок 37: отключение скрипта Guard.
Наконец, перейдём к объекту Looker и прикрепим к нему скрипт Looker. На этот раз объекту Looker потребуется объект Guard в качестве значения его переменной Guard. Так же, как мы назначали объект Player переменной Player скрипта Guard, мы сделаем то же самое с объектом Guard и скриптом Looker. Перетащите Guard из Hierarchy в поле Guard скрипта Looker. И на этом проект завершён! Нажмите на кнопку Play в верхней части редактора Unity, чтобы проверить свой проект.
Рисунок 38: тестирование проекта.
Попробуйте переместить объект Player в объект Looker (не забывайте, что перемещение выполняется стрелками!). Заметьте, что после этого объект Guard начнёт преследовать игрока. Он будет продолжать преследование примерно 5 секунд, после чего сдастся.
Рисунок 39: полностью готовый проект в действии.
Заключение
Этот ИИ очень прост, но его запросто можно расширить. Допустим, если мы представим, что объект Looker — это камера, а охранник смотрит через неё, чтобы найти вас, то будет логично дать объекту Guard собственную пару глаз. Игрок может проходить рядом с камерами, но они должны учитывать и глаза охранника. Также можно скомбинировать этот проект с концепцией поиска пути: дать охраннику путь, по которому он будет следовать, создав таким образом более интересную для игрока среду.
Подобный простой ИИ можно развить множеством разных способов. Возможно, вы не захотите делать ничего вышеизложенного и решите сделать что-то своё. Советую вам экспериментировать, возможно, у вас появится идея интересного проекта, который стоит довести до конца.