Привет и добро пожаловать в этот урок. Этот туториал поможет вам создать сетку инвентаризации с помощью Godot.

Предпосылка

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

Начиная

Убедитесь, что на вашем компьютере установлен Godot. Затем загрузите стартовый проект здесь. Он содержит шрифт, изображения и файл JSON, которые я включил в папку assets. Разархивируйте эти файлы. Запустите Godot и импортируйте проект.

Импорт данных об элементе из файла JSON

Существуют различные способы хранения данных в Godot. Самый распространенный способ — хранить информацию в формате JSON, который мы будем использовать в этом руководстве. Обратите внимание, что файл JSON не является встроенным ресурсом в Godot, поэтому нам придется открыть его из редактора кода. Если вы откроете файл res://assets/json/items.json, я приготовил для начала несколько фиктивных элементов: «меч», «броня», «яблоко» и «зелье». .

Посмотрите внимательно на структуру данных. Элемент представлен в виде словаря с парой ключ-значение. Ключ представляет собой строку (уникальный ключ элемента), а значение — словарь, в котором хранятся любые ключи и связанные значения для свойств элемента.

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

Мы определяем функцию полезности с именем read_from_JSON. Он принимает путь к файлу в качестве своего параметра, а затем использует встроенный класс Godot File, чтобы открыть и прочитать файл, если указанный путь к файлу существует, иначе он выведет ошибку на консоль. Если ошибки нет, содержимое файла извлекается, анализируется и возвращается в виде значения, чтобы мы могли сохранить его в переменной.

В функции _ready мы вызываем функцию read_from_JSON для загрузки файла res://assets/json.items.json и сохранения возвращаемого значения в переменной с именем items. Внутри цикла мы также сохраняем ключ как свойство элемента. Поскольку каждый элемент идентифицируется уникальным ключом, мы можем обратиться к этому свойству, чтобы позже определить, относятся ли два элемента к одному и тому же типу.

Чтобы получить глобальный доступ к данным, давайте превратим их в синглтон, автоматически загрузив скрипт. Перейдите в раздел Проект > Настройки проекта, затем на вкладке Автозагрузка выберите и добавьте в список скрипт global.gd. Установите для параметра Имя узла значение Глобальное. Теперь к синглтону можно получить доступ напрямую с именем Global.

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

Эта функция принимает ключ элемента в качестве параметра, а затем возвращает уникальную копию элемента, если он существует.

Чтобы проверить, все ли работает правильно, сначала нам нужно добавить в проект основную сцену. Создайте новую сцену, добавьте узел Node2D и переименуйте его в Main. Сохраните сцену как main.tscn в корневом каталоге. Запустите проект (F5) и выберите main.tscn в качестве основной сцены.

Теперь воспользуемся функцией get_item_by_key, чтобы получить элемент «меч» и вывести его в консоль. Добавьте строку в конце вызова функции _ready в сценарии global.gd:

print(get_item_by_key("sword"))

Запустите проект еще раз (F5), чтобы подтвердить следующий вывод в консоли:

Ссылка на файлы проекта

Инвентарь для хранения предмета игрока

В этом разделе мы создадим инвентарь для хранения предмета игрока. Создайте новый скрипт inventory.gd в корневом каталоге и добавьте следующее:

Во-первых, мы объявляем четыре переменные:

  • cols - это количество колонок, которые у нас есть в инвентаре, т.е. 9 колонок.
  • rows - это количество строк, которые у нас есть в инвентаре, т.е. 3 строки.
  • slots — это общее количество слотов в нашем инвентаре, т. е. 9 x 3 = 27.
  • items — это массив для хранения предмета игрока.

Изначально инвентарь будет пуст. В функции _ready мы заполняем массив items пустым словарем, перебирая количество слотов, которые у нас есть.

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

items[0]

В верхней части скрипта мы объявляем пользовательский сигнал под названием items_changed с ключевым словом signal. Сигнал будет испускаться каждый раз, когда мы работаем с массивом items array. Мы отправляем массив порядковых номеров в переменной indexes вместе с сигналом, чтобы сообщить, какой слот элемента изменился. Позже мы подключим этот сигнал для уведомления пользовательского интерфейса инвентаря.

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

  • Функция set_item позволяет нам добавить предмет в слот предмета. Он принимает два параметра: index (порядковый номер слота предмета) и item (словарь предмета для добавления в слот предмета). Функция возвращает обратно любой предмет, ранее сохраненный в слоте предмета.
  • remove_item, которую мы можем вызвать, чтобы удалить предмет из слота предмета. В качестве параметра он принимает порядковый номер слота предмета. Функция возвращает обратно любой предмет, ранее сохраненный в слоте предмета.
  • Функция set_item_quantity позволяет нам прибавить или вычесть определенную сумму к количеству товара. Он принимает два параметра: index (порядковый номер слота предмета) и amount (сумма, которую нужно добавить или вычесть). Мы удалим предмет, если его количество упадет ниже нуля.

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

Наконец, давайте также автоматически загрузим скрипт inventory.gd и установим для Имя узла значение Inventory. На данный момент у нас есть два одноэлементных объекта: Global и Inventory.

Ссылка на файлы проекта

Создание пользовательского интерфейса инвентаря

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

Начнем со слота для предметов. Слот предмета имеет единственную функцию, которая заключается в отображении значка и количества предмета.

Создайте новую сцену, добавьте узел ColorRect и переименуйте его в ItemSlot. В Инспекторе установите для параметра Цвет значение #333333, затем в разделе Прямая -› Минимальный размер установите x и y до 18 и 18 соответственно. Кроме того, в разделе Прямоугольник-› Размер установите значения x и y на 18. и 18 соответственно. Сохраните сцену как item_slot.tscn в папке ui/item_slot.

Добавьте дочерний узел в сцену ItemSlot: узел TextureRect и переименуйте его в ItemIcon. В Инспекторе перетащите файл apple.png из папки assets/images в его свойство Texture. В разделе Rect -› Size установите значения x и y на 16 и 16. соответственно и установите для параметра Макет значение По центру.

Добавьте еще один дочерний узел в сцену ItemSlot: узел Label и переименуйте его в ItemQuantity. В Инспекторе установите для параметра Текст значение 1, а для параметра Выровнять — значение По правому краю.

Чтобы использовать наш собственный шрифт, нам нужно создать новый ресурс типа DynamicFont. На панели Файловая система щелкните правой кнопкой мыши папку assets/font, выберите Новый ресурс, выберите DynamicFont в списке list и сохраните его как файл custom_font.tres. В Инспекторе установите следующее:

  • В разделе Настройки установите для параметра Размер значение 4.
  • В разделе Font/FontData выберите New DynamicFontData, затем нажмите DynamicFontData, чтобы развернуть его. Теперь в разделе Путь к шрифту щелкните значок папки, чтобы загрузить шрифт PressStart2P.ttf в папку assets/font.

Вернитесь к узлу ItemQuantity, установите Custom Font для загрузки custom_font.ttf в папку assets/font и установите Макет на По ширине снизу. Снова сохраните сцену, и ваша сцена ItemSlot должна выглядеть так:

Давайте также добавим в группу сцену ItemSlot. В редакторе щелкните, чтобы переключиться на вкладку Узел рядом с вкладкой Инспектор. Затем рядом с Сигналы нажмите Группы, введите текстовое поле с названием новой группы «item_slot» и нажмите кнопку Добавить. Это будет удобно, когда мы позже захотим получить все слоты предметов в массиве.

Теперь добавим в сцену скрипт:

Мы получаем ссылку на дочерние узлы с ключевым словом onready. Функцию display_item можно вызвать с элементом, тогда она обновит значок и количество предмета в элементах пользовательского интерфейса.

Теперь давайте создадим скрипт, расширяющий базовый класс GridContainer, который мы будем использовать на панели быстрого доступа и в меню инвентаря. Создайте новый скрипт slot_container.gd и сохраните его в папке ui/slot_container. Добавьте в скрипт следующее:

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

В верхней части скрипта мы объявляем переменную ItemSlot и используем ключевое слово export (PackedScene), чтобы позволить нам установить сцену ItemSlot, в которой мы хотим создать экземпляр. Инспектор.

В функцию display_item_slots мы можем передать, сколько столбцов и строк слотов предметов мы хотим отобразить в пользовательском интерфейсе инвентаря. Функция обновит свойство «columns» в GridContainer, чтобы соответствующим образом упорядочить дочерние узлы. Затем мы перебираем количество слотов, которые у нас есть, создаем новую сцену ItemSlot и добавляем ее в качестве дочернего узла в сцену.

Наконец, мы подключаем сигнал items_changed, объявленный ранее в синглтоне Inventory, для обновления слота предмета.

Теперь давайте создадим панель быстрого доступа. Создайте новую сцену, добавьте узел GridContainer и переименуйте его в Hotbar. Затем в разделе Rect -› Size установите значения x и y на 0 и 0 соответственно. Сохраните сцену как hotbar.tscn в папке ui/hotbar. Затем добавьте скрипт в сцену:

Сценарий расширяет базовый класс SlotContainer выше, так что мы можем получить доступ и вызвать функцию display_item_slots. Для панели быстрого доступа мы хотим отображать только одну строку слотов элементов, поэтому мы передаем параметры Inventory.cols для количества столбцов и 1 для количества строк, которые мы хотим отобразить. Наконец, мы обновляем свойство «rect_position» узла, чтобы выровнять его по центру нижней части области просмотра после прохождения игрового кадра. Вернитесь в редактор, перетащите сцену res://ui/item_slot/item_slot.tscn в свойство ItemSlot в Инспекторе.

Последняя часть пользовательского интерфейса, которая нам нужна, — это меню инвентаря. Настройка очень похожа на панель быстрого доступа. Создайте новую сцену, добавьте узел GridContainer и переименуйте его в InventoryMenu. Сохраните сцену как inventory_menu.tscn в папке ui/inventory_menu. Затем добавьте скрипт в сцену:

Меню инвентаря отобразит все слоты предметов в инвентаре. Он расположен в центре окна просмотра. По умолчанию меню инвентаря не отображается, поэтому нам также нужно скрыть его в функции _ready. Также в редакторе перетащите сцену res://ui/item_slot/item_slot.tscn в свойство ItemSlot в Инспекторе.

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

Ссылка на файл проекта

Захват ввода игрока с действием

Теперь, когда мы создали панель быстрого доступа и меню инвентаря. Давайте добавим их в сцену Main. Откройте res://Main.tscn и добавьте узел CanvasLayer в качестве дочернего. Переименуйте узел в UI. Узел CanvasLayer позволяет отображать все элементы пользовательского интерфейса поверх остальной части игры.

Нажмите, чтобы снова выбрать узел UI, затем нажмите кнопку Экземпляр, давайте добавим сцену Hotbar и InventoryMenu в качестве дочерних элементов. узла UI. Когда вы закончите, ваша основная сцена должна выглядеть так:

Игрок может открыть меню инвентаря, нажав клавишу «Escape» или «E». Для этого воспользуемся входной картой Godot. Перейдите в раздел Проект > Настройки проекта и выберите вкладку Входная карта. Создайте новое действие «ui_menu» и нажмите кнопку Добавить. Прокрутите вниз, и вы увидите новое действие, добавленное в список. Чтобы назначить ввод, щелкните значок плюса справа, выберите Клавиша, нажмите клавишу «Escape» на клавиатуре и нажмите кнопку ОК. Повторите этот шаг, чтобы добавить клавишу «E».

Теперь добавим скрипт в Mainсцену:

Мы можем использовать функцию _unhandled_input, которая будет вызываться при возникновении входного события. Затем мы будем искать действие «ui_menu», вызвав event.is_action_pressed. Мы переключаем свойство visible панели быстрого доступа и inventory_menu, если это происходит.

Запустите проект (F5) и подтвердите, что теперь вы можете открывать и закрывать меню инвентаря.

Ссылка на файл проекта

Подбери и брось предмет с помощью мыши

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

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

Создайте новую сцену, добавьте узел Control и переименуйте его в DragPreview. В Инспекторе в разделе Rect -› Size установите значения x и y на 18 и 18. соответственно. Установите для параметра Мышь > Фильтр значение Игнорировать, чтобы игнорировать события мыши. Сохраните сцену как drag_preview.tscn в папке ui/drag_preview.

Предварительный просмотр перетаскивания должен отображать значок и количество предмета. Давайте добавим дочерний узел TextureRect в сцену DragPreview и переименуем его в ItemIcon. В Инспекторе в разделе Прямоугольник -› Размер установите значения x и y на 16 и 16соответственно. Также установите для параметра Мышь > Фильтр значение Игнорировать. Затем установите для параметра Макет значение По центру.

Наконец, добавьте дочерний узел Label в сцену DragPreview и переименуйте его в ItemQuantity. Установите Пользовательские шрифты для загрузки файла custom_font.tres в папку assets/font. Установите для параметра Выровнять значение Вправо, а для параметра Макет установите значение Внизу по ширине. Когда вы закончите, сохраните сцену, и ваша DragPreview сцена должна выглядеть так:

Теперь добавим в сцену скрипт:

Здесь мы используем переменную dragged_item для хранения перетаскиваемого элемента. Изначально он пустой. Мы также определяем функцию установки set_dragged_item с ключевым словом setget. Когда значение перетаскиваемого элемента изменяется, он автоматически вызывает эту функцию установки для обновления значка и количества элементов пользовательского интерфейса.

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

Теперь мы завершили настройку узла для предварительного просмотра перетаскивания. Давайте добавим сцену DragPreview в Main сцену. Откройте res://Main.tscn, щелкните, чтобы выбратьUIузел, добавьте DragPreview scene в качестве дочернего узла. До этого момента ваша основная сцена должна выглядеть так:

В сценарии res://main.gd давайте получим ссылку на сцену DragPreview:

Далее подключаем сигнал gui_input из слота предмета. В этот же скрипт добавьте следующее:

Чтобы получить все слоты предметов в массиве, мы можем вызвать SceneTree.get_nodes_in_group, передав имя группы, которую мы ищем (помните, что ранее мы добавляли слоты предметов в группу с именем «item_slot»). Внутри петли подключаем сигнал gui_input из слота предмета. Этот сигнал будет испущен, когда узел получит входное событие. Мы также отправляем переменную index (индекс слота предмета) вместе с сигналом.

Теперь мы можем определить функцию _on_ItemSlot_gui_input:

Если игрок щелкнет левой кнопкой мыши по слоту предмета при открытом меню инвентаря, мы вызовем функцию drag_item:

Наконец, давайте изменим функцию _unhandled_input в скрипте res://main.gd:

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

Чтобы проверить, все ли работает правильно, добавим несколько предметов в инвентарь. Откройте скрипт res://inventory.gd и добавьте следующее в конце вызова функции _ready:

Запустите проект (F5), откройте меню инвентаря (Escape или E) и подтвердите, что вы можете перемещать предметы.

Ссылка на файл проекта

Укладка и разделение предметов

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

Откройте скрипт res://main.gd и давайте изменим функцию drag_item:

Что здесь нового, так это то, что в строке 12 мы проверяем, относятся ли как предмет инвентаря, так и перетаскиваемый предмет к одному и тому же типу (путем сравнения уникального «ключевого» свойства предмета), и что предмет может складываться. Если оба условия истинны, мы можем сложить их вместе, иначе мы поменяем два элемента местами.

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

Здесь у нас новые строки 6–8. Если игрок щелкнет правой кнопкой мыши по слоту предмета при открытом меню инвентаря, мы вызовем функцию split_item:

Давайте рассмотрим, как это работает. Во-первых, мы получаем ссылку на предмет инвентаря и перетаскиваемый предмет. Мы возвращаемся из функции раньше, если в слоте нет предмета или предмет не стекируемый.

Затем мы получаем split_amount вдвое меньше количества предмета в инвентаре.

В строках 6–8, если у нас есть перетаскиваемый предмет и он того же типа, что и предмет в инвентаре (у них одинаковое свойство «ключ»), мы прибавляем к перетаскиваемому предмету, а вычитаем из предмета в инвентаре. , split_amount от количества товара.

В строках 9–13, если еще нет перетаскиваемого предмета, мы добавим предмет того же типа, что и предмет инвентаря, и изменим его количество, чтобы оно было таким же, как значение split_amount. Наконец, мы вычитаем ту же сумму из предмета инвентаря.

Запустите проект (F5) и убедитесь, что укладка и разделение элементов теперь работает правильно.

Ссылка на файлы проекта

Следить за выбранным элементом

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

Во-первых, мы объявляем переменную selected для хранения порядкового номера выбранного предмета в инвентаре. Откройте скрипт res://inventory.gd и добавьте следующую строку:

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

Теперь всякий раз, когда выбранный предмет изменился, мы можем захотеть уведомить, например, игрока об обновлении предмета, который он держит в руке. Для этого мы будем использовать пользовательский сигнал. Объявим сигнал selected_changed вверху скрипта после объявления сигнала item_changed:

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

Эта функция принимает в качестве параметра массив порядковых номеров. Сначала он выдает сигнал items_changed, затем просматривает весь массив, чтобы увидеть, изменился ли выбранный элемент. Если верно последнее, он также выдаст сигнал selected_changed.

Перед этим мы используем функцию emit_signal для генерации сигнала в коде. Теперь нам нужно изменить их, чтобы использовать вместо них функцию broadcast_signal. Собрав их все вместе, вот полный листинг кода для скрипта res://inventory.gd:

Чтобы визуально увидеть, какой слот предмета выбран в данный момент, давайте сделаем так, чтобы слот предмета отображал светлый цвет фона (#7b7b7b) для выбранного. Откройте res://ui/item_slot/item_slot.tscn и измените функцию display_item:

Обратите внимание на новые строки 8–12 здесь. Мы используем функцию get_parent, чтобы получить родительский узел слота предмета. Если это панель быстрого доступа, мы покажем светлый цвет фона для выбранного слота.

Последняя часть позволяет игроку изменить выбранный элемент. Откройте скрипт res://main.gd и измените функцию _on_ItemSlot_gui_input:

В новых строках 6–7, когда игрок щелкает левой кнопкой мыши по слоту предмета на панели быстрого доступа, мы будем вызывать функцию select_item:

Здесь мы вызываем функцию set_selected в синглтоне Inventory для обновления выбранного индекса.

Предоставим еще два способа, которыми игрок может изменить выбранный элемент: колесиком мыши или нажатием цифр от «1» до «9» на клавиатуре. Для этого добавим функцию _input в скрипт res://ui/hotbar/hotbar.gd:

Если игрок прокручивает колесо мыши вверх, мы поднимаем выбранный индекс на единицу. Это будет работать до тех пор, пока выбранный индекс не станет последним: в этом случае мы хотим вернуть его к первому. Для этого мы можем использовать операцию по модулю (%). Мы делаем то же самое, когда игрок прокручивает колесо мыши вниз с оператором if-else, чтобы проверить, какой текущий выбранный индекс установлен в инвентаре.

Обратите внимание, что мы также получаем метку времени в миллисекундах, вызывая OS.get_ticks_msec и сохраняя ее в переменной tick. Это связано с тем, что мы не хотим, чтобы событие ввода срабатывало слишком часто, а обрабатываем его, только если прошло 8 мс.

Наконец, игрок может нажимать на клавиатуре цифры от «1» до «9», чтобы изменить выбранный элемент, как в строках 13–15. Значение ключей от 1 до 9 находится в диапазоне от 49 до 57, поэтому мы вычтем из него 49, чтобы получить правильный выбранный индекс.

Запустите проект (F5), и теперь мы можем взаимодействовать с выбранным элементом.

Ссылка на файлы проекта

Подсказка к отображаемому элементу

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

Во-первых, давайте создадим новую сцену Tooltip. Создайте новую сцену, добавьте узел ColorRect и переименуйте его в Tooltip. В Инспекторе установите для параметра Цвет значение #000000, затем установите для параметра Мышь > Фильтр значение Игнорировать<. /strong> игнорировать событие мыши. Сохраните сцену как tooltip.tscn в папке ui/tooltip.

Добавьте узел MarginContainer в качестве дочернего элемента в сцену Tooltip. В Инспекторе в разделе Пользовательские константы установите для параметра Поля справа, сверху, слева и снизу значения 4, 2, 4 и 2. соответственно и установите для параметра Мышь > Фильтр значение Игнорировать.

Наконец, добавьте дочерний узел к узлу MarginContainer: узел Label и переименуйте его в ItemName. В Инспекторе установите Пользовательские шрифты для загрузки файла custom_font.tres в папку assets/font. Сохраните сцену, и ваша Подсказка должна выглядеть следующим образом:

Теперь добавим в сцену скрипт:

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

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

В скрипте res://main.gd давайте получим ссылку на всплывающую подсказку:

Чтобы показать или скрыть всплывающую подсказку, подключим сигнал mouse_entered и mouse_exited из слота предмета. В скрипте Main измените функцию _ready:

Теперь мы можем определить функции show_tooltip и hide_tooltip:

В функции show_tooltip мы хотим показывать всплывающую подсказку только в том случае, если в слоте есть предмет инвентаря, а перетаскиваемого предмета нет.

Наконец, мы также хотим скрыть всплывающую подсказку, когда игрок переключает меню инвентаря или перетаскивает предмет. В скрипте res://main.gd измените следующую функцию:

Что здесь нового, так это строки для вызова функции hide_tooltip, чтобы скрыть всплывающую подсказку.

Запустите проект (F5), чтобы увидеть работающую подсказку при наведении курсора мыши на слот предмета.

Ссылка на файлы проекта

Заканчивать

Спасибо за чтение этого! Я надеюсь, что вам понравится этот урок и вы узнаете здесь пару вещей. Если у вас есть какие-либо вопросы, не стесняйтесь оставлять комментарии. До скорого!