Arduino Retro Gaming с OLED-дисплеем

Когда-нибудь задумывались, сколько работы нужно, чтобы написать свои собственные ретро-игры? Насколько легко Понг кодировать для Arduino? Присоединяйтесь ко мне, покажу вам, как создать мини-ретро-игровую консоль на Arduino и программировать Pong с нуля. Вот конечный результат:

План строительства

Это довольно простая схема. потенциометр (горшок) будет управлять игрой, а дисплей OLED будет управляться Arduino. Это будет сделано на макете, однако вы можете сделать это постоянным контуром и установить его в футляр. Мы писали о воссоздании понга

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

Что вам нужно

Настройка ретро Arduino

Вот что вам нужно:

  • 1 х Arduino (любая модель)
  • 1 х 10 К Потенциометр
  • 1 x 0,96 ″ I2C OLED-дисплей
  • 1 х макет
  • Ассорти мужской> мужской провода

если вы не уверены, какую модель купить.

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

Схема

Это довольно простая схема. Если у вас нет большого опыта работы с Arduino, посмотрите эти проекты для начинающих.

первый.

Вот:

Макет понг

Глядя на переднюю часть горшка, подключите левый штифт к +5V и правильный штифт земля. Подключите средний штифт к аналоговый вывод 0 (А0).

OLED-дисплей подключен с использованием протокола I2C. соединять VCC а также GND в Ардуино +5V а также земля. соединять SCL в аналоговая пятерка (A5). соединять SDA в аналог 4 (A4). Причина, по которой это связано с аналоговыми выводами, проста; эти контакты содержат схемы, необходимые для протокола I2C. Убедитесь, что они правильно подключены и не пересекаются. Точные контакты будут различаться в зависимости от модели, но A4 и A5 используются на Nano и Uno. Проверьте документацию библиотеки Wire для вашей модели, если вы не используете Arduino или Nano.

Пот тест

Загрузите этот тестовый код (не забудьте выбрать правильную плату и порт из инструменты > доска а также инструменты > порт меню):

void setup() {
// put your setup code here, to run once:
Serial.begin(9600); // setup serial
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(analogRead(A0)); // print the value from the pot
delay(500);
}

Теперь откройте серийный монитор (В правом верхнем углу > Серийный монитор) и переверните горшок. Вы должны увидеть значение, отображаемое на последовательном мониторе. Полностью против часовой стрелки должно быть нуль, и полностью по часовой стрелке должно быть +1023:

Понг серийный монитор

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

OLED Тест

OLED Графика

OLED-дисплей немного сложнее в настройке. Сначала вам нужно установить две библиотеки, чтобы управлять дисплеем. Загрузите библиотеки Adafruit_SSD1306 и Adafruit-GFX с Github. Скопируйте файлы в папку с вашими библиотеками. Это зависит от вашей операционной системы:

  • Mac OS: / Пользователи / Имя пользователя / Documents / Arduino / библиотеки
  • Linux: / Главная / Имя пользователя / Sketchbook
  • Окна: / Пользователи / Arduino / библиотеки

Теперь загрузите тестовый эскиз. Идти к файл > Примеры > Adafruit SSD1306 > ssd1306_128x64_i2c. Это должно дать вам большой эскиз, содержащий много графики:

OLED Графика

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

Код

Теперь пришло время для кода. Я буду объяснять каждый шаг, так что пропустите до конца, если вы просто хотите запустить его. Это достаточное количество кода, поэтому, если вы не уверены, посмотрите эти 10 бесплатных ресурсов

научиться кодировать.

Начните с включения необходимых библиотек:

#include
#include
#include
#include 

SPI а также ПРОВОД две библиотеки Arduino для обработки связи I2C. Adafruit_GFX а также Adafruit_SSD1306 это библиотеки, которые вы установили ранее.

Далее настройте отображение:

Adafruit_SSD1306 display(4);

Затем настройте все переменные, необходимые для запуска игры:

int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)};
const int PIXEL_SIZE = 8, WALL_WIDTH = 4, PADDLE_WIDTH = 4, BALL_SIZE = 4, SPEED = 3;
int playerScore = 0, aiScore = 0, playerPos = 0, aiPos = 0;
char ballDirectionHori = 'R', ballDirectionVerti = 'S';
boolean inProgress = true;

В них хранятся все данные, необходимые для запуска игры. Некоторые из них хранят местоположение мяча, размер экрана, местоположение игрока и так далее. Обратите внимание, как некоторые из них Const Это означает, что они постоянны и никогда не изменятся. Это позволяет компилятору Arduino ускорить процесс.

Разрешение экрана и расположение мяча хранятся в массивы. Массивы представляют собой наборы похожих вещей, а для мяча хранят координаты (Икс а также Y). Доступ к элементам в массивах очень прост (не включайте этот код в свой файл):

resolution[1];

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

ball[1] = 15;

внутри void setup (), настроить отображение:

void setup()   {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.display();
}

Первая строка сообщает библиотеке Adafruit, какие измерения и протокол связи использует ваш дисплей (в данном случае 128 Икс 64 а также I2C). Вторая строка (display.display ()) говорит экрану показывать все, что хранится в буфере (что является ничем).

Создайте два метода с именем drawBall а также eraseBall:

void drawBall(int x, int y) {
display.drawCircle(x, y, BALL_SIZE, WHITE);
}
void eraseBall(int x, int y) {
display.drawCircle(x, y, BALL_SIZE, BLACK);
}

Эти берут Икс а также Y координаты шара и нарисовать его на экране, используя DrawCircle метод из библиотеки отображения. Это использует константу BALL_SIZE определено ранее. Попробуйте изменить это и посмотреть, что произойдет. Этот метод drawCircle принимает цвет пикселя — ЧЕРНЫЙ или же WHITE. Поскольку это монохромный дисплей (один цвет), белый цвет означает, что пиксель включен, а черный выключает пиксель.

Теперь создайте метод с именем moveAi:

void moveAi() {
eraseAiPaddle(aiPos);
if (ball[1] > aiPos) {
++aiPos;
}
else if (ball[1] < aiPos) {
--aiPos;
}
drawAiPaddle(aiPos);
}

Этот метод обрабатывает перемещение Искусственный интеллект или же искусственный интеллект игрок. Это довольно простой компьютерный противник - если мяч находится над веслом, двигайтесь вверх. Если оно под веслом, двигайтесь вниз. Довольно просто, но работает хорошо. Используются символы увеличения и уменьшения (++aiPos а также -aiPos) добавить или вычесть один из aiPosition. Вы можете добавить или вычесть большее число, чтобы заставить ИИ двигаться быстрее, и, следовательно, его будет сложнее победить. Вот как вы это сделаете:

aiPos += 2;

А также:

aiPos -= 2;

Плюс равно а также Минус равно знаки являются сокращением для сложения или вычитания двух из / в текущего значения aiPos. Вот еще один способ сделать это:

aiPos = aiPos + 2;

а также

aiPos = aiPos - 1;

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

drawNet Метод использует две петли для рисования сети:

void drawNet() {
for (int i = 0; i < (resolution[1] / WALL_WIDTH); ++i) {
drawPixel(((resolution[0] / 2) - 1), i * (WALL_WIDTH) + (WALL_WIDTH * i), WALL_WIDTH);
}
}

Это использует WALL_WIDTH переменные, чтобы установить его размер.

Создать методы с именем drawPixels а также erasePixels. Как и в случае с шариками, единственное различие между ними состоит в цвете пикселей:

void drawPixel(int posX, int posY, int dimensions) {
for (int x = 0; x < dimensions; ++x) {
for (int y = 0; y < dimensions; ++y) {
display.drawPixel((posX + x), (posY + y), WHITE);
}
}
}
void erasePixel(int posX, int posY, int dimensions) {
for (int x = 0; x < dimensions; ++x) {
for (int y = 0; y < dimensions; ++y) {
display.drawPixel((posX + x), (posY + y), BLACK);
}
}
}

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

drawScore Метод использует текстовые функции библиотеки, чтобы вывести на экран счет игрока и AI. Они хранятся в playerScore а также aiScore:

void drawScore() {
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(45, 0);
display.println(playerScore);
display.setCursor(75, 0);
display.println(aiScore);
}

Этот метод также имеет eraseScore аналог, который устанавливает пиксели на черный или выключен.

Последние четыре метода очень похожи. Они рисуют и стирают лопатки ИИ и игрока:

void erasePlayerPaddle(int row) {
erasePixel(0, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH);
erasePixel(0, row - PADDLE_WIDTH, PADDLE_WIDTH);
erasePixel(0, row, PADDLE_WIDTH);
erasePixel(0, row + PADDLE_WIDTH, PADDLE_WIDTH);
erasePixel(0, row + (PADDLE_WIDTH + 2), PADDLE_WIDTH);
}

Обратите внимание, как они называют erasePixel метод создания ранее. Эти методы рисуют и стирают соответствующие весла.

В основном цикле немного больше логики. Вот весь код:

#include
#include
#include
#include
Adafruit_SSD1306 display(4);
int resolution[2] = {128, 64}, ball[2] = {20, (resolution[1] / 2)};
const int PIXEL_SIZE = 8, WALL_WIDTH = 4, PADDLE_WIDTH = 4, BALL_SIZE = 4, SPEED = 3;
int playerScore = 0, aiScore = 0, playerPos = 0, aiPos = 0;
char ballDirectionHori = 'R', ballDirectionVerti = 'S';
boolean inProgress = true;
void setup()   {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.display();
}
void loop() {
if (aiScore > 9 || playerScore > 9) {
// check game state
inProgress = false;
}
if (inProgress) {
eraseScore();
eraseBall(ball[0], ball[1]);
if (ballDirectionVerti == 'U') {
// move ball up diagonally
ball[1] = ball[1] - SPEED;
}
if (ballDirectionVerti == 'D') {
// move ball down diagonally
ball[1] = ball[1] + SPEED;
}
if (ball[1] = resolution[1]) {
// bounce the ball off the bottom
ballDirectionVerti = 'U';
}
if (ballDirectionHori == 'R') {
ball[0] = ball[0] + SPEED; // move ball
if (ball[0] >= (resolution[0] - 6)) {
// ball is at the AI edge of the screen
if ((aiPos + 12) >= ball[1] && (aiPos - 12)  (aiPos + 4)) {
// deflect ball down
ballDirectionVerti = 'D';
}
else if (ball[1] < (aiPos - 4)) {
// deflect ball up
ballDirectionVerti = 'U';
}
else {
// deflect ball straight
ballDirectionVerti = 'S';
}
// change ball direction
ballDirectionHori = 'L';
}
else {
// GOAL!
ball[0] = 6; // move ball to other side of screen
ballDirectionVerti = 'S'; // reset ball to straight travel
ball[1] = resolution[1] / 2; // move ball to middle of screen
++playerScore; // increase player score
}
}
}
if (ballDirectionHori == 'L') {
ball[0] = ball[0] - SPEED; // move ball
if (ball[0] = ball[1] && (playerPos - 12)  (playerPos + 4)) {
// deflect ball down
ballDirectionVerti = 'D';
}
else if (ball[1]  playerScore) {
display.println("YOU  LOSE!");
}
else if (playerScore > aiScore) {
display.println("YOU  WIN!");
}
}
display.display();
}
void moveAi() {
// move the AI paddle
eraseAiPaddle(aiPos);
if (ball[1] > aiPos) {
++aiPos;
}
else if (ball[1] < aiPos) {
--aiPos;
}
drawAiPaddle(aiPos);
}
void drawScore() {
// draw AI and player scores
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(45, 0);
display.println(playerScore);
display.setCursor(75, 0);
display.println(aiScore);
}
void eraseScore() {
// erase AI and player scores
display.setTextSize(2);
display.setTextColor(BLACK);
display.setCursor(45, 0);
display.println(playerScore);
display.setCursor(75, 0);
display.println(aiScore);
}
void drawNet() {
for (int i = 0; i < (resolution[1] / WALL_WIDTH); ++i) {
drawPixel(((resolution[0] / 2) - 1), i * (WALL_WIDTH) + (WALL_WIDTH * i), WALL_WIDTH);
}
}
void drawPixel(int posX, int posY, int dimensions) {
// draw group of pixels
for (int x = 0; x < dimensions; ++x) {
for (int y = 0; y < dimensions; ++y) {
display.drawPixel((posX + x), (posY + y), WHITE);
}
}
}
void erasePixel(int posX, int posY, int dimensions) {
// erase group of pixels
for (int x = 0; x < dimensions; ++x) {
for (int y = 0; y < dimensions; ++y) {
display.drawPixel((posX + x), (posY + y), BLACK);
}
}
}
void erasePlayerPaddle(int row) {
erasePixel(0, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH);
erasePixel(0, row - PADDLE_WIDTH, PADDLE_WIDTH);
erasePixel(0, row, PADDLE_WIDTH);
erasePixel(0, row + PADDLE_WIDTH, PADDLE_WIDTH);
erasePixel(0, row + (PADDLE_WIDTH + 2), PADDLE_WIDTH);
}
void drawPlayerPaddle(int row) {
drawPixel(0, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH);
drawPixel(0, row - PADDLE_WIDTH, PADDLE_WIDTH);
drawPixel(0, row, PADDLE_WIDTH);
drawPixel(0, row + PADDLE_WIDTH, PADDLE_WIDTH);
drawPixel(0, row + (PADDLE_WIDTH + 2), PADDLE_WIDTH);
}
void drawAiPaddle(int row) {
int column = resolution[0] - PADDLE_WIDTH;
drawPixel(column, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH);
drawPixel(column, row - PADDLE_WIDTH, PADDLE_WIDTH);
drawPixel(column, row, PADDLE_WIDTH);
drawPixel(column, row + PADDLE_WIDTH, PADDLE_WIDTH);
drawPixel(column, row + (PADDLE_WIDTH * 2), PADDLE_WIDTH);
}
void eraseAiPaddle(int row) {
int column = resolution[0] - PADDLE_WIDTH;
erasePixel(column, row - (PADDLE_WIDTH * 2), PADDLE_WIDTH);
erasePixel(column, row - PADDLE_WIDTH, PADDLE_WIDTH);
erasePixel(column, row, PADDLE_WIDTH);
erasePixel(column, row + PADDLE_WIDTH, PADDLE_WIDTH);
erasePixel(column, row + (PADDLE_WIDTH * 2), PADDLE_WIDTH);
}
void drawBall(int x, int y) {
display.drawCircle(x, y, BALL_SIZE, WHITE);
}
void eraseBall(int x, int y) {
display.drawCircle(x, y, BALL_SIZE, BLACK);
}

Вот что у вас получилось:

OLED Pong

Если вы уверены в коде, вы можете внести множество изменений:

  • Добавить меню для уровней сложности (изменить AI и скорость мяча).
  • Добавьте случайное движение к мячу или искусственному интеллекту.
  • Добавьте еще один банк для двух игроков.
  • Добавьте кнопку паузы.

Теперь взгляните на эти ретро-игровые проекты Pi Zero

,

Вы кодировали Понг, используя этот код? Какие модификации вы сделали? Дайте мне знать в комментариях ниже, я хотел бы показывать некоторые фотографии!

Ссылка на основную публикацию