Общие сведения
В этом проекте мы соберём видеоигру на Arduino. Управлять мы будем при помощи Trema модуля Потенциометр, а вывод будет осуществятся на матрицу 8x8 Flash-I2C. Игрок управляет бригадой, которая отлавливает падающие сверху камни оползня и выглядит эта бригада как платформа из трёх пикселей внизу игрового поля. При пропуске каждого камня ниже платформы игрок теряет одну жизнь и платформа уменьшается. При пропуске трёх камней игра заканчивается. Каждый пойманный камень - сто очков, очки мы будем выводить в последовательный порт. Каждую 1000 очков игрок получает бонус, если не полное количество жизней - жизнь добавляется.
Видео
Нам понадобится
- Arduino/Piranha UNO
- Trema Shield
- Trema модуль - Патенциометр
- Trema модуль - Матрица 8x8 Flash-I2C
- Trema модуль - Зуммер со встроенным генератором
Подключение
Устанавливаем Trema Shield на Piranha UNO

Подключаем модули

Скетч проекта
Необходимо установить библиотеку iarduino_I2C_Matrix_8x8. Если Вы не знаете как устанавливать библиотеки в Arduino IDE - Вы можете узнать по этой ссылке
// Подключаем библиотеку для работы с аппаратной шиной I2C, до подключения библиотеки iarduino_I2C_Matrix_8x8.
#include <Wire.h>
#include <iarduino_I2C_Matrix_8x8.h>
// Определяем количество жизней
#define LIFES 3
// Определяем интервал смены игрового поля
#define SET_INTERVAL 500
// Определяем вывод зуммера
#define ZUM 2
// Определяем сложность игры, меньше - сложнее
#define SET_DIFICULTY 20
iarduino_I2C_Matrix_8x8 disp;
// Переменная для хранения очков
unsigned long score;
// Переменная для хранения millis начала звука зуммера
unsigned long sound_millis;
// Сложность игры (растёт с каждой 1000 очков)
uint8_t difficulty = 0;
// Насколько быстрее становится игра при смене уровня
const byte increase_rate = 10;
// Флаг работы зуммера
bool sound_flag;
// Переменные интервалов
unsigned long interval;
unsigned long old_millis;
unsigned long sound_interval;
// Текущий ряд (если 7, то остаётся 7 до конца игры)
uint8_t row;
// Переменная жизней игрока
byte life_insurance;
// Состояние игры
byte game_state;
// Возможные состояния игры
enum {
wait,
play,
game_over
};
// Массивы дисплея и игрового поля
byte game_field[8];
uint8_t matrix[8];
// Положение игрока
byte player_pos;
void setup()
{
Serial.begin(9600);
Serial.println("start");
pinMode(ZUM, INPUT);
disp.begin();
// Сбрасываем количество жизней и игровую матрицу
reset();
// Инициируем переменные
sound_millis = 0;
sound_interval = 100;
sound_flag = false;
old_millis = 0;
game_state = wait;
}
void loop()
{
// Считываем потенциометр
byte old_pot = readPot();
// Если игра ожидает, просто ортисовываем игрока
if (game_state == wait)
drawPlayer();
// Если состояние в игре - запускаем игру
if (game_state == play)
game();
// Если игра окончена - сбрасываем всё в изначальное состояние
if (game_state == game_over) {
delay(2000);
reset();
delay(1000);
old_pot = readPot();
game_state = wait;
}
// Выводим картинку на модуль матрицы
disp.drawImage(matrix);
// Небольшая задержка для устранения джиттера потенциометра
delay(50);
// Елси потенциометр был повёрнут - переходим в режим игры
if (old_pot != readPot() && game_state == wait)
game_state = play;
}
void reset()
{
disp.clrScr();
for (auto& i:game_field)
i = 0;
row = 0;
for (auto& i:matrix)
i = 0;
score = 0;
interval = SET_INTERVAL;
life_insurance = LIFES;
}
void game()
{
// Если пришло время обновлять матрицу
if (millis() - old_millis > interval) {
// Запоминаем это время
old_millis = millis();
// Вызываем функцию измененя игрового поля
progress(row);
// Издаём звук
advanceSound();
// Елси игрок не поймал пиксель
if (!checkCollision()) {
// Отнимаем одну жизнь
life_insurance--;
// Проигрываем звук
missedSound();
}
// Иначе, если в последнем ряду есть пиксель врага
else if (row == 7 && game_field[7] != 0) {
// прибавляем очки
score += 100;
// Если количество очков кратно 1000
if (!(score % 1000)) {
// Усложняем игру
if (difficulty < SET_DIFICULTY - 7)
difficulty++;
rewardAndPunish();
levelUpSound();
}
Serial.println((String)"Score:\t" + score);
}
// Если жизни закончились - конец игры
if (!life_insurance)
gameOver();
// Приращиваем ряды
if (row < 7)
row++;
}
// Обновляем матрицу
for (auto i = 0; i < sizeof(matrix); i++) {
matrix[i] = game_field[i];
}
// Выводим пиксели игрока на матрицу
drawPlayer();
// Прерываем зуммер, если подошло время
shutUp();
}
// Добавляем жизнь, если меньше 3 и увеличиваем скорость игры.
void rewardAndPunish()
{
interval -= increase_rate;
if (life_insurance < 3)
life_insurance++;
}
void drawPlayer()
{
player_pos = readPot();
matrix[7] = player_pos;
}
byte readPot()
{
uint8_t pot_value = map(analogRead(A0), 0, 1023, 7, 0);
byte _player_pos;
_player_pos ^= _player_pos;
bitSet(_player_pos, pot_value);
if (life_insurance > 2)
bitSet(_player_pos, pot_value+1);
if (life_insurance > 1)
bitSet(_player_pos, pot_value-1);
return _player_pos;
}
uint8_t generate()
{
uint8_t rnd = random(SET_DIFICULTY - difficulty);
if (rnd > 8)
return 0;
uint8_t prj;
prj ^= prj;
bitSet(prj, rnd);
return prj;
}
void progress(uint8_t n)
{
if (n == 0) {
game_field[n] = generate();
return;
}
else {
game_field[n] = game_field[n-1];
progress(--n);
}
}
bool checkCollision()
{
byte test = 0xFF;
if (row == 7 && game_field[7] != 0)
test = player_pos & game_field[7];
return !!test;
}
void gameOver()
{
game_state = game_over;
gameoverSound();
}
void makeNoise()
{
pinMode(ZUM, OUTPUT);
digitalWrite(ZUM, HIGH);
}
void shutUp()
{
if (millis() - sound_millis > sound_interval) {
pinMode(ZUM, INPUT);
sound_flag = false;
}
}
void advanceSound()
{
if (!sound_flag) {
makeNoise();
sound_millis = millis();
sound_interval = 100;
sound_flag = true;
}
}
void missedSound()
{
if (!sound_flag) {
makeNoise();
sound_millis = millis();
sound_interval = 300;
sound_flag = true;
}
}
void gameoverSound()
{
for (int i = 0; i < 4; i++) {
missedSound();
delay(300);
}
}
void levelUpSound()
{
for (int i = 0; i < 2; i++) {
advanceSound();
if (i)
break;
delay(50);
}
}

Обсуждение