Движение игрока
При написании программы мы используем набор техник, которые были рассмотрены в разных уроках на нашем сайте. Во-первых, нам потребуется знать основы программирования: переменные разного типа, циклы, условия, массивы, функции. Во-вторых — основы работы с электроникой в целом и с Ардуино в частности: сборка устройства по принципиальной схеме, работа с макетной платой, считывание сигнала с кнопки, передача сигнала на динамик. В-третьих следует ознакомиться с техникой выполнения параллельных задач таймеру и с поработать с графическими индикаторами, особенно с OLED дисплеями.
Первое, что мы реализуем в программе нашего игрового автомата — движение игрока по игровому полю. Нам нужно, чтобы контроллер определял, какую из двух кнопок мы нажимаем и реагировал на это перемещением игрока влево или вправо.
Для начала будем перемещать по полю один пиксел, символизирующий игрока. Чтобы выводить пиксел на дисплей, нужно будет воспользоваться какой-нибудь библиотекой для работы с OLED дисплеями. В этом уроке будем работать с библиотекой Adafruit_SSD1306.
Также для создания основного алгоритма игры применим подход для выполнения параллельных задач по таймеру. Ведь в игре одновременно происходит сразу несколько действий: движение игрока, движение врагов, движение снарядов, проигрывание музыки. Нужно чтобы контроллер выполнял это все параллельно, и ещё не забывал опрашивать состояние кнопок.
Начнем написание программы с подключения библиотеки и определения полезных статичных значений через директиву #define.
#include <Adafruit_SSD1306.h>
#define WIDTH 128 // ширина дисплея в пикселах
#define HEIGHT 64 // высота дисплея в пикселах
#define CTRL_TO 50 // период вызова главного обработчика событий
Объявим глобальные переменные
byte pinLeft = A1; // пин контроллера, к которому подключена кнопка Влево
byte pinRight = A0; // пин для кнопки Вправо
int x,y; // переменные для хранения координат игрока
unsigned long t, ctrl_next; // переменные для обеспечения работы таймера
Последние приготовления — создание объекта для работы с OLED дисплеем.
Adafruit_SSD1306 display(4);
Число 4 в данном случае означает пин, к которому подключен вывод RST дисплея. Поскольку мы этот вывод никуда не подключали, можем указать там любой номер.
Следом идёт функция setup.
void setup() {
// инициализируем пины для работы с кнопками
pinMode( pinLeft, INPUT );
pinMode( pinRight, INPUT );
x = WIDTH/2; // начальная позиция игрока - середина экрана
y = HEIGHT-1; // начальная позиция по оси y - самый низ дисплея
// инициализация дисплея
display.begin(SSD1306_SWITCHCAPVCC, 0x3D);
// очистка памяти дисплея, на всякий случай
display.clearDisplay();
}
С этим всё понятно, а теперь суперцикл (он же loop) нашей программы.
void loop() {
unsigned long t = millis();
// проверяем, не настало ли время запуска обработчика
if( t > ctrl_next ){
ctrl_next = t + CTRL_TO;
// собственно, всё что ниже - код обработчика
// обработка нажатий кнопок
if( digitalRead(pinLeft) && x>0 )
x = x - 1;
if( digitalRead(pinRight) && x<WIDTH-1 )
x = x + 1;
// отрисовка пиксела на фрейме
display.drawPixel(x, y, 1);
// вывод фрейма на дисплей
display.display();
}
}
Что такое фрейм? Фрейм — это некая область памяти, в которой формируется изображение перед тем, как попасть на дисплей. В нашем случае, это участок оперативной памяти (по факту, обычный массив). Всё это реализовано внутри библиотеки Adafruit и мы не будем это подробно разбирать.
Загружаем программу на дисплей и пробуем нажимать кнопки. Наш игрок-пиксел будет двигаться, как и задумывалось, но после него будет оставаться след! Оно и понятно, ведь каждый раз, когда мы вызываем функцию setPixel — мы добавляем новую точку в память дисплея, а старую не стираем.
У этой проблемы есть два решения.
1 — перед каждой отрисовкой игрового фрейма, очищать всю память дисплея. Плюс такого решения — не нужно думать про предыдущую позицию игрока и врагов. Минус — контроллеру придется каждый раз заново отрисовывать все игровое поле (это пока оно у нас из одного пиксела, а потом будет много чего еще).
2 — каждый раз затирать только предыдущие позиции игрока и врагов.
Чтобы не усложнять программный код, выберем вариант номер 1. Для очистки фрейма вызовем функцию clearDisplay перед началом отрисовки игрока.
...
// очистка фрейма
display.clearDisplay();
// отрисовка пиксела на фрейме
display.drawPixel(x, y, 1);
// вывод фрейма на дисплей
display.display();
...
Загружаем программу, пробуем. Ну вот, теперь пиксел двигается как надо, без следов. Жалко, что только один пиксел. Скучновато как-то. Следующий шаг — нарисуем вместо одинокой точки целый пиксельный космолет!
