Академический Документы
Профессиональный Документы
Культура Документы
ПОЯСНИТЕЛЬНАЯ ЗАПИСКА
выпускной квалификационной работы:
Разработка игрового приложения в жанре «survival-horror» для снятия
психофизического напряжения
(тема)
Директор института
___________________ _________________ ___________ ____________________
(наименование института) (уч. степень, звание) (подпись) (фамилия, имя, отчество)
Новокузнецк
2021 г.
Министерство науки и высшего образования Российской Федерации
Кафедра ________________________________________________________________
УТВЕРЖДАЮ
Заведующий кафедрой
_________ ___________________
(подпись) (ФИО)
«____» _________________ 20__г.
ЗАДАНИЕ
на выпускную квалификационную работу
обучающегося ___________________________________________________________
(фамилия, имя, отчество)
группы ________________
Руководитель ___________________
(подпись)
ВВЕДЕНИЕ..........................................................................................................................8
1 Общая часть......................................................................................................................9
1.1 Характеристика объекта информатизации...............................................................9
1.2 Обзор и анализ известных разработок....................................................................10
1.2.1 Knock knock.........................................................................................................10
1.2.2 Limbo....................................................................................................................14
1.2.3 Creaks...................................................................................................................17
1.2.4 Little Nightmares..................................................................................................21
1.2.5 Darkest Dungeon..................................................................................................24
1.3 Сравнительный анализ существующих разработок..............................................27
1.4 Система-прототип по видам обеспечения..............................................................28
1.4.1 Информационное обеспечение системы-прототипа.......................................28
1.4.2 Алгоритмическое обеспечение системы-прототипа.......................................31
2 Специальная часть..........................................................................................................34
2.1 Техническое задание: создание игрового приложения в жанре «survival-horror»
для снятия психофизического напряжения..................................................................34
2.1.1 Введение.............................................................................................................34
2.1.2 Назначение разработки.....................................................................................34
2.1.3 Требования к функциональным характеристикам.........................................34
2.1.3.1 Состав выполняемых функций....................................................................34
2.1.3.2 Требования к надёжности............................................................................36
2.1.3.3 Требования к мобильности..........................................................................36
2.1.3.4 Условия эксплуатации..................................................................................36
2.1.3.5 Требования к составу и параметрам технических средств.......................37
2.1.3.6 Требования к информационной и программной совместимости.............37
2.1.3.7 Требования к маркировке и упаковке.........................................................37
2.1.3.8 Требования к информационной и программной совместимости.............37
2.1.4 Требования к программной документации......................................................38
2.1.5 Требования к дизайну игрового продукта........................................................38
2.1.6 Технические показатели.....................................................................................39
2.1.7 Стадии и этапы разработки................................................................................40
2.1.8 Порядок контроля и приемки............................................................................41
2.1.9 Экономическое обоснование.............................................................................41
2.2 Описание разработанного продукта по видам обеспечения................................45
2.2.1 Информационное обеспечение..........................................................................45
2.2.1.1 Модульная структура работы программы..................................................45
2.2.1.2 Модульная структура работы искусственного интеллекта......................49
2.2.2 Алгоритмическое обеспечение..........................................................................52
2.2.2.1 Алгоритмы работы программы и ее основных составляющих объектов52
2.2.2.2 Алгоритмы работы искусственного интеллекта........................................80
2.2.3 Техническое обеспечение................................................................................107
2.2.4 Программное обеспечение...............................................................................107
2.2.4.1 Используемые программы.........................................................................107
2.2.4.2 Разработка графического интерфейса.......................................................110
2.2.4.3 Создание анимации.....................................................................................116
2.2.4.4 Создание саундтреков и звуковых эффектов...........................................123
2.2.5 Концептуальное обеспечение..........................................................................132
2.2.6 Лингвистическое обеспечение........................................................................143
2.3 Тестирование продукта..........................................................................................144
2.3.1 Тест-план...........................................................................................................144
2.3.2 Тест-комплекты.................................................................................................146
2.3.3 Результаты выполнения...................................................................................149
ЗАКЛЮЧЕНИЕ................................................................................................................153
БИБЛИОГРАФИЧЕСКИЙ СПИСОК............................................................................155
ВВЕДЕНИЕ
8
1 Общая часть
9
1.2 Обзор и анализ известных разработок
10
Еще одним отличием данной игры от подобных является то, что она
фактически не даёт возможности пользователю взаимодействовать с врагами. Это
означает, что главный герой никак не сможет остановить «гостя», и чтобы спастись
ему необходимо либо искать укрытие, либо починить освещение в проблемной
комнате до того, как враг настигнет его.
Врагов в игре можно условно разделить на три категории:
неподвижные. Самые безобидные враги, использующиеся лишь в целях
нагнетания пугающей атмосферы;
подвижные. Основной тип врагов, в основном занимающийся
патрулированием закрепленных за ними территорий, либо преследованием игрока;
редкие (подвижные) враги, обладающие специальными возможностями.
Самый опасный подвид противников, ввиду их исключительных способностей,
таких как обыскивать укрытия, перемещаться по совершенно неожиданным
траекториям и тому подобное.
Примеры врагов в игре представлены на рисунках 1 и 2.
11
Рисунок 2 – Неподвижный враг в «Knock-knock»
12
Рисунок 4 – Комната в «Knock-knock» (вариант 2)
13
сосредоточенность на концептуальной составляющей явно не пошла на пользу
геймплею.
Хотя многие критики отмечают уникальность механик в игре, все они быстро
надоедают и к середине прохождения пользователь ощущает монотонность
игрового процесса.
Чтобы избавиться от такой проблемы, следует детально продумать ход
игрового процесса, а также длительность прохождения, так как любая игра может
стать неинтересной если пытаться специально растянуть её, при этом, не
оглядываясь на то, что геймплей не подразумевает долгого исследования или
повествования.
1.2.2 Limbo
14
Геймплей же представляет собой метод «проб и ошибок». Пользователю
предстоит путем исследования мира, выбрать правильный вариант прохождения
определенной локации, однако в процессе, как правило главный герой несколько раз
умирает из-за ошибок пользователя. Игра никак не наказывает игрока за смерти и
даже поощряет такой подход к прохождению.
Мир лимбо наполнен большим количеством ловушек, поэтому эту игру часто
сравнивают с классическими платформерами из-за схожести геймплея. Как и в
большинстве проектов данного типа, игровой процесс строится на физическом
взаимодействии главного героя с окружением. Игрок может идти вправо, влево,
прыгать, взбираться на небольшие уступы, карабкаться по верёвкам или лестницам,
а также толкать и тянуть предметы.
Примеры игровых локаций представлены на рисунках 5 и 6.
15
Рисунок 6 – Локация в «Limbo» (вариант 2)
16
Анализируя данную игру, можно прийти к нескольким важным выводам. Во-
первых, ставка на геймплей в играх жанра «survival-horror» является скорее
ошибкой, так как для удержания внимания пользователя этого недостаточно. Игрок
должен следовать за сюжетом, который увлекал бы его, мотивируя и дальше
проходить игру. В «Limbo» решили оставить сюжет на заднем плане, в итоге
получился скорее типичный платформер с элементами головоломки, а не
полноценное хоррор произведение.
В отличие от рассмотренной ранее «Knock-knock», игровой процесс в «Limbo»
занимает не более 6 часов, однако именно из-за отсутствия повествования,
пользователи отмечают, что после второй половины игры, интерес к ней резко
падает, пользователь не видит прогресса при продвижении к финалу.
Однако пример данной игры, показывает то, насколько важно создавать
оригинальный авторский графический и звуковой стиль, ведь именно из-за него,
многим так понравилась и запомнилась «Limbo».
В целом, можно сказать, что данный игровой продукт является
противоположностью рассматриваемой ранее игры, но при этом некоторые
типичные проблемы жанра «survival-horror» присутствуют в двух этих играх. Чтобы
избежать их, следует прежде всего соблюдать баланс геймплейной и
концептуальной составляющей.
1.2.3 Creaks
17
возможности, используя освещение, нейтрализовать их (под светом ламп враги
превращаются в мебель).
В игре присутствуют несколько разновидностей врагов, каждый из них имеет
свою логику поведения, которая строга задана и не подстраивается под действия
персонажа. К примеру, собаки двигаются строго по горизонтальной траектории и не
имеют возможности преодолеть преграды, в то время как медузы могут двигаться
как в вертикальной, так и в горизонтальной плоскости. Пример врагов в игре
представлен на рисунке 7.
18
Графическая составляющая игры представляет собой фирменный стиль
студии Amanita Design. Характерной особенностью этого стиля является
проработанность деталей окружения, при этом все локации и персонажи
нарисованы вручную. Именно такой стиль принес успех студии в прошлом. [3]
Примеры локаций представлены на рисунках 8 и 9.
19
Звуковая составляющая в игре также хорошо проработана и представляет
собой ритмичный «ambient», который заставляет пользователя концентрироваться, и
находится в напряжении при встрече с врагами. Однако нельзя сказать, что данная
музыка является типичной для жанра, разработчики, понимая это, добавили в игру
резких, пугающих звуков игрового окружения, которые и выполняют требуемую
функцию, а именно создание пугающей, тревожной атмосферы.
В данном проекте нельзя выявить новых уникальных механик, все
головоломки в игре представляются типичными для подобных платформенных игр
и по мнению некоторых игровых критиков не отличаются разнообразием.
Подводя итоги, можно выявить следующие плюсы игры:
музыкальная составляющая и в целом звуковой дизайн на достаточно
высоком уровне и хорошо выполняют свои функции;
уникальная графическая составляющая, все детали которой сделаны
вручную и впоследствии обработаны специализированным программным
обеспечением;
множество различных деталей, которые может исследовать игрок,
например, коллекция картин, которые главный герой находит по ходу игры. В
каждой из таких картин имеется своя уникальная головоломка.
Из минусов же можно отметить следующее:
плохо проработанный сюжет и история мира, в который попадает главный
герой игры;
однообразные головоломки, которые могут оттолкнуть от прохождения;
непривычное управление и анимации главного героя.
Проанализировав данную игру, можно заметить, что некоторые её проблемы
схожи с игрой «Limbo», рассмотренной ранее, а именно плохая проработка сюжета
и истории игрового мира. Также явно видна проблема однотипности геймплея, что
характерно для многих проектов в данном жанре.
Разработчики явно делали акцент на графической и звуковой составляющих,
однако при всей уникальности и необычности решений в этих областях, только эти
два аспекта не могут долго удержать внимание пользователя.
20
Делая выводы по данной и предыдущим играм, можно прийти к заключению,
что следует большое внимание уделять не только графической и звуковой
составляющей, но и чётко построенному сюжету, который раскрывал бы мысли и
поступки персонажей, описывал окружающий мир и мотивировал бы игрока в
дальнейшем прохождении.
21
Рисунок 10 – Локация в «Little Nightmares»
22
этим приёмам игра сильно запомнилась многим и получила множество
положительных отзывов от критиков.
Звуковой дизайн также был детально проработан. Чаще всего саундтрек
определенной локации представляет собой музыку в стиле мрачного эмбиента с
добавлением партий пианино и женского вокала. На фоне периодически можно
услышать плач, вой и стоны. Всё это в совокупности привносит ощущения
тревожности и мрачности, характерные для игр в жанре хоррор.
Основные плюсы данной игры:
интересные и сложные головоломки;
хорошо проработанный игровой мир, выдержанный в единой атмосфере;
уникальное звуковое оформление и авторские саундтреки.
Из минусов можно отметить:
множество бесполезных, пустых локаций;
плохо проработанная система ключевых точек для сохранения игрового
процесса;
плохая оптимизация.
Проанализировав данный игровой проект, можно сделать вывод, что в плане
подхода к сюжету он является типичным представителем жанра платформера с
элементами «survival-horror» и все недостатки такого подхода, а именно
постепенное уменьшение заинтересованности пользователя в дальнейшем
прохождении, также присутствуют в игре.
В «Little Nightmares» была сделана большая ставка на графику и звук и даже
геймплей отходит на второй план в игровом процессе рассматриваемого
приложения. При этом, несмотря на то, что геймплей не был так старательно
проработан, он всё-таки остается на достаточно высоком уровне, так как он тесно
связан с графической составляющей игры. Это связь проявляется, например, в том,
что одна и та же головоломка, которую должен решить игрок, в различных локациях
предстаёт по-разному, что для конечного пользователя выглядит привлекательнее,
чем решение однотипно сделанных головоломок.
23
В заключении можно сделать вывод, что «Little Nightmares» является
достаточно хорошо проработанной с точки зрения визуальной, звуковой и
геймплейной части игрой, однако из-за типичного пренебрежения сюжетной
составляющей, заинтересованность человека в полном прохождении резко падает.
24
Рисунок 12 – Битва в игре «Darkest Dungeon»
28
Подмодуль «Обработка нажатия на кнопку»;
Модуль «Игровой процесс»;
Подмодуль «UI/UX интерфейс»;
Подмодуль «Скрипты»;
Модульная единица «Сохранение игрового процесса»;
Модульная единица «Обеспечение интерактивного взаимодействия»;
Модульная единица «ИИ врагов».
Рассмотрим каждый из них подробнее.
В модуле игрового меню содержатся три подмодуля и две модульные
единицы, которые отвечают за непосредственное начало нового игрового процесса,
при этом модульная единица «Продолжить» появляется только при наличии уже
существующего сохранения, или загрузки уже сохраненного ранее, за просмотр
имен создателей приложения и за выход из данного игрового приложения с
сохранением текущего игрового прогресса.
В модуле операций пользователя содержится только один подмодуль, который
отвечает за обработку нажатий определенных клавиш на игровых манипуляторах и
вызов соответствующих им методов из кода игры.
В модуле «Игровой процесс» представлены два важнейших подмодуля,
отвечающих за непосредственно работу игрового приложения.
Первый из них, подмодуль «UI/UX интерфейс», включает в себя всю
графическую составляющую, а именно такие элементы, как: анимированные
спрайты, бэкграунды локаций, информационные элементы.
Следующим рассматриваемым компонентом является подмодуль «Скрипты».
Он отвечает за автоматическое сохранение игрового прогресса пользователя в
определенные моменты, а также за логику поведения враждебных персонажей и за
обеспечение интерактивного игрового взаимодействия.
29
Рисунок 15 – Модульная структура система-прототипа
30
1.4.2 Алгоритмическое обеспечение системы-прототипа
31
Рисунок 16 – Блок-схема алгоритма работы системы-прототипа (часть 1)
32
Рисунок 17 – Блок-схема алгоритма работы системы-прототипа (часть 2)
33
2 Специальная часть
2.1.1 Введение
34
программа должна иметь «простой» графический интерфейс, содержащий
минимальное количество, интуитивно понятных любому пользователю, кнопок,
осуществляющих основные функции взаимодействия с игровым миром;
программа должна работать под операционными системами Windows,
начиная с Windows 7, и Android;
программа должна предоставить возможность осуществление управлением
игрой с помощью манипуляторов – клавиатуры, мыши и тачпада;
программа должна вести статистику: после прохождения каждого уровня
игрок может наблюдать за временем, потраченным на уровень, а также за тем с
какого раз он прошел уровень (количеством проигрышей);
программа должна показывать пользователю в ходе игрового процесса
сообщение о проигрыше («Безумие») прямо посередине экрана;
программа должна содержать в игровом поле кнопку «Пауза», по нажатию
которой игровой процесс на время приостанавливался и позволял бы пользователю
при желании выйти в главное меню;
программа должна содержать раздел главного меню «Об игре», где
пользователю должна предоставляться возможность прочитать краткую
информацию об игре и правила игры;
программа должна содержать разнообразный ИИ для врагов, управляемых
компьютером и препятствующему персонажу в прохождении;
компьютерный ИИ будет представлен в виде нескольких алгоритмов: когда
игрок не контактирует с монстрами, последние двигаются по определенной заранее
траектории (патрулируют местность) или ждут определенных событий, при
контакте с персонажем каждый вид врагов начинает следовать своему собственному
алгоритму поведения. Также существуют специальные враги, которые, например,
могут: просто появиться перед игроком из ниоткуда и стоять на месте, при
преследовании обыскивать «укрытия», ходить сквозь стены и другие препятствия,
стрелять в игрока различными снарядами;
программа должна в определенные заранее моменты времени
автоматически сохранять текущий процесс прохождения.
35
2.1.3.2 Требования к надёжности
Не предъявляются.
Не предъявляются.
37
2.1.4 Требования к программной документации
38
предметы взаимодействие;
спрайты игровой локации.
интерфейс при переходах на новые уровни.
Прототип интерфейса игрового процесса представлен на рисунке 18.
39
2.1.7 Стадии и этапы разработки
40
Продолжение таблицы 2
Первое, что нужно отметить, рынок видеоигр в России растет с каждым годом.
И по данным 2018 года, представленных на рисунке 19, Россия входит в 20 стран с
крупнейшими доходами с создания и сбыта игровых продуктов, занимая 11 место.
41
Рисунок 19 – Рейтинг стран с крупнейшими доходами с видеоигр
42
Рисунок 20 – Рейтинг популярности платформ для игровых приложений
43
можно получить качественный продукт, имеющий возможность заинтересовать
пользователей.
На основе всего вышеперечисленного, можно сделать вывод, что при наличии
текущего количества ресурсов и возможностей, самым экономически выгодным
решением будет создание 2D игры в жанре «survival-horror», для последующего
выпуска на ПК и Android.
44
2.2 Описание разработанного продукта по видам обеспечения
45
Всю программу условно можно разделить на три больших модуля «Игровое
меню», «Операции пользователя» и «Игровой процесс», которые включают в себя
множество подмодулей и модульных единиц, отождествляемых с операциями,
данными (объектами) и преобразованиями внутри приложения.
Модуль игрового меню содержит четыре подмодуля и три модульных
единицы.
Подмодуль «Играть» включает в себя две модульные единицы, отвечающие за
начало новой игры, либо за продолжение уже сохраненной ранее, если такая
присутствует (в противном случае кнопка «Продолжить» будет заблокирована). В
случае начала новой игры, при наличии некоторого прогресса прохождения,
полученного ранее, сохранение будет перезаписано. Иными словами, с точки зрения
«движения» информации в игровом приложении этот элемент структуры отвечает
либо за ее загрузку из памяти жесткого диска компьютера, либо за ее полную
перезапись.
Подмодуль «Параметры», аналогично предыдущему, включает в себя две
модульные единицы, отвечающие непосредственно за настройку звуков окружения
и музыки в игре. Стоит отметить, что полученные данные о выставленных
пользователем значениях с помощью слайдеров, как на рисунке 23, также будут
подвергнуты сохранению и при последующих запусках игры удобные для игрока
настройки будут подгружены сразу без его непосредственного участия.
Последний же из подмодулей внутри первого блока отвечает за выход из игры
с сохранением текущего прогресса игрока в прохождении, при этом сам процесс
записи информации о прохождении будет выполняться в автоматическом режиме
даже при условии аварийного выхода – отключения питания от компьютера,
закрытия приложения через диспетчер задач и тому подобное.
В модуле операций пользователя содержится только один подмодуль, который
отвечает за обработку нажатий пользователем определенных клавиш на игровых
манипуляторах и вызов соответствующих им методов из кода игры.
Связь между фиксациями нажатий и их программной реализацией через
алгоритмы осуществляется с помощью встроенных средств самого Unity, среди
46
которых можно выделить многочисленные методы покадровых проверок, в
частности, на заранее выделенные и записанные программистом условия и на
некоторые физические взаимодействия, предусмотренные самим движком
(столкновения, триггеры или пересечения объектов).
В последнем модуле «Игровой процесс» представлены два самых важных
подмодуля, отвечающих за непосредственно работу игрового приложения.
Первый из них, подмодуль «UI/UX интерфейс», включает в себя все
графические компоненты, а именно такие элементы, как: спрайты главного героя,
противников и игровых объектов, бэкграунды локаций, информационные элементы,
предоставляющие пользователю сведения о текущем прогрессе по уровню. Каждая
из этих структурных частей программы хранится в папке с игрой на жестком диске.
Вторым рассматриваемым компонентом является подмодуль «Скрипты». Он
отвечает за автоматическое сохранение игрового прогресса пользователя в
определенные моменты, а также за искусственный интеллект враждебных
персонажей и за обеспечение интерактивного игрового взаимодействия:
возможность перемещения главного героя по локации и взаимодействия с
определенными объектами, проигрывание катсцен после некоторых
предопределенных действий пользователя.
47
Рисунок 24 – Модульная структура программы
48
2.2.1.2 Модульная структура работы искусственного интеллекта
49
Рисунок 26 – BoxCollider2D
Рисунок 27 – CirlceCollider2D
50
Rigidbody – объект движка, который управляет положением объекта
через имитацию физики. Причем значения данного объекта у врагов должны
быть следующими:
Body Type = Dynamic;
Simulated = true;
Use Auto Mass = false;
Mass = 1;
Linear Drag = 0;
Angular Drag = 0.05;
Gravity Scale = 3;
Collision Detection = Discreate;
Sleeping Mode = Start Awake;
Interpolate = None;
Freeze Rotation X = false;
Freeze Rotation Y = false;
Freeze Rotation Z = true. [6]
Animator – определяет структуру скелета объекта, но контроллер аниматора
(AnimatorController) также требуется для применения анимаций к скелету.
Контроллер аниматора создается Unity и позволяет руководить набором анимаций
для персонажа и переключаться между ними, когда выполняется некоторое условие;
AudioSource – звуковой файл, который будет использоваться данным
объектом;
Script – файл с алгоритмами на языке C#, по которым происходит
управление данных объектом;
RectTransform - используются для графического интерфейса, но также могут
использоваться для других целей. Он используется для хранения и управления
положением, размером и привязкой прямоугольника и поддерживает различные
формы масштабирования.
51
2.2.2 Алгоритмическое обеспечение
53
Рисунок 28 – Блок-схема работы алгоритма (часть 1)
54
Однако помимо основного алгоритма работы самой программы,
представленного взаимодействием всех элементов, в игре присутствуют множество
других алгоритмов, присущих исключительно определенным объектам.
Рассмотрим каждый из них подробнее в привязке к соответствующим
сущностям.
Объект «Character» (здесь и далее имена идентичны тем, что имеют объекты
на форме визуального редактора Unity 3D).
В игровом процессе обозначает главного героя, за которого
пользователю предстоит пережить всю историю заброшенного особняка.
Управление персонажем будет происходить посредством таких игровых
манипуляторов, как клавиатура или тачпад, в зависимости от выбранной
платформы. Как объект алгоритмизации у него имеется несколько встроенных
алгоритмов, которые условно можно разделить на две группы:
явные;
неявные.
Главное отличие между ними заключается в том, что работа алгоритмов
первой группы зачастую управляется пользователем и явно ему видна, то есть
имеет в своем составе какой-либо анимационный эффект, а второй – скрыта
от его глаз и происходит вне его ведома.
В соответствие с описанными выше различиями к первой группе будут
отнесены следующие алгоритмы.
Алгоритм «Run».
Отвечает за передвижение персонажа по карте игрового мира,
регистрируя нажатия на игровых манипуляторах от пользователя. Так как в
игре присутствуют не только горизонтальные поверхности (пол, земля), но и
вертикальные (лестницы), то алгоритм был составлен таким образом, чтобы в
зависимости от текущего местоположения героя вычислялась его возможность
передвижения во всех плоскостях. Сам же выбор направления движения
осуществляет игрок. Код алгоритма представлен в листинге 1.
Листинг 1 – Алгоритм передвижения
55
private void Run(bool isSpider)
{
speed = !isSpider ? 5f:5f;
if ((!audio.isPlaying)) { audio.clip =
Resources.Load<AudioClip>(isSpider?
(ColliderLadderTriger?"st1":"st2") : "st1"); audio.Play(); }
Vector3 direction = (!isSpider ? transform.right :
transform.up) * Input.GetAxis(!isSpider ? "Horizontal" : "Vertical") *
2;
transform.position = Vector3.MoveTowards(transform.position,
transform.position + direction, speed * Time.deltaTime);
sprite.flipX = direction.x < 0.0f;
animator.SetFloat("Multy", !isSpider ? 0.68f:1f);
if (isSpider)
animator.SetInteger("State",ColliderLadderTriger ? 1 : 3);
else
animator.SetInteger("State", 1);
}
Представленный выше алгоритм реализует следующие функции:
устанавливает скорость передвижения персонажа в зависимости от
направления движения (горизонтально или вертикально);
устанавливает определенную аудиодорожку – либо звук ходьбы по полу,
либо звук карабканья по лестнице;
реализует считывание направления движения с игровых манипуляторов
игрока;
в зависимости от выбранной плоскости передвижения происходит
перемещение персонажа с заданной ранее скоростью;
в процессе ходьбы по горизонтальной поверхности определяется
направление взгляда главного героя (зависит от направления);
устанавливается нужная анимация и ее скорость проигрывания. [7]
На рисунках 30 и 31 можно увидеть два различных режима работы
алгоритма «Run».
56
Рисунок 30 – Работа алгоритма «Run» (ходьба)
57
Отвечает за взаимодействие персонажа с дверьми на карте игрового
мира, регистрируя нажатия на игровых манипуляторах от пользователя. Так
как в игре присутствуют препятствия не только в виде вертикальных стен, но
и в виде закрытых дверей, соединяющих различные комнаты в
горизонтальной плоскости и, по сути, организующие проходы между стенами,
то алгоритм для их открытия необходим для продвижения по уровням. Запуск
алгоритма осуществляет игрок по нажатию клавиши при условии
непосредственной близости персонажа и двери. Код алгоритма представлен в
листинге 2.
Листинг 2 – Алгоритм открытия дверей
private IEnumerator Opening(float t,GameObject Door)
{
Stop = true;
animator.SetInteger("State",2);
yield return new WaitForSeconds(t);
Door.GetComponent<Animator>().Play("Opening");
Door.GetComponent<BoxCollider2D>().enabled = false;
Stop = false;
}
Представленный выше алгоритм реализует следующие функции:
устанавливает ограничение на любые действия персонажа во время
открытия дверей (и соответственно управление данным объектом для пользователя
на это время недоступно);
устанавливается нужная анимация для главного героя;
вызывается задержка на время проигрывания анимации персонажа;
вызывается проигрывание анимации открытия уже самой двери;
отключается, созданный на старте уровня, коллайдер двери (невидимый
статический осязаемый игровой объект, чаще всего в виде геометрических фигур,
например, прямоугольника или круга, реализованный, в данном случае, как
препятствие);
отключается ограничение персонажа на посторонние действия, иными
словами, игрок снова может им управлять.
На рисунке 32 можно увидеть работу алгоритма «Opening».
58
Рисунок 32 – Работа алгоритма «Opening»
Алгоритм «Hider».
Отвечает за взаимодействие персонажа с укрытиями на карте игрового
мира, как на рисунке 33, регистрируя нажатия на игровых манипуляторах от
пользователя. Так как в игре присутствуют враги, которые охотятся за
персонажем и далеко не от всех можно просто убежать, а дать им отпор
попросту невозможно ввиду их сверхъестественного происхождения, то
возможность спрятать персонажа по различным урытиям, разбросанным по
уровням, необходима для прохождения. Запуск алгоритма осуществляет игрок
по нажатию клавиши при условии непосредственной близости персонажа и
укрытия. Код алгоритма представлен в листинге 3.
Листинг 3 – Алгоритм взаимодействия с укрытием
private IEnumerator Hider()
{
59
int layer =
gameObject.GetComponentInChildren<SpriteRenderer>().sortingOrder;
GameObject.FindGameObjectWithTag("Shkaf").GetComponent<Anima
tor>().Play(layer == 0 ? "Shk" : "Khf");
if (layer == 0) EventManager.CallDarkness("Тьма"); else
EventManager.CallDarkness("Свет");
yield return new WaitForSeconds(layer == 0 ? 0f : 1f);
if (layer == 0) Stop = true; else Stop = false;
gameObject.GetComponentInChildren<SpriteRenderer>().sortingOrder =
layer == 0 ? -10 : 0;
gameObject.GetComponentInChildren<Light>().enabled = layer ==
0 ? false : true;
gameObject.tag = layer == 0 ? "HiddenPlayer" : "Player";
}
Представленный выше алгоритм реализует следующие функции:
запоминается текущее положение персонажа на структуре слоев (для
последующего возращения); [8]
устанавливается нужная анимация для укрытия в зависимости от того
выходит или входит в него главный герой;
вызывается сторонний метод, который затемняет все окружение, кроме
небольшого пространства вокруг, для создания ощущения нахождения внутри
укрытия, в случае, если персонаж выходит из него, то происходит ровно
противоположный эффект (через тот же метод, но с другими аргументами);
вызывается задержка перед «выходом» персонажа, для того чтобы анимация
укрытия успела закончиться (при входе никакой задержки нет);
включается (или выключается) ограничение персонажа на различные
действия, иными словами, игрок не может (может) им управлять (главный герой не
должен иметь возможность передвигаться или делать что-либо посторонее внутри
укрытия);
сам персонаж при входе в укрытие перемещается на другой слой (эффект
сокрытия), у него отключается область видимости (в виде тусклого ореола около
головы), изменяется тег объекта (используемый для его определения на игровой
сцене, то есть, например, монстры больше не смогут его преследовать, так как
попросту перестанут его «видеть»).
60
Рисунок 33 – Работа алгоритма «Hider» (для демонстрации специально увеличена
яркость освещения)
Алгоритм «Send».
Отвечает за визуальное отображение мыслей главного героя в виде
сообщений, как на рисунке 34, при взаимодействии с различными объектами
на карте игрового мира, регистрируя нажатия на игровых манипуляторах от
пользователя, столкновения или вхождения в радиус действия триггера
(неосязаемый объект на базе коллайдера, при пересечении которого
происходит какое-либо действие). Так как в игре присутствует множество
разных элементов, не все назначения которых элементарно ясны, то требуется
неким образом доносить до игрока информацию об этих объектах, например в
виде диалоговых сообщений. В отличие от прошлых методов, здесь
реализация алгоритма осуществляется не напрямую, а посредством других
методов (часто неявных), которые будут рассмотрены позже. Код алгоритма
представлен в листинге 4.
Листинг 4 – Алгоритм вывода диалоговых сообщений
private IEnumerator Send(string c, float t,float z)
{
yield return new WaitForSeconds(z);
61
EventManager.CallDialog(c, true);
Timer = t;
reachTimer = false;
}
Представленный выше алгоритм реализует следующие функции:
вызывается задержка в определенное число секунд перед выводом
сообщения;
через систему событий сторонним методом вызывается метод вывода текста
на экран через диалоговую компоненту (этому методу сразу же передается строка с
сообщением);
устанавливается время для таймера и происходит его запуск (таймер
определяет время отображения текста на экране, после истечения которого другой
неявный метод отключает визуальный показ сообщения).
62
Отвечает за проверку выполнения игровых условий или методов каждый
кадр, независимо от нажатий на игровых манипуляторах, столкновений
объектов или их вхождений в радиус действия триггеров. В работе программы
он предсталвен связующим звеном для работы других методов и отличается
от последних тем, что является встроенным методом Unity. Код алгоритма
представлен в листинге 5.
Листинг 5 – Алгоритм «Update»
private void Update()
{
if (!reachTimer && Timer >= 0) Timer -= Time.deltaTime;
else { reachTimer = true; EventManager.CallDialog("", false);}
if (!Stop)State = CharState.Idle;
SenAnimationHero();
if (Input.GetButton("Horizontal")&&!Stop) Run(false);
else if (Input.GetButton("Vertical") && isClimbing&& !Stop)
Run(true);
else if (Hide && Input.GetKeyUp(KeyCode.Space))
{
StartCoroutine("Hider");
}
else if (Open &&!Stop&& Input.GetKeyUp(KeyCode.E))
{
StartCoroutine(Opening(2f, NearDoor));
}
}
Представленный выше алгоритм реализует следующие функции:
реализует сокрытие диалоговой компоненты с сообщением в течение
некоторого времени после ее появления;
регистрирует нажатия пользователя на манпуляторах и вызывает
соответствующие методы: движение по горизонтали или вертикали, открытие двери,
использование укрытий; [9]
вследствие вызова других методов происходит переключение анимации
главного героя (а в случае отсутствия воздействия со стороны пользователя
начинает проигрываться анимация стояния на месте у персонажа);
каждый кадр вызывается метод «SenAnimationHero», который будет
рассмотрен далее.
Алгоритм «SenAnimationHero».
63
Отвечает за проверку такого игрового условия, как нахождение главного
героя на вертикальной лестнице (каждый кадр). Данный метод в зависимости
от текущего положения персонажа на лестнице опосредованно вызывает
проигрывание соответствующей анимации. По сути, является расширением
метода «Run», в котором прописано движение по вертикали, и предоставляет
для этого метода более четкие границы смены анимации ходьбы и лазанья.
Код алгоритма представлен в листинге 6.
Листинг 6 – Алгоритм «SenAnimationHero»
private void SenAnimationHero()
{
float NewPositionOnY = transform.position.y;
bool up;
up = NewPositionOnY - OldPositionOnY > 0;
OldPositionOnY = NewPositionOnY;
RaycastHit2D[] hit = new RaycastHit2D[5];
int hitcount = Physics2D.RaycastNonAlloc(new
Vector2(transform.position.x, transform.position.y), Vector2.right,
hit, 10f);
bool check1=false, check2=false;
for (int i = 0; i < hitcount; i++)
{
if (hit[i].collider != null &&
hit[i].collider.gameObject.tag == "Pol1")
check1 = true;
if (hit[i].collider != null &&
hit[i].collider.gameObject.tag == "Pol2")
check2 = true;
}
if (up)
{
if(check1)
ColliderLadderTriger = true;
if (check2)
ColliderLadderTriger = false;
}
else
{
if (check1)
ColliderLadderTriger = false;
if (check2)
ColliderLadderTriger = true;
}
}
Алгоритмы «OnTriggerEnter2D», «OnTriggerStay2D»,
«OnTriggerExit2D».
64
Встроенные методы Unity, которые отвечают за проверки таких игровых
условий, как вхождение (коллайдера привязанного к главному герою) в
область триггера, перемещение внутри и выход из нее (каждый кадр)
соответственно. При выполнении данных условий могут напрямую или
опосредовано вызывать другие методы, например, вывод текстового
сообщения на экран или переключение состояния главного героя, при котором
он способен спрятаться за укрытием или карабкаться по лестнице. Ввиду
слишком большого количества различных условий внутри алгоритма блок с
листингом кода приводиться не будет.
Алгоритмы «OnCollisionEnter2D», «OnCollisionExit2D».
Встроенные методы Unity, которые отвечают за проверки таких игровых
условий, как вхождение (коллайдера привязанного к главному герою) в
область столкновения с другим коллайдером и выход из нее (каждый кадр)
соответственно. Оба этих метода отвечают только за одну функцию –
переключение состояния главного героя, при котором он способен открыть
дверь (так как находится рядом с ней), либо нет. При этом первый метод
также вызывает диалоговую компоненту, сообщающую о возможности
действия. Код алгоритмов представлен в листинге 7.
Листинг 7 – Алгоритмы «OnCollisionEnter2D» и «OnCollisionExit2D»
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Door")
{
Open = true;
NearDoor = collision.gameObject;
StartCoroutine(Send("Можно попробовать открыть дверь...",
5f, 0f));
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.tag == "Door")
{
Open = false;
NearDoor = null;
}
}
Алгоритмы «Awake», «Start».
65
Встроенные методы Unity, которые выполняются один раз – «Start» на
старте сцены, «Awake» до старта сцены. При этом первый метод для данного
объекта (персонажа) используется для подключения различных компонентов
(к примеру, «физического тела»), а второй задает главному герою различные
стартовые характеристики (например, скорость, возможность открыть дверь).
Код алгоритмов представлен в листинге 8.
Листинг 8 – Алгоритмы Awake» и «Start»
private void Awake()
{
rigidbody = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
sprite = GetComponentInChildren<SpriteRenderer>();
audio = GetComponent<AudioSource>();
}
private void Start()
{
NearDoor = null;
isClimbing = false;
animator.SetFloat("Multy", 0.68f);
Hide = false;
reachTimer = true;
Timer = 5f;
Open = false;
Stop = false;
OldPositionOnY = transform.position.y;
}
Следующим, не менее важным, объектом является «RoomPlacer».
В игровом процессе обозначает пустой (невидимый и неосязаемый)
объект, существующий на игровой сцене. К его главной функции можно
отнести процедурную генерацию нескольких элементов:
комнат;
дверей;
монстров.
Главными в этом списке являются именно комнаты, все остальное – так
или иначе связано именно с ними (монстры, появившиеся в определенной
комнате, чаще всего будут патрулировать именно ее, а двери непосредственно
привязаны к структурам комнат).
66
Как и персонаж, «RoomPlacer» имеет несколько алгоритмов.
Рассмотрим каждый из них подробнее.
Алгоритм «Start».
Встроенный метод Unity, который выполняется один раз на старте
сцены. Преследует три важные функции – создание пустого массива, куда
впоследствии будут «заноситься» уже созданные комнаты, определение
стартовой комнаты, с которой начнется генерация и собственно запуск самого
алгоритма процедурной генерации. Код алгоритма представлен в листинге 9.
Листинг 9 – Алгоритм старта процедурной генерации
private void Start()
{
spawnedRooms = new Room[10, 4];
spawnedRooms[4, 0] = StartingRoom;
for (int i = 0; i < 10; i++)
{
Placing();
}
}
Алгоритм «Placing».
Отвечает за такую часть процедурной генерации, как
рандомизированное размещение некоторых перечисленных выше в списке
игровых объектов на сцене при старте последней в соответствии с
определенными ограничениями, вызванными размерами массива комнат,
положением стартовой точки и количеством генерируемых элементов,
которые определялись в прошлом алгоритме. При этом реализация метода
осуществляется не полностью напрямую, а частично, так как внутри
вызывается другой основной алгоритм, реализующий оставшую часть
генерации. Код алгоритма представлен в листинге 10.
67
if (spawnedRooms[x, y] == null) continue;
int maxX = spawnedRooms.GetLength(0) - 1;
int maxY = spawnedRooms.GetLength(1) - 1;
if (x > 0 && spawnedRooms[x - 1, y] == null)
vacantPlaces.Add(new Vector2Int(x - 1, y));
if (x < maxX && spawnedRooms[x + 1, y] == null)
vacantPlaces.Add(new Vector2Int(x + 1, y));
if (y > 0 && spawnedRooms[x, y - 1] == null)
vacantPlaces.Add(new Vector2Int(x, y - 1));
if (y < maxY && spawnedRooms[x, y + 1] == null)
vacantPlaces.Add(new Vector2Int(x, y + 1));
}
}
Room newRoom = Instantiate(RoomPrefabs[Random.Range(0,
RoomPrefabs.Length)]);
newRoom.transform.SetParent(C.transform, false);
int r = Random.RandomRange(0, 2);
Monsters ghost=null;
if (r == 1)
{
int c = Random.RandomRange(0, 5);
ghost = Instantiate(GG[с]);
ghost.room = newRoom;
ghost.transform.SetParent(C.transform, false);
}
int limit = 666;
while (limit-- > 0)
{
Vector2Int position =
vacantPlaces.ElementAt(Random.Range(0, vacantPlaces.Count));
if (Connect(newRoom, position))
{
newRoom.transform.position = new Vector3(position.x -
4, position.y ) * 33;
if (r == 1) ghost.transform.localPosition = new
Vector3(newRoom.transform.localPosition.x,
newRoom.transform.localPosition.y, -304f);
spawnedRooms[position.x, position.y] = newRoom;
return;
}
}
Destroy(newRoom.gameObject);
}
Представленный выше алгоритм реализует множество различных
функций:
организация списка, состоящего из уникальных значений – вакантных мест
для создания комнат;
68
в цикле проверяются все уже созданные комнаты, занесенные в массив, и
определяются их возможные «соседи» (которые заносятся в соответствующий
список);
из заранее заготовленных префабов (заготовок) комнат и монстров
выбираются радномные и на их основе создаются уже новые объекты, при этом
положение монстра сразу же соотносят с положением самой комнаты;
из списка вакантных мест выбирается рандомное и с помощью вызова
метода «Connect» в итеративной форме проверяется возможность подсоединения
новой комнаты к соседней, в случае неудачи выбирается другое место.
Алгоритм «Connect».
Отвечает за такую часть процедурной генерации, как
рандомизированное создание проходов между комнатами. Всего возможны
четыре варианта: два прохода слева и справа в виде проемов (или дверей) и
два прохода вниз и вверх в виде дыр (и лестницы) в полу или в потолке
соответственно. Реализация метода осуществляется не полностью напрямую, а
частично, так как внутри вызывается другой алгоритм «RandDoor»,
отвечающий за создание дверей. Код алгоритма представлен в листинге 11.
Листинг 11– Алгоритм «Connect»
private bool Connect(Room room, Vector2Int p)
{
int maxX = spawnedRooms.GetLength(0) - 1;
int maxY = spawnedRooms.GetLength(1) - 1;
List<Vector2Int> neighbours = new List<Vector2Int>();
if (room.DoorU != null && p.y < maxY && spawnedRooms[p.x, p.y
+ 1]?.DoorD != null) neighbours.Add(Vector2Int.up);
if (room.DoorD != null && p.y > 0 && spawnedRooms[p.x, p.y -
1]?.DoorU != null) neighbours.Add(Vector2Int.down);
if (room.DoorR != null && p.x < maxX && spawnedRooms[p.x + 1,
p.y]?.DoorL != null) neighbours.Add(Vector2Int.right);
if (room.DoorL != null && p.x > 0 && spawnedRooms[p.x - 1,
p.y]?.DoorR != null) neighbours.Add(Vector2Int.left);
if (neighbours.Count == 0) return false;
Vector2Int selectedDirection = neighbours[Random.Range(0,
neighbours.Count)];
Room selectedRoom = spawnedRooms[p.x + selectedDirection.x,
p.y + selectedDirection.y];
if(selectedDirection == Vector2Int.up)
{
69
room.DoorU.SetActive(true);
selectedRoom.DoorD.SetActive(true);
selectedRoom.D.enabled =false;
}
else if (selectedDirection == Vector2Int.down)
{
room.DoorD.SetActive(true);
selectedRoom.DoorU.SetActive(true);
room.D.enabled =false;
}
else if (selectedDirection == Vector2Int.right)
{
room.DoorR.SetActive(true);
selectedRoom.DoorL.SetActive(true);
room.R.enabled =false;
selectedRoom.L.enabled =false;
RandDoor(ref room.DR, ref selectedRoom.DL);
}
else if (selectedDirection == Vector2Int.left)
{
room.DoorL.SetActive(true);
selectedRoom.DoorR.SetActive(true);
room.L.enabled =false;
selectedRoom.R.enabled =false;
RandDoor(ref room.DL, ref selectedRoom.DR);
}
return true;
}
Представленный выше алгоритм реализует следующие функции:
проверяются все направления вокруг комнаты и организуется список,
состоящий из всех возможных «соседей»;
рандомно выбирается соседняя комната и к ней в зависимости от
направления прокладывается «маршрут» в виде двух проемов (со стороны самой
комнаты и «соседа»);
в процессе прокладывания пути вызывается метод «RandDoor», реализация
которого будет описана ниже.
Алгоритм «RandDoor».
Отвечает за рандомное создание двери в просвете между комнатами. Так
как при присоединении комнат по бокам получаются двойные проемы, то
требуется метод, который выбирал бы всего лишь одну из возможных сторон
и создавал там дверь. Код алгоритма представлен в листинге 12.
Листинг 12– Алгоритм «RandDoor»
70
private void RandDoor(ref GameObject DL, ref GameObject DR)
{
int r = Random.RandomRange(0, 2);
switch (r)
{
case 0:
DL.SetActive(true);
DR.SetActive(false);
break;
case 1:
DL.SetActive(false);
DR.SetActive(true);
break;
}
}
На рисунках 35, 36 и 37 приведены возможные примеры генерации
уровня.
71
Рисунок 36 – Второй вариант генерации уровня (средний)
72
Рисунок 38 – Камера выходит за границы уровня
Именно поэтому для камеры тоже были написаны несколько
алгоритмов, которые будут подробно расписаны далее.
Алгоритм «Clamp».
Отвечает за ограничения движения камеры по уровню во избежание
ситуации, описанной выше (когда ее вид выходит за пределы уровня).
Работает только при приближенном виде, в случае отдаления камеры, для
просмотра всей сцены сразу, метод не производит никаких эффектов. Листинг
алгоритма ввиду наличия большого количества строк кода здесь приводится
не будет, однако будут описаны несколько важных функций, реализуемых
методом:
73
на основе ширины текущего разрешения настраивается допустимый
«отступ» справа и слева;
из центра камеры в каждую из сторон (вправо и влево) откладываются два
невидимых луча по длине «отступа», как на рисунке 39;
лучи каждый кадр «проверяют» выделенный им промежуток на присутствие
какого-либо объекта, в случае нахождения препятствия в виде стены камера
центрируется на ней и прекращает свое движение до тех пор, пока персонаж не
отойдет от стены, или, иными словами, пока луч снова не «сообщит», что на
протяжении его расстояния нет границы уровня (либо другого нежелательного
объекта).
74
{
if (Input.GetKeyUp(KeyCode.Tab))
tabbed = !tabbed;
if (tabbed && q < 22f || !tabbed && q > 8f)
{
if (tabbed)
{
q += 0.025f;
vol += 0.5f / 560f;
Vector3 vvv = new Vector3(transform.position.x,
transform.position.y + 0.025f, transform.position.z);
transform.position = vvv;
}
else
{
q -= 0.1f;
vol -= 0.5f / 140f;
Vector3 vvv = new Vector3(transform.position.x,
transform.position.y - 0.1f, transform.position.z);
transform.position = vvv;
}
_camera.orthographicSize = q;
EventManager.SetVolume(vol);
}
else if (!tabbed&& t.GetComponentInChildren<SpriteRenderer>())
Clamp();
Ниже приведены несколько важных реализуемых функций:
считывается нажатие пользователем клавиши отдаления камеры (в случае,
если она уже отдалена, то наоборот будет произведено приближение);
в процессе отдаления немного изменяется положение камеры
(центрирование на середине уровня) и звук окружения (становится лучше слышно
звуки вне дома);
в случае приближенной камеры каждый кадр проверяется необходимость в
вызове метода «Clamp»;
изменение звука происходит через событийный аппарат посредством
вызова метода «SetVolume».
75
Рисунок 40 – Отдаление камеры
Алгоритм «Start».
Встроенный метод Unity, который выполняется один раз на старте
сцены. Преследует две важные функции – задает камере различные стартовые
характеристики и подключает необходимые для работы приведенных выше
методов компоненты. Код алгоритма представлен в листинге 14.
Листинг 14– Алгоритм работы стартового метода у камеры
private void Start()
{
_camera = GetComponent<Camera>();
tabbed = false;
q = 8f;
vol = 0.5f;
t = GameObject.FindGameObjectWithTag("Player");
}
Последним из рассматриваемых объектов является «Manager».
В игровом процессе обозначает пустой (невидимый и неосязаемый)
объект, существующий в единичном экземпляре на всех игровых сценах, в
отличие от других элементов при запуске новой сцены не пересоздается и не
удаляется. Работает только в связке с другим классом (и соответственно с
другим объектом) «EventManager», который имеет в своем составе различные
предопределённые события и методы для их вызова из любого места кода, в
свою очередь в классе «Manager» эти события связываются с идентичными по
сигнатуре методами (событийный аппарат).
76
Как и любой из объектов выше, «Manager» тоже имеет несколько
алгоритмов. Рассмотрим каждый из них подробнее.
Алгоритм «Awake».
Встроенный метод Unity, который выполняется один раз до старта
сцены. Преследует всего две цели – соединить события из «EventManager» с
методами из «Manager» и указать неразрушаемость, связанного с алгоритмом
объекта. Код алгоритма представлен в листинге 15.
Листинг 15– Алгоритм работы «связующего» метода у менеджера
private void Awake()
{
DontDestroyOnLoad(this.gameObject);
EventManager.Darkness +=Fade;
EventManager.Dialog += Conversation;
EventManager.Volume += EditVolume;
EventManager.Teleporting += Teleport;
EventManager.Dead += Death;
}
Алгоритм «Teleport».
Реализует функцию перехода с одного уровня на другой (путем вызова
этого метода через «EventManager» в другом месте кода при выполнении
каких-то условий). Код алгоритма представлен в листинге 16.
Листинг 16 – Алгоритм перехода между уровнями
private void Teleport()
{
SceneManager.LoadScene(++Count);
}
Алгоритм «Conversation».
Реализует функцию вывода переданного в качестве аргумента текста в
диалоговое окно (нужная строка передается через «EventManager» из другого
места). Код алгоритма представлен в листинге 17.
Листинг 17 – Алгоритм перехода между уровнями
private void Conversation(string c,bool t)
{
GameObject Dial =EventManager.objRefs["Dialog " +
SceneManager.GetActiveScene().buildIndex];
Dial.GetComponentInChildren<Text>().text = c;
Dial.SetActive(t);
}
Ниже приведены две реализуемые функции:
77
из массива диалоговых компонент выбирается нужная (в зависимости от
сцены);
на экран выводится сообщение с текстом переданным в качестве аргумента
функции.
Алгоритм «Fade».
Реализует функцию затемнения экрана и включения подсветки в виде
ореола у определенных объектов (к примеру, укрытий). Код алгоритма
представлен в листинге 18.
Листинг 18 – Алгоритм затемнения экрана
private void Fade( string c)
{
switch (c)
{
case "Свет":
foreach (GameObject g in
GameObject.FindGameObjectsWithTag("Lights"))
{
g.GetComponentInChildren<Light>().enabled = false;
}
dark.color = Color.white;
break;
case "Тьма":
foreach (GameObject g in
GameObject.FindGameObjectsWithTag("Lights"))
{
g.GetComponentInChildren<Light>().enabled = true;
}
dark.color = new Color(0.1f, 0.1f, 0.1f, 1f);
break;
}
}
Алгоритм «EditVolume».
Реализует функцию изменения звука (предварительно находит объект на
текущей сцене, связанный со звуковым компонентом). Код алгоритма
представлен в листинге 19.
Листинг 19 – Алгоритм изменения звука
private void EditVolume(float value)
{
GameObject c = GameObject.FindGameObjectWithTag("Canv");
AudioSource audio = c.GetComponent<AudioSource>();
audio.volume = value;
}
78
Алгоритм «Death».
Реализует функцию «смерти» главного героя после физического
столкновения с монстрами или другой опасностью. Код алгоритма
представлен в листинге 20.
Листинг 20 – Алгоритм «смерти» персонажа
private void Death()
{
Character target =
GameObject.FindGameObjectWithTag("Player").GetComponent<Character>();
GameObject.FindGameObjectWithTag("SpriteChar").SetActive(false);
target.Stoping = true;
target.GetComponent<Collider2D>().enabled = false;
target.GetComponent<Rigidbody2D>().gravityScale = 0;
DeadScreen = GameObject.FindGameObjectWithTag("DeadScreen");
DeadScreen.GetComponent<Image>().color = new Color(1f, 1f, 1f,
1f);
}
Ниже приведены две реализуемые функции:
у персонажа на сцене отключаются практически все компоненты (в
частности, реализующие его отображение, передвижение и физические
взаимодействия);
на весь экран выводится сообщение (в виде изображения) о проигрыше
(безумном помешательстве) персонажа.
79
Смена значения переменной «patrul» происходит, когда игрок подходит
к врагу и между ними нет непроходимых препятствий (например, закрытой
двери).
На рисунке 41 представлена блок-схема работы данного алгоритма.
80
: Vector2.right, hit, Vector3.Distance(transform.position,
target.transform.position));
bool check = false;
}
bool ExitTrigerTeleport = false, GhostBlink=false;
private void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
patrul = true;
ExitTrigerTeleport = true;
Return;
}
}
//отдельный метод для призрака распознавания того, что
призрак находится в зоне контакта с игроком. Он использует
Trigger так как, коллайдеры призрака являются триггерами.
private void OnTriggerEnter2D(Collider2D collider)
{
if (collider.gameObject.tag == "Player")
{
if (gameObject.tag == "Ghost")
{
patrul = false;
return;
}
}
Теперь рассмотрим подробнее алгоритмы каждого из врагов при
условии обнаружения персонажа.
Алгоритм врага «Изгой».
81
При патрулировании данный монстр передвигается между двумя
точками – краями комнаты, в которой он был заспавнен (создан). Причем,
когда персонаж доходит до точки, спрайт (текстура персонажа)
разворачивается (зеркально отражается). Алгоритм патруля представлен на
рисунке 42.
При преследовании главного героя, «Изгой» медленно передвигается за
первым, однако раз в 3 секунды противник телепортируется в
противоположную, от главного героя, сторону и продолжает преследовать
игрока. Происходит это следующим образом:
если переменная TeleportTime <= 0, что значит, что прошло 3 секунды с
момента предыдущего полного исполнения алгоритма, то:
запускается анимация и звук телепорта, враг остается на том же месте;
спустя пол секунды, что эквивалентно половине анимации, спрайт
врага разворачивается и меняется местоположение врага за спину игрока,
после чего анимация завершается в течение еще пол секунды;
враг продолжает преследование игрока, а переменной TeleportTime
присваивается значение 3 (эквивалентно 3 секундам).
если же переменная TeleportTime > 0, то ей присваивается разница между
ней самой и прошедшим временем DeltaTime (TeleportTime = TeleportTime –
DeltaTime). Сама DeltaTime берется из игрового движка Unity и возвращает время,
прошедшее между кадрами.
Для контроля выполнения перемещения врага в нужный момент
анимации, в программе так же присутствуют две переменный TeleportDelay1 и
teleportDelay2, которым присваиваются значения по аналогии с переменной
TeleportTime.
82
Рисунок 42 – Блок-схема работы алгоритма патруля «Изгоя»
83
Рисунок 43 – Блок-схема работы алгоритма преследования «Изгоя»
84
TeleportDelay2 = 1f;
TeleportDelay3 = 1f;
animator.SetInteger("State", 1);
sprite.flipX =
gameObject.transform.position.x >=
moveSpots[randomSpot].position.x;
transform.position = Vector3.MoveTowards
(new Vector3(transform.position.x,
transform.position.y, transform.position.z), new
Vector3(moveSpots[randomSpot].position.x, transform.position.y,
transform.position.z), speed * Time.deltaTime);
State = CharState.Run;
if (Vector2.Distance(transform.position,
moveSpots[randomSpot].position) < 10f)
{
if (waitTime <= 0)
{
randomSpot = Random.Range(0,
moveSpots.Length);
waitTime = 0.1f;
}
85
v.y = transform.position.y;
v.z = transform.position.z;
transform.position = v;
TeleportDelay3 =0.98f;
}
else if(TeleportDelay2 > 0 &&
TeleportDelay3 >= 1f)
{
TeleportDelay2 -= Time.deltaTime;
}
TeleportDelay -= Time.deltaTime;
}
}
86
перед ним находится закрытая дверь. Запускаются анимация и звук
телепортации, на половине проигрывания которых персонаж перемещается в
непроходимую комнату к закрытой двери. После чего он продолжает
движение к контрольной точке, при достижении которой, он разворачивается,
и будет двигаться в противоположную сторону. Если персонаж встретит, и
будет гнаться за игроком, а потом потеряет его, движение продолжится в
заданную контрольную точку. Алгоритм патруля представлен на рисунке 44.
При преследовании главного героя, «Изгой» медленно передвигается за
первым, однако раз в 3 секунды противник телепортируется в
противоположную, от главного героя, сторону и продолжает преследовать
игрока. Происходит это следующим образом:
если переменная TeleportTime <= 0, что значит, что прошло 3 секунды с
момента предыдущего полного исполнения алгоритма, то:
запускается анимация и звук телепорта, враг остается на том же месте;
спустя пол секунды, что эквивалентно половине анимации, спрайт
врага разворачивается и меняется местоположение врага за спину игрока,
после чего анимация завершается в течении еще пол секунды;
враг продолжает преследование игрока, а переменной TeleportTime
присваивается значение 3 (эквивалентно 3 секундам).
если же переменная TeleportTime > 0, то ей присваивается разница между
ней самой и прошедшим временем DeltaTime (TeleportTime = TeleportTime –
DeltaTime). Сама DeltaTime берется из игрового движка Unity и возвращает время,
прошедшее между кадрами. [11]
Для контроля выполнения перемещения врага в нужный момент
анимации, в программе так же присутствуют две переменные TeleportDelay1 и
teleportDelay2, которым присваиваются значения по аналогии с переменной
TeleportTime.
87
Рисунок 44 – Блок-схема работы алгоритма патруля «Главного изгоя»
88
что если во время телепортации врага, игрок убежит на определенное
расстояние, когда включается патрулирование (patrul = true), переменным
TeleportTime и TeleportDelay присваиваются начальные значения, а анимация
прерывается. Данный алгоритм кратко представлен на рисунке 45.
89
Код алгоритма представлен в листинге 23.
Листинг 23 – Алгоритм «Главного изгоя»
if (patrul)
{
if (gameObject.tag == "Man2")
{
moveSpots = new Transform[2];
RaycastHit2D[] hit1 = new RaycastHit2D[25], hit2 = new
RaycastHit2D[25];
int indexwall1 = -1, indexwall2 = -1;
int hitcount01 = Physics2D.RaycastNonAlloc(new
Vector2(transform.position.x, transform.position.y), Vector2.left,
hit1, Mathf.Infinity);
for (int i = 0; i < hitcount01; i++)
if (hit1[i].collider != null &&
hit1[i].collider.gameObject.tag == "Wall")
{
indexwall1 = i;
break;
}
hitcount01 = Physics2D.RaycastNonAlloc(new
Vector2(transform.position.x, transform.position.y), Vector2.right,
hit2, Mathf.Infinity);
for (int i = 0; i < hitcount01; i++)
if (hit2[i].collider != null &&
hit2[i].collider.gameObject.tag == "Wall")
{
indexwall2 = i;
break;
}
moveSpots[0] =
hit1[indexwall1].collider.gameObject.transform;
moveSpots[1] =
hit2[indexwall2].collider.gameObject.transform;
if (ExitTrigerTeleport)
{
TeleportDelay = TeleportTime;
TeleportDelay2 = 1f;
TeleportDelay3 = 1f;
ExitTrigerTeleport = false;
}
animator.SetInteger("State", 1);
sprite.flipX = gameObject.transform.position.x >=
moveSpots[randomSpot].position.x;
if (!animateteleport) {
transform.position = Vector3.MoveTowards
(new Vector3(transform.position.x,
transform.position.y, transform.position.z), new
Vector3(moveSpots[randomSpot].position.x, transform.position.y,
transform.position.z), speed * Time.deltaTime);
State = CharState.Run;
90
}
if (Vector2.Distance(transform.position,
moveSpots[randomSpot].position) < 12f)
{
if (waitTime <= 0)
{
randomSpot = Random.Range(0,
moveSpots.Length);
waitTime = 0.1f;
}
else waitTime -= Time.deltaTime;
}
else
{
RaycastHit2D[] hit = new RaycastHit2D[15];
int hitcount = Physics2D.RaycastNonAlloc(new
Vector2(transform.position.x, transform.position.y),
transform.position.x > moveSpots[randomSpot].position.x ? Vector2.left
: Vector2.right, hit, 5f);
int checki = -1;
for (int i = 0; i < hitcount; i++)
if (hit[i].collider != null &&
hit[i].collider.gameObject.tag == "Door")
{
checki = i;
break;
}
if (checki != -1)
{
animateteleport = true;
animator.SetInteger("State", 2);
if (Audio != null && !Audio.isPlaying)
Audio.Play();
if (TeleportDelay2 <= 0 && TeleportDelay3 >=
1f)
{
if (checki != -1)
{
Vector3 v = new Vector3();
v.y = transform.position.y;
v.z = transform.position.z;
v.x =
hit[checki].collider.gameObject.transform.position.x - 5f *
(sprite.flipX ? 1 : -1);
transform.position = v;
}
TeleportDelay3 = 0.98f;
animateteleport = false;
}
else if (TeleportDelay2 > 0 && TeleportDelay3
>= 1f)
{
TeleportDelay2 -= Time.deltaTime;
91
}
}
}
}
else
{
RaycastHit2D[] hit = new RaycastHit2D[7];
int hitcount = Physics2D.RaycastNonAlloc(new
Vector2(transform.position.x, transform.position.y),
transform.position.x > target.transform.position.x ? Vector2.left :
Vector2.right, hit, 10f);
v.x = hit[checki].collider.gameObject.transform.position.x - 3f *
(sprite.flipX ? -1 : 1);
Vector3 v2 = new Vector3();
92
}
else
v.x = target.transform.position.x - 6f *
(sprite.flipX ? 1 : -1);
v.y = transform.position.y;
v.z = transform.position.z;
transform.position = v;
TeleportDelay3 = 0.98f;
}
else if (TeleportDelay2 > 0 && TeleportDelay3 >=
1f)
{
TeleportDelay2 -= Time.deltaTime;
}
TeleportDelay3 -= Time.deltaTime;
}
else if (TeleportDelay2 <= 0 && TeleportDelay3 <=
0f)
{
TeleportDelay = TeleportTime;
TeleportDelay2 = 1f;
TeleportDelay3 = 1f;
animator.SetInteger("State", 1);
}
}
else
{
transform.position = Vector3.MoveTowards
(new Vector3(transform.position.x,
transform.position.y, transform.position.z), new
Vector3(target.transform.position.x, transform.position.y,
transform.position.z), speed * Time.deltaTime);
TeleportDelay -= Time.deltaTime;
}
93
переменной TimerSleep. Если она равна 0, то враг засыпает, а переменной
присваивается значение 30. Если она равна 20, то враг просыпается.
Независимо от значения, переменная уменьшается на 1 каждую секунду.
Данный алгоритм патруля представлен на рисунке 46.
При преследовании главного героя, «Шоггот» очень медленно
передвигается за первым, однако раз в 4 секунды противник «Плюется»
кислотой в игрока. Происходит это следующим образом:
если переменная VenomTime <= 0, что значит, что прошло 4 секунды с
момента предыдущего полного исполнения алгоритма, то:
запускается анимация и звук плевка кислотой, враг остается на том же
месте;
спустя секунду, враг начинает передвигаться;
враг продолжает преследование игрока, а переменной VenomTime
присваивается значение 4 (эквивалентно 4 секундам);
кислота летит по прямой траектории от Шоггота по направлению к
игроку, со скоростью, большей скорости шоггота в 2 раза.
если же переменная VenomTime > 0, то ей присваивается разница между
ней самой и прошедшим временем DeltaTime (VenomTime = VenomTime –
DeltaTime). Сама DeltaTime берется из игрового движка Unity и возвращает время,
прошедшее между кадрами.
Кислота, в свою очередь, летя по прямой траектории, исчезает при
столкновении с любым препятствием, либо по истечении заданного
расстояния. Если игрок соприкоснется с кислотой, то он погибает. Данный
алгоритм представлен на рисунке 47.
94
Рисунок 46 – Блок-схема работы алгоритма патруля «Шоггота»
95
Рисунок 47 – Блок-схема работы алгоритма преследования «Шоггота»
96
//спим
animator.SetInteger("State", 0);
SleepDelay = 30f;
}
else
{
SleepDelay -= Time.deltaTime;
if (SleepDelay < 20)
{
//просыпаемся
if (Vector2.Distance(transform.position,
moveSpots[randomSpot].position) < 12f)
{
if (waitTime <= 0)
{
randomSpot = Random.Range(0,
moveSpots.Length);
waitTime = 0.1f;
}
else waitTime -= Time.deltaTime;
}
else
{
transform.position = Vector3.MoveTowards
(new Vector3(transform.position.x,
transform.position.y, transform.position.z), new
Vector3(target.transform.position.x, transform.position.y,
transform.position.z), speed * Time.deltaTime);
}
}
else
{
animator.SetInteger("State", 2);//
//спим
}
}
else
{
VenomTime2 -= Time.deltaTime;
if (VenomTime2 <= 0)//плевок
{
State = CharState.Idle;
ven = Instantiate(MO[0]);
transform.SetParent(transform, false);
Vector3 v = new Vector3();
flipplevok = sprite.flipX;
GameObject.FindGameObjectWithTag("VenomTag").GetComponentInChildr
en<SpriteRenderer>().flipX = !flipplevok;
v.x = transform.position.x - 2 *
(flipplevok ? 1 : -1);
v.y = transform.position.y;
97
v.z = transform.position.z;
ven.transform.position = v;
VenomTime2 = 4f;
VenomTime3 = 1f;
}
else
{
VenomTime3 -= Time.deltaTime;
if (VenomTime3 <= 0)
{
State = CharState.Run;
transform.position =
Vector3.MoveTowards(transform.position,
target.transform.position, speed * Time.deltaTime);
}
}
}
}
if (ven != null)
{
VenomTime1 -= Time.deltaTime;
ven.transform.position = Vector3.MoveTowards
(new Vector3(ven.transform.position.x,
ven.transform.position.y, ven.transform.position.z), new
Vector3(ven.transform.position.x - 5 * (flipplevok ? 1 : -1),
ven.transform.position.y, ven.transform.position.z), speed *
Time.deltaTime * 2f);
if (VenomTime1 <= 0)
{
VenomTime1 = 1.5f;
Destroy(GameObject.FindGameObjectWithTag("VenomTag"));
ven = null;
}
}
if (ven != null)
{
If(ven.destroy)
VenomTime1 = 1.5f;
Destroy(GameObject.FindGameObjectWithTag("VenomTag"));
ven = null;
}
Алгоритм врага «Гуль».
При патрулировании данный монстр передвигается между двумя
точками – краями комнаты, в которой он был заспавнен, однако эти точки
98
могут меняться во время игры в зависимости от результатов погони за
игроком. Причем, когда персонаж до ходит до точки, спрайт (текстура
персонажа) разворачивается (зеркально отражается). [12] Алгоритм патруля
представлен на рисунке 48.
100
Рисунок 49 – Блок-схема работы алгоритма преследования «Гуля»
101
Код алгоритма представлен в листинге 25.
Листинг 25 – Алгоритм «Гуля»
If(patrul)
{
//поворот спрайта
sprite.flipX = gameObject.transform.position.x >=
moveSpots[randomSpot].position.x;
//устанвока анимации бега
animator.SetInteger("State", 1);
if(GrowlTime < 2)
GrowlTime = 3f;
//если дошли до контрольной точки
if (Vector2.Distance(transform.position,
moveSpots[randomSpot].position) < 5f)//
{
if (waitTime <= 0)
{
randomSpot = Random.Range(0,
moveSpots.Length);
waitTime = 0.1f;
}
else waitTime -= Time.deltaTime;
}
else
{
transform.position = Vector3.MoveTowards
(new Vector3(transform.position.x,
transform.position.y, transform.position.z), new
Vector3(moveSpots[randomSpot].position.x, transform.position.y,
transform.position.z), speed * Time.deltaTime);
}
}
Else
{
//переопределние точек движения
if (moveSpots[0].position.x >
target.transform.position.x)
moveSpots[0] = target.transform;
if (moveSpots[1].position.x <
target.transform.position.x)
moveSpots[1] = target.transform;
//вычитание прошедшего времени от переменной GrowlTime
GrowlTime -= Time.deltaTime;
if (GrowlTime <= 0)
{
//установление анимации рычания
animator.SetInteger("State", 2);//
//воиспроизведение звука рычания
if (Audio != null && !Audio.isPlaying)
Audio.Play();
GrowlTime = 3f;
}
102
else
{
if(GrowlTime <= 2)
{
//установление анимации движения
animator.SetInteger("State", 1);
//движение за игроком
transform.position = Vector3.MoveTowards
(new Vector3(transform.position.x,
transform.position.y, transform.position.z), new
Vector3(target.transform.position.x, transform.position.y,
transform.position.z), speed * Time.deltaTime);
GrowlTime-= Time.deltaTime;
}
}
}
Алгоритм врага «Призрак».
В отличие от других, у данного персонажа количество точек
патрулирования равно количеству комнат на уровне. То есть призрак
беспрепятственно передвигается по всему уровню. Алгоритм патруля
представлен на рисунке 50.
По сюжету призрак не является врагом для главного героя, однако до
раскрытия сюжета, он пугает главного героя, тем, что при появлении вблизи
игрока, он со страшным звуком и огромной скоростью летит на главного
героя. Однако при контакте с игроком, призрак исчезает (переносится в
другую комнату). Происходит это следующим образом:
если patrul = ложь, призрак передвигается по всему дому, между своими
контрольными точками, которые находятся в каждой комнате; [13]
если расстояние между игроком и призраком меньше заданного,
воспроизводится звук призрака, и он быстро летит к игроку;
когда призрак достигает игрока, случайно выбирается следующая
контрольная точка и призрак переносится туда.
Таким образом, призрак не представляет угрозы для главного героя, а
присутствует просто, чтобы пугать игрока. Алгоритм представлен на рисунке
51.
103
Рисунок 50 – Блок-схема работы алгоритма патруля «Призрака»
105
Рисунок 51 – Блок-схема работы алгоритма преследования «Призрака»
106
2.2.3 Техническое обеспечение
107
изображения, возможность использовать текстуры для кистей, гибкая
настройка кистей и так далее.
Dragon Bones Pro – является программой для создания скелетной
анимации от китайских разработчиков. Самый значимый плюс этой
программы, это её бесплатное распространение и интуитивно понятный
интерфейс.
В качестве основного формата анимационных и статических спрайтов
был выбран PNG как наиболее подходящий формат для экспорта цифровой
графики. Он является растровым графическим форматом, разработанным в
качестве альтернативы GIF, который обладал коммерческой лицензией. В его
основе находятся лучшие возможности предшественника, в том числе сжатие
без потерь и поддержка прозрачного фона. Технология PNG обеспечивает
сохранение всех этапов редактирования и восстановление шага с сохранением
качества.
FL studio – это среда для создания и записи звука с его последующей
обработкой в новое музыкальное произведение, использование для
одновременных записей сигнала с внутренних дорожек программного
обеспечения (ресемплинг), либо с других доступных внешних источников.
При помощи данной программы можно создавать отдельные саундтреки при
помощи встроенных инструментов (например, синтезатора пианино), а также
редактировать уже существующие музыкальные произведения, накладывая
эффект реверберации, изменяя частотный диапазон, добавляя новые звуковые
дорожки и так далее.
Плагин DarkKZ32 – данный плагин подходит для различных звуковых
станций (в том числе и для FL studio) и позволяет использовать различные
музыкальные пресеты для написания композиций к фильмам ужасов. Так как
разрабатываемый игровой проект принадлежит к жанру «survival-horror», то
этот плагин подходит для написания саундтреков к нему.
В качестве основного формата звука был выбран WAV, так как с
помощью него проще всего экспортировать готовый саундтрек в игру. Данный
108
формат не предполагает сжатия звука, что в положительную сторону влияет
на качество звука.
Дизайн пользовательского интерфейса игры является фактором,
оказывающим влияние на три основных показателя качества программного
продукта: его функциональность, эстетику и производительность.
Функциональность является фактором, на который обращают основное
внимание. Попытка создавать интерфейс так, чтобы пользователи могли
выполнять свои задачи, и им было удобно это делать. Функциональность
важна, но, тем не менее, это не единственный показатель, который должен
учитываться в ходе разработки интерфейса.
Эстетичный внешний вид пользовательского интерфейса и способа его
представления позволяет сформировать у потребителя положительное мнение
о программном продукте.
Эстетические характеристики в видеоиграх играют большую роль, они
позволяют удержать внимание пользователя, разнообразить его игровой
процесс и в целом влияют на общее впечатление от игры и вероятность того,
что игрок порекомендует данный проект другим людям.
Среди параметров, которые можно было бы соотнести с эстетическими
характеристиками, можно выделить:
степень сочетания цветов;
степень поддержки одного стиля графического оформления;
показатель разрешения графических элементов;
показатель баланса яркости и контрастности изображений.
Производительность, а равно и надежность, также влияют на
перспективу применения игрового продукта. Если он хорошо выглядит, имеет
простое и удобное управление, но при этом страдает производительность, есть
вероятность, что конечный пользователь прекратит прохождение игры и
оставит плохой отзыв. В свою очередь, быстрая и стабильная работа игрового
продукта могут отчасти компенсировать его не самый стильный дизайн или
отсутствие некоторых функций.
109
Для обеспечения успешной работы пользователя от дизайнера
интерфейса требуется соблюдать баланс между вышеперечисленными
факторами на протяжении всего жизненного цикла разработки приложения.
Это достигается последовательной и тщательной проработкой деталей
интерактивного взаимодействия на каждом из этапов разработки
пользовательского интерфейса, включающих:
проектирование:
функциональные требования: определение цели разработки и
исходных требований;
анализ пользователей: определение потребностей пользователей,
разработка сценариев, оценка соответствия сценариев ожиданиям
пользователей;
концептуальное проектирование: моделирование процесса, для
которого разрабатывается приложение;
логическое проектирование: определение информационных потоков в
приложении;
физическое проектирование: выбор платформы, на которой будет
реализован проект и средств разработки.
реализация:
прототипирование: разработка бумажных и/или интерактивных
макетов экранных форм.
110
полупрозрачная панель (контейнер) для кнопок;
фон главного меню.
Рисунок пункта «Главное меню» представлен на рисунке 52.
111
Рисунок 53 – Пункт «Параметры»
Непосредственно сама локация представляет собой особняк, который
состоит из совокупности спрайтов различных комнат, которые соединены
между собой дверями или же лестницами.
Комнаты в игре делятся на несколько типов, таких как:
боковые комнаты правой части дома;
боковые комнаты левой части дома;
центральные комнаты дома;
локации вне дома.
Примеры данных локаций представлены на рисунках 54-57
соответственно.
112
Рисунок 54 – Пример боковой комнаты правой части дома
113
Рисунок 56 – Пример центральной комнаты дома
114
фрагменты записки, нахождение и сбор которых является ключевым
условием для прохождения уровня;
дверь, которая преграждает путь героя в определенную комнату;
ключ, с помощью которого можно открыть запертые двери;
лестница, благодаря ей у главного героя имеется возможность
перемещаться между этажами.
Пользователь может взаимодействовать с этими объектами посредством
главного героя, чтобы выполнить поставленные игрой задачи.
Пример локации представлены на рисунках 58 и 59.
115
диалоговая компонента;
прогресс прохождения данного уровня.
Скриншот интерфейса представлен на рисунке 60.
116
Суть скелетной анимации заключается в том, что мультипликатор или
моделер создаёт скелет, представляющий собой, как правило, древообразную
структуру костей, в которой каждая последующая кость «привязана» к
предыдущей, то есть повторяет за ней движения и повороты с учётом
иерархии в скелете. Далее каждая вершина модели «привязывается» к какой-
либо кости скелета. Таким образом, при движении отдельной кости двигаются
и все вершины, привязанные к ней. Благодаря этому задача аниматора сильно
упрощается, потому что отпадает необходимость анимировать отдельно
каждую вершину модели, а достаточно лишь задавать положение и поворот
костей скелета.
Также благодаря такому методу сокращается и объём информации,
необходимой для анимирования. Достаточно хранить информацию о
движении костей, а движения вершин высчитываются уже исходя из них.
При создании скелетной анимации следует учитывать несколько
главных требований:
анимация должна быть плавной, без резких переходов между кадрами;
при анимации, спрайты, составляющие персонажа не должны разрываться;
анимация персонажа должна соответствовать его анатомическим
особенностям;
между двумя кадрами анимации должно быть одинаковое время;
начальная анимация должна создаваться «на месте», без сдвига кадров;
каждый кадр анимации должен быть одного размера и разрешения.
Учитывая вышеизложенных требований, была создана анимация для
игрового проекта в программе «DragonBones Pro». Рассмотрим этапы создания
анимации персонажа.
Начальное создание рисунка персонажа и разбиение его на части.
При создании графического изображения персонажа, для дальней его
анимации необходимо разбить каждую часть его тела на отдельные рисунки.
Сделать это можно либо после создания рисунка, либо во время рисования
117
путём прорисовки каждой части тела на отдельном слое в графическом
редакторе.
В данном проекте был использован второй способ, так как с помощью
него можно получить более чёткие и качественные изображения, также при
использовании этого способа можно избежать эффекта «лесенки».
Персонаж, разбитый на спрайты, представлен на рисунке 61.
118
При правильном построении этой сетки, отдельные спрайты рисунка
можно деформировать в процессе анимации, что делает конечную анимацию
более плавной и реалистичной.
Пример построения сетки искривления представлен на рисунке 62.
119
движение ног, привязываются к кости нижней части туловища, таким
образом, точка вращения спрайтов ног будет в пределах области нижней части
туловища.
Вес кости – это процент того, насколько сильно кость влияет на
конкретный спрайт. Благодаря правильному распределению веса костей
конечная анимация получается более плавной.
Пример создания скелета персонажа приведен на рисунке 63.
120
Для создания анимации в программе «DragonBones Pro» следует создать
несколько ключевых кадров, их количество зависит от сложности анимации, к
примеру, для того чтобы персонаж поднял руку вверх может понадобиться 2-4
ключевых кадра, а для того чтобы сделать анимацию ходьбы необходимо
сделать 12-16 ключевых кадра. [15]
На ключевом кадре благодаря изменению положения костей задаётся
необходимая поза модели. Пример создания ключевого кадра представлен на
рисунке 64.
121
Стоит отметить, что, хотя данная программа способна самостоятельно
построить простую анимацию, однако, при создании более сложной, можно
столкнуться с разрывом между спрайтами на определенных кадрах,
неправильным искривлением спрайта и другим подобным проблемами.
Пример ошибки при построении анимации представлен на рисунке 65.
122
Рисунок 66 – Устранения ошибки автоматической анимации при помощи сетки
искривления
После исправления всех ошибок анимации, её кадры экспортируются в
формате PNG для дальнейшей вставки в игровое приложение с помощью
платформы Unity 3D.
123
Прибегать к самостоятельному написанию музыки приходится из-за
сложности нахождения нужного саундтрека для определенного момента в
игре.
Современные программы для создания электронной музыки
предоставляют широкий спектр опций, которые могут помочь человеку,
незнакомому с музыкальной теорией, написать свой саундтрек. Поэтому было
решено создать несколько композиций самостоятельно. [16]
Редактирование же уже готовых звуковых файлов необходимо по
нескольким причинам:
необходимость обрезать часть звуковой дорожки;
необходимость переработать баланс частот в треке;
необходимость добавить новые звуковые эффекты;
необходимость добавить новую звуковую линию;
необходимость изменить параметры саундтрека (скорость, тональность и
так далее).
Всё вышеперечисленное проделывается для создания целостного
конечного звукового оформления.
Стоит отметить характерные черты музыкальных треков в играх
подобного жанра, на основе которых создавались саундтреки для данного
игрового проекта:
преобладание низких частот;
периодические резкие частотные переходы;
отсутствие четко выраженной мелодической линии (эмбиент-эффект);
более высокий темп музыки, играющей при появлении опасности;
фоновое звучание композиций.
Для создания и редактирования звукового оформления применялась
программа FL Studio.
Рассмотрим подробнее каждый из этапов создания музыкального
произведения.
124
Выбор основных музыкальных инструментов и написание для них
музыкальной линии.
FL Studio содержит множество встроенных плагинов, которые
имитируют звуки реальных музыкальных инструментов, также имеется
возможность добавлять их из открытых источников. Учитывая специфику
игры, был выбран плагин DarkKZ, который содержит в себе большую
библиотеку звуковых эффектов из различных фильмов ужасов. После выбора
плагина следует создать звуковую дорожку для каждого инструмента при
помощи инструмента piano roll. При этом в программе можно изначально
выставить необходимый темп композиции и её тональность. Как правило, в
саундтреках для подобных игр применяется не более 4-5 звуковых дорожек
Пример написания басовой линии приведен на рисунке 67.
125
При создании композиции следует учитывать, что все саундтреки в игре
идут друг за другом и поэтому чётко выраженного начала и концовки трека
создавать не стоит.
На временной дорожке можно создать автоматизированные регуляторы
громкости звука для каждой дорожки или для всего трека в целом, а также
производить манипуляции с самими звуковыми дорожками, например,
разрезание одной из них на несколько новых. [17]
Пример создания общей композиции трека на временной дорожке
представлен на рисунке 68.
126
панорамирование.
Стоит отметить, что в отличие от мастеринга (следующего этапа), в
процессе сведения работа ведется с отдельными звуковыми дорожками, а не с
треком в целом.
Пример настройки громкости звуковых дорожек и панорамирование
представлен на рисунке 69.
127
Рисунок 70 – Пример балансировки частот
Также следует выделить процесс наложения реверберации, так как
именно этот эффект можно встретить в саундтреках множества игр. Он
добавляет музыке глубины и пространственный эффект, что позволяет создать
необходимую атмосферу, особенно в играх жанра horror.
Реверберация сопровождает любой звук, возникший в естественной
акустической среде. Возникает она при отражении звуковой волны от каких-
либо препятствий и ее возврата в точку прослушивания. Поэтому, в
восприятии акустического звука присутствует его прямой источник и
многочисленные отражения от ближайших поверхностей – преград.
В FL Studio для наложения реверберации можно использовать
встроенный плагин, однако его возможности крайне ограничены, а пресетов
(готовых настроек) практически нет. Поэтому было решено использовать
сторонний плагин Valhalla Vintage Verb.
Стоит отметить, что как правило в композициях данный эффект не
накладывается на басовую линию, однако при создании саундтрека к игре
данное наложение является уместным, так как добавляет нагнетания путём
создания объёмного басового звучания.
128
Пример наложения эффекта реверберации представлен на рисунке 71.
129
художественной – сделать звучание более насыщенным, ярким или
прозрачным.
Пример эквализации конечного трека приведен на рисунке 72. На
данном примере были занижены амплитуды самых низких и самых высоких
частот в треке.
130
регулировкой времени атаки и восстановления, что обеспечивает плотную, но,
в то же время музыкальную компрессию без слышимых изменений,
искажений и других побочных эффектов.
Пример наложения эффекта компрессии представлен на рисунке 73.
131
Хотя геймплей таких игр может включать в себя сражения с какими-
либо противниками, как и в играх других жанров, игрок в «survival-horror» не
ощущает той степени контроля над происходящим, которая типична для
большинства экшн-игр.
Это достигается различными ограничениями – нехваткой боеприпасов,
низким уровнем здоровья протагониста, скоростью передвижения, видимости,
а также различными препятствиями, усложняющими взаимодействие с
игровой механикой.
Зачастую игрок вынужден искать в игре предметы, которые открывают
доступ в новые области игры, решать различные загадки и головоломки.
Дизайн уровней в «survival-horror» зачастую также используется для создания
атмосферы ужаса или ожидания чего-то, пугающего – например, игровой
персонаж может обследовать темные мрачные помещения, напоминающие
лабиринт, и подвергаться неожиданным нападениям врагов. [18]
Выбор такого жанра может показаться несколько противоречивым, если
учитывать главную цель проекта – снятие психофического напряжения,
однако для данного выбора есть обоснование. Дело в том, что хорошо
созданный фильм ужасов или компьютерная игра в жанре horror помогают
человеку отвлечься от собственных проблем и переживаний. Пользователь
переключается с собственных проблем и страхов на вымышленные и таким
образом на какое-то время получает необходимый эффект снятия стресса.
Стоит отдельно отметить, что многие произведения подобного
направления заканчиваются хорошим концом для главного героя, то есть
герой побеждает источник своих страхов и страхов зрителя (или игрока),
таким образом также удаётся получить эффект снятия стресса.
В доказательство того, что в трудные для себя времена многие люди
прибегают к жанру horror для снятия психофического напряжения, можно
привести инфографику популярности различных жанров кинематографа в
США в 20 -21 веках. Из графика видно, что жанр ужасов привлекал к себе
внимание в 30-х и 40-х годах, что соотносится со временами экономического
132
кризиса, из-за которой многие люди испытывали большие трудности и
соответственно переносили чрезмерный стресс.
Инфографика популярности жанров кинематографа приведена на
рисунке 74.
133
Главной концепцией данной игры является поиск ответов на странные
события, которые случаются с главным героем. Сюжет в игре передаётся
посредством:
монологов главного героя;
диалога с другими персонажами;
записок, оставленных в локациях игры;
заметок в дневнике главного героя.
Из-за ограничений проекта, сюжет в нём передаётся путём текста, а не с
помощью озвучки.
Сюжет в игре линейный и предоставляет пользователю существенного
выбора, также концовка у игры всего одна.
Это связано, прежде всего, с особенностями жанра, главный герой в
подобных играх практически не может влиять на происходящее, ему остаётся
лишь пытаться остаться в живых и разгадать подготовленную для него загадку
какого-либо явления или события. [20]
Также было решено ограничиться лишь хорошей концовкой, так как
плохие варианты концовки могут негативно сказаться на эмоциональном фоне
пользователя и вместо психической разрядки, он может получить от игры ещё
больше стресса.
Игровой процесс представляет собой поиск некоторых объектов в
локации уровня. В начале уровня пользователь может увидеть общую
структуру локации. Пользователю необходимо исследовать все комнаты в
особняке в поисках записок.
Для того, чтобы помешать игроку беспрепятственно ходить по локации
в игру были добавлены монстры, преследующие главного героя, а также
запертые двери, для открытия которых пользователю следует найти
специальный ключ.
Пользователь не может заранее видеть монстров, они могут появится
случайно в каждой из комнат дома. Если монстр заметит главного героя,
игроку следует искать убежище в локации. Убежище представляет собой
134
шкаф, в котором главный герой может на время спрятаться от вражеского
моба. Также от некоторых мобов главный герой может спастись, перемещаясь
между этажами с помощью лестницы.
Стоит отметить, что в игре не предусмотрено оружия, то есть главный
герой никак не может сопротивляться монстрам, единственный шанс
спастись, это спрятаться от них. У героя игры всего одно очко здоровья, это
означает, что если герой не сможет избежать вражеского моба, то он погибнет
и уровень придётся перепроходить заново.
Главной особенностью игры, является случайная генерация положения
комнат на каждом уровне. То есть, если пользователь по тем или иным
причинам будет вынужден переиграть какой-либо уровень, то будет
сгенерирована другая уникальная локация и ему придется заново запоминать
положение комнат и выстраивать свой маршрут.
Данная игровая механика создавалась с целью удержания интереса
пользователя при повторном прохождении уровня.
Сложность игры увеличивается по мере прохождения, сложность уровня
характеризуется несколькими параметрами:
количество комнат на локации;
количество врагов на локации;
количество фрагментов записки, которые необходимо собрать игроку;
количество закрытых дверей на локации.
Всего в игре десять уровней, которые необходимо пройти игроку, чтобы
получить концовку.
Каждый монстр в игре обладает собственной уникальной механикой
поведения. Пользователю необходимо изучить особенности поведения врагов,
чтобы более эффективно противостоять им. Монстры вводятся в игру
постепенно, к примеру, на первом уровне игрок встретит всего одного
монстра.
Вражеских мобов можно разделить на несколько основных категорий:
враги, стоячие на месте;
135
враги, которые могут передвигаться в пределах этажа;
враги, которые могут перемещаться в пределах всей локации.
Первый тип врагов блокирует главному герою доступ в определенную
комнату. При построении локации учитывается этот момент и в такие
комнаты не добавляются основные интерактивные предметы, однако в случае
обнаружения такого противника, пользователю иногда придется менять свой
маршрут.
Пример врага первого типа представлен на рисунке 75.
136
местах генерации такого типа мобов может быть расположен шкаф для
укрытия, но это не обязательное условие.
Пример врага второго типа представлен на рисунке 76.
137
успел дойти до него. Некоторые из врагов данного типа способны проходить
сквозь стены.
Пример врага третьего типа представлен на рисунке 77.
140
Рисунок 79 – Схематичное представление сюжета игры (часть 1)
141
Рисунок 80 – Схематичное представление сюжета игры (часть 2)
142
2.2.6 Лингвистическое обеспечение
143
2.3 Тестирование продукта
2.3.1 Тест-план
Функциональное
Объект системы и
Стратегия тестирование
действия
Тесты
Так как основную нагрузку для
игрового процесса представляют
постоянно появляющиеся
Производите ТК1
льность. противники, то стоит проверить, при
каком количестве мобов игра
начинает подтормаживать и «лагать».
Следует провести тест по сохранению
игрового процесса в экстренных
Сохранение
игрового ситуациях выхода из программы,
процесса в таких как зависание, с последующим ТК2
экстренных
ситуациях. «вылетом», принудительное закрытии
игры через диспетчер задач и другие
возможные выходы.
145
Коллизия. Проверить не проходят ли объекты ТК4
сквозь друг друга (персонаж сквозь
мобов и т.д).
Продолжение таблицы 3
Тесты
Рандомизация
Проверить правильность работы
комнат и
алгоритмов рандомизации (комнат, ТК5
связанных с ними
дверей, лестниц и прочих объектов).
объектов.
Проверить правильность работы
Алгоритм алгоритмов рандомизации (комнат,
ТК6
поведения мобов. дверей, лестниц и других элементов
интерактивного взаимодействия)
2.3.2 Тест-комплекты
Таблица 4 – Тест-кейс 1
ТС ID/Приоритет TK1 2
Идея: Максимальное количество мобов на уровне не снижает производительность
до критического уровня, при котором происходят «лаги» внутри игры.
Часть выполнения
Процедура Ожидаемый результат
1. Запустить видеоигру; Максимально возможное количество на
2. Замерить показатели уровне не снижает производительность
производительности до добавления и не приводит к появлению «лагов»
мобов;
3. Добавить максимально возможное
146
количество мобов на локацию (20-30);
4. Сравнить показатели
производительности после добавления
мобов, выявить «подтормаживания», если
они имеются.
Таблица 5 – Тест-кейс 2
ТС ID/Приоритет TK2 2
Идея: В экстремальных ситуациях, провоцирующих выход из игры, игровой
процесс пользователя не должен теряться.
Часть выполнения
Процедура Ожидаемый результат
1. Запустить видеоигру в редакторе После вылета игры и повторного её
Unity; запуск, весь игровой процесс будет
2. Начать прохождение; сохранен и не поврежден.
3. Создать условия в редакторе Unity,
при которых видеоигра зависнет и
вылетит;
4. Повторно зайти в игру;
5. Проверить целостность сохранений.
Таблица 6 – Тест-кейс 3
ТС ID/Приоритет TK3 1
Идея: Персонаж не должен выходить за пределы локации.
Часть выполнения
Процедура Ожидаемый результат
1. Запустить видеоигру; Отсутствие багов, связанных с выходом
2. По очереди запускать уровни игры; персонажа за пределы локации и
3. На каждом уровне проверить проваливания в текстуры.
способен ли персонаж выйти за пределы
локации;
4. На каждом уровне пытаться
спровоцировать баг, путем нажатия
нескольких кнопок действия (к примеру,
выстрел и бег вправо одновременно).
Таблица 7 – Тест-кейс 4
ТС ID/Приоритет TK4 1
Идея: Объекты на уровне не проходят сквозь друг друга.
147
Часть выполнения
Процедура Ожидаемый результат
1. Запустить видеоигру в редакторе Персонаж не может пройти сквозь
Unity; мобов. Пули и другие снаряды не
2. Запустить любой уровень; пролетают сквозь мобы. То есть все
3. Добавить на уровень 20-30 мобов; «коллизии» работают корректно.
Продолжение таблицы 7
ТС ID/Приоритет TK4 1
Идея: Объекты на уровне не проходят сквозь друг друга.
Часть выполнения
Процедура Ожидаемый результат
4. Пытаться пройти сквозь мобов Персонаж не может пройти сквозь
(персонаж не должен проходить сквозь мобов. Пули и другие снаряды не
них). пролетают сквозь объекты. То есть все
«коллизии» работают корректно.
Таблица 8 – Тест-кейс 5
Таблица 9 – Тест-кейс 6
149
Рисунок 82 – Результат тест-кейса ТК2
Результат выполнения тест-кейса ТК3 соответствует ожидаемому
результату и представлен на рисунке 83.
150
Рисунок 84 – Результат тест-кейса ТК4
Результат выполнения тест-кейса ТК5 соответствует ожидаемому
результату и представлен на рисунке 85.
151
Рисунок 86 – Результат тест-кейса ТК6 (враг «Главный изгой»)
152
Рисунок 88 – Результат тест-кейса ТК6 (враг «Шоггот»)
ЗАКЛЮЧЕНИЕ
153
На основе сравнительного анализа было принято решения взять за
прототип «Knock-Knock».
Далее система-прототип была рассмотрена по ее основным видам
обеспечения:
алгоритмическое обеспечение;
информационное обеспечение.
Кроме этого дополнительно был рассмотрен следующий материал:
основные понятия жанра «survival-horror»;
алгоритмы и методики, применяемые в приложении для достижения
эффекта расслабления.
Для реализации программного обеспечения было сформировано
техническое задание.
В конце отчета было проведено тестирование программного продукта по
выбранным критериям и составлено описание создаваемого приложения по
видам обеспечения:
в информационном была составлена схема модульной структуры
программы, отражающая потоки информации внутри последней, и модульная
структура искусственного интеллекта;
в алгоритмическом, по аналогии с предыдущим, приведены блок-схемы
алгоритма программы, алгоритмов ее основных составляющих элементов, а также
алгоритмы и механики искусственного интеллекта;
в лингвистическом был описан выбранный язык программирования с точки
зрения его достоинств и преимуществ конкретно для движка Unity;
в техническом – рекомендуемые технические требования к персональному
компьютеру;
в программном обеспечении были описаны UI/UX интерфейс, анимация и
музыкальное оформление с точки зрения их создания и редактирования;
наконец в концептуальном описана разработка общей концепции игры, ее
основных механик; в этом же разделе была приведена краткая схема сюжета.
154
БИБЛИОГРАФИЧЕСКИЙ СПИСОК
155
14. Timing for Animation /Harold Whitaker, John Halas, Tom Sito. — London:
Focal Press, Elsevier, 2009. — 184с.
15. Аниматор. Набор для выживания /Уильямс Ричард. — Москва: Бомбора,
2019. — 392с.
16. Звук на компьютере. Трюки и эффекты /Белунцов Владимир. — Санкт-
Петербург: СПб: Питер, 2009. — 451с.
17. Сочинение и аранжировка музыки на компьютере /Дейв Калабресе. —
Санкт-Петербург: БХВ - Петербург, 2016 г. — 611с.
18. Геймдизайн: Как создать игру, в которую будут играть все /Альпина
Паблишер. — Москва: Шелл Д, 2019 г. — 405с.
19. Проектирование и архитектура игр /Эндрю Роллингз, Дэйв Моррис. —
Москва: Издательский дом «Вильямс», 2005. — 1035с.
20. Unity Game Development Essentials /Will Goldstone. — Москва:
Издательский дом «Книга по Требованию», 2009. — 316с.
156