Игровая ТВ-приставка на Arduino. Часть 2

Приступим к созданию игры с условным названием «Арифметический сборщик». Игрок управляется джойстиком с возможностью перемещения по полю размером 128×90. При нулевых отклонениях джойстика игрок находится в центре. Максимальное отклонение джойстика соответствует максимальному перемещению игрока. С определенным интервалом времени генерируются объекты-цифры, которые движутся сверху вниз. По достижении нижнего положения экрана объект-цифра исчезает, уменьшая количество баллов игрока на величину данной цифры. Если игрок перехватывает объект-цифру на экране, то это приводит к исчезновению объекта-цифры и увеличению счетчика баллов игрока. С определенной периодичностью значок игрока меняет свое значение с цифры 0 до 9. При перехвате объекта-цифры счетчик баллов игрока увеличивается на сумму, равную сумме объекта-цифры и цифры игрока, если цифра игрока равняется цифре объекта-цифры, то счетчик баллов игрока увеличивается на произведение цифр. По достижении определенных порогов баллов, происходит переход на более высокий уровень игры, что приводит к увеличению скорости движения и скорости генерации объектов-цифр. Кроме того с 4 уровня игры столкновение игрока с объектом-цифрой приводит не только к увеличению счетчика игрока, но и уменьшению (если цифра игрока меньше цифры объекта-цифры). Если количество баллов игрока становится меньше 0, игра начинается сначала.

Вот видео того, что получилось

Скачать архив со скетчем и файлами библиотеки TVOut можно по ссылке

И сам процесс создания игры

Создание переменных игры

Для управления игрой создадим объекты для хранения текущего положения игры. Символ, отображающий игрока, и символы, отображающие объекты-цифры, выводятся как текстовая информация, поэтому поделим все поле игры на строки и все перемещения объектов будем производить как вывод символа в знакоместо на поле. Переменные MAX_X=31 и MAX_Y=14 определяют размер поля игры по количеству знакомест по горизонтали и вертикали. Переменная MAX_OBJ=30 определяет максимальное количество одновременно находящихся на поле игры объектов-цифр. Массив int FIGURA[30][3] хранит информации об объектах-цифрах, находящихся на поле игры следующим образом:

    FIGURA[i][0] – числовое значение объекта-цифры (0 – пустой объект);
    FIGURA[i][1] – текущая координата x объекта-цифры;
    FIGURA[i][2] – текущая координата y объекта-цифры.

Для хранения прочих переменных, описывающих текущее состояние игры, создадим структуру GAME. Список полей структуры:

    xk – координата x(/4) игрока;
    yk – координата y(/6) игрока;
    tekCursor – текущее значение курсора;
    blinkCursor – текущее состояние блинка курсора;
    vblink – скорость blink в vk ;
    vk – скорость движения игрока — проверка входов A0,A1;
    vo_10 – скорость изменения цифры игрока;
    vo_11 – скорость появления объектов-цифр;
    vo_12 – скорость движения объектов-цифр;
    count_objects – кол-во объектов-цифр на поле;
    level – уровень игры;
    balls – кол-во баллов.

int MAX_X=31;
int MAX_Y=14;
// структура данных игры
struct GAME // структура для данных игры
{
int xk; // координата x(/4) игрока
int yk; // координата y(/6) игрока
int tekCursor; // текущее значение курсора
int blinkCursor; // текущее состояние блинка курсора
int vblink; // скорость blink в vk
long vk; // скорость движения игрока - проверка входов A0,A1
long vo_10; // скорость изменения цифры игрока
long vo_11; // скорость появления цифр
long vo_12; // скорость движения цифр
int count_objects; //кол-во объектов на поле
int level; // уровень игры
int balls; // кол-во баллов
};

int MAX_OBJ=30;
int FIGURA[30][3]={{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},
{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},
{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},
{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},
{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0},
{0,0,0},{0,0,0},{0,0,0},{0,0,0},{0,0,0}
};

Управления положением игрока с помощью джойстика.

Положение игрока на экране определяется отклонением джойстика. Выводы джойстика подключены к аналоговым портам A0, A1 платы Arduino. Опрос портов происходит через время, определенное параметром GAME.vk, эти данные обрабатываются функцией map(), которая пропорционально переносит значение из текущего диапазона 0-124 в новый диапазон (значения ширины и высоты экрана). Затем это значение переводится в координаты знакоместа. В это знакоместо необходимо переместить изображение символа игрока, предварительно поместив в предыдущее положение игрока символ пробела. Для отображения игрока используется мигающий символ – цифра и пробел.

//****************** установка нового положения игрока
void set_iam()
{
TV.set_cursor(min(123,GAME1.xk*4),min(84,GAME1.yk*6));
TV.print(" ");
GAME1.xk=map(analogRead(A0), 0, 1024, 0, 128);
GAME1.yk=map(analogRead(A1), 0, 1024, 0, 96);
GAME1.xk=GAME1.xk/4;
GAME1.yk=GAME1.yk/6;
GAME1.vblink--;
if(GAME1.vblink<0) { GAME1.blinkCursor=1-GAME1.blinkCursor; GAME1.vblink=5+GAME1.blinkCursor*5; } TV.set_cursor(min(123,GAME1.xk*4),min(84,GAME1.yk*6)); if(GAME1.blinkCursor==1) TV.print(GAME1.tekCursor); else TV.print(" "); }

Символ, отображающий игрока меняется через время, определенное параметром GAME.vo_10, вызовом функции set_new_cursor(), изменение происходит случайным образом с помощью функции random().
//****************** установка нового вида символа игрока
void set_new_cursor()
{
GAME1.tekCursor=random(0,10);
}

Генерация и перемещение объектов-цифр.

Объекты-цифры генерируются через время, определенное параметром GAME.vo11. Вызывается функция set_new_object(). Информация обо всех объектах-цифрах хранится в массиве FIGURA[30][3]. Программа ищет первый пустой индекс в массиве (FIGURA[30][3]=0), и помещает в него новый объект-цифру. Цифровое значение и горизонтальная координата нового объекта генерируются функцией random, а вертикальная координата устанавливается равной нулю. Символ, отображающий новый объект-цифру, выводится на экран.

//****************** появление нового объекта-цифры
void set_new_object()
{
int null_index=0;
if(GAME1.count_objects)<>
{
for(int i=0;i;i++)<>
{
if(FIGURA[i][0]==0)
{null_index=i;break;}
}
FIGURA[null_index][0]=random(1,9);
FIGURA[null_index][1]=random(0,MAX_X);
FIGURA[null_index][2]=0;
// вывод на доску
TV.set_cursor(FIGURA[null_index][1]*4,0);
TV.print(FIGURA[null_index][0]);
GAME1.count_objects++;
}
}

Функция движении объектов-цифр (go_object()) вызывается через время, определенное параметром GAME.vo12. Визуализация движения объектов-цифр происходит путем стирания символа из предыдущего положения объекта(запись символа пробела), и записью символа объекта в новое положение (вертикальная координата знакоместа объекта-цифры увеличивается на единицу). По достижении низа экрана происходит удаление объекта-цифры (запись 0 в элемент массива FIGURA[i][0]), а также уменьшение количества баллов игрока.
//****************** движение объекта-цифры
void go_object()
{
for(int i=0;i;i++)<>
{
if(FIGURA[i][0]>0)
{
TV.set_cursor(FIGURA[i][1]*4,FIGURA[i][2]*6);
TV.print(" ");
if(FIGURA[i][2])<>
{
FIGURA[i][2]++;
TV.set_cursor(FIGURA[i][1]*4,FIGURA[i][2]*6);
TV.print(FIGURA[i][0]);
}
else
{
TV.tone(294,200);
change_balls(FIGURA[i][0]*(-1));
FIGURA[i][0]=0;
GAME1.count_objects--;
}
}
}
}

Проверка столкновения игрока и объектов-цифр.

При перемещении символа игрока по экрану необходимо проверять столкновения игрока с объектами-цифрами. Для этого используем функцию collision(). После установки символа, соответствующего игроку, проверяем элементы массива FIGURA на соответствие координат объектов-цифр координатам символа игрока. При совпадении координат выполняем следующие действия:

    уничтожается объект-цифра из массива FIGURA (запись 0 в FIGURA[[i][0]);
    уменьшается на 1 счетчик количества объектов-цифр (GAME.count_objects)
    производится изменение счетчика количества баллов игрока (вызов функции change_balls()).

//****************** проверка столкновения
void collision()
{
for(int i=0;i;i++)<>
{
if(FIGURA[i][0]>0)
{
if(FIGURA[i][1]==GAME1.xk && FIGURA[i][2]==GAME1.yk)
{
TV.tone(740,200);
if(FIGURA[i][0]==GAME1.tekCursor)
change_balls(GAME1.tekCursor*GAME1.tekCursor);
else if(FIGURA[i][0]>GAME1.tekCursor && GAME1.level>3)
change_balls(FIGURA[i][0]*(-1));
else
change_balls(FIGURA[i][0]+GAME1.tekCursor);
FIGURA[i][0]=0;
GAME1.count_objects--;
}
}
}
}

В функцию изменение счетчика количества баллов игрока (change_balls()) в качестве аргумента передается число, на которое следует увеличить (уменьшить) счетчик баллов игрока. Это число равно сумме объекта-цифры и цифры игрока. Если цифра игрока равняется цифре объекта-цифры, то передается значение, равное произведению цифр. Для повышения сложности игры с 4 уровня столкновение игрока с объектом-цифрой приводит не только к увеличению счетчика игрока, но и уменьшению (если цифра игрока меньше цифры объекта-цифры).

Счетчик баллов игрока.

Функция change_balls() производит изменение счетчика баллов игрока на величину входящего аргумента. Если счетчик количества баллов становится меньше 0, игра заканчивается, экран очищается и происходит перехов на начало игры. Кроме того, при достижении счетчиком определенных значений, происходит переход игры на новый, более высокий, уровень. Установку значений для нового уровня выполняет функция new_level().
//****************** изменение набранных балов
void change_balls(int ball)
{
GAME1.balls=GAME1.balls+ball;
if(GAME1.balls<0) start_game(); if(GAME1.balls>(GAME1.level+1)*100)
new_level(GAME1.level);
set_data_tablo();
}

Переход на новый уровень.

При переходе на новый уровень игры происходит установка новых значений для скорости генерации и скорости перемещения объектов-цифр. Первоначально предполагалось менять и значение скорости изменения цифры игрока, скорости движения игрока, но в ходе испытания игры решил от этого отказаться. Действия установки значений переменных для нового уровня игры производятся в функции new_level()

//****************** изменение уровня игры
void new_level(int tek_level)
{
GAME1.level++;
GAME1.vo_10=5000;
GAME1.vo_11=2000-(GAME1.level-1)*100;
GAME1.vo_12=1000-(GAME1.level-1)*100;
}

Отображение данных игры на табло.

Табло с данными игрока находится внизу экрана. Здесь отображается текущие значения количество набранных баллов и уровень игры. При изменении счетчика баллов игрока происходит вызов функции set_data_tablo(), которое изменяет значение количества набранных баллов и уровень игры в переменных структуры GAME и на табло.

//****************** вывод данных на табло
void set_data_tablo()
{
TV.print(20,91," balls= ");
TV.print(70,91," level= ");
TV.print(48,91,GAME1.balls);
TV.print(98,91,GAME1.level);
}

Звуковое сопровождение игры.

Для звукового оформления игры будем использовать функцию tone(frequency, duration) библиотеки TVOut. При достижении объектом-цифрой низа игрового поля – TV.tone(294,200) (функция go_object()), при столкновении игрока и объекта-цифры – TV.tone(740,200) (функция collision()). Если вы захотите в проект вставить фрагмент из последовательного воспроизведения нескольких нот, после вывода каждой ноты командой tone(frequency, duration) нобходимо вставлять командой delay() задержку длительностью не меньшей, чем параметр duration,6 – последовательное воспроизведении е двух но с паузой.


TV.tone(294,200);
TV.delay(400);
TV.tone(740,200);

Основной цикл игры.

Основной цикл программы состоит из вызова рассмотренных нами функций set_iam(), collision(), go_object(), set_new_object(), set_new_cursor() через промежуток времени установленный в переменных GAME.vk, GAME.vo_10, GAME.vo_11, GAME.vo_12.

int game1()
{
while(GAME1.balls>0 && GAME1.level<6) { long time2=millis(); if(time2-time11>GAME1.vk)
{set_iam();
collision();
time11=time2;
}
if(time2-time12>GAME1.vo_12)
{go_object();time12=time2;}
if(time2-time13>GAME1.vo_11)
{set_new_object();time13=time2;}
if(time2-time14>GAME1.vo_10)
{set_new_cursor();time14=time2;}
TV.delay_frame(10);
}
if(GAME1.balls<0) return 0; else if(GAME1.level>5)
return 0;
else
return 1;
}

Добавляем меню для выбора игр.

Добавим в скетч меню для вывода трех игр , которые вы можно дописать самостоятельно.

void loop() {
switch(menu(pmenu))
{
case 1:start_game();
while(game1()>0);
break;
default:
break;
}
}

//***** меню для выбора игры
int menu(int poz)
{
TV.clear_screen();
pmenu=max(poz,1);
int pmenu1=pmenu;
TV.println(60,30,"Game 1");
TV.println(60,50,"Game 2");
TV.println(60,70,"Game 3");
TV.draw_rect(50,5+20*pmenu,40,10,WHITE,INVERT);
TV.delay(500);
while(digitalRead(12)==LOW)
{
if(analogRead(A1)<100) pmenu=max(pmenu-1,1); else if(analogRead(A1)>900)
pmenu=min(pmenu+1,3);
else
;
if(pmenu1!=pmenu)
{
TV.delay(500);
TV.draw_rect(50,5+20*pmenu1,40,10,BLACK,INVERT);
TV.draw_rect(50,5+20*pmenu,40,10,WHITE,INVERT);
pmenu1=pmenu;
}
}
return pmenu;
}

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>