SGDK. Управляем джойстиком.

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, мы обработали нажатия.

Переход между сценами.

Для примера, покажу как переходить между сценами.

Функции window1 и window2 выводят текст и, в бесконечном цикле, ждут нажатия кнопки Start. Если Start нажата, выходят из цикла, а следовательно и из функции.

window1 и window2, таким обрразом, после window2 мы вернемся в window1.

Заключение.

Резюмируя. Есть 2 способа обработки нажатий: последовательный и парралельный.

  • последовательный — получаем состояние джойстика каждый кадр.
  • парралельный — получаем состояние джойстика в момент изменения.

Я для себя выбрал, последовательный, т.к. мне с ним удобней работать.

Если есть вопросы, спрашивайте в комментариях к статье, в группе ВК, или в группе Discord.

Итоговый результат.

avatar