Розробка ігор для консолі на Arduino в літньому таборі
Минулого року ми в літній комп'ютерній школі проводили гурток по Arduino. Там взяли участь і викладачі, в результаті чого з'явилася 8-бітна ігрова консоль з екраном 64x64.
Тепер же ми вирішили зробити гурток, в якому хлопці зможуть створити власні ігри для цієї консолі. Завдяки її мінімалізму, коду повинно вийти небагато. Всього на гурток було заплановано близько 14 годин протягом зміни, так що низький поріг входу в програмування таких ігор був важливою особливістю.
4095 світлодіодів і всі-всі-всі
API для створення ігор
Залізка у нас була тільки одна, тому спочатку треба було зробити емулятор. Адже над іграми буде працювати кілька команд. Крім того, вихідно весь код був в одному * .ino файлі, тому треба було розбити його на частини, щоб додавання нових ігор зводилося до програмування їх поведінки та відмальовки. Середовище збірки ми залишили Arduino IDE, щоб спростити налаштування робочих машин.
Для виводу зображення на екран процесор постійно сканує його і завантажує кольори пікселів у зсувні регістри. Тому правильна процедура оновлення екрана повинна віддавати тільки одну лінію пікселів. Але такий інтерфейс був би занадто незручним, тому ми зробили набір функцій типу game_draw_sprite, game_draw_text, а вони вже всередині перевіряють яку лінію треба виводити (і чи треба взагалі). Через це виникає деякий оверхед, оскільки всі виклики будуть робитися для кожного рядка, а не тільки для потрібних.
Емулятор надає ті ж функції, але він відображає все у фреймбуфері, до того ж працює набагато швидше, ніж консоль. Тому не завжди можна оцінити те, що вийде на залізі. Зате він вирішує більшість проблем з налагодженням, тому що можна запустити Visual Studio (або gdb) і подивитися як працює програма. Всі скріншоти тут саме з цього емулятора.
Розробник повинен реалізувати дві функції, щоб вийшла гра: малювання екрана і оновлення внутрішнього стану. Оновлення відбувається із заздалегідь заданою періодичністю. Функція малювання викликається для кожного рядка екрана (насправді для груп по 4 рядки відразу через особливості адресації).
Функція оновлення реагує на натиснуті на джойстику клавіші і змінює внутрішні дані гри (наприклад, координати об'єктів, щоб їх перемістити). Оскільки в кожен момент часу виконується тільки одна гра, немає сенсу в кожній з них оголошувати статичні змінні. Адже всього у нас 2 кілобайти пам'яті. Тому робота з пам'яттю у нас трохи незручна - всі дані ігри зберігаються в структурі, до якої доводиться звертатися через покажчик.
Щоб перевірити працездатність API, а також меню для вибору ігор, ми реалізували гру «Змійка». Робили трохи поспіхом, тому кілька багів залишилося. Діти їх потім з радістю виявляли.
Спільна розробка
Для початку ми розмістили наш проект з гітхабу на внутрішньому сервері з gitlab. Хлопці працювали з форками цього репозитарію, а потім надсилали пулл реквести, щоб зібрати все в купу. В цілому все пройшло вдало, але глибоко в пояснення принципів роботи git ми не занурювалися.
Всього учасників проектів було не дуже багато. Спочатку заявилося 4 команди, але до кінця дійшли тільки 2. Це були хлопці з групи, яка тільки починає вивчати алгоритми, тому було цікаво, чи вийде у них що-небудь.
Сапер
Перша команда вирішила робити класичний сапер. Міни розміщувалися дуже хитро - по одній в стовпці - тому їм знадобилися тільки спрайти з цифрами від 1 до 3. Програма складається з 475 рядків, але там є сміття і залишені коментарі з шаблону. Прискіпуватися до коду в пулл реквестах вже не залишалося часу.
Найбільша складність у реалізації сапера - це відкривання всіх вільних клітин, суміжних з тією вільною, на яку натиснув користувач. Обхід у ширину було пояснювати довго. Та й пам'ять він під чергу займає. Тому хлопці просто кілька разів сканували масив і відкривали сусідні і відкритими клітини. Це нечаста операція, тому все спрацювало нормально.
Ще тут якраз виникли проблеми з відображенням - якщо кожен раз намагатися вималювати все поле, то виходить занадто повільно, і картинка починає мерехтіти. Довелося зробити невелику милицю - функцію, яка дозволяє скіпати цілі рядки зі спрайтів, якщо вони не потрапляють на рядок, що відображається. Далі ми плануємо проапгрейдити процесор до ATmega2560, щоб вистачило пам'яті на фреймбуфер. Тоді всі проблеми з мерехтінням для подібних ігор ізчезнуть.
Breakout
Гра називається Breakout, тому що хлопці спочатку хотіли робити саме його. Але потім вирішили, що все складно і у них вийшов Pong. У грі кілька режимів - гра двох гравців, гра з комп'ютером, демонстрація (гра комп'ютера проти себе). Правда ось комп'ютер програвати не вміє - йому завжди вдається відбити м'яч. Більш хитрий алгоритм хлопці придумати не встигли. Всього файл з грою займає 272 рядки.
Flappy submarine
Паралельно з дітьми я зробив «Flappy submarine», щоб показувати їм як працювати зі спрайтами і управлінням.
Всіх цих ігор вистачило, щоб забити 32 кілобайти програмної пам'яті. Але після апргейду до ATmega2560 пам'яті буде 256 кілобайт, так що в наступному році ми плануємо повторити цей гурток не викидаючи вже створені ігри.
Вихідники проекту на гітхабі












