Статьи

Corona SDK: создайте бесконечную игру для бегунов с нуля!

В этой серии уроков вы узнаете, как создать бесконечный стиль игры с использованием Corona SDK. Игра в бесконечный стиль — это игра типа Monster Dash, Canabalt или NinJump. Постоянная прокрутка спрайтов по сцене создает видимость очень большого мира, с которым пользователь может взаимодействовать. У этого стиля игры есть много преимуществ: уровни могут создаваться динамически и случайным образом, предоставляя пользователям больше возможностей для повторного воспроизведения, игры могут сохранять небольшой объем памяти, поскольку ресурсы можно использовать повторно, а игровой процесс прост и быстр, предоставляя геймерам быстрый и приятный доступ к играм. геймплей. К концу этой серии вы создадите следующую игру (будьте осторожны с графикой — найдите дизайнера и сделайте лучше!):

Corona SDK Final Preview

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

  • Часть 1. Начало работы : введение, основы Corona SDK и языка Lua, настройка проекта и базовая отладка проекта
  • Часть 2: базовая прокрутка : добавление фонов к игре, добавление земли, перемещение земли, добавление параллакс-прокрутки
  • Часть 3. Работа с листами спрайтов : создание листов спрайтов, анимация объектов с помощью листов спрайтов
  • Часть 4: Создание героя : добавление нашего героя, взаимодействие нашего героя с миром
  • Часть 5. Создание системы событий : Основы событий, Изменение события возвышения, Добавление события «Яма»
  • Часть 6: Внедрение системы подсчета очков : отображение дистанции, добавление множителей очков
  • Часть 7: Добавление препятствий : Добавление разрушаемых препятствий (монстров или разрушаемых стен)
  • Часть 8. Добавление события босса . Добавление события босса в игру.
  • Часть 9. Добавление системы меню . Использование класса Director для добавления системы меню в игру.
  • Часть 10. Отправка игры : Отправка нашей игры в App Store

Это похоже на большую работу? Ну, это так, так что давайте начнем! Вот список того, что вам нужно для этого проекта:

  1. Corona SDK — отличный кроссплатформенный SDK, помогающий быстро выкачивать мобильные приложения! Полная лицензия стоит 200 долларов США и позволяет компилировать устройства как на iOS, так и на Android. Профессиональная лицензия стоит 350 долларов США и позволяет создавать приложения для обоих типов устройств. Если вы учитесь в каком-либо аккредитованном учебном заведении, вы можете получить их студенческие цены, которые снизят цену на 100 долларов США за штуку. Если вы еще не приобрели подписку, вы все равно можете загрузить программу и дополнить учебное руководство пробной версией. Если вы не программист, это отличное место для изучения некоторых основ и быстрого создания отличных приложений. Если вы программист, вы будете удивлены, насколько быстро вы сможете создавать качественный контент с помощью Corona SDK!
  2. Photoshop (коммерческий) или Gimp (бесплатный). Фактически, любая программа для редактирования графики будет работать для создания изображений, которые вам понадобятся для создания собственной игры. Лично мне больше всего нравятся Photoshop и Gimp, но я использую все, что вам нравится больше всего. Другие программы, которые вы, возможно, захотите использовать, — это программы векторной графики, такие как Adobe Illustrator (коммерческий) или Inkscape (бесплатный). Графика для этой серии была сделана с использованием комбинации Inkscape и Photoshop.

Для тех из вас, у кого нет большого опыта в программировании, мы начнем с того, что рассмотрим некоторые из базовых принципов, которые вы увидите на этом пути. Таким образом, вы не теряете разум каждый раз, когда мы добавляем код в проект. Первое, что вы захотите сделать, это открыть симулятор Corona SDK и консоль Corona SDK (командный терминал). В Windows, если вы откроете симулятор, он автоматически откроет и консоль. В Mac OS вам нужно будет вручную открыть программу под названием Corona Terminal. Теперь, когда они запущены, откройте ваш любимый текстовый редактор. В Windows я использую Notepad ++, а в Mac OS лично я предпочитаю Unipad. Вы можете использовать любой редактор, который вам нравится. Как только у вас откроется ваш любимый редактор, создайте файл с именем main.lua . Каждая программа, которую мы создаем с использованием Corona SDK, будет иметь это в качестве отправной точки. Когда у вас откроются симулятор, консоль и текстовый редактор, мы готовы приступить к программированию! Вот краткий обзор некоторых основных вещей, которые вы увидите на протяжении всей серии.

Это может показаться довольно скучным способом начать, но это важно. Все, что идет после символов на одной строке, НЕ будет запускаться симулятором. Это важно, потому что это позволяет вам, разработчику, добавлять «комментарии» в код, которые помогут вам вспомнить, почему вы сделали что-то определенным образом позже. Поверь мне, когда я говорю, что это важно. Придет время, когда вам придется сделать перерыв (может быть, вы устали работать над этим проектом и хотите работать над чем-то еще пару дней, недель или месяцев!) И когда вы вернетесь к нему, вы поймете, что ты не представляешь, что ты пытался сделать, когда уходил. Или, может быть, вы заканчиваете проект и через год решаете, что хотите обновить его. Это также помогает другим людям, которые смотрят на ваш код, точно знать, что вы пытаетесь сделать. Кроме того, хорошо прокомментированный код позволяет легко передавать код между разработчиками. Мораль этой истории в том, что комментировать код так же просто, как добавить к вашему проекту, так что сделайте это!

Первое, что мы собираемся сделать в нашем коде, это научиться выводить данные на консоль (или терминал). Зачем использовать консоль, если пользователь приложения никогда ее не увидит? Вот где мы делаем нашу отладку. Отладка — это процесс, который разрабатывают разработчики, когда в их коде есть «ошибка» (т.е. что-то не так), и они должны выяснить, где она находится. Откройте файл main.lua и скопируйте следующее:

1
2
—This is a comment, it will not do anything when run!
print(«Hello, World!»)

Вот что вы должны увидеть в своей консоли:

Corona SDK Hello World

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

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

1
2
3
4
5
6
7
8
—variables
name = «Bob»
age = 25
male = true
—print the variables
print(name)
print(age)
print(male)

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

Corona SDK Переменная

Есть 3 основных типа переменных, которые мы только что использовали: строки (любой текст в кавычках), целые числа (целые числа) и логические значения (истина или ложь). Есть определенно больше типов переменных, но этого достаточно, чтобы мы начали. Одна из приятных особенностей Lua заключается в том, что переменные слабо типизированы. Это означает, что вам не нужно явно объявлять, какой это тип переменной, компилятор просто знает. Переменные будут использоваться очень часто, поэтому привыкайте к ним. Еще пара вещей, которые вы увидите и должны знать о переменных, являются ли они локальными или глобальными, понятие, называемое «область действия». Мы коснемся этого чуть позже.

Я знаю, о чем ты думаешь: «Бла! Нам не нужна математика, чтобы делать игры!». К сожалению, однако, вы делаете. На самом деле, вам нужно много математики, чтобы создавать игры, поэтому мы расскажем, как сделать некоторые основы в lua. Вернитесь к нашему основному файлу, сотрите все и поместите это на место:

1
—math with no variables print(1 + 2) —math using variables a = 1 b = 2 c = a + b print(c) print(((a + b) * c) / 3) —print something useful with strings and variables bob_age = 25 susan_age = 20 print(«Bob is » .. bob_age .. » years old, Susan is » .. susan_age .. » years old, and combined they are » .. bob_age + susan_age .. » years old!»)

Ваш экран должен теперь показать:

Corona Math

Большая часть того, что мы там увидели, просто строится на том, что мы уже сделали. Новым является то, что мы теперь печатаем несколько вещей внутри наших операторов печати. Операторы печати могут быть настолько простыми или сложными, насколько нам нужно. Мы даже можем сделать нашу математику прямо изнутри! Стоит отметить, что при объединении больших операторов воедино каждый элемент должен сочетаться с «..». Математика может быть сделана исключительно с различными управляющими знаками, однако, когда вы добавляете туда строки, вы должны использовать «..».

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

1
2
3
4
5
6
7
8
—prints out a random integer between 1 and 100
print(math.random(100))
 
—prints out the closest integer rounded up, in this case it will print out 2
print(math.ceil(1.5))
 
—prints out the closest integer rounded down, in this case it will print out 1
print(math.floor(1.5))

Любая игра, которая использует любой вид рандомизации, будет использовать случайную функцию, и часто, если число не является целым числом, нам нужно преобразовать его в целое число, прежде чем мы распечатаем его. Кроме того, для двух функций округления не имеет значения, насколько близко это к одному целому числу или другому. Например: math.floor(1.99999) все еще будет округляться до 1. Это всего лишь несколько примеров того, что они могут сделать, и для всего, что не встроено, вы можете выполнить свои основные математические операции и немного немного практики.

Операторы If / Else — это так называемые управляющие операторы. Управляющие операторы позволяют нам управлять движением программы или приложения в зависимости от конкретных условий. Например, что если бы у нас была игра, в которой была дверь, которую можно было бы заблокировать или разблокировать, когда игрок пытается открыть дверь, нам нужен способ проверить, заблокирована она или нет. Это достигается с помощью операторов if / else. Давайте запустим этот пример и посмотрим, как он себя ведет. Очистите main.lua и вставьте вместо этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
—create our variables
 —notice that there doesn’t need to be spaces between the name of the variable and
 —the equals sign.
 —it is considered a good practice to pick one style and stick with it
 door1Locked=false
 door2_locked = true
  
 —run our if/else statements
 —In the first ‘if’ statement, notice how they can be by themselves and do not require an
 —else.
 —happen and the code will simply continue on.
 —only happen if what is in the parentheses is true.
 if(door1Locked == false) then
  print(«Door 1 is unlocked!»)
 end
  
—our second if statement, this time it has an attached else
—now, if the statement is not true we will jump into the else section
if(door2_locked == true) then
  —If the door is locked print out a locked statement and then set door2_locked to false.
  —Notice that when using variables ‘=’ is used to set variables while ‘==’ is
  —used to compare variables.
  print(«Door 2 is locked…unlocking…»)
  door2_locked = false
 else
  print(«Door 2 is unlocked!»)
end
  
if(door2_locked == true) then
  print(«Door 2 is still locked… :p»)
else
  print(«Door 2 is finally unlocked!»)
end

Когда вы запустите его, вы увидите это:

If / Else Corona SDK

Итак, что там произошло? В первом операторе if мы проверили, разблокирована ли door1. Это было, поэтому мы распечатали сообщение. Во втором операторе if мы проверили, заблокирована ли door2. Это было, поэтому мы разблокировали его (изменили переменную) и проверили снова. Использование операторов if / else таким образом позволяет нам создавать очень интересные элементы игрового процесса для всех типов игр. Итак, еще раз: важно помнить, что для операторов if / else все, что находится внутри скобок, должно оцениваться как true или false. Если это правда, тогда он будет запускать то, что следует после «тогда», если нет, то он будет запускать то, что следует после «еще», если есть еще (Вы видите, как я крадучий вбил выражение if в пример. … бесстыдно знаю!) Вот еще несколько примеров того, как вы можете использовать операторы if:

01
02
03
04
05
06
07
08
09
10
11
12
—will always return true
if(true) then
 
—compares the first number to the second
if(3 < 5) then
 
—compare variables
if(a == b) then
 
—compare variables and strings
name = «Bob»
if(name == «Bob») then

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

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

01
02
03
04
05
06
07
08
09
10
11
12
—create our function, all functions start with <i>function functionName ()</i>
—and end with the keyword <i>end</i>.
—you can call it in the code!
 function updateScore()
  score = score + 1
 end
  
 score = 0
 print(«Score before the function call: » .. score)
 —to call a function just type the functions name followed by <i>()</i>
 updateScore()
 print(«Score after the function call: » .. score)

Попробуйте и убедитесь, что вы получите этот результат:

Corona SDK - Учим Луа

Функции также хороши тем, что вы можете сгруппировать их все в отдельный файл (вне main.lua), включить файл, а затем вызвать их как обычно. Эта
помогает поддерживать ваш код в чистоте и порядке, что особенно полезно в больших проектах.

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

1
2
3
for a = 1, 10, 1 do
updateScore()
end

Сохраните это и запустите ваш код, и для ваших результатов теперь ваш вывод должен выглядеть так:

Для петли

Итак, почему мы получили 10 вместо 1 в то время? Ответ в том, что цикл for, который мы вызвали, запускал все внутри do и end 10 раз. В этом случае updateScore () вызывался 10 раз. Итак, давайте посмотрим, что именно происходит, потому что есть несколько важных вещей, на которые следует обратить внимание.

Когда мы создаем цикл for, в объявлении есть 3 раздела, каждый из которых разделен запятыми. Первый раздел используется для объявления переменных. Мы могли бы просто сказать, что для monkey = 1, 10, 1 единственное, что делает этот раздел, это инициализирует переменную. В этом случае мы инициализировали переменную a и установили ее равной 0. Во втором разделе мы сообщаем циклу for, как далеко считать, а в третьем разделе мы говорим циклу for, на сколько рассчитывать. Давайте посмотрим на еще один простой пример. Избавьтесь от старого кода и замените его следующим.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
—count to 10 by 1
for a = 0, 10, 1 do
print («a: » .. a)
end
 
—count to 10 by the variable countByMe
countByMe = 3
for b = 0, 10, countByMe do
print («b: » .. b)
end
 
—count from 10 to 0 by 1
for c = 10, 0, -1 do
print («c: » .. c)
end

Вы должны увидеть что-то вроде этого:

для цикла

Как вы можете видеть, цикл for просто делает то, что находится внутри него, столько раз, сколько вам нужно. Следует также отметить, что переменная не изменяется до тех пор, пока не будет выполнено содержимое цикла for. Надеюсь, вы увидите, как циклы используются в программировании. Существует больше типов циклов, чем просто для циклов, и все они полезны, но освоитесь с циклами for, и вы сможете выполнить практически все, что вам нужно. Хотя другие циклы (while и do-while) так же просты в использовании.

Экранные объекты — это то, на что они похожи: объекты, отображаемые на вашем экране. Почти все, что вы видите на экране, является экранным объектом. Corona SDK позволяет легко и просто выводить информацию на экран. Единственное, что вам нужно сделать, чтобы получить что-то там, это сделать следующее:


displayObject = display.newImage("imageName")

Чтобы это действительно показывало что-то на экране, вам нужно иметь изображение с именем « imageName » в той же папке, что и ваш файл main.lua. Вот быстрое изображение, которое я сделал за 4 секунды, если вы слишком ленивы, чтобы сделать быстрое изображение для себя. 😉

Corona Image

Так что в следующем примере, в той же папке, что и мой файл main.lua, у меня есть папка с именем images. В папке изображений у меня есть изображение с именем «man.png». Для доступа к этому файлу из main.lua я буду использовать newImage («images / man.png»), как показано здесь:

1
2
3
4
5
6
7
8
9
—note that the name of the display Object can be anything
—also be sure to include the file extension of the file image name
—we can create basic shapes using this method, this will give us a white background
background = display.newRect(0,0,320,480)
hero = display.newImage(«images/man.png»)
 
—next we need to tell the image where to be placed
hero.x = display.contentWidth/2
hero.y = display.contentHeight/2

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

При назначении значений x и y Объектам, верхний левый угол экрана (x = 0, y = 0) независимо от ориентации игры. Также обратите внимание, что координаты x и y объекта относятся к центру объекта. Поэтому, установив координаты нашего объекта в (0,0), вы поместите наше изображение наполовину за пределы экрана. Теперь, поскольку наш герой является объектом, мы можем назначить ему больше переменных, в которых мы можем хранить всю информацию, касающуюся нашего маленького героя. Хорошим примером этого являются координаты x и y для героя. Если после этого вы добавите оператор print, то назначите эти 2 значения и произнесете print (hero.x), он выведет значение, сохраненное в hero.x. Давайте продолжим и добавим еще несколько переменных для нашего героя, которые нам могут понадобиться позже. Поместите следующий код под существующий код из предыдущего примера:

01
02
03
04
05
06
07
08
09
10
11
—try to keep the variables to things that will makes sense later on as you make your game
hero.name = «Larry»
hero.level = 1
hero.score = 1500
  
—now let’s print everything out
print(«hero.x = » .. hero.x)
print(«hero.y = » .. hero.y)
print(«hero.name = » .. hero.name)
print(«hero.level = » .. hero.level)
print(«hero.score = » .. hero.score)

Запустите его, и вы должны увидеть это в своей консоли:

Корона функции

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

Еще одна вещь, которую вы увидите, использовала несколько мест в коде, это группы. Группировка подобных предметов позволяет легко отслеживать вещи. Скажем, например, что у нас есть 3 типа элементов на экране: хорошие парни, плохие парни и пули. Когда мы обновляем игру, мы должны обновлять каждый отдельный объект, с помощью групп мы можем упростить это, сказав, что обновите все в группе, и наша программа позаботится обо всем за нас. Давайте посмотрим, как это сделать. Идите дальше и удалите весь наш предыдущий код и замените его следующим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
—Make some heros and monsters, and give them some information
—remember in order for newImage to work you would need to have an image name
—man.png inside of a folder named images that is in the
—same directory as your main.lua file.
—in the same folder as main.lua you can simply say man.png
hero1 = display.newImage(«images/man.png»)
hero1.name = «Homer»
hero1.score = 0
hero2 = display.newImage(«images/man.png»)
hero2.name = «Bart»
hero2.score = 0
hero3 = display.newImage(«images/man.png»)
hero3.name = «Lisa»
hero3.score = 0
monster1 = display.newImage(«images/man.png»)
monster1.name = «Ned»
monster1.score = 0
monster2 = display.newImage(«images/man.png»)
monster2.name = «Nelson»
monster2.score = 0
monster3 = display.newImage(«images/man.png»)
monster3.name = «Milhouse»
monster3.score = 0
 
—create our groups
heroGroup = display.newGroup()
monsterGroup = display.newGroup()
screenGroup = display.newGroup()
 
—insert our heros and monsters into their respective groups
—note that the order you put them in will determine their draw order
—also note that we can put groups into other groups
heroGroup:insert(hero1)
heroGroup:insert(hero2)
heroGroup:insert(hero3)
monsterGroup:insert(monster1)
monsterGroup:insert(monster2)
monsterGroup:insert(monster3)
screenGroup:insert(heroGroup)
screenGroup:insert(monsterGroup)
 
—cycle through our groups using for loops
—groupname.numChildren is a variable that tells you how many items are in a group
—this will update the heroGroup
for a = 1, heroGroup.numChildren, 1 do
 —we are going to print out the information then change it, so even though they are
 —happening in the same loop we will not see the changes we made on this round
 print(heroGroup[a].name .. «: » .. heroGroup[a].score)
 heroGroup[a].score = heroGroup[a].score + 5
end
 
—give ourselves a clear seperation in the console
print(«»)
 
—now let’s look at how display the everything inside of the screen group
for a = 1, screenGroup.numChildren, 1 do
 for b = 1, screenGroup[a].numChildren, 1 do
  print ((screenGroup[a])[b].name .. «: » .. (screenGroup[a])[b].score)
 end
end

Ваш выход в вашем терминале должен выглядеть следующим образом:

Corona SDK Groups

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

Последний раздел, который мы рассмотрим в этом учебнике, — это «таймеры». Таймеры довольно понятны. Они позволяют нам сделать 2 основных вещи. Во-первых, они будут ждать определенное время, а затем выполнить действие. Во-вторых, они будут запускать определенное событие в течение определенного периода времени. Оба из которых вы будете использовать в играх все время. Очистите все и запустите этот код.

1
2
3
4
5
6
7
print(«Waiting for the timer to finish…»)
 
function afterTimer()
print(«Timer is done!»)
end
 
timer.performWithDelay(3000, afterTimer, 1)

Когда вы запускаете этот код, вы должны видеть первый оператор печати непосредственно в терминале, второй оператор печати (то есть тот, который находится внутри функции afterTimer) не должен появляться в течение 3 секунд. Итак, вы можете видеть, что есть 3 параметра для этой функции. Первый параметр, как долго вы
хотите, чтобы между каждым таймером запускались миллисекунды. Второе — это имя функции, которую вы хотите запустить, когда закончится первая продолжительность. Третье — это количество раз, когда вы хотите, чтобы функция была запущена. Таким образом, если бы мы ввели 5 для последнего параметра, мы бы запустили функцию afterTimer 5 раз каждый с 3-секундной паузой между ними. Если вы хотите, чтобы что-то продолжалось вечно, просто введите -1 для последнего параметра. Это полезно для таких вещей, как функции обновления, которые должны постоянно обновлять вашу игру.


Концепции, представленные в этом руководстве, конечно, не все, что вы увидите в серии проектов, но, надеюсь, это руководство было достаточно ускоренным курсом, что вам будет удобно со всем остальным, что всплывает в последующих постах. Так что продолжайте практиковаться и учиться, и вы будете готовы к следующим разделам, где мы перейдем к реальному игровому процессу. Если у вас есть какие-либо вопросы или предложения озвучьте в комментариях!