Tutorials
Tutorials

This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English

Beginner

Змійка

У цьому уроці ви пройдете процес створення однієї з найпоширеніших класичних ігор, які можна спробувати відтворити. Ця гра має чимало різновидів; у нашому змійка їсть «їжу» й росте лише тоді, коли їсть. Ця змійка також повзає ігровим полем із перешкодами.

Мініатюра

Чого ви навчитеся

У цьому уроці ви навчитеся:

  • Створювати гру з нуля в Defold
  • Налаштовувати й обробляти введення
  • Створювати карти плиток і змінювати їх під час виконання
  • Писати скрипти мовою Lua

Примітка для початківців

Цей урок розрахований на початківців, але якщо ви зовсім не знайомі з Defold і розробкою ігор, радимо спершу прочитати кілька вступних посібників, особливо про будівельні блоки Defold і глосарій. Якщо ви ще не завантажили Defold, перегляньте посібник зі встановлення. Також радимо переглянути огляд редактора, щоб швидше освоїтися в ньому, хоча тут ми також наводимо знімки екрана для кожного кроку.

Створення проєкту

Запустіть Defold і:

  1. Виберіть Create FromTemplates ліворуч.
  2. Виберіть Empty Project.
  3. Введіть назву проєкту в полі Title.
  4. Виберіть розташування проєкту в полі Location.
  5. Натисніть Create New Project.

Початок роботи

Готово!

Налаштування проєкту

Почнемо з визначення роздільності гри.

  1. Коли відкриється редактор, знайдіть файл game.project ліворуч, на панелі Assets. Двічі клацніть його, щоб відкрити.
  2. Перейдіть до розділу Display файлу game.project.
  3. Установіть розміри гри (Width і Height) на 768⨉768 або інше значення, кратне 16.

Налаштування екрана

Це потрібно тому, що гра відображатиметься на сітці, кожен сегмент якої матиме розмір 16x16 пікселів, і так екран гри не обрізатиме частини сегментів. Файл game.project містить усі важливі налаштування проєкту — про них можна прочитати в посібнику з налаштувань проєкту.

Готово!

Створення нових папок на панелі Assets

Для мінімалістичного клону «Змійки» потрібно зовсім небагато графіки. Один зелений сегмент розміром 16⨉16 для змійки, один білий блок для перешкод і один менший червоний блок, що позначатиме їжу.

Спершу створіть каталог для ресурсів у редакторі Defold:

  1. Клацніть правою кнопкою миші папку main
  2. Виберіть New Folder.
  3. З’явиться вікно із запитом назви — введіть assets і натисніть Create Folder.

Нова папка

Готово!

Додавання графіки до гри

Зображення нижче — єдиний ресурс, який вам знадобиться:

Спрайти змійки

  1. Клацніть правою кнопкою миші зображення вище й збережіть його на локальному диску. Потім перетягніть (або скопіюйте та вставте) завантажене зображення до щойно створеного каталогу в папці проєкту.

Нова папка

Докладніше про імпортування ресурсів можна прочитати тут.

Готово!

Додавання джерела плиток

Defold має вбудований компонент (component) карти плиток, який ви використаєте для створення ігрового поля з плиток, розташованих на сітці. Карта плиток дає змогу задавати та зчитувати окремі плитки, що чудово підходить для цієї гри. Оскільки карти плиток беруть графіку з джерела плиток, потрібно його створити:

  1. Клацніть правою кнопкою миші папку assets.
  2. Виберіть NewTile Source у розділі «Resources».
  3. Назвіть новий файл «snake» (редактор збереже його як snake.tilesource).

Нове джерело плиток

Джерело плиток відкриється у спеціальному редакторі Tile Source Editor для цього типу файлів, і вам буде запропоновано вказати для нього зображення, щоб воно запрацювало. Праворуч розташована панель Properties:

  1. Задайте для властивості Image щойно імпортований графічний файл. Джерело плиток

  2. Залиште значення властивостей Width і Height рівними 16 (типове значення). Так зображення розміром 32⨉32 пікселі буде поділено на 4 плитки з номерами 1–4.

Властивості джерела плиток

Зверніть увагу, що властивість Extrude Borders має значення 2 пікселі. Це запобігає появі візуальних артефактів навколо плиток, у яких графіка сягає самого краю.

Якщо ви змінюєте файл, поруч із його назвою на вкладці з’являється зірочка *. Виберіть FileSave All або натисніть Ctrl+S (⌘Cmd + S на Mac), щоб зберегти всі файли.

Готово!

Створення карти плиток ігрового поля

Тепер у вас є готове джерело плиток, тож час створити компонент карти плиток ігрового поля:

  1. Клацніть правою кнопкою миші папку main і виберіть NewTile Map у розділі «Components». Назвіть новий файл «grid» (редактор збереже його як «grid.tilemap»). Додавання карти плиток

  2. Він відкриється в редакторі Tile Map Editor, який підсвітить обов’язкове поле Tile Source, тож задайте для властивості Tile Source раніше створений файл «snake.tilesource». Вибір джерела плиток

Готово!

Малювання плиток на карті плиток

Defold зберігає лише ту ділянку карти плиток, яка насправді використовується, тож потрібно додати достатньо плиток, щоб заповнити межі екрана.

  1. Виберіть шар layer1 на панелі Outline праворуч.
  2. Виберіть пункт меню EditSelect Tile... або натисніть Space, щоб відкрити палітру плиток, а потім клацніть плитку, якою хочете малювати. Карта плиток

  3. Намалюйте рамку вздовж краю екрана й кілька перешкод. Готова карта плиток

Щоб заповнити екран гри, вам знадобиться карта розміром 48x48 плиток (оскільки розмір нашого екрана — 768 пікселів, а плитки мають розмір 16 пікселів, отримуємо 768/16 = 48).

Коли закінчите, збережіть карту плиток.

Готово!

Додавання карти плиток до гри

Тепер потрібно додати нашу карту плиток до гри. Якщо ви знайомі з будівельними блоками Defold, то знаєте, що компоненти є частинами ігрових об’єктів (game objects), а ігрові об’єкти можна визначати в колекціях (collections).

  1. Відкрийте main.collection, двічі клацнувши його на панелі Assets. У шаблоні Empty Project це типова стартова колекція (bootstrap collection), яка завантажується під час запуску рушія.

  2. Клацніть правою кнопкою миші кореневий елемент на панелі Outline і виберіть Add Game Object, щоб створити новий ігровий об’єкт у колекції, яка завантажується на початку гри. Додавання ігрового об’єкта

  3. Клацніть правою кнопкою миші новий ігровий об’єкт і виберіть Add Component File. Виберіть щойно створений файл «grid.tilemap». Додавання компонента

Тепер у колекції нашої гри є карта плиток. Вона має бути видимою, коли ви запустите гру з редактора.

  1. Виберіть ProjectBuild або натисніть Ctrl + B (⌘Cmd + B на Mac).

Запуск гри

Готово!

Додавання скрипту до гри

  1. Клацніть правою кнопкою миші папку main у вікні Assets і виберіть NewScript у розділі Scripts. Назвіть новий файл скрипту «snake» (його буде збережено як «snake.script»). Цей файл міститиме всю логіку гри. Додавання скрипту

  2. Поверніться до main.collection і клацніть правою кнопкою миші ігровий об’єкт із картою плиток. Виберіть Add Component File і вкажіть файл «snake.script».

Головна колекція

Тепер компонент карти плиток і скрипт на своїх місцях.

Готово!

Скрипт гри

Скрипт, який ви напишете, керуватиме всією грою. Ми додаватимемо можливості одну за одною.

Простий алгоритм руху

Ідея його роботи така:

  1. Скрипт зберігає список позицій плиток, які зараз займає змійка.
  2. Якщо гравець натискає клавішу напрямку, зберегти напрямок, у якому має рухатися змійка.
  3. Через однакові проміжки часу переміщувати змійку на один крок у поточному напрямку руху.

Ініціалізація

Відкрийте snake.script і знайдіть функцію init(). Рушій викликає її під час ініціалізації скрипту на початку гри. Замініть код на такий:

function init(self)
    self.segments = { -- <1>
        {x = 7, y = 24},
        {x = 8, y = 24},
        {x = 9, y = 24},
        {x = 10, y = 24}
    }
    self.dir = {x = 1, y = 0} -- <2>
    self.speed = 7.0 -- <3>
    self.time = 0 -- <4>
end

У цьому коді ми:

  1. Зберігаємо сегменти змійки в таблиці Lua з назвою self.segments, яка містить список таблиць, кожна з яких зберігає позицію сегмента за X і Y.
  2. Зберігаємо поточний напрямок у таблиці self.dir, що містить напрямок за X і Y.
  3. Зберігаємо поточну швидкість руху в self.speed, виражену в плитках за секунду.
  4. Зберігаємо значення таймера в self.time, яке використовуватимемо для відстеження швидкості руху.

Код скрипту вище написано мовою Lua. У ньому є кілька моментів, на які варто звернути увагу, але якщо ви ще не розумієте чогось із наведеного нижче, не хвилюйтеся. Просто рухайтеся далі, експериментуйте й дайте собі час — зрештою ви розберетеся. Поки що достатньо запам’ятати, що в init() ми щойно ініціалізували змінні, які використовуватимемо.

  • Defold резервує набір вбудованих функцій зворотного виклику, які викликаються протягом життєвого циклу компонента-скрипту. Це не методи, а звичайні функції.
  • Середовище виконання передає посилання на поточний екземпляр (instance) компонента-скрипту через параметр self. Посилання self використовується для зберігання даних екземпляра.
  • Посилання self можна використовувати як таблицю Lua для зберігання даних. Просто використовуйте крапковий запис, як і для будь-якої іншої таблиці: self.data = "value". Посилання дійсне протягом усього життєвого циклу скрипту, у цьому випадку — від початку гри до виходу з неї.
  • Літерали таблиць Lua записуються у фігурних дужках {}.
  • Елементи таблиці можуть бути парами ключ-значення ({x = 10, y = 20}), вкладеними таблицями Lua ({ {a = 1}, {b = 2} }) або даними інших типів.

Готово!

Оновлення

Функція init() викликається рівно один раз, коли у запущеній грі створюється екземпляр компонента-скрипту. Натомість функція update() викликається один раз кожного кадру. Це робить її ідеальною для ігрової логіки реального часу.

Ідея оновлення така: через певний заданий проміжок часу виконувати такі дії:

  1. Знайти, де розташована голова змійки, і створити нову голову в сусідній позиції, зміщеній у поточному напрямку руху. Отже, якщо змійка рухається на X=1 і Y=0, а поточна голова розташована в X=0 і Y=0, нова голова має бути в X=1 і Y=0.
  2. Зберегти позицію нової голови в списку сегментів, з яких складається змійка.
  3. Отримати позицію хвоста з таблиці сегментів.
  4. Очистити плитку хвоста в цій позиції.
  5. Намалювати всі сегменти змійки (плитки) в позиціях із таблиці.

Алгоритм

:::sidenote Пам’ятайте, що голова нашої змійки розташована в кінці таблиці, а хвіст — на початку. :::

  1. Знайдіть функцію update() у snake.script і замініть код на такий:
function update(self, dt)
    self.time = self.time + dt -- <1>
    if self.time >= 1.0 / self.speed then -- <2>
        local head = self.segments[#self.segments] -- <3>

        local newhead = {
            x = head.x + self.dir.x,
            y = head.y + self.dir.y
        } -- <4>

        table.insert(self.segments, newhead) -- <5>

        local tail = table.remove(self.segments, 1) -- <6>

        tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 0) -- <7>

        for i, s in ipairs(self.segments) do -- <8>
            tilemap.set_tile("#grid", "layer1", s.x, s.y, 2) -- <9>
        end

        self.time = 0 -- <10>
    end
end

У цьому коді ми:

  1. Збільшуємо таймер на різницю часу (у секундах) від попереднього виклику update() — так званий «дельта-час», або dt.
  2. Якщо минуло достатньо часу:
  3. Отримуємо позицію поточної голови. # — це оператор для отримання довжини таблиці за умови, що вона використовується як масив, як у нашому випадку: усі сегменти є значеннями таблиці без явно заданого ключа.
  4. Створюємо новий сегмент голови на основі поточного розташування голови та напрямку руху (self.dir).
  5. Додаємо нову голову до кінця таблиці сегментів.
  6. Видаляємо хвіст із початку таблиці сегментів.
  7. Очищаємо плитку в позиції видаленого хвоста. Наша карта плиток #grid має лише 1 шар із назвою layer1.
  8. Проходимо циклом по елементах таблиці сегментів. На кожній ітерації i міститиме позицію в таблиці (починаючи з 1), а s — поточний сегмент.
  9. Установлюємо плитку в позиції сегмента в значення 2 (це плитка зеленого кольору змійки).
  10. Після завершення скидаємо таймер до нуля.

Якщо зараз запустити гру, ви маєте побачити, як змійка завдовжки 4 сегменти повзе ігровим полем зліва направо.

Запуск гри

Готово!

Введення гравця

Перш ніж додавати код для реагування на введення гравця, потрібно налаштувати прив’язки введення.

Прив’язки введення

  1. Знайдіть файл game.input_binding у папці input і двічі клацніть його, щоб відкрити.
  2. Додайте набір прив’язок Key Trigger для руху вгору, вниз, ліворуч і праворуч. У стовпці Input виберіть клавіші клавіатури, а у стовпцях Action введіть назви дій.

Введення

Файл прив’язок введення зіставляє фактичне введення користувача (клавіші, рухи миші тощо) з назвами дій, які передаються скриптам, що запросили введення.

Готово!

Отримання фокусу введення

Налаштувавши прив’язки, відкрийте snake.script і додайте такий рядок на початку функції init():

function init(self)
    msg.post(".", "acquire_input_focus") -- <1>

    self.segments = {
        {x = 7, y = 24},
        {x = 8, y = 24},
        {x = 9, y = 24},
        {x = 10, y = 24}
    }
    self.dir = {x = 1, y = 0}
    self.speed = 7.0
    self.time = 0
end

Доданий рядок:

  1. Надсилає повідомлення поточному ігровому об’єкту («.» — скорочення для поточного ігрового об’єкта), указуючи йому почати отримувати введення від рушія.

Потім знайдіть функцію on_input і введіть такий код:

function on_input(self, action_id, action)
    if action_id == hash("up") and action.pressed then -- <1>
        self.dir.x = 0 -- <2>
        self.dir.y = 1
    elseif action_id == hash("down") and action.pressed then
        self.dir.x = 0
        self.dir.y = -1
    elseif action_id == hash("left") and action.pressed then
        self.dir.x = -1
        self.dir.y = 0
    elseif action_id == hash("right") and action.pressed then
        self.dir.x = 1
        self.dir.y = 0
    end
end

Ці гілки if...elseif... виконують такі дії:

  1. Якщо отримано дію введення «up», налаштовану в прив’язках введення, і в таблиці action поле pressed має значення true (гравець натиснув клавішу), тоді:
  2. Задають напрямок руху.

Запустіть гру ще раз і переконайтеся, що можете керувати змійкою.

Готово!

Поліпшення обробки введення

Тепер зверніть увагу: якщо натиснути дві клавіші одночасно, це спричинить два виклики on_input(), по одному для кожного натискання. У наведеному вище коді лише останній виклик вплине на напрямок змійки, оскільки наступні виклики on_input() перезаписуватимуть значення в self.dir.

Також зауважте: якщо змійка рухається ліворуч, а ви натиснете клавішу right, вона поверне в себе. На перший погляд, очевидний спосіб виправити цю проблему — додати ще одну умову до гілок if у on_input():

if action_id == hash("up") and self.dir.y ~= -1 and action.pressed then
    ...
elseif action_id == hash("down") and self.dir.y ~= 1 and action.pressed then
    ...

Проте якщо змійка рухається ліворуч, а гравець швидко натисне спершу up, а потім right до наступного кроку руху, лише натискання right матиме ефект, і змійка вріжеться в себе. З доданими до гілок if умовами, показаними вище, введення буде проігноровано. Недобре!

Правильне розв’язання цієї проблеми — зберігати введення в черзі та брати з неї записи під час руху змійки:

function init(self)
    msg.post(".", "acquire_input_focus")

    self.segments = {
        {x = 7, y = 24},
        {x = 8, y = 24},
        {x = 9, y = 24},
        {x = 10, y = 24}
    }
    self.dir = {x = 1, y = 0}
    self.speed = 7.0
    self.time = 0

    self.dirqueue = {} -- <1>
end

Цього разу ми:

  1. Додали змінну self.dirqueue, яка ініціалізується як порожня таблиця.

У функції update() додайте:

function update(self, dt)
    self.time = self.time + dt
    if self.time >= 1.0 / self.speed then
        local newdir = table.remove(self.dirqueue, 1) -- <1>
        if newdir then
            local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y -- <2>
            if not opposite then
                self.dir = newdir -- <3>
            end
        end

        local head = self.segments[#self.segments]
        local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}

        table.insert(self.segments, newhead)

        local tail = table.remove(self.segments, 1)
        tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 0)

        for i, s in ipairs(self.segments) do
            tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)
        end

        self.time = 0
    end
end
  1. Беремо перший елемент із черги напрямків.
  2. Якщо елемент є (newdir не дорівнює null), перевіряємо, чи newdir вказує в напрямку, протилежному до self.dir.
  3. Задаємо новий напрямок, лише якщо він не протилежний.

І змініть on_input, щоб натомість зберігати поточне введення в черзі:

function on_input(self, action_id, action)
    if action_id == hash("up") and action.pressed then
        table.insert(self.dirqueue, {x = 0, y = 1}) -- <1>
    elseif action_id == hash("down") and action.pressed then
        table.insert(self.dirqueue, {x = 0, y = -1})
    elseif action_id == hash("left") and action.pressed then
        table.insert(self.dirqueue, {x = -1, y = 0})
    elseif action_id == hash("right") and action.pressed then
        table.insert(self.dirqueue, {x = 1, y = 0})
    end
end
  1. Додаємо напрямок введення до черги напрямків замість безпосереднього встановлення self.dir.

Запустіть гру й переконайтеся, що вона працює як очікується.

Готово!

Їжа та зіткнення з перешкодами

Змійці потрібна їжа на карті, щоб ставати довшою та швидшою. Додаймо її!

Створення їжі

Над функцією init() додайте нову функцію:

local function put_food(self) -- <1>
    self.food = {x = math.random(2, 47), y = math.random(2, 47)} -- <2>
    tilemap.set_tile("#grid", "layer1", self.food.x, self.food.y, 3) -- <3>
end

У цій функції ми:

  1. Оголошуємо нову функцію put_food(), яка розміщує їжу на карті.
  2. Зберігаємо випадкову позицію за X і Y у змінній self.food.
  3. Установлюємо плитку в позиції X і Y в значення 3, яке відповідає графіці їжі.

Потім викличте її в кінці функції init():

function init(self)
    msg.post(".", "acquire_input_focus")

    self.segments = {
        {x = 7, y = 24},
        {x = 8, y = 24},
        {x = 9, y = 24},
        {x = 10, y = 24}
    }
    self.dir = {x = 1, y = 0}
    self.dirqueue = {}
    self.speed = 7.0
    self.time = 0

    math.randomseed(socket.gettime()) -- <1>
    put_food(self) -- <2>
end
  1. Перш ніж почати отримувати випадкові значення за допомогою math.random(), задаємо початкове значення генератора випадкових чисел, інакше генеруватиметься та сама послідовність випадкових значень. Це початкове значення потрібно задавати лише один раз.
  2. Викликаємо функцію put_food() на початку гри, щоб гравець починав із їжею на карті.

Готово!

Поїдання їжі

Тепер, щоб визначити, чи зіткнулася змійка з чимось, достатньо подивитися, що розташовано на карті плиток там, куди вона прямує, і відреагувати.

Додайте змінну, що відстежує, чи жива змійка:

function init(self)
    msg.post(".", "acquire_input_focus")

    self.segments = {
        {x = 7, y = 24},
        {x = 8, y = 24},
        {x = 9, y = 24},
        {x = 10, y = 24}
    }
    self.dir = {x = 1, y = 0}
    self.dirqueue = {}
    self.speed = 7.0
    self.time = 0

    self.alive = true -- <1>

    math.randomseed(socket.gettime())
    put_food(self)
end
  1. Прапорець, який указує, чи жива змійка.

Потім додайте логіку перевірки зіткнення зі стіною/перешкодою та їжею:

function update(self, dt)
    self.time = self.time + dt
    if self.time >= 1.0 / self.speed and self.alive then -- <1>
        local newdir = table.remove(self.dirqueue, 1)

        if newdir then
            local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y
            if not opposite then
                self.dir = newdir
            end
        end

        local head = self.segments[#self.segments]
        local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}

        table.insert(self.segments, newhead)

        local tile = tilemap.get_tile("#grid", "layer1", newhead.x, newhead.y) -- <2>

        if tile == 2 or tile == 4 then
            self.alive = false -- <3>
        elseif tile == 3 then
            self.speed = self.speed + 1 -- <4>
            put_food(self)
        else
            local tail = table.remove(self.segments, 1) -- <5>
            tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 1)
        end

        for i, s in ipairs(self.segments) do
            tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)            
        end

        self.time = 0
    end
end
  1. Переміщуємо змійку, лише якщо вона жива.
  2. Перш ніж малювати на карті плиток, зчитуємо, що розташовано в позиції, де буде нова голова змійки.
  3. Якщо плитка — це перешкода або інша частина змійки, гру завершено!
  4. Якщо плитка — це їжа, збільшуємо швидкість, а потім розміщуємо нову їжу.
  5. Зауважте, що хвіст видаляється лише тоді, коли немає зіткнення. Це означає, що коли гравець їсть їжу, змійка виростає на один сегмент, оскільки під час цього ходу хвіст не видаляється.

Тепер спробуйте пограти й переконайтеся, що все працює добре!

На цьому урок завершується, але продовжуйте експериментувати з грою та виконайте кілька вправ нижче!

Готово!

Повний скрипт

Ось повний код скрипту для довідки:

local function put_food(self)
    self.food = {x = math.random(2, 47), y = math.random(2, 47)}
    tilemap.set_tile("#grid", "layer1", self.food.x, self.food.y, 3)        
end

function init(self)
    msg.post(".", "acquire_input_focus")

    self.segments = {
        {x = 7, y = 24},
        {x = 8, y = 24},
        {x = 9, y = 24},
        {x = 10, y = 24}
    }
    self.dir = {x = 1, y = 0}
    self.dirqueue = {}
    self.speed = 7.0
    self.time = 0

    self.alive = true

    math.randomseed(socket.gettime())
    put_food(self)
end

function update(self, dt)
    self.time = self.time + dt
    if self.time >= 1.0 / self.speed and self.alive then
        local newdir = table.remove(self.dirqueue, 1)

        if newdir then
            local opposite = newdir.x == -self.dir.x or newdir.y == -self.dir.y
            if not opposite then
                self.dir = newdir
            end
        end

        local head = self.segments[#self.segments]
        local newhead = {x = head.x + self.dir.x, y = head.y + self.dir.y}

        table.insert(self.segments, newhead)

        local tile = tilemap.get_tile("#grid", "layer1", newhead.x, newhead.y)

        if tile == 2 or tile == 4 then
            self.alive = false
        elseif tile == 3 then
            self.speed = self.speed + 1
            put_food(self)
        else
            local tail = table.remove(self.segments, 1)
            tilemap.set_tile("#grid", "layer1", tail.x, tail.y, 1)
        end

        for i, s in ipairs(self.segments) do
            tilemap.set_tile("#grid", "layer1", s.x, s.y, 2)            
        end

        self.time = 0
    end
end

function on_input(self, action_id, action)
    if action_id == hash("up") and action.pressed then
        table.insert(self.dirqueue, {x = 0, y = 1})
    elseif action_id == hash("down") and action.pressed then
        table.insert(self.dirqueue, {x = 0, y = -1})
    elseif action_id == hash("left") and action.pressed then
        table.insert(self.dirqueue, {x = -1, y = 0})
    elseif action_id == hash("right") and action.pressed then
        table.insert(self.dirqueue, {x = 1, y = 0})
    end
end

Вправи

Корисною вправою буде спробувати реалізувати такі поліпшення:

  1. Додайте обробку натискання клавіші, щоб перезапускати гру після її завершення.
  2. Додайте нарахування очок і лічильник очок, використовуючи лише компонент текстової мітки (простіше) або цілий GUI.
  3. Функція put_food() не враховує позицію змійки чи перешкоди. Виправте її, щоб їжа з’являлася лише у вільних місцях.
  4. Коли гра завершується, покажіть повідомлення «Game Over» і дайте гравцеві змогу спробувати ще раз.
  5. Додаткове завдання: додайте другу змійку, якою керує гравець.
  • Beginner