Общие сведения
В этом проекте мы соберём видеоигру на 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); } }
Обсуждение