Описание:
Игра Space Invader – одна из первых супер-популярных аркадных игр, выпущенная компанией Taito в 1978 году. Игра проложила путь для жанра шутем ап (shoot'em up), и упоминания о ней повсеместны в массовой культуре, начиная от ТВ-сериалов и журналов и заканчивая самодельными футболками с иконическим изображением пришельца. Многие публикации используют этот символ как обозначение видеоигр в общем. И это неудивительно — Space Invader считается первым блокбастером среди видеоигр. Только к концу 1978г было изготовлено и продано сто тысяч аркадных машин! И это не пластиковые диски с видеоигрой, это огромные шкафы с ЭЛТ экраном.
Наши «космические пришельцы» основаны на скетче, написанным Элвином Ли в 2012 году и являются своеобразной пародией на Space Invaders. Мы сделали наших «пришельцев» больше и лучше. Теперь вместо одной матрицы восемь на восемь, мы используем четыре. Можем мы себе это позволить благодаря тому, что нам не надо развертывать каждую матрицу выводами Arduino, мы используем Trema-модули светодиодных матриц, которые работают по шине I2C (айтуси). Используя нашу библиотеку для этих матриц мы быстро адаптировали и расширили оригинальный скетч, и даже добавили звуков.
Логика игры использует двух-мерные и трех-мерные массивы и 2-х килобайт оперативной памяти Arduino UNO для их хранения уже не хватает, поэтому мы взяли за основу наш сверх-секретный прототип Piranha ULTRA, которая вот-вот должна появиться в продаже и идеально подойдет, если нужен форм-фактор UNO. С ULTRA можно четырёх-кратно расширить возможности UNO по статической памяти и восьми-кратно по динамической.
Вернёмся к игре: у игры 3 уровня, 3-й уровень — это босс, пройдя которого игра начинается сначала, но уже на более высокой скорости. Так называемый 2-й луп. С каждым лупом сложность игры растёт за счёт увеличения скорости всей игры (переменная loopDelay). Данный проект идеально подойдёт, если вы всегда хотели научиться делать игры, но не знали с чего начать.
Видео:
Нам понадобится:
- 1х Piranha ULTRA
- 1x Trema Shield
- 1x Battery Shield
- 2x Trema-модуль LED Матрица 8х8 синяя
- 2x Trema-модуль LED Матрица 8х8 красная
- 1x Trema-модуль I2C Hub
- 1x Trema-модуль Кнопка
- 1x Trema-модуль Слайдер
- 1х Trema-модуль Зуммер
- 1x 4-х проводной шлейф «мама-мама»
Для реализации проекта нам понадобится установить библиотеки:
- Библиотека iarduino_I2C_Matrix_8x8
- Библиотека Battery_Shield
О том, как устанавливать библиотеки, Вы можете ознакомиться на странице Wiki - Установка библиотек в Arduino IDE.
Подключение:
Для удобства подключения мы воспользуемся Trema Shield для Arduino и i2C Hub (Trema-модуль) для Arduino. Так же нам понадобятся: Trema-модуль Кнопка, Trema-модуль Слайдер, Trema-модуль Зуммер, 3 штуки 3-проводных шлейфов «мама-мама» (в комплекте с зуммером, слайдером, кнопкой), 5 штук 4-проводных шлейфов «мама-мама» (4 в комплекте, по одному с каждой LED Матрицей), 4 штуки Trema-модуль LED Матрица 8x8 и Battery Shield.
Перед подключением необходимо настроить адреса матриц. Это можно сделать по этой инструкции.
Для начала подключим Trema Shield к Battery Shield:

Теперь подключим все провода и модули к Trema Shield:

Прежде чем загружать скетч, необходимо скачать и установить библиотеку для матриц I2C.
Код игры:
// основано на *Space Vaders*, Elwin Lee, elwinlee.com
#include <Wire.h>
#include <Battery_Shield.h>
#include <iarduino_I2C_Matrix_8x8.h>
iarduino_I2C_Matrix_8x8 disp[] = {0x10, 0x11, 0x12, 0x13};
#define NOTE_C4 262
#define NOTE_A4 440
#define NOTE_F5 698
// матрица для временного хранения массива экрана:
byte tempMatrix[16][16] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
// заставка первого уровня:
#define level1_ani { \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
}
// первый уровень:
#define level1 { \
{0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0}, \
{0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, \
{0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0}, \
{0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} \
}
// заставка второго уровня:
#define level2_ani { \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
}
// второй уровень:
#define level2 { \
{0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}, \
{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0}, \
{0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, \
{0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0}, \
{0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} \
}
// заставка третьего уровня:
#define level3_ani { \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
}
// третий уровень:
#define level3 { \
{0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, \
{0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0}, \
{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0}, \
{0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0}, \
{0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0}, \
{0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} \
}
// экран выигрыша:
#define win { \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1}, \
{1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1}, \
{1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1}, \
{1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1}, \
{1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1}, \
{1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1}, \
{1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, \
{1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, \
{1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, \
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} \
}
//
const uint8_t sizeX = 16; //размер экрана
const uint8_t sizeY = 16; //размер экрана
uint8_t pixels[sizeY][sizeX]; //массив пикселей для матриц
uint8_t loopDelay = 32;
bool levelAni = true;
bool levelStart = false;
bool levelComplete = false;
bool fireSoundPlayed = false;
bool speedIncreasedFlag = false;
int levelsArrayIndex = 0;
const uint8_t _lvls = 7;
byte levels[_lvls][sizeY][sizeX] = {
level1_ani, level1, level2_ani, level2, level3_ani, level3, win
};
//copy of levels array for resetting purposes
byte initial[_lvls][sizeY][sizeX] = {
level1_ani, level1, level2_ani, level2, level3_ani, level3, win
};
// звуки
int boom = NOTE_C4;
boolean loseSound = true;
int lose[] = { NOTE_F5, NOTE_A4, NOTE_C4 };
int loseNoteDurations[] = { 2, 4, 4};
uint16_t muteTimer = 3000;
bool mute = false;
// таймеры
unsigned long aniTime;
unsigned long completeTime;
unsigned long enemyTime;
unsigned long gameOverTime;
unsigned long muteMillis;
unsigned long bossTimer;
// игрок
uint8_t bulletRow = sizeY - 3;
int bulletCol;
byte bulletArray[6];
int bulletDelayCount = 0;
boolean fired = false;
// выводы управления
const uint8_t btnPin = 3;
const uint8_t potPin = A3;
const uint8_t spkPin = 6;
int potVal;
boolean btnDown = false;
// враги
boolean enemyWon = false;
boolean enemyWaiting = false;
boolean enemyFired = false;
boolean enemyBulletCollision = false;
int enemyAttackSpeed[7] = {0, 150, 0, 100, 0, 25, 0};
int enemyBulletSpeed[7] = {0, 12, 0, 8, 0, 2, 0};
int enemyBulletRow;
int enemyBulletDelayCount = 0;
int enemyBulletArray[8][2] = { {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0} };
int enemyBottomCount = 0;
int randomBullet;
// флаги сдвига врагов
boolean shiftLevel = false;
boolean shiftLeft = true;
boolean shiftDown = false;
boolean shiftRight = false;
boolean shiftUp = false;
int shiftCount = 0;
int shiftSpeed[2] = {70, 35};
// переменные для вывода в матрицы 8х8
byte matrix1[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
byte matrix2[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
byte matrix3[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
byte matrix4[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
void setup() {
delay(500);
// создаём объекты LED матриц:
uint8_t k = sizeof(disp)/sizeof(iarduino_I2C_Matrix_8x8);
// инициируем матрицы:
for(int i = 0; i < k; i++){
disp[i].begin();
disp[i].clrScr();
disp[i].angle(270);
disp[i].bright(255);
disp[i].fps(120);
}
// устанавливаем вывод кнопки и слайдера на ввод:
pinMode(btnPin, INPUT);
pinMode(potPin, INPUT);
//устанавливаем таймер анимации уровня:
aniTime = millis();
}
void loop() {
// заполнение первой матрицы:
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (pixels[row][col] == 1) {
bitSet(matrix1[row], col);
} else {
bitClear(matrix1[row], col);
}
}
}
// заполнение второй матрицы:
for (int row = 8; row < 16; row++) {
for (int col = 0; col < 8; col++) {
if (pixels[row][col] == 1) {
bitSet(matrix2[row-8], col);
} else {
bitClear(matrix2[row-8], col);
}
}
}
// заполнение третьей матрицы:
for (int row = 0; row < 8; row++) {
for (int col = 8; col < 16; col++) {
if (pixels[row][col] == 1) {
bitSet(matrix3[row], col-8);
} else {
bitClear(matrix3[row], col-8);
}
}
}
//заполнение четвёртой матрицы:
for (int row = 8; row < 16; row++) {
for (int col = 8; col < 16; col++) {
if (pixels[row][col] == 1) {
bitSet(matrix4[row-8], col-8);
} else {
bitClear(matrix4[row-8], col-8);
}
}
}
setPattern(levelsArrayIndex); // создаём уровни
if (levelAni) playLevelAni(); // проигрываем заставку
if (levelStart) readPot(); // считываем слайдер
if (levelStart) refreshPlayer(); // обновляем положение игрока, опираясь на слайдер
if (levelStart) readBtn(); // считываем кнопку
if (levelStart && !enemyWon) readEnemy(); // узнаём в каком ряду есть ближайший к игроку враг, и стреляем в игрока
if (levelStart && !levelComplete) checkLevelState(); // проверяем наличие врагов
if (levelComplete) levelFinished(); // уровень выигран!
if (levelStart && shiftLevel) shiftRow(); // сдвигаем пиксели уровня
if (enemyWon) gameOver(); // проигрываем, если в игрока попали
updatePixels(); // обновляем пиксели игровой матрицы
disp[0].drawImage(matrix3); // выводим игровое поле по четвертям на матрицы 8х8
disp[1].drawImage(matrix2); //
disp[2].drawImage(matrix1); //
disp[3].drawImage(matrix4); //
delay(loopDelay); // задержка, чтоб игра не была слишком быстрой
if(levelsArrayIndex == 7 && !speedIncreasedFlag){ // если игра пройдена (луп),
increaseSpeed(); // увеличиваем скорость
}
if(levelsArrayIndex == 0){ // при переходе на новый луп
speedIncreasedFlag = false; // сбрасываем флаг увеличения скорости
}
}
void increaseSpeed(){
if (loopDelay > 0) loopDelay -= 16;
speedIncreasedFlag = true;
}
// копирование пикселей из временной матрицы:
void setPattern(int pattern) {
if (levelsArrayIndex < 7) { //if not last level
for (int i = 0; i < sizeY; i++) {
for (int j = 0; j < sizeX; j++) {
if ( levels[levelsArrayIndex][i][j] == 1 ) { //copy only "ones" to temporary matrix
tempMatrix[i][j] = levels[levelsArrayIndex][i][j];
}
}
}
} else {
restart(); //restart if end of the game has been reached
}
}
// функция считывания слайдера:
void readPot() {
potVal = analogRead(potPin); // считываем слайдер
delay(1); // ждём одну миллисекунду
int potVal2 = analogRead(potPin); // считываем опять
potVal = (potVal + potVal2)/2; // среднее арифметическое, для уменьшения "джитера"
potVal = map(potVal, 1023, 0, 0, sizeX-3); // маппим значение слайдера на положение игрока
}
// функция обновления пикселей игрока:
void refreshPlayer() {
byte _playerRows[2][sizeX] = { //временная матрица для хранения положения игрока
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
//draw player pixels
_playerRows[0][potVal+1] = 1;
_playerRows[1][potVal] = 1;
_playerRows[1][potVal+1] = 1;
_playerRows[1][potVal+2] = 1;
for (int i = sizeY-2; i < sizeY; i++) {
for (int j = sizeX-1; j >= 0; j--) {
if(_playerRows[i-(sizeY-2)][j] == 1) {
tempMatrix[i][j] = _playerRows[i-(sizeY-2)][j];
}
}
}
}
// функция считывания кнопки:
void readBtn() {
int btnPress = digitalRead(btnPin); // считываем кнопку
if (btnPress == 1 && !fired && !btnDown){ // если кнопка нажата
bulletCol = potVal+1; // записываем из какой колонки стрелять
btnDown = true;
fired = true;
if (!mute) playFireSound();
} else if (!btnPress) {
muteMillis = millis();
}
if (btnDown) {
if (btnPress == 0) {
btnDown = false;
}
}
if (millis() - muteMillis > muteTimer){
mute = !mute;
muteMillis = millis();
}
if (fired) {
shoot(bulletCol); //стреляем
}
}
// фунуция проигрывания звука выстрела:
void playFireSound(){
for (int i = 1200; i > 250; i--){
tone(spkPin, i, 1);
}
noTone(spkPin);
}
// функция стрельбы игрока:
void shoot(int _bulletCol) {
//if (bulletDelayCount == 1) { //раскомментировать, если нужно увеличить сложность
bulletRow--; // ^
if (bulletRow < 0) { //если снаряд достиг конца матрицы |
bulletRow = sizeY-3; //сбрасываем положение выстрела |
fired = false; // |
} else { // |
if (tempMatrix[bulletRow][_bulletCol] == 0) { //если следующая позиция свободна |
tempMatrix[bulletRow][_bulletCol] = 1; //снаряд на переходит на эту позицию |
} else { //если нет, |
collisionBullet(bulletRow, _bulletCol); //снаряд попал |
} // |
} // |
//}-------------------------------------------------------------------------------------------------------------------|
}
// функция обнаружения попадания:
void collisionBullet(int _row, int _col) {
if (!mute){
for (int i = 0; i < 1000; i++){
tone(spkPin, random(2000, 10000), 0.25); // звук удара снаряда о врага
}
}
noTone(spkPin);
levels[levelsArrayIndex][_row][_col] = 0; //удаляем убитый пиксель из матрицы
tempMatrix[_row][_col] = 0; // обновляем временную матрицу
bulletRow = sizeY-3; //устанавливаем положение выстрела над игроком
fired = false; //обнуляем флаг выстрела
}
// функция проигрывания звука смерти игрока:
void playDeathSound() {
for (int i = 0; i < 2000; i++){
tone(spkPin, random(2000, 10000), 0.25);
}
for (int i = 0; i < 1000; i++){
tone(spkPin, random(2000, 10000), 0.5);
}
for (int i = 0; i < 1000; i++){
tone(spkPin, random(1000, 5000), 0.5);
}
for (int i = 0; i < 2000; i++){
tone(spkPin, random(5000), 0.5);
}
noTone(spkPin);
}
// функция анимации врагов:
void readEnemy() {
// в этой функции все комментарии оставлены авторские
if (!enemyWaiting && !enemyFired && !enemyBulletCollision){ //if enemy isn't doing anything
enemyTime = millis(); //start timer
enemyWaiting = true;
}
if (enemyWaiting) {
if (millis() - enemyTime > enemyAttackSpeed[levelsArrayIndex]) { //attack speed for each level according to array
int _row = 9; //temp row value
boolean dobreak = false; //for double break;
//loop to get the enemy's pixels of highest row (closest to the player)
for (int i = 9; !dobreak && i >= 0; i--) { //backwards loop to get highest row
for (int j = 0; j < sizeX; j++) {
if(levels[levelsArrayIndex][i][j] == 1) { //if a "one" is found in the row
_row = i; //save row index
enemyBulletRow = _row + 1; //value to put enemy bullet 1 row below enemy
dobreak = true; //double break;
break;
}
}
}
enemyBottomCount = 0; //counter for number of "one" pixels in highest row (closest to the player)
for (int c = 0; c < sizeX; c++) { //loop through highest row
if(levels[levelsArrayIndex][_row][c] == 1) { //check if column contains "one"
enemyBulletArray[enemyBottomCount][0] = _row;
enemyBulletArray[enemyBottomCount][1] = c;
enemyBottomCount++;
}
}
fireSoundPlayed = false;
enemyWaiting = false;
enemyFired = true; //fire enemy bullet
randomBullet = random(enemyBottomCount); //randomly select one of the column pixels
}
}
if (enemyFired) {
if (enemyBulletDelayCount == (enemyBulletSpeed[levelsArrayIndex]-1)) { //enemy bullet speed (higher value = slower)
enemyBulletArray[randomBullet][0]++; //row + 1
if (!fireSoundPlayed && !mute){
playEnemyFireSound();
fireSoundPlayed = true;
} else if(!mute) playEnemyBulletSound();
if (enemyBulletArray[randomBullet][0] > sizeY - 1) { //if bullet reaches bottom
enemyBulletDelayCount = 0; //reset and remove bullet
enemyFired = false;
} else {
if (tempMatrix[enemyBulletArray[randomBullet][0]] [enemyBulletArray[randomBullet][1]] == 0) { //if next row is empty
tempMatrix[enemyBulletArray[randomBullet][0]] [enemyBulletArray[randomBullet][1]] = 1; //bullet claims row
} else { //bullet hit something!!
if (!levelComplete) {
enemyBulletDelayCount = 0;
enemyFired = false;
enemyBulletCollision = true;
}
}
}
} else {
tempMatrix[enemyBulletArray[randomBullet][0]][enemyBulletArray[randomBullet][1]] = 1;
}
enemyBulletDelayCount = (enemyBulletDelayCount+1) % enemyBulletSpeed[levelsArrayIndex]; //enemy bullet speed
}
if (enemyBulletCollision) {
if (enemyBulletArray[randomBullet][0] == sizeY-2 || enemyBulletArray[randomBullet][0] == sizeY-1) { //check if enemy's bullet hit player
enemyWon = true;
gameOverTime = millis();
}
enemyBulletCollision = false;
}
}
// функция проигрывания звука выстрелов врага:
void playEnemyFireSound(){
for(int i = 1500; i > 750; i--){
tone(spkPin, i, 0.5);
}
noTone(spkPin);
}
// функция проигрывания звука полёта пули врагов:
void playEnemyBulletSound(){
for(int i = 1000; i > 900; i--){
tone(spkPin, i, 0.1);
}
noTone(spkPin);
}
// функция сдвига босса:
void shiftBoss(){
int j,k;
byte _levelTemp[9][sizeX] = { //временная матрица для хранения пикселей босса
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
for (int i = 0; i < 9; i++) { // загружаем пиксели в матрицу
for (int j = 0; j < sizeX; j++) {
_levelTemp[i][j] = levels[levelsArrayIndex][i][j];
}
}
for (int i = 0; i < 9; i++){ // переворачиваем положение босса
for (j = (sizeX - 1), k = 0; j >= 0; k++, j--){
levels[levelsArrayIndex][i][k] = _levelTemp[i][j];
}
}
}
// функция сдвига врагов (2-й и 3-й уровни):
void shiftRow() {
int _speed;
if (levelsArrayIndex == 3) {
_speed = shiftSpeed[0];
} else if (levelsArrayIndex == 5) {
_speed = shiftSpeed[1];
}
byte _levelTemp[9][sizeX] = { // создаём временную матрицу для хранения положения врагов
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
for (int i = 0; i < 9; i++) { // загружаем в неё врагов
for (int j = 0; j < sizeX; j++) {
_levelTemp[i][j] = levels[levelsArrayIndex][i][j];
}
}
if (shiftCount == (_speed-1)) { //интервал сдвига сдвигом
if (shiftLeft == true) { //сдвиг влево
for (int i = 0; i < 9; i++) {
for (int j = 0; j < sizeX; j++) {
if (j == sizeX-1) {
levels[levelsArrayIndex][i][sizeX-1] = _levelTemp[i][0];
} else {
levels[levelsArrayIndex][i][j] = _levelTemp[i][j+1];
}
}
}
shiftCount = 0;
shiftLeft = false;
shiftDown = true;
} else if (shiftDown == true) { //сдвиг вниз
for (int i = 0; i < 9; i++) {
for (int j = 0; j < sizeX; j++) {
if ( i == 0 ) {
levels[levelsArrayIndex][i][j] = 0;
} else {
levels[levelsArrayIndex][i][j] = _levelTemp[i-1][j];
}
}
}
shiftDown = false;
shiftRight = true;
} else if (shiftRight == true) { //сдвиг вправо
for (int i = 0; i < 9; i++) {
for (int j = sizeX-1; j >= 0; j--) {
if (j == 0) {
levels[levelsArrayIndex][i][0] = _levelTemp[i][sizeX-1];
} else {
levels[levelsArrayIndex][i][j] = _levelTemp[i][j-1];
}
}
}
shiftCount = 0;
shiftRight = false;
shiftUp = true;
} else if (shiftUp == true) { //сдвиг вверх
for (int i = 0; i < 9; i++) {
for (int j = 0; j < sizeX; j++) {
if ( i == 8 ) {
levels[levelsArrayIndex][i][j] = 0;
} else {
levels[levelsArrayIndex][i][j] = _levelTemp[i+1][j];
}
}
}
shiftUp = false;
shiftLeft = true;
}
}
shiftCount = (shiftCount+1) % _speed;
if ((millis() - bossTimer > 8000) && levelsArrayIndex == 5){
shiftBoss();
bossTimer = millis();
}
}
// функция включения сдвига врагов и показа заставки уровня:
void playLevelAni() {
if (millis() - aniTime < 3000) { // время перехода от номера уровня к игре
} else {
levelAni = false;
levelStart = true;
levelsArrayIndex++;
if (levelsArrayIndex == 3 || levelsArrayIndex == 5) { // если второй или третий уровень
shiftLeft = true; // включаем анимацию
shiftDown = false; //
shiftRight = false; //
shiftUp = false; //
shiftLevel = true; //
} else {
shiftLevel = false;
}
}
}
// функция проверки завершённости уровня (есть ли кто живой?):
void checkLevelState() {
int _countLevelPixels = 0;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < sizeX; j++) {
if(levels[levelsArrayIndex][i][j] == 1){
_countLevelPixels++; // считаем сколько осталось "единиц"
}
}
}
if((_countLevelPixels) == 0){ // если не осталось - мы выиграли!
completeTime = millis();
levelComplete = true;
}
}
// функция завершения уровня:
void levelFinished() {
if (!mute) winSound();
if (millis() - completeTime > 2000) { // время перед запуском следующего уровня
//reset all booleans
enemyWaiting = false;
enemyFired = false;
enemyBulletCollision = false;
levelStart = false;
levelComplete = false;
levelAni = true;
aniTime = millis();
levelsArrayIndex++;
}
}
// функция обновления пикселей
void updatePixels() {
for (int i = 0; i < sizeY; i++) {
for (int j = 0; j < sizeX; j++) {
pixels[i][j] = tempMatrix[i][j];
}
}
//reset all values for temp matrix
for (int i = 0; i < sizeY; i++) {
for (int j = 0; j < sizeX; j++) {
tempMatrix[i][j] = 0;
}
}
}
// функция проигрывания мелодии выигрыша
void winSound(){
for (int thisNote = 0; thisNote < 3; thisNote++) {
int noteDuration = 250/loseNoteDurations[thisNote];
tone(spkPin, lose[thisNote], noteDuration);
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
noTone(spkPin);
}
}
// функция конца игры
void gameOver() {
if( loseSound) {
loseSound = false;
if (!mute) playDeathSound();
loopDelay = 32;
}
if (millis() - gameOverTime < 3000) { // время до перезапуска
delay(250);
} else {
restart();
}
}
// функция перезапуска игры
void restart() {
// сбрасываем флаги:
enemyWon = false;
enemyWaiting = false;
enemyFired = false;
enemyBulletCollision = false;
levelAni = true;
levelStart = false;
levelComplete = false;
levelsArrayIndex = 0;
loseSound = true;
// сбрасываем уровни:
for (int level = 0; level<_lvls; level++) {
for (int x = 0; x < sizeY; x++) {
for (int y = 0; y < sizeX; y++) {
levels[level][x][y] = initial[level][x][y];
}
}
}
aniTime = millis();
}
Ссылки:
- Wiki - Установка библиотек в Arduino IDE
- Библиотека iarduino_I2C_Matrix_8x8
- Библиотека Battery_Shield
- Wiki Battery Shield
- Wiki Trema Shield
- Wiki Ползунковый потенциометр
- Wiki Матрица 8х8
- Wiki Кнопка
- Wiki i2C Hub
- Wiki Зуммер
- Elwin Lee's Space Invaders 8x8

Обсуждение