Описание:
Игра 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
Обсуждение