Создание образовательной игры по информатике на Unity

XXVII Международный конкурс научно-исследовательских и творческих работ учащихся
Старт в науке

Создание образовательной игры по информатике на Unity

Сергиенко С.И. 1
1ГБОУ МО "Одинцовский "Десятый лицей"
Пименова О.Р. 1
1ГБОУ МО "Одинцовский "Десятый лицей"
Автор работы награжден дипломом победителя I степени
Текст работы размещён без изображений и формул.
Полная версия работы доступна во вкладке "Файлы работы" в формате PDF

Паспорт проекта

Название

Создание образовательной игры по информатике на Unity

Автор

Сергиенко Сергей Игоревич, ученик 10 «В» класса

Научный руководитель

Пименова Ольга Рушановна, учитель информатики

Продукт

Образовательная игра по информатике

Актуальность

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

Цели

Создать образовательную игру по информатике на
Unity.

Задачи

  1. Получить базовые знания для предстоящей работы.

  2. Установить на свой компьютер необходимые программы.

  3. Написать сюжет игры.

  4. Создать образовательную игру с помощью установленных программ и
    приобретенных знаний.

Практическая значимость

Этот проект можно будет использовать:

- На уроках информатики, чтобы разнообразить учебный процесс.

- Для проведений интеллектуальных соревнований со сверстниками в знаниях по информатике.

- Для выступления на различных конкурсах.

Этапы проекта

Сентябрь-Октябрь: Теоретическая часть.

Ноябрь-Январь: Практическая часть.

Февраль-Март: Тестирование и исправление ошибок.

Введение

Проблема:

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

Актуальность:

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

Цели:

Создать образовательную игру по информатике на Unity.

Задачи:

  1. Получить базовые знания для предстоящей работы.

  2. Установить на свой компьютер необходимые программы.

  3. Написать сюжет игры.

  4. Создать образовательную игру с помощью установленных программ и
    приобретенных знаний.

Практическая значимость:

Этот проект можно будет использовать:

- На уроках информатики, чтобы разнообразить учебный процесс.

- Для проведений интеллектуальных соревнований со сверстниками в знаниях по информатике.

- Для выступления на различных конкурсах.

Этапы проекта:

- Сентябрь-Октябрь: Теоретическая часть.

- Ноябрь-Январь: Практическая часть.

- Февраль-Март: Тестирование и исправление ошибок.

Основная часть

Теория

На просторах интернета на самом деле не так много образовательных видео игр. Единственное место, где можно найти какие-то образовательные игры это сайт «Яндекс Игры», но там все образовательные игры выглядят как просто тесты на каком-то другом сайте, то есть они не похожи на игры. Игра должна завлечь игрока, чтобы ему хотелось играть в нее, только тогда игра может называться игрой.

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

Структура моей игры:

Свою игру я хочу сделать по следующей структуре:

- При запуске игры будет открываться главное меню. В нём будет название игры, и три кнопки: начать игру, настройки и выйти из игры.

- В самой игре будет 4 локации: начальная локация (обычный лес), еловый лес, горы и поляна с высокими кустами.

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

Среда разработки:

Есть много разных игровых движков для создания игр, такие как: Unity, Unreal Engine, Source, CryEngine и др. Я буду писать игру на Unity. Unity — это кроссплатформенная среда разработки компьютерных игр, созданная американской компанией Unity Technologies. Unity позволяет создавать приложения, работающие на более чем 25 различных платформах, включающих персональные компьютеры, игровые консоли, мобильные устройства, интернет-приложения и другие. Основными преимуществами Unity являются наличие визуальной среды разработки, межплатформенной поддержки и модульной системы компонентов. Также в Unity есть огромной выбор библиотеки ассетов и плагинов, с помощью которых можно значительно ускорить процесс разработки игры. Этот игровой движок очень легок в изучении, а значит он идеально подойдет для создания своей первой игры.

Практика

Установка Unity:

Сначала надо установить Unity:

- На официальном сайте Unity надо нажать кнопку «Начать».

- В открывшейся вкладке выбрать «Физическое лицо» и бесплатный проект.

- Далее выбрать операционную систему (Mac OS или Windows).

- Скачать установщик и установить программу следуя инструкциям в установщике.

- После запуска «Unity Hub» надо создать аккаунт и войти в него.

- Откроется приветственная страница, надо нажать «Got it». Откроется установщик, который предложит загрузить последнюю актуальную версию Unity.

- После завершения загрузки надо создать новый проект и зайти в него.

Также чтобы написать свою игру нужно будет писать скрипты на C#. Скрипт — это набор команд, написанных на языке программирования, которые выполняют определённую задачу. Для этого я буду использовать Visual Studio Code, так как я в нем работаю давно и уже привык к нему.

Установка Visual Studio Code.

Сначала надо установить Visual Studio Code.

Установка в macOS:

-Скачать установочный файл (ссылка в источниках информации).

-Открыть папку с загрузками и найти скачанный архив.

-Извлечь содержимое и запустить приложение.

-Перетащить Visual Studio Code.app в папку Программы.

-Теперь приложение можно запускать из меню приложений.

Установка в Windows:

-Скачать установщик (ссылка в источниках информации).

-Открыть загрузки и найти скачанный файл.

-Запустить двойным кликом VSCodeUserSetup-{version}.exe.

-По умолчанию приложение появится в папке Programs\Microsoft\VS Code.

Установка в Linux:

-Скачать установщик, подходящий к дистрибутиву (ссылка в источниках информации).

-Запустить файл и следовать инструкции.

Также для работы со скриптами нужно в Visual Studio Code установить язык программирования C#, так как Unity поддерживает только этот язык программирования.

Установка C#:

- Надо открыть Visual Studio Code и нажать на значок «Расширения» в панели активности на левой стороне редактора.

- В поле поиска надо ввести «C# Dev Kit» и нажать «Enter». В списке результатов должно появиться расширение C# Dev Kit от Microsoft.

- Нажать на кнопку «Установить», чтобы установить расширение. После установки надо будет перезагрузить VS Code для активации расширения.

Сюжет игры:

Сюжет для своей игры я придумал следующий:

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

- В еловом лесу главные персонажи замечают тарелку для антенны, застрявшую высоко на дереве. Виртекс предлагает достать тарелку, но, если игрок поможет ему рассчитать координаты этой детали в десятичной системе счисления, так как он записывает координаты в других системах счисления. Датчики Виртекса неисправны после крушения корабля, поэтому он не может самостоятельно перевести координаты правильно из одной системы счисления в другую. По завершению этого квеста будет два варианты развития сюжета: 1) Игрок ответил правильно на вопрос и получает тарелку. 2) Игрок не ответил правильно на вопрос, и тарелка осталась на дереве. После Виртекс замечает следующую деталь в горах.

- В горах игрок не может пройти из-за двух обвалов камней. Виртекс может помочь убрать камни если игрок ответит на 2 вопроса по языку программирования Python, который на планете Виртекса называется Snake. После двух вопросов игрок получает основание для антенны, а Виртекс не может заметить следующую деталь и предлагает пойти дальше.

- Главные персонажи попадают на поляну с высокими кустами. Виртекс предлагает игроку пока поискать в высокой траве деталь. Игрок не находит деталь в кустах и сообщает об этом Виртексу. Виртекс предлагает снова помочь ему правильно перевести координаты детали, после игрок находит ножку антенны в кустах. Главные герои не знают где найти последнюю деталь, но Стёпа вспоминает что рядом с его домом есть последняя недостающая деталь. Главные персонажи возвращаются в самое начало к дому Виртекса.

- По пути домой игроки проходят снова через еловый лес. Если ранее игрок не получил тарелку антенны в еловом лесу, то она будет лежать рядом с деревом, а с Виртексом будет диалог, в котором Стёпа скажет о находке. После в независимости от того, нашел ли игрок ранее тарелку для антенны или нет, Виртекс скажет ему, что будет ждать его около своего разрушенного корабля и надо будет ему скинуть детали в определенном порядке чтобы он смог построить антенну.

- Около дома игрок подбирает последнюю деталь и направляется к Виртексу. Виртекс просит детали в определенном порядке: основание антенны, ножка антенны, тарелка антенны и мини антенна. После игрок отвечает на 4 вопроса: 2 про системы счисления и 2 на знания Python. Потом прилетает корабль друзей Виртекса с его планеты и благодарят игрока за помощь.

- В самом конце появляется окно с правильностью всех ответов.

Написание игры:

Я разделю задачу 4 на несколько подзадач:

  1. Создание начальной игровой локации.

  2. Создание игрока и его управление.

  3. Создание анимаций для игрока.

  4. Привязка камеры к игроку.

  5. Добавление инвентаря.

  6. Создание следующих игровых локаций и переход между ними.

  7. Создание квестов с NPC.

  8. Создание главного меню и конечного экрана.

Подзадача 4.1 (Создание начальной игровой локации):

Я решил создавать свою игру в 2D, так как для первой игры браться за 3D проект — это очень сложно, а стиль я выбрал 8-бит (8-битный стиль в игры — это эстетика, ассоциирующаяся с ранними домашними играми и аркадами).

Для создания начальной игровой локации я взял готовую графику из бесплатного магазина ассетов Unity (Ассеты — это компоненты, которые представляют собой графику, звуковое сопровождение или скрипты). Я выбрал ассет «Platformer Tileset - Pixelart Grasslands». Для того чтобы добавить его в проект нужно его скачать с официального сайта Unity Asset Store и выбрать пункт «Open in Unity». Дальше в самом Unity импортировать его в проект и создать новую папку для текстур в папке Assets папку Texture. В ней будут храниться все текстуры для игры.

Также я определил удобное для себя расположение элементов в Unity, для этого я в верхней панели выбрал пункт «Window» «Layouts» «2 by 3». В пункте «Hierarchy» создал новый пустой объект, в котором будут храниться все элементы начальной локацией и назвал его Frame. Нажал правой кнопкой мыши по пустому месту под пунктом Hierarchy и выберу CreateEmptyи переименовал его в Frame.

Из импортированных ассетов выбираем нужные, это: земля, на которой будет стоять игрок, небо и другие элементы заднего фона.

Также надо добавить несколько слоев, чтобы задний фон не перекрывал землю и игрока. Для этого под пунктом Hierarchy нужно выбрать любой объект и нажатием на кнопку Default в настройках объекта и добавить новые сортировочные слои нажав на Addsortinglayer. После добавляю три новых слоя: BG (Background – задний фон), Player (фон игрока), FG (Foreground – передний фон). Затем каждому объекту на сцене присваиваю свой слой.

Под объектом Frame создаю пустые объекты Bg (Background – задний фон) и Gr (Ground – земля). Под Bg перетаскиваю все элементы заднего фона, а под Gr все элементы земли так как в ассете который я выбрал, землю нужно собирать из разных кусочков, как и небо.

Также поставлю на локацию дом главного персонажа из бесплатного ассета Free Pixel Army - Platformer Packи разбавлю все деревьями и кустами. Добавлю еще корабль и камни рядом с ним чтобы сделать вид что он разрушен, корабль я возьму из бесплатного ассета Robot Shooting Game Sprite (Free), а камни из Pixel Art Top Down – Basic.

Начальная локация

Подзадача 4.2 (Создание игрока и его управление):

После того как появилась локация нужно также перетащить на сцену персонажа за которого будет играть игрок, его я взял из того же ассета где взял землю и небо, поставил ему слой Playerи переименовал эту текстуру в Player. Также игроку надо присвоить особые компоненты, которые позволят взаимодействовать с игрой и быть физическим объектом. Для этого надо выбрать игрока в иерархии игрока и нажать Addcomponent, в поиске надо найти CapsuleCollider 2D и Rigidbody 2D.

В CapsuleCollider 2Dнадо нажать на Edit Capsule и растянуть его по игроку чтобы он доходил до ног. Если после этого запустить игру, то игрок просто провалится под землю и будет все время падать. Это происходит, потому что игрок стал физическим объектом, а земля нет. Чтобы игрок смог стоять на земле надо в иерархии выбрать Gr и добавить под него новый пустой объект назвав его hitbox и ему присвоить компонент PolygonCollider 2Dи растянуть его по всей земле. После запуска игры игрок не будет проваливаться сквозь землю, но будет заваливаться в бок. Чтобы это исправить нужно в компоненте Rigidbody 2Dигрока в пункте Constraints нажать на FreezeRotationZ.

Чтобы игрок смог двигаться нужно написать скрипт. В Unity нужно писать скрипты чтобы происходило какое-либо действие. В папке Assets создам новую папку для скриптов Scripts. В новой папке нажму правой кнопкой и выберу «Create» «Scripting» «Empty C# Script». Переименую его в PlayerController и зайду в него.

В самом верху уже будет написано:

using UnityEngine;

В C# так подключаются библиотеки. Надо подключить ещё пару библиотек:

using System.Collections;

using System.Collections.Generic;

Также чтобы скрипт мог брать всю информацию из проекта игры нужно в строчке public class PlayerController добавить через пробел:

 : MonoBehaviour

Между фигурных скобок под строчкой publicclassPlayerController : MonoBehaviourнадо добавить переменные для скорости и силы прыжка игрока:

public float speed;

    public float jumpForce;

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

privatefloatmoveInput;

Также нужно создать переменную, в которой будет хранится коллайдер игрока для перемещения, который создал ранее, добавив для игрока компонент Rigidbody 2D. Чтобы сделать переменную не просто численную, а с информацией из Rigidbody 2D, нужно после типа переменной указать Rigidbody2D. Переменную я назову сокращённо rb:

private Rigidbody2D rb;

Чтобы переменная rb получила компоненты Rigidbody 2D нужно написать стартовую функцию, которая будет их брать:

private void Start()

    {

        rb = GetComponent<Rigidbody2D>();

    }

Функция Start срабатывает только при запуске игры. Чтобы скрипт всегда был готов к нажатию клавиш на клавиатуре нужно написать функцию FixedUpdate, которая будет работать каждый кадр пока игрок играет в игру. В нее надо записать строку, которая будет позволять игроку двигаться только по горизонтали, то есть вправо или влево:

private void FixedUpdate()

    {

moveInput = Input.GetAxis("Horizontal");

}

Также в эту функцию нужно добавить скорость игрока, она основана на векторах 2D:

rb.velocity = new Vector2(moveInput * speed, rb.velocity.y);

После этого можно сохранить скрипт и вернутся обратно в Unity. Если в Unity после сохранения скрипта появится окно, которое предложит исправить ошибки в скрипте, надо нажать No, так как он переделает скрипт, и он работать не будет. Этот скрипт надо добавить игроку. Для этого нужно также выбрать игрока и в поиске Addcomponentвбить имя скрипта. В пункте скрипта PlayerController появятся Speed и Jump Force, слева от них можно ввести значения. Я выберу значения 6 для скорости и силы прыжка. Если запустить игру, то игрок сможет двигаться влево и вправо если нажимать «A» и «D», но он будет смотреть только в одну сторону.

Чтобы это исправить нужно в скрипте добавить новую переменную, которая будет определять его направление:

privateboolfacingRight = true;

Также надо создать функцию по которой игрок будет разворачиваться, назову ее Flip. Чтобы игрок разворачивался скрипт будет брать его изначальное положение текстуры и умножать на -1, то есть отзеркалит:

void Flip()

    {

        facingRight = !facingRight;

        Vector3 Scaler = transform.localScale;

        Scaler.x *= -1;

        transform.localScale = Scaler;

    }

Но чтобы эта функция срабатывала нужно написать условие. Условие надо написать в функции FixedUpdate. Условие будет проверять, если игрок смотрит в другую сторону, и он двигается, тогда будет срабатывать функция Flip:

if (facingRight == false && moveInput > 0)

        {

            Flip();

        }

else if(facingRight == true && moveInput < 0)

        {

            Flip();

        }

После этого можно сохранять скрипт и при запуске игры игрок будет поворачиваться при ходьбе вправо и влево.

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

private bool isGrounded;

    public Transform feetPos;

    public float checkRadius;

    public LayerMask whatIsGround;

Надо написать новую функцию Update, она позволяет вносить изменения во время игры. В ней будет проверяться приземлился ли игрок и нажата ли клавиша пробел для прыжка:

private void Update()

    {

        isGrounded = Physics2D.OverlapCircle(feetPos.position, checkRadius, whatIsGround);

if(isGrounded == true && Input.GetKeyDown(KeyCode.Space))

        {

            rb.velocity = Vector2.up * jumpForce;

        }

}

После этого можно сохранить скрипт и вернуться обратно в Unity. Там надо будет указать позицию ног для игрока. Для этого надо создавать новый пустой объект под игроком и назвать его feetPos, переместить его под ноги игроку и перетащить его в параметры скрипта напротив FeetPos. Также в параметрах игрока сверху в пункте Layer нужно создать новый слой Ground и указать его в параметрах скрипта PlayerController у игрока. Check Radius надо поставить на 0.3, а объект hitbox перетащить в поле Hitbox в параметрах скрипта игрока. Если запустить игру, то игрок сможет ходить вправо и влево, поворачиваться и прыгать.

Прыжок игрока

Подзадача 4.3 (Создание анимаций для игрока):

Чтобы игра выглядела не так скучно нужно анимировать игрока. У игрока должны быть 4 анимации: спокойствия, бега, отрывания от земли и приземления. Так как я делаю игру в стиле 8-бит то анимация состоит просто из разных моделей персонажа, которые быстро сменяются, создавая эффект что персонаж двигается. В ассете, откуда я брал текстуру персонажа есть почти все анимации кроме спокойствия, поэтому ее нужно нарисовать самостоятельно.

Для создания графики я буду использовать обычную программу Paint. В ней открываю Png файл из ассета с персонажем. На нем будут нарисованы рядом друг с другом все анимации персонажа, это сделано для оптимизации, чтобы загружать не все файлы с моделями по отдельности, а грузить только один файл. Чтобы создать анимацию спокойствия нужно нарисовать такую же модель персонажа, только убрать ей в ногах один слой пикселей, чтобы при смене кадров казалось, что персонаж приседает.

После этого надо сохранить файл, но если вернуться в Unity, то на персонаже появится белая рамка. Это потому, что Paint добавляет белый фон к файлу если на нем не было фона. Чтобы это исправить нужно в фотошопе убрать фон и полученный файл перетащить в папку Texture.

Чтобы все текстуры были не размыты и правильных размеров, нужно в папке Texture выбрать файл с моделями и в пункте SpriteMode выбрать Multiple, а в FilterMode выбрать Point (nofilter). Чтобы изменить размер изначальной модели на сцене нужно изменить значение в пункте PixelsPerUnit, чем ближе к нулю, тем больше они будут.

Окно анимаций открывается если нажать на ctrl + 6. Надо выбрать в папке текстур две модели для анимации спокойствия зажав shift и перетащить их на модель игрока на сцене. В появившемся окне переименовываем его в idle и в папке Assets создаем новую папку Animations и создаем этот файл в нее. После отводим второй ромбик от первого на кадров 7–10 как больше нравится, и персонаж начинает двигаться. Перемещаете второй ромбик пока удовлетворит анимация.

После ее сохраняем и создаем по такому же принципу новые анимации: run, jump, takeOf и land.

Чтобы анимации проигрывались правильно нужно перейти в специальное окно аниматор и настроить их там. Чтобы в него перейти надо нажать на верхней панели: Window Animation Animator.

В появившемся окне появятся блоки с анимациями. Чтобы сделать переход от одной анимации к другой нужно по блоку нажать правой кнопкой мыши и MakeTransition. Такнужносделатьследующиепереходы: Any State takeOf, idle run, run idle, takeOf jump, jump land, land run, land idle. Чтобы анимации сменялись нужно также создать переменные, которые будут отвечать за активность той или иной анимации. Для этого в левой вкладке надо выбрать Parameters, и на нажав на плюс создать 2 переменные вида bool и одну вида trigger. Переменные вида bool надо переименовать в isJumping и isRunning, а переменную вида trigger в takeOf.

Теперь все эти переменные нужно расставить по переходам, нажав в пункте Conditions плюс: в idle runisRunning true; run idleisRunning false; Any State takeOf takeOf; takeOf jump isJumping true; jump land isJumping false; land idle isRunning false; land run isRunning true. Дляпереходов idle run, run idle, Any State takeOf, takeOf jump, jump land убратьгалочку Exit time, а Transition Duration поставитьна 0.1. А у land run и land idle Exit time поставить на 1.

Также чтобы анимации срабатывали нужно их применить к скрипту PlayerController. Для этого надо создать новую переменную, которая берет информацию из аниматора:

private Animator anim;

Чтобы значения присваивались нужно добавить строку в начальную функцию Start:

anim = GetComponent<Animator>();

Нужно также написать новые условия в функцию FixedUpdate при которых будут изменяться значения переменных и переключаться анимации:

if(moveInput == 0)

        {

            anim.SetBool("isRunning", false);

        }

        else

        {

            anim.SetBool("isRunning", true);

        }

В функции Update в условии надо вставить строку, добавляющую тригер для прыжка:

anim.SetTrigger("takeOf");

Также надо написать еще одно условие для прыжка, которое проверяет, стоит ли игрок на земле:

if(isGrounded == true)

        {

            anim.SetBool("isJumping", false);

        }

        else

        {

            anim.SetBool("isJumping", true);

        }

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

Анимация бега игрока

Подзадача 4.4 (Привязка камеры к игроку):

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

Cinemachine изначально в Unity нету, поэтому сначала надо его установить. Дляэтогонадонажатьна Window Package Manager Unity Registry. Cinemachine можно найти либо в поиске либо среди списка других пакетов, далле нажать Install и подождать конца загрузки. Теперь на верхней панели в пункте Component появился Cinemachine, и в нем надо выбрать CreateVirtualCamera. После этого она появиться в иерархии объектов.

Теперь VirtualCamera надо настроить. Нужно выбрать новую камеру и в пункте Follow ее настроек перетащить игрока, за которым она будет следовать. В MainCamera нужно добавить компонент CinemachineBrain.

После этого, при запуске игры камера будет следовать за игроком, но будет выходить за границы локации. Чтобы это исправить нужно на объект Frameдобавить компонент PolygonCollider 2D. Теперь его нужно растянуть по всей локации, главное, чтобы точки не выходили за границу локации. И в настройках этого полигона надо поставить галочку IsTrigger, чтобы он был не физическим объектом.

Для камеры Cinemachine нужно добавить компонент CinemachineConfiner. В пункте BoundingShape надо перетащить объект Frame. Теперь при запуске игры камера будет следовать за игроком и не будет выходить за граница локации.

Камера

Подзадача 4.5 (Добавление инвентаря):

Чтобы игрок смог собирать предметы и отдавать их, нужно добавить инвентарь. Инвентарь будет состоять из иконок сундука и 4 слотов для предметов. Сундук и иконки я взял из бесплатного ассета «Undead Survivor Assets Pack», а крестик, по которому игрок сможет нажимать, чтобы выбросить предмет я нарисовал в Paint и перенес его в Unity также как в подзадаче 4.3.

Так как инвентарь это не просто объект на сцене, а часть интерфейса, то для него нужно создать в иерархии объект Canvas, находящийся в пункте UI. Два раза нажав на Canvas камера сильно отдалиться, а под ним нужно создать 3 объекта UI Image. В пункте SourceImage каждого нужно перетащить текстуры сундука, слота и крестика. Также их нужно правильно расставить и переименовать чтобы было удобнее с ними работать. Нужно создать еще один пустой объект, который переименую в Inventory, и перенесу под него слот и крестик. После скопирую слот и крестик и вставлю их еще 3 раза, так как мне нужно чтобы в инвентаре было всего 4 слота.

Сейчас если растянуть экран, то все элементы инвентаря будут расположены не корректно. Чтобы это исправить нужно под пунктом Rest Transform каждого объекта инвентаря нажать на квадрат и выбрать расположение на экране. Я выбрал расположение в левом верхнем углу.

Чтобы инвентарь работал, нужно для него тоже написать скрипт. Для этого в папке Assets создам новую папку Inventory и создам еще 4 скрипта: Inventory, Pickup, Slot и Spawn.

В скрипте Inventory нужно сначала добавить несколько переменных: список наполненности, список слотов, инвентарь и проверку инвентаря:

public bool[] isFull;

    public GameObject[] slots;

  public GameObject inventory;

    private bool inventoryOn;

Нужно создать начальную функцию, которая будет ставить значение инвентаря:

private void Start()

    {

        inventoryOn = false;

    }

Чтобы инвентарь открывался или закрывался по нажатию на сундук нужно создать функцию Chest:

public void Chest()

    {

        if(inventoryOn == false)

        {

            inventoryOn = true;

            inventory.SetActive(true);

        }

        else if (inventoryOn == true)

        {

            inventoryOn = false;

            inventory.SetActive(false);

        }

    }

В скрипте Pickup в стартовой функции нужно получить компоненты инвентаря:

private void Start()

    {

Inventory = GameObject.FindGameObjectWithTag("Player").GetComponent<Inventory>();

    }

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

private void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            for (int i = 0; i < inventory.slots.Length; i++)

            {

                if(inventory.isFull[i] == false)

                {

                    inventory.isFull[i] = true;

                    Instantiate(slotButton, inventory.slots[i].transform);

                    Destroy(gameObject);

                    break;

                }

            }

        }

    }

В скрипте Slot нужно также в стартовой функции получить компоненты инвентаря:

private void Start()

    {

inventory = GameObject.FindGameObjectWithTag("Player").GetComponent<Inventory>();

    }

Чтобы нельзя было подбирать предметы, если инвентарь уже заполнен, нужно написать функцию Update, которая каждый кадр проверяет, можно ли подобрать предмет:

privatevoidUpdate()

    {

        if(transform.childCount <= 0)

        {

            inventory.isFull[i] = false;

        }

    }

Также в этом скрипте нужно написать функцию, по которой игрок сможет выбрасывать предмет. В ней будет удаляться объект из Canvas и добавляться на сцену:

public void DropItem()

{

foreach(Transform child in transform)

{

child.GetComponent<Spawn>().SpawnDroppedItem();

GameObject.Destroy(child.gameObject);

}

}

В скрипте Spawn в стартовая функция будет получать координаты игрока:

private void Start()

    {

        player = GameObject.FindGameObjectWithTag("Player").transform;

    }

В этом скрипте будет функция, которая будет возвращать предмет на сцену. В ней будет появляться объект на сцене, но с координатами игрока, изменёнными на пару единиц чтобы предмет появлялся не в игроке, а рядом с ним:

public void SpawnDroppedItem()

    {

        Vector2 playerPos = new Vector2(player.position.x + 2, player.position.y - 1);

        Instantiate(item, playerPos, Quaternion.identity);

    }

Чтобы в инвентарь добавлялись предметы, поднятые игроком нужно на игрока добавить в компонентах скрипт Inventory. В нем нужно указать количество слотов в Size (в моем случае 4) и под ним в элементы перенести слоты.

Чтобы добавить предметы, которые будет поднимать игрок, нужно сначала их перенести на сцену. Мне нужны запчасти от антенны поэтому я их нарисую самостоятельно в Paint и перенесу в Unity. Им нужно поставить сортировочный слой Playerи добавить компонент CircleCollider 2D, не забыв поставить галочку IsTrigger. Также нужно добавить им компонент Rigidbody 2D и изменить пункт BodyType на Kinematic, и добавить скрипт Pickup.

Чтобы их можно было неоднократно использовать на сцене, нужно сделать их префабами. Префаб (prefab) — это шаблон для объекта, в котором хранится какой-либо объект со всеми его свойствами и характеристиками. Для этого в Assets надо создать новую папку Prefabs и перетащить в нее со сцены предметы, которые сможет поднимать игрок.

Предметы нужно также разместить в Canvas, чтобы они правильно отображались в инвентаре. Для этого под Canvas нужно создать столько Image сколько будет предметов, и в их SourceImage перетащить текстуры предметов, а также поставить их координаты: x=0, y=0. Также их надо переименовать и в конце добавить приписку Button. Чтобы предметы можно было использовать не однократно, их нужно тоже перетащить в папку Prefabs, а из сцены можно удалить. Затем им нужно добавить скрипт Spawn, в который надо перетащить префаб обычного предмета.

В префабе обычного предмета в скрипте Pickup нужно в SlotButton перетащить префаб этого предмета с припиской Button.

Всем слотам в Canvas нужно добавить скрипт Slot и назначить им номера по очереди, в которой они стоят начиная с 0. Всем объектам крестикам в Canvas нужно добавить компонент Button, чтобы можно было нажимать на них. В его настройках надо добавить события нажав на плюс. В поле None (object) нужно перетащить из Canvas соответствующий слот, а в поле NoFunction нужно выбрать Slot DropItem.

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

Инвентарь

Подзадача 4.6 (Создание следующих игровых локаций и переход между ними):

Нужно создать ещё 3 локации: еловый лес с большим деревом в середине, горы с подъемом и поляна с высокими кустами за, которыми будет видна только половина персонажа. Все эти локации я создам также, как в подзадаче 4.1, добавлю им PolygonCollider 2D и виртуальную камеру, как в подзадаче 4.4. Важно чтобы границы коллайдеров не пересекались.

Чтобы камера перемещалась из одной локации в следующую, когда игрок будет подходить к е ё краю, нужно создать новый скрипт FrameSwitch. Это также хороший способ оптимизировать игру, так как при переходе на другую локацию, прошлая будет не активна и не будет тратить ресурсы компьютера.

В скрипте FrameSwitch нужно сделать 2 функции, одна из которых при входе в локацию будет ее активировать, а при выходе отключать:

private void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            activeFrame.SetActive(true);

        }

    }

    private void OnTriggerExit2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            activeFrame.SetActive(false);

        }

    }

В иерархии объектов нужно создать столько пустых объектов, сколько локаций, и переименовать их в Edge. В них нужно добавить компонент BoxCollider 2D, поставить галочку IsTrigger и растянуть по границам локации, только на этот раз важно чтобы границы каждого BoxCollider 2D пересекались с соседними BoxCollider 2D соседней локации. Это нужно чтобы игрок не смог провалиться сквозь локации. Также им нужно добавить скрипт FrameSwitch и в поле ActiveFrame перетащить соответствующую локацию.

Чтобы переходы были резкими и выглядели красиво нужно в MainCamera в пункте CinemachineBrain поле DefaultBlend поставить на Cut.

Теперь игрок может перемещаться между локациями.

Все локации

Подзадача 4.7 (Создание квестов с NPC):

Теперь нужно на локациях разместить Нпс, с которыми будет взаимодействовать игрок. Нпс (неигровой персонаж, англ. Non-Player Character (NPC)) — самостоятельный персонаж, управляемый не игроком, а кодом игры.

Модель Нпс я нарисую самостоятельно в Paint и анимирую, как в подзадаче 4.3. Ему, также, как и игроку нужно добавить CircleCollider 2D и растянуть. Это и будет зона, в которой будут активироваться действия с Нпс, обязательно в его настройках поставить галочку IsTrigger.

Чтобы реализовать диалоги, я воспользуюсь бесплатной библиотекой в Unity «Dialogue Editor». Её можно установить также через Asset Unity Store как ассеты с текстурами.

Чтобы добавить диалог к Нпс, нужно создать новый пустой объект с диалогом и ему присвоить компонент NPCConservation. Это скрипт, который добавился вместе с библиотекой «Dialogue Editor». После нужно включить панель редактирования диалогов. Для этого нужно нажать на Window DialogueEditor. В этом окне можно перетаскивать блоки с диалогами, создавать новые речи, нажав правой кнопкой по последнему блоку и выбрать CreateSpeech или CreateOption для создания кнопки с вариантом ответа. При выборе речи или кнопки справа в окне можно вписать имя, кто будет говорить речь, саму речь, поставить иконку того, кто говорит, и поставить функцию. Чтобы функция срабатывала, нужно ее сначала написать в отдельном скрипте, добавить его к объекту с диалогом и в поле функции выбрать нужную.

Теперь нужно расставить всех Нпс по всем локациям, написать для них речи и функции.

Диалоги с Нпс

Подзадача 4.8 (Создание главного меню и конечного экрана):

Для игры обязательно нужно главное меню, в котором можно посмотреть управление, начать игру или выйти из игры. Для этого нужно в папке Scenes создать новую пустую сцену, нажав Creating Scene Scene, и переименовать ее в MainMenu. В нее я скопирую Frame из сцены SampleScene. Это и будет фоном главного меню, только надо удалить из объекта Player скрипт PlayerController, чтобы нельзя было управлять игроком.

Создаю новый слой Canvas как в подзадаче 4.5 и добавляю через Image 4 объекта: 3 кнопки и название. Кнопки я взял из бесплатного ассета «2D Simple UI Pack», на 3 кнопки надо поставить компонент Button. Переименую кнопки в StartButton, OptionButtonиQuitButton. Также под Canvas создаю пустой объект MainMenu и перемещаю эти кнопки и название под него.

Также под Canvas создаю объект ImageOptionsMenu, ему добавляю текстуру фона, а под него создаю объект ButtonQuitButton и Text. В тексте пишу управление для игры.

Нужно создать 2 функции у кнопки OptionButton, в первую надо перенести объект MainMenu и выбрать SetActive(bool) без галочки, а во вторую перенести объект OptionsMenu и выбрать SetActive(bool) с галочкой. Для кнопки QuitButton нужно сделать тоже самое, только наоборот.

Чтобы работали кнопки старта и выхода из игры, нужно написать скрипт MainMenu. В функции запуска игры будет меняться текущая сцена на следующую, а в функции выхода из игры игра будет закрываться командой:

public void PlayGame()

    {

        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);

    }

    public void ExitGame()

    {

        Debug.Log("End");

        Application.Quit();

    }

Под кнопку StartButton нужно поставить функцию PlayGame, а для кнопки QuitButton поставить функцию ExitGame.

Чтобы сделать конечный экран после игры, нужно на сцене с игрой под Canvas создать новый Image объект EndScreen, на него добавить текст и кнопку для перехода на главное меню. Также надо написать скрипт BackButton, который будет замораживать игру и менять сцену:

public void MainMenu()

    {

        Time.timeScale = 1f;

        SceneManager.LoadScene("MainMenu");

    }

Чтобы на конечном экране выводился процент правильности ответов нужно в скрипте с первым квестом написать переменную с типом static, чтобы ее можно было изменять из любого другого скрипта. У меня будет всего 3 степени ответов: за правильный ответ с 1 попытки – максимальное количество баллов, правильный ответ со 2 попытки – в 2 раза меньше баллов, с 3 попытки не правильный ответ – ещё в 2 раза меньше баллов:

public void Answer1()

    {

        GameProcentage += 12.5f;

    }

    public void Answer2()

    {

        GameProcentage += 6.25f;

    }

    public void Answer3()

    {

        GameProcentage += 3.125f;

    }

Эти строчки нужно добавить в каждый скрипт для квестов и выбрать подходящую функцию для каждого варианта ответа. В объекте EndScreen нужно добавить пустой объект и на него добавить скрипт, который будет выводить значение переменной с процентом правильности ответов:

public void Update()

    {

        TextProcentage.text = Quests.GameProcentage.ToString();

    }

Главный экран

Конечный экран

На этом создание игры полностью закончено.

Заключение

Этот проект получился очень объемным по количеству выполненных работ. В процессе его реализации я приобрел новые знания в создании видео игр и новый для себя языке программирования C#, а также попробовал себя в роли разработчика видео игр. Я узнал, насколько тяжело создавать игры, но в той же степени это было очень увлекательной и интересной работой. Благодаря этому проекту я узнал, почему некоторые вещи в играх, в которые я играл, работают именно так, потому что на протяжении работы над проектом постоянно сам сталкивался с похожими алгоритмами.

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

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

Источники информации:

  1. Platformer Tileset - Pixelart Grasslands - https://assetstore.unity.com/packages/2d/environments/platformer-tileset-pixelart-grasslands-248158

  2. Free Pixel Army - Platformer Pack - https://assetstore.unity.com/packages/2d/characters/free-pixel-army-platformer-pack-168264

  3. Robot Shooting Game Sprite (Free) - https://assetstore.unity.com/packages/2d/environments/robot-shooting-game-sprite-free-93902

  4. Pixel Art Top Down – Basic - https://assetstore.unity.com/packages/2d/environments/pixel-art-top-down-basic-187605

  5. Undead Survivor Assets Pack - https://assetstore.unity.com/packages/2d/undead-survivor-assets-pack-238068

  6. 2D Simple UI Pack - https://assetstore.unity.com/packages/2d/gui/icons/2d-simple-ui-pack-218050

  7. Dialogue Editor - https://assetstore.unity.com/packages/tools/utilities/dialogue-editor-168329

  8. Unity Уроки C# || Создание 2D Игры на Android - https://www.youtube.com/playlist?list=PLRHtm1zQx-f_iPJNssrmZFSj9H5a00e5I

  9. Как анимировать 2D-персонажей в Unity 2022 LTS - https://unity.com/ru/how-to/2d-characters-and-animation-unity-2022-lts

  10. Создать глобальные переменные, которые будут присутствовать во всех сценах проекта - https://www.cyberforum.ru/unity/thread1272659.html

  11. Яндекс игры - https://yandex.ru/games/category/educational

Приложение

PlayerController

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class PlayerController : MonoBehaviour

{

    public float speed;

    public float jumpForce;

    private float moveInput;

    private Rigidbody2D rb;

   

    private bool facingRight = true;

    private bool isGrounded;

    public Transform feetPos;

    public float checkRadius;

    public LayerMask whatIsGround;

    private Animator anim;

    private void Start()

    {

        anim = GetComponent<Animator>();

        rb = GetComponent<Rigidbody2D>();

    }

    private void FixedUpdate()

    {

        moveInput = Input.GetAxis("Horizontal");

        rb.velocity = new Vector2(moveInput * speed, rb.velocity.y);

        if(facingRight == false && moveInput > 0)

        {

            Flip();

        }

        else if(facingRight == true && moveInput < 0)

        {

            Flip();

        }

        if(moveInput == 0)

        {

            anim.SetBool("isRunning", false);

        }

        else

        {

            anim.SetBool("isRunning", true);

        }

    }

    private void Update()

    {

        isGrounded = Physics2D.OverlapCircle(feetPos.position, checkRadius, whatIsGround);

        if(isGrounded == true && Input.GetKeyDown(KeyCode.Space))

        {

            rb.velocity = Vector2.up * jumpForce;

            anim.SetTrigger("takeOf");

        }

        if(isGrounded == true)

        {

            anim.SetBool("isJumping", false);

        }

        else

        {

            anim.SetBool("isJumping", true);

        }

    }

    void Flip()

    {

        facingRight = !facingRight;

        Vector3 Scaler = transform.localScale;

        Scaler.x *= -1;

        transform.localScale = Scaler;

    }

}

Inventory

Inventory

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class Inventory : MonoBehaviour

{

    public bool[] isFull;

    public GameObject[] slots;

    public GameObject inventory;

    private bool inventoryOn;

    private void Start()

    {

        inventoryOn = false;

    }

    public void Chest()

    {

        if(inventoryOn == false)

        {

            inventoryOn = true;

            inventory.SetActive(true);

        }

        else if (inventoryOn == true)

        {

            inventoryOn = false;

            inventory.SetActive(false);

        }

    }

}

Pickup

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class Pickup : MonoBehaviour

{

    private Inventory inventory;

    public GameObject slotButton;

    public int id;

    private void Start()

    {

        inventory = GameObject.FindGameObjectWithTag("Player").GetComponent<Inventory>();

    }

    private void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            for (int i = 0; i < inventory.slots.Length; i++)

            {

                if(inventory.isFull[i] == false)

                {

                    inventory.isFull[i] = true;

                    Instantiate(slotButton, inventory.slots[i].transform);

                    Destroy(gameObject);

                    break;

                }

            }

        }

    }

}

Slot

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class Slot : MonoBehaviour

{

    private Inventory inventory;

    public int i;

    private void Start()

    {

        inventory = GameObject.FindGameObjectWithTag("Player").GetComponent<Inventory>();

    }

    private void Update()

    {

        if(transform.childCount <= 0)

        {

            inventory.isFull[i] = false;

        }

    }

    public void DropItem()

    {

        foreach(Transform child in transform)

        {

            child.GetComponent<Spawn>().SpawnDroppedItem();

            GameObject.Destroy(child.gameObject);

        }

    }

}

Spawn

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class Spawn : MonoBehaviour

{

    public GameObject item;

    private Transform player;

    private void Start()

    {

        player = GameObject.FindGameObjectWithTag("Player").transform;

    }

    public void SpawnDroppedItem()

    {

        Vector2 playerPos = new Vector2(player.position.x + 2, player.position.y - 1);

        Instantiate(item, playerPos, Quaternion.identity);

    }

}

FrameSwitch

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class FrameSwitch : MonoBehaviour

{

    public GameObject activeFrame;

    private void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            activeFrame.SetActive(true);

        }

    }

    private void OnTriggerExit2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            activeFrame.SetActive(false);

        }

    }

}

MainMenu

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.SceneManagement;

public class MainMenu : MonoBehaviour

{

    public void PlayGame()

    {

        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);

    }

    public void ExitGame()

    {

        Debug.Log("End");

        Application.Quit();

    }

}

EndScreen

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

using TMPro;

public class EndScreen : MonoBehaviour

{

    public TextMeshProUGUI TextProcentage;

    public void Update()

    {

        TextProcentage.text = Quests.GameProcentage.ToString();

    }

}

BackButton

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.SceneManagement;

public class BackButton : MonoBehaviour

{

    public void MainMenu()

    {

        Time.timeScale = 1f;

        SceneManager.LoadScene("MainMenu");

    }

}

Conservations

Dialogue3

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Dialogue3 : MonoBehaviour

{

    public GameObject Barrier;

    public GameObject NPC;

    public GameObject Trigger;

    public void EndDialogue()

    {

        Barrier.SetActive(false);

        Trigger.SetActive(true);

        Destroy(NPC,5f);

    }

}

DropQuest

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class DropQuest : MonoBehaviour

{

    public int questNumber;

    public int[] items;

    public GameObject[] clouds;

    public GameObject NPCDrop;

    public GameObject NPCFinal;

    int i = 0;

    public void OnTriggerEnter2D(Collider2D other)

    {

        if(other.tag != "Playyer" && other.gameObject.GetComponent<Pickup>().id == items[questNumber])

        {

            if (i == questNumber)

            {

                questNumber++;

                if (questNumber == 4)

                {

                    Debug.Log("dfdsfdsfsd");

                    NPCDrop.SetActive(false);

                    NPCFinal.SetActive(true);

                }

                Destroy(other.gameObject);

                clouds[i].SetActive(false);

                i++;

                clouds[i].SetActive(true);

               

            }

        }

    }

}

Quest1

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Quest1 : MonoBehaviour

{

    public NPCConversation myConversation;

    public Animator startAnim;

    public void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            ConversationManager.Instance.StartConversation(myConversation);

        }

    }

   

    public void OnTriggerExit2D(Collider2D other)

    {

        ConversationManager.Instance.EndConversation();

    }

}

Quest2

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Quest2 : MonoBehaviour

{

    public NPCConversation myConversation;

    public GameObject NPC;

    public void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            ConversationManager.Instance.StartConversation(myConversation);

        }

    }

    public void OnTriggerExit2D(Collider2D other)

    {

        ConversationManager.Instance.EndConversation();

        Destroy(NPC,10f);

    }

}

Quest3_1

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Quest3_1 : MonoBehaviour

{

    public GameObject NPC;

    public GameObject NPCDel;

    public GameObject Barrier;

    public GameObject Trigger;

    public void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            NPC.SetActive(true);

            NPCDel.SetActive(false);

            Barrier.SetActive(true);

            Trigger.SetActive(false);

        }

    }

}

Quest3_2

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class Quest3_2 : MonoBehaviour

{

    public GameObject NPC;

    public GameObject Trigger;

    public GameObject Barrier;

    public void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            NPC.SetActive(true);

            Trigger.SetActive(false);

            Barrier.SetActive(true);

        }

    }

}

Quest3

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Quest3 : MonoBehaviour

{

    public GameObject NPC;

    public GameObject Cloud;

    public GameObject Trigger;

    public GameObject Barrier;

    public void OnTriggerEnter2D(Collider2D other)

    {

        if (other.CompareTag("Player"))

        {

            NPC.SetActive(true);

            Cloud.SetActive(true);

            Trigger.SetActive(false);

            Barrier.SetActive(true);

        }

    }

}

Quests

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Quests : MonoBehaviour

{

    private Transform player;

    public GameObject plateDel;

    public GameObject plateIns;

    public GameObject Barrier1;

    public GameObject Barrier2;

    public GameObject NPCDel2;

    public GameObject NPCDel1;

    public GameObject NPCIns1;

    public static int LevelPass = 0;

    public static float GameProcentage = 0;

    private void Start()

    {

        player = GameObject.FindGameObjectWithTag("Player").transform;

    }

    public void StartQuest1()

    {

        Barrier2.SetActive(true);

        NPCDel1.SetActive(false);

        NPCIns1.SetActive(true);

    }

    public void EndQuest1()

    {

            Destroy(plateDel);

            Vector2 playerPos = new Vector2(player.position.x + 8, player.position.y - 1);

            Instantiate(plateIns, playerPos, Quaternion.identity);

            Destroy(Barrier1);

            Barrier2.SetActive(false);

            LevelPass = 1;

            Destroy(NPCDel2, 10f);

    }

    public void EndQuest1Fail()

    {

        Destroy(Barrier1);

        Barrier2.SetActive(false);

        Destroy(NPCDel2, 10f);

    }

    public void Answer1()

    {

        GameProcentage += 12.5f;

    }

    public void Answer2()

    {

        GameProcentage += 6.25f;

    }

    public void Answer3()

    {

        GameProcentage += 3.125f;

    }

}

Quests2

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Quests2 : MonoBehaviour

{

    public GameObject NPC1;

    public GameObject NPC2;

    public GameObject NPCAFK;

    public GameObject Stones;

    public GameObject BarrierStones;

    public GameObject BarrierEnd;

    public void HideNPC()

    {

        Destroy(NPC1);

        NPC2.SetActive(true);

        BarrierStones.SetActive(false);

    }

    public void HideNPCLast()

    {

        Destroy(NPC1);

        NPC2.SetActive(true);

        NPCAFK.SetActive(true);

        BarrierStones.SetActive(false);

    }

    public void DelNPC()

    {

        BarrierEnd.SetActive(false);

        Destroy(NPC2,10f);

    }

    public void Barrier()

    {

        Stones.SetActive(false);

    }

        public void Answer1()

    {

        Quests.GameProcentage += 12.5f;

    }

    public void Answer2()

    {

        Quests.GameProcentage += 6.25f;

    }

    public void Answer3()

    {

        Quests.GameProcentage += 3.125f;

    }

}

Quests3

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Quests3 : MonoBehaviour

{

    public Vector2 V2;

    public GameObject NPC1;

    public GameObject NPC2;

    public GameObject Barrier1;

    public GameObject Barrier2;

    public GameObject Handle;

    public GameObject Antenna;

    public GameObject Plate;

    public GameObject Trigger1;

    public GameObject Trigger2;

    public GameObject NPCIns;

    public GameObject NPCInsFail;

    public GameObject BarrierFail;

    public void Start()

    {

        V2 = Plate.transform.position;

    }

    public void QuestStart()

    {

        Barrier1.SetActive(true);

    }

    public void QuestStart1()

    {

        Barrier2.SetActive(true);

    }

    public void Quest1End()

    {

        Destroy(NPC1,3f);

        Trigger1.SetActive(true);

    }

    public void Quest2End()

    {

        Handle.SetActive(true);

        Trigger2.SetActive(true);

    }

    public void DelBarrier()

    {

        Barrier2.SetActive(false);

        Barrier1.SetActive(false);

    }

   

    public void EndQuest()

    {

        if (Quests.LevelPass == 1)

        {

            Destroy(NPC2,10f);

            Barrier2.SetActive(false);

            Antenna.SetActive(true);

            BarrierFail.SetActive(true);

            NPCIns.SetActive(true);

        }

        else

        {

            Destroy(NPC2,10f);

            Barrier2.SetActive(false);

            Antenna.SetActive(true);

            Plate.transform.position = new Vector2(61, -3);

            BarrierFail.SetActive(true);

            NPCInsFail.SetActive(true);

        }

    }

    public void test1()

    {

        NPCIns.SetActive(true);

    }

    public void Answer1()

    {

        Quests.GameProcentage += 12.5f;

    }

    public void Answer2()

    {

        Quests.GameProcentage += 6.25f;

    }

    public void Answer3()

    {

        Quests.GameProcentage += 3.125f;

    }

}

Quests4

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using DialogueEditor;

public class Quests4 : MonoBehaviour

{

    public GameObject Barrier1;

    public GameObject SpaceShip;

    public GameObject EndScreen1;

    public GameObject AntennaAFK1;

    public GameObject AntennaShoot;

   

    public void StartFinalQuest()

    {

        Barrier1.SetActive(true);

    }

    public void Final()

    {

        SpaceShip.SetActive(true);

    }

    public void AntennaAFK()

    {

        AntennaAFK1.SetActive(true);

        AntennaShoot.SetActive(false);

    }

    public void AntennaActive()

    {

        AntennaShoot.SetActive(true);

        AntennaAFK1.SetActive(false);

    }

    public void Answer1()

    {

        Quests.GameProcentage += 12.5f;

    }

    public void Answer2()

    {

        Quests.GameProcentage += 6.25f;

    }

    public void Answer3()

    {

        Quests.GameProcentage += 3.125f;

    }

    public void EndScreen()

    {

        EndScreen1.SetActive(true);

        Time.timeScale = 0f;

    }

}

Просмотров работы: 5