Auto Race 2023 - ретро-консоль на Arduino

7 Ноября

В какой-то момент на Ютьюбе начала выходить серия роликов "История портативок". В первом же видео там показали приставку Mattel Auto Race и сын уболтал меня сделать нечто подобное.

Mattel Auto Race

Mattel Auto Race

Идея

Оригинальная Mattel Auto Race - крайне простая по нынешним меркам приставка. Это top down гонялка, где машина игрока всегда находится внизу экрана, а трёхполосная трасса с другими машинами движется сверху вниз. Игрок управляет своей машиной трёхпозиционным переключателем. Также есть возможность менять сложность игры.

Сразу оговорюсь что обычно, когда я что-то делаю/мастерю, я стараюсь продумывать всё заранее. С этим проектом всё было иначе: я продумал лишь самую основу и в процессе постоянно докупал какие-то детали и подолгу их ждал. Поэтому вся разработка заняла около 4 месяцев и в конце у меня осталось несколько неиспользованных деталей.

Комплектующие

Для меня сразу было очевидно, что основой приставки станет Arduino. 10-летний заказчик хотел "очень" пиксельную игру, так что вместо экрана надо будет использовать LED-матрицу. Какую LED-матрицу я точно не знал. Сначала купил две матрицы 8х8 без платы и контроллера, но получив их понял, что в этом проекте точно их использовать не буду. Матрицу 8х16 на чипе AIP1640 я тоже купил наобум и, к счастью, она подошла. В какой-то момент встал вопрос что делать с игровым счётом и я решил выводить его на отдельном экране. Для этого была докуплена ещё одна семисегментная LED-матрица. Для питания выбрал два аккумулятора 18650, так как приставка должна быть портативной. Финальный список комплектующих выглядит так:

  • Arduino Nano
  • LED-матрица 8х16 на чипе AIP1640 (китайский клон матрицы от Keystudio)
  • Семисегментнfz LED-матрица на 4 цифры на чипе TM1637
  • Две кнопки OFF-(ON) для управления машиной
  • Два переключателя ON-OFF для выключения приставки и переключения сложности игры
  • Корпус для двух аккумуляторов 18650

Схема подключения Auto Race 2023

Схема подключения Auto Race 2023

Программирование

Первым тьюториалом, что я нашёл, была эта статья. Сейчас могу сказать, что это плохой опыт. Статья помогла быстро "подружиться" с матрицей, но код представленный там заставил мозг мой вскипеть. Проблема была в том, что изображение для матрицы надо было формировать сразу целиком, а потом можно было лишь его двигать. Думать пришлось много, но решение было найдено: можно на старте игры генерить всю трассу из нескольких заранее созданных паттернов с расставленными машинками. Полученная трасса двигалась бы по экрану, а при достижении края зацикливалась, с каждой новой итерацией ускоряя игру. Через какое-то время код был написал, игра игралась, вёлся подсчёт очков. И вроде всё норм, но... не нравилась мне эта игра. Как-то всё компромиссно.

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

С системой генерации машин на трассе пришлось поэкспериментировать. Полностью рандомный вариант себя не оправдал: через какое-то время машины могли слепиться в "гусеничку". Самый оптимальный вариант, что я придумал - каждая последующая машина появляется на некотором рандомном расстоянии от предыдущей. Переключатель сложности отвечает за то две или три машины одновременно будут на трассе. Позднее заметил небольшой глюк: при переключении со сложного (3 машины) на простой режим (2 машины), одна из машин на трассе исчезает. Не мешает вообще, так что не буду исправлять.

При обгоне игроком каждой машины, счётчик очков увеличивается на единицу, а сама игры ускоряется на 2%. В сложном режиме, из-за большего количества машин, увеличение скорости идёт быстрее. Также опытным путём подобрал оптимальную яркость главного экрана - минимальную, иначе слепит.

Скетч игры Auto Race 2023
#include <Adafruit_GFX.h>
#include <TM1640.h>
#include <TM1637.h>
#include <TM16xxMatrixGFX.h>
#include <TM16xxDisplay.h>

TM1637 module37(3, 4);	// DIO=3, CLK=4
TM16xxDisplay display37(&module37, 4);	// TM16xx object, 4 digits

TM1640 module(A4, A5);	// SDA=Analogue 4, SCL=Analogue 5
#define MATRIX_NUMCOLUMNS 16
#define MATRIX_NUMROWS 8
TM16xxMatrixGFX matrix(&module, MATRIX_NUMCOLUMNS, MATRIX_NUMROWS);	// TM16xx object, columns, rows



#define BUTTON_MODE 5	// Game mode switch
#define BUTTON_LEFT 6	// Move left button
#define BUTTON_RIGHT 7	// Move right button
int car_pos = 3;
int game_speed = 50;	// Initial game speed
int game_score = 0;

int enemy_x[3] = {0, 3, 6};
int cur_enemy[3][2] = {};

int enemies_count = 2;	// 2 for normal mode, 3 for hard mode
int game_maxspeed = 50;

void setup() {
  Serial.begin(115200);

  // Setup buttons
  pinMode(BUTTON_MODE, INPUT_PULLUP);
  pinMode(BUTTON_LEFT, INPUT_PULLUP);
  pinMode(BUTTON_RIGHT, INPUT_PULLUP);
  
  matrix.setIntensity(1); // Set brightness between 0 and 7
  randomSeed(analogRead(A0)); // Initialize random generator

  // Init first 3 cars
  cur_enemy[0][0] = enemy_x[random(3)];
  cur_enemy[1][0] = enemy_x[random(3)];
  cur_enemy[2][0] = enemy_x[random(3)];
  cur_enemy[0][1] = 16;
  cur_enemy[1][1] = 23 + random(3);
  cur_enemy[2][1] = 30 + random(3);
}

void loop() {
  // User car moving
  if (digitalRead(BUTTON_LEFT) == LOW) {
    if (car_pos > 0) {
      car_pos--;
    }
  }

  if (digitalRead(BUTTON_RIGHT) == LOW) {
    if (car_pos < 6) {
      car_pos++;
    }
  }

  // Game mode switch
  if (digitalRead(BUTTON_MODE) == HIGH) {
    enemies_count = 3;
  } else {
    enemies_count = 2;
  }

  // User car draw
  matrix.drawRect(car_pos, 0, 2, 3, HIGH);

  // Traffic cars draw
  for(char i = 0; i < enemies_count; i++) {
    matrix.drawRect(cur_enemy[i][0], cur_enemy[i][1], 2, 3, HIGH);
    cur_enemy[i][1]--;
    
    if (cur_enemy[i][1] <= -2) {
      if (i == 0) {
        cur_enemy[i][1] = cur_enemy[enemies_count-1][1] + 6 + random(5);
      } else {
        cur_enemy[i][1] = cur_enemy[i-1][1] + 6 + random(5);
      }

      if (cur_enemy[i][1] < 16) {
        cur_enemy[i][1] = 16;
      }      

      cur_enemy[i][0] = enemy_x[random(3)];
      game_score++;

      // Show updated game score  
      module37.clearDisplay();
      module37.setDisplayToDecNumber(game_score);
    }
  
    // Player and traffic collision check
    if (cur_enemy[i][1] <= 2 && (cur_enemy[i][0] == car_pos || cur_enemy[i][0] == (car_pos - 1) || cur_enemy[i][0] == (car_pos + 1))) {
      game_over();
    }
  }

  // Update main screen
  matrix.write(); 

  if(game_speed > game_maxspeed) {
    game_speed = 100 - game_score;
  }

  delay(game_speed);
  matrix.fillScreen(LOW);
}

void game_over () {
  // Reset all variables
  car_pos = 3;
  game_score = 0;

  // Draw Gameover screen
  matrix.fillScreen(HIGH);
  matrix.drawRect(1, 9, 2, 2, LOW);
  matrix.drawRect(5, 9, 2, 2, LOW);
  matrix.drawLine(2, 7, 5, 7, LOW);
  matrix.drawLine(1, 6, 2, 6, LOW);
  matrix.drawLine(5, 6, 6, 6, LOW);
  matrix.drawPixel(1, 5, LOW);
  matrix.drawPixel(6, 5, LOW);
  
  matrix.write(); 
  delay(1000);
}

Корпус

Оригинальный корпус сейчас кажется мне издевательством над игроком: асимметричный, играть можно только большим пальцем правой руки - жуть. Эргономику своего корпуса я частично подсмотрел у Brick Game. "Бугорки" под кнопками управления были сделаны как для удобства, так и чтобы выиграть место у достаточно высоких кнопок и сделать корпус в целом тоньше. Сначала я во Fusion 360 замоделил верхнюю половину, распечатал её и закрепил на ней все кнопки и экраны. Затем уже, зная где что и на сколько выступает, сделал нижнюю часть. Саму плату Arduino поставил так, что к ней есть доступ без разбора корпуса.

Корпус Auto Race 2023

Корпус Auto Race 2023

Модель корпуса в stl для печати можно скачать здесь:

Результат

Результат превзошёл ожидания. Достаточно примитивная консоль оказалась весьма интересной. Вдобавок, так как доступ к Arduino сохранён, можно позднее на неё запрограммировать ещё игр. Змейку, например. Корпус тоже получился отличным! Удобно сидит в руках, аккумуляторы снизу не мешают. По итогу я очень доволен результатом: сын получил уникальную игрушку, а я удовольствие от её создания.

Auto Race 2023Auto Race 2023 Auto Race 2023Auto Race 2023

Не накомментили ещё. Би зэ фёст!

Оставить комментарий

Цитировать
в комментарии