Прокачайте свою DIY-консоль: оживляем игру звуковыми эффектами и сохраняем лучшие результаты в памяти ESP32.
Что нового в версии v1.3?
Главная цель этого обновления — непредсказуемость. Теперь игроки не смогут просто заучить ритм; им придется реагировать на ситуацию в реальном времени.
Схема подключения
Краткое напоминание распиновки. Для звуковых эффектов обязательно используйте пассивный зуммер.
Важно: резистор на 100–220 Ом между GPIO 25 и плюсом зуммера нужен, чтобы ограничить ток и не перегружать порт ESP32
Тип зуммера: код использует функцию tone(), поэтому лучше всего подойдет пассивный зуммер (он позволяет менять тональность). Активный зуммер будет просто пищать на одной ноте.
| Компонент | Пин дисплея / кнопки | Пин ESP32 (GPIO) | Описание |
|---|---|---|---|
| OLED Display | VCC | 3.3V | Питание дисплея |
| OLED Display | GND | GND | Общий минус |
| OLED Display | SCL | GPIO 22 | Тактовая линия I2C |
| OLED Display | SDA | GPIO 21 | Линия данных I2C |
| Кнопка | Pin 1 | GPIO 15 | Сигнал прыжка |
| Кнопка | Pin 2 | GND | Замыкание на землю |
| Buzzer | Через резистор 100 Ом | GPIO 25 | Выход звукового сигнала |
| Buzzer | Negative (-) | GND | Общая земля |
Симуляция в Wokwi: Тестируем без железа
Перед тем как брать в руки паяльник, я рекомендую проверить всё в онлайн-симуляторе. Это сэкономит время на отладку.
Попробовать игру в браузере:
Разбор ключевых механик v1.3
Истинная случайность
Чтобы функция random() выдавала разные значения при каждом включении устройства, мы инициализируем её шумом с неиспользуемого аналогового пина:
randomSeed(analogRead(0));Логика появления препятствий по уровням
Мы реализовали сокращение «окна ожидания» по мере роста уровня. Это создает ощущение, что препятствий становится больше:
int maxSpawnDelay = (level >= 2) ? 40 : 90;
obstacleX = 128 + random(0, maxSpawnDelay);Полный исходный код: ESP32 Dino Run v1.3 (от AndiBond)
Скопируйте и вставьте этот код в ваш Arduino IDE.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define BUZZER_PIN 25
#define BUTTON_PIN 15
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Preferences preferences;
// Игровые параметры
int dinoY = 45;
int velocity = 0;
int gravity = 2;
bool isJumping = false;
int obstacleX = 128;
int score = 0;
int highScore = 0;
// Параметры сложности
int gameSpeed = 7;
int obstacleWidth = 6;
int obstacleHeight = 10;
int level = 1;
bool inverted = false;
// Прототипы функций
void showStartScreen();
void handleGameOver();
void drawGame();
void playSound(int freq, int duration);
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
randomSeed(analogRead(0)); // Инициализация случайных чисел
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for(;;);
}
preferences.begin("dino-game", false);
highScore = preferences.getInt("highscore", 0);
showStartScreen();
}
void playSound(int freq, int duration) {
tone(BUZZER_PIN, freq, duration);
}
void loop() {
// 1. ВВОД
if (digitalRead(BUTTON_PIN) == LOW && !isJumping) {
velocity = -12;
isJumping = true;
playSound(600, 80);
}
// 2. ФИЗИКА
if (isJumping) {
dinoY += velocity;
velocity += gravity;
if (dinoY >= 45) {
dinoY = 45;
isJumping = false;
}
}
// ЛОГИКА УРОВНЕЙ
level = (score / 10) + 1;
gameSpeed = 6 + level;
// Инверсия на 3-м уровне (30 очков)
if (level >= 3 && !inverted) {
inverted = true;
display.invertDisplay(true);
playSound(400, 200); delay(100); playSound(800, 200);
}
obstacleX -= gameSpeed;
// Появление нового препятствия
if (obstacleX < -20) {
score++;
// Случайная дистанция: плотнее на уровне 2+
int maxSpawnDelay = (level >= 2) ? 40 : 90;
obstacleX = 128 + random(0, maxSpawnDelay);
// Случайная высота
obstacleHeight = random(8, 16);
if (score % 10 == 0) playSound(1200, 50);
}
// Проверка столкновений
if (obstacleX > 15 && obstacleX < 25 && dinoY > (55 - obstacleHeight)) {
handleGameOver();
}
drawGame();
delay(30);
}
void showStartScreen() {
inverted = false;
display.invertDisplay(false);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(25, 5);
display.print("DINO RUN ESP32");
display.setCursor(18, 18);
display.print("v1.3 by AndiBond");
display.setCursor(28, 38);
display.print("HI-SCORE: ");
display.print(highScore);
display.setCursor(20, 54);
display.print("Press to Start");
display.display();
while(digitalRead(BUTTON_PIN) == HIGH) { delay(10); }
playSound(800, 100);
delay(200);
}
void handleGameOver() {
playSound(150, 600);
if (score > highScore) {
highScore = score;
preferences.putInt("highscore", highScore);
}
display.clearDisplay();
display.setCursor(10, 10);
display.setTextSize(2);
display.print("GAME OVER");
display.setTextSize(1);
display.setCursor(15, 35);
display.print("Score: "); display.print(score);
display.print(" Lv: "); display.print(level);
display.setCursor(30, 50);
display.print("HI-Score: "); display.print(highScore);
display.setCursor(30, 57);
display.print("andibond.com");
display.display();
delay(2500);
score = 0;
obstacleX = 128;
dinoY = 45;
showStartScreen();
}
void drawGame() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("L:"); display.print(level);
display.setCursor(35, 0);
display.print("S:"); display.print(score);
display.setCursor(80, 0);
display.print("HI:"); display.print(highScore);
display.drawLine(0, 55, 128, 55, WHITE);
display.fillRect(20, dinoY, 10, 10, WHITE);
display.fillRect(obstacleX, 55 - obstacleHeight, obstacleWidth, obstacleHeight, WHITE);
display.display();
}
AndiBond.com
Заключение
Мы прошли долгий путь от рисования одной точки на экране до создания полноценной игры с:
- Физикой прыжка и гравитацией.
- Системой рекордов в энергонезависимой памяти (NVS).
- Звуковой обратной связью.
- Случайной генерацией уровней.
Проект v1.3 от AndiBond — это отличная база для ваших собственных модификаций. Попробуйте добавить летающих врагов-птиц или бонусы для ускорения!
Делитесь вашими рекордами в комментариях и подписывайтесь на новые DIY-гайды!
Пропустили предыдущие части?
Поддержите проект AndiBond
Создание качественных гайдов, поиск рабочих решений и отладка кода занимают много времени. Все мои проекты остаются открытыми и бесплатными, чтобы каждый мог войти в мир электроники с минимальным порогом входа.
Если этот туториал сэкономил ваше время или помог запустить вашу первую игру на ESP32, вы можете поддержать развитие блога. Ваша поддержка помогает мне покупать новые датчики, дисплеи и контроллеры для будущих обзоров.
Каждый донат — это топливо для новых статей и видео. Спасибо, что вы со мной!»

