Update: исправлен нерабочий код. Спасибо Dmitriy за указание на проблему.
Предисловие
В этом уроке, мы рассмотрим 2 способа обработки нажатий:
- Последовательный
- Параллельный.
А также, научимся выполнять действие 1 раз, в момент нажатия (например вызов паузы).
Приступим.
Последовательный способ обработки нажатий.
Создайте функцию handleInput(), внутри main.c вашего проекта. И перепишите туда следующий код.
void handleInput(){
VDP_clearText(1,1,5);
u16 value = JOY_readJoypad(JOY_1);
if(value & BUTTON_LEFT) {
VDP_drawText("Left",1,1);
}
if(value & BUTTON_RIGHT) {
VDP_drawText("Right",1,1);
}
}
Рассмотрим новые комманды:
VDP_clearText(1,1,5);
Данная комманда удаляет тайлы из указанной области. Синтаксис у неё следующий:
VDP_clearText(x_tile, y_tile, длина);
- x_tile — указывает тайл по оси x
- y_tile — задает тайл по оси y
- длина — задает длину удаляемой области.
Получается, этой коммандой
VDP_clearText(1,1,5);
Мы удалили тайлы в области 1,1 по 1,5 (включительно).
u16 value = JOY_readJoypad(JOY_1);
- Тип u16, можно представить как unsigned 16, что значит без-знаковое 16-ти битное число.
- Функция JOY_readJoypad, задает джойстик (JOY_1), с которого мы будем считывать информацию, и возвращает информацию о нажатых кнопках.
if(value & BUTTON_LEFT) {
VDP_drawText("Left",1,1);
}
Здесь мы, при помощи битовой маски(BUTTON_LEFT), проверили нажата-ли кнопка влево. Если нажата, то выводим текст на экран.
if(value & BUTTON_RIGHT) {
VDP_drawText("Right",1,1);
}
Тут, тоже самое, только для кнопки вправо.
Список битовых масок, доступен по адресу путь_установки_SGDK/doc/html/joy_8h.html
Функция готова, осталось её вызвать внутри main.
int main()
{
while(1)
{
handleInput();
SYS_doVBlankProcess();
}
return (0);
}
Теперь, скомпилируйте, запустите, и по-нажимайте влево, вправо. При нажатии на кнопку, должен появится текст на экране.
Теперь, разберем второй способ обработки нажатий.
Параллельный способ обработки нажатий.
Данный способ, в отличии от предыдущего, не требует постоянного вызова функции. Потому-что, он основан на обработчиках событий (Event Handler). Алгоритм тут следующий, создаем callback функцию, передаем её в другую функцию, и все, готово.
Callback функция — это функция, которую другая функция, принимает в качестве аргумента.
Создайте новую функция внутри main.c вашего проекта.
void handleInputSecond(u16 joy, u16 changed, u16 state) {
VDP_clearText(1,1,5);
if(joy == JOY_1) {
if(state & BUTTON_LEFT) {
VDP_drawText("Left",1,1);
}
if(state & BUTTON_RIGHT) {
VDP_drawText("Right",1,1);
}
}
}
В предыдущем примере, handleInput выполнялся каждый кадр. Здесь же напротив, функция handleInputSecond будет выполняется только, в момент изменения состояния джойстика (нажал/отжал кнопку, нажал несколько кнопок).
Разберем данный код:
void handleInputSecond(u16 joy, u16 changed, u16 state)
Данная функция принимает 3 аргумента:
- joy — число задающее джойстик, с которого была нажата кнопка. (JOY_1, JOY_2)
- changed — возвращает код кнопки, у которой сменилось состояние (BUTTON_UP, BUTTON_DOWN …)
- state — задает состояние джойстика (число u16 из которого, при помощи битовой маски & можем узнать какая кнопка нажата)
Остальной код, мы разобрали выше. Теперь, создадим обработчик событий внутри main.
int main()
{
JOY_init();
JOY_setEventHandler(&handleInputSecond);
while(1)
{
SYS_doVBlankProcess();
}
return (0);
}
Разберем код.
JOY_init();
Производит первичную настройку джойстика (без этой комманды, данный способ работать не будет).
JOY_setEventHandler(&handleInputSecond);
Здесь мы передали, наш обработчик событий (callBack функция).
Выполняем действия 1 раз в момент нажатия/отжатия.
Есть 3 типа нажатия кнопки:
- pressed — действие выполняется каждый кадр (уже рассматривали)
- justPressed — действие выполняется 1 раз, при нажатии кнопки.
- justReleased — действие выполняется 1 раз, при отжатии кнопки.
Чтобы получить justPressed и justReleased, необходимо сравнить предыдущие значение кнопки и текущее.
Разберем justPressed:
u16 value = 0;
u16 prevValue = 0;
void handleInput(){
VDP_clearText(1,1,5);
prevValue = value;
value = JOY_readJoypad(JOY_1);
//Just pressed START button
if((value & BUTTON_START) && !(prevValue & BUTTON_START)) {
VDP_drawText("Start",1,1);
}
}
int main()
{
while(1)
{
handleInput();
SYS_doVBlankProcess();
}
return (0);
}
Т.е. если нажата кнопка старт и, кадр назад, эта кнопка не была нажата, то выводим сообщение Start.
Для justReleased делаем наоборот:
//Just released START button
if(!(value & BUTTON_START) && (prevValue & BUTTON_START)) {
VDP_drawText("Start",1,1);
}
Все, готово.
Считываем последовательно, данные с 2-х джойстиков.
Также, для удобства, вы можете, вместо переменных value и prevValue использовать структуру joyInput, хранящее эти 2 значения.
В этом примере я покажу, как обработать нажатие с 2-х джойстиков.
#include "genesis.h"
typedef struct {
u16 value;
u16 prevValue;
} joyInput;
joyInput joy1;
joyInput joy2;
void handleInput(joyInput* joy){
if((joy->value & BUTTON_START) && !(joy->prevValue & BUTTON_START)) {
VDP_drawText("Start",1,1);
}
}
int main()
{
while(1)
{
joy1.prevValue = joy1.value;
joy1.value = JOY_readJoypad(JOY_1);
joy2.prevValue = joy2.value;
joy2.value = JOY_readJoypad(JOY_2);
VDP_clearText(1,1,5);
handleInput(&joy1);
handleInput(&joy2);
SYS_doVBlankProcess();
}
return (0);
}
Разберем код.
joy1.prevValue = joy1.value;
joy1.value = JOY_readJoypad(JOY_1);
joy2.prevValue = joy2.value;
joy2.value = JOY_readJoypad(JOY_2);
Здесь, мы записали текущее состояние джойстика (value) и предыдущее (prevValue) в переменные joy1 и joy2.
handleInput(&joy1);
handleInput(&joy2);
Ссылки на эти переменные передали в функцию handleInput.
void handleInput(joyInput* joy){
if((joy->value & BUTTON_START) && !(joy->prevValue & BUTTON_START)) {
VDP_drawText("Start",1,1);
}
}
И, получая данные по ссылке (->) из указателя joy, мы обработали нажатия.
Переход между сценами.
Для примера, покажу как переходить между сценами.
void window1(){
VDP_drawText("Window1",1,3);
while(1){
joy1.prevValue = joy1.value;
joy1.value = JOY_readJoypad(JOY_1);
if((joy1.value & BUTTON_START) && !(joy1.prevValue & BUTTON_START)) {
break;
}
SYS_doVBlankProcess();
}
}
void window2(){
VDP_drawText("Window2",1,3);
while(1){
joy1.prevValue = joy1.value;
joy1.value = JOY_readJoypad(JOY_1);
if((joy1.value & BUTTON_START) && !(joy1.prevValue & BUTTON_START)) {
break;
}
SYS_doVBlankProcess();
}
}
int main()
{
while(1)
{
window1();
window2();
}
return (0);
}
Функции window1 и window2 выводят текст и, в бесконечном цикле, ждут нажатия кнопки Start. Если Start нажата, выходят из цикла, а следовательно и из функции.
if((joy1.value & BUTTON_START) && !(joy1.prevValue & BUTTON_START)) {
break;
}
window1 и window2, таким обрразом, после window2 мы вернемся в window1.
while(1)
{
window1();
window2();
}
Заключение.
Резюмируя. Есть 2 способа обработки нажатий: последовательный и парралельный.
- последовательный — получаем состояние джойстика каждый кадр.
- парралельный — получаем состояние джойстика в момент изменения.
Я для себя выбрал, последовательный, т.к. мне с ним удобней работать.
Если есть вопросы, спрашивайте в комментариях к статье, в группе ВК, или в группе Discord.
Исходный код доступен на github