Разберался с меню в игре, вот задачи которые хотелось решить:
1. необходимость в быстром подключении меню к любому проекту.
(решено частично, устраивает).
2. меню должно создаваться из файла, или string переменной, например, переданной по интернет.
(string это строка, но она может в себе содержать знак "новая строка", а это значит что по сути в ней несколько строк, правильнее их называть части строк разделённые знаком "новая строка".
натафтологил уже, но подругому не сказать :D, дальше будет яснее)
3. меню должно быть гибким.
(например, если в подменю жмём "эскейп", то возвращаемся в предыдущее, при этом на тот пункт куда заходили)
у нас будет модуль "menu.bgt", файл "start_menu.dat" в папке data, и код в главном скрипте игры.
ещё нужен звуковой файл "menumove.wav" в папке "sounds".
разбираемся с файлом "start_menu.dat". назвал я его так потому что делаем главное меню игры, для другого меню можно создавать другие файлы и сколько угодно. его содержимое в нашем примере такое:
menu=Начать игру,Создать героя,Настройки,Выход|Добропожаловать на земли ависа
menu=Сервер1,Сервер2,Сервер3,Сервер4,В предыдущее меню|Выберите сервер
menu=Настройка1,Настройка2,Назад|Настройки
все строчки начинаются с "menu=", должно понадобиться при получении меню через интернет, чтобы отличить строку меню от других строковых данных.
дальше перечисление пунктов меню через запятую.
и в конце описание меню, отделено вертикальной чертой
первая строка используется как начальное меню, вторая - как подменю первого пункта, а третья - как подменю третьего пункта. для остальных пунктов, а именно для 2 и 4 нам подменю пока не нужны.
кодим menu.bgt, коментарии будут не очень подробдные, знающий разберётся.
#include "dynamic_menu.bgt"; // подключили модуль меню
dynamic_menu menu; // объект класса dynamic_menu
sound menu_movement; // объект sound, звук движения по меню
int menu_position; // позиция в меню
// функция создания меню
int create_menu(string file_menu, string str_menu, int n_str, bool allow_escape, bool wrap, bool enable_home_and_end, int start_position) {
menu.reset(true); // обнуляем полностью меню
menu.allow_escape=allow_escape; // указали реакцию на клавишу "эскейп"
menu.wrap=wrap; // указали необходимость цикличного перемещения
menu.enable_home_and_end = enable_home_and_end; // указали реакцию клавиш "home" и "end"
menu.set_speech_mode(what_screen_reader()); // указали какой скринридер используется из функции what_screen_reader(), которая ниже
menu_movement.load("sounds/menumove.wav"); // подгрузили звуковой файл
menu.set_callback(check_menu_events, ""); // указали функцию обратного вызова, которая тоже будет ниже
if (str_menu =="") { // условие если строка пришла пустая то работаем с файлом
file f; // объявили файловую переменную
f.open(file_menu, "r"); // открыли файл для чтения
str_menu = f.read(); // заполнили нашу пустую переменную содержимым из файла
f.close(); // закрыли файл
}
string[] array_str_menu = string_split(str_menu, "\n", true); // создали массив строк из строки str_menu на основе разделения по знаку "новая строка". например из нашего файла будет создан массив из трёх строк (см. содержимое файла выше)
string Description_menu = string_split(array_str_menu[n_str -1], "|", true)[1]; // вытащили из нужной строки описание меню
string[] array_items = string_split(string_split(string_split(array_str_menu[n_str -1], "|", true)[0], "=", true)[1], ",", true); // создали массив и внесли в него пункты меню из нужной строки
// перебираем этот массив и заполняем меню
for (int counter = 0; counter < array_items.length; counter++) {
menu.add_item_tts(array_items[counter]);
}
int result_menu = menu.run_extended(Description_menu, true, start_position, true); // запустили меню, указав при этом какой пункт меню будет установлен, в result_menu возвращается пункт меню на который было нажато, здесь начинает отрабатывать функция обратного вызова
menu_movement.close(); // меню отработало, закрываем звуковой файл
return result_menu; // вернули пункт меню на который было нажато
// здесь отработка модуля завершена, но ниже две функции которые вызываются из текущей функции
}
// функция обратного вызова, циклично вызывается пока меню запущено, то есть пока не нажато на любой пункт меню
int check_menu_events(dynamic_menu@ game_menu, string data) {
if(game_menu.get_position()!=menu_position) { // условие. если текущая позиция отличается от сохранённой
menu.set_speech_mode(what_screen_reader()); // снова берём значение запущенного скринридера
menu_movement.stop(); // останавливаем воспроизведение звука
menu_movement.play(); // воспроизводим звук движения по меню
menu_position=game_menu.get_position(); // сохраняем позицию меню
}
return 0; // возвращаем 0, иначе меню закроется, то есть в функции обратного вызова можно прерывать меню, может быть где-то полезным, но нам пока не нужно
}
// функция проверки запущенного скринридера, сделал её здесь, чтобы модуль меню был как можно более независимый
int what_screen_reader() {
// если запущен джоз то его и возвращаем
if(screen_reader_is_running(JAWS)) {
return JAWS;
}
// также с nvda
if(screen_reader_is_running(NVDA)) {
return NVDA;
}
return JAWS; // если условия не выполнились то все равно возвращаем джоз, можно было бы и SAPI вернуть но я не хочу с этим связываться
}
конец кодинга
теперь опишу входные параметры функции
int create_menu(string file_menu, string str_menu, int n_str, bool allow_escape, bool wrap, bool enable_home_and_end, int start_position)
string file_menu - путь до файла меню, в нашем случае это sounds/start_menu.dat
string str_menu - строка меню, если она пустая то берётся из файйла. если передаём её заполненную, то file_menu можно передавать пустым
int n_str - номер элемента массива, который заполняется строчками, когда мы делили основную строку на другие по знаку "новая строка"
bool allow_escape - если труе то то клавишей "эскейп" меню возвращает 0, иначе не реагирует на неё
bool wrap - если true, то меню будет прокручиваться циклично
bool enable_home_and_end - если true, то клавишами "home" и "end" будет происходить переход в начало или в конец меню
int start_position - номер того меню, на которое попадает курсор при открытии меню, этот параметр очень важен, и далее мы увидим как здорово получается манипулировать менюшкой
теперь в главном скрипте мы во входной функции вызываем наше меню, хотя это не обязательно можно делать здесь, можно меню вызывать в других местах, с другими данными, имею ввиду либо из файла либо из строки.
значит в main() пишем
void main() {
show_start_menu(1);
}
тем самым вызвали функцию передав значение 1.
а теперь самое интересное, я тремя похожими функциями обрабатываю движения по меню, первую из них только что вызывали из main()
кодим их
// первая функция открывает начальное меню
void show_start_menu(int pos) { // принимаемое значение, это номер пункта, который станет активным
int r = create_menu("data/start_menu.dat", "", 1, true, false, true, pos); // вызываем функцию создания меню с нужными параметрами и результат записываем в r
// обратите внимание на параметр 1, это означает первая строка в файле, откуда берётся начальное меню
// ниже условие switch, обрабатываем r
switch(r){
case -1: exit(); break; // если -1 то ошибка, закрываемся
case 0: exit(); break; // если 0 то нажата была клавиша "эскейп", ошибки нет, пользователь закрыл сам, ну тоже закрываемся
case 1: show_start_menu1(1); break; // если 1, то есть нажато на первый пункт в меню, то создаём следующее меню, со стартовой позицией 1
case 2: game(); break; // если 2 то пока запускаем функцию game(), надо конечно обрабатывать но у нас тема другая
case 3: show_start_menu3(1); break; // если 3, то есть нажато на третий пункт в меню, то создаём следующее меню, со стартовой позицией 1
case 4: exit(); break; // если нажато на пункт 4, а это выход, то закрываемся
}
}
void show_start_menu1(int pos) {
int r = create_menu("data/start_menu.dat", "", 2, true, false, true, pos); // аналогично с функцией выше, создали меню, но берём уже вторую строчку из файла
switch(r){
case -1: exit(); break;
case 0: show_start_menu(2); break; // запускаем предыдущую функцию, то есть создаём снова главное меню и активный пукнт уже второй. то есть если пользователь нажмёт "эскейg" то попадает в главное меню но остаётся на пункте откуда вышел
case 1: game(); break; // вызываем game(), вобщем не обрабатываем пока
case 2: game(); break; // аналогично
case 3: game(); break; // тоже самое
case 4: game(); break; // и опять
case 5: show_start_menu(1); break; // а тут аналогично с эскейпом, только активным будет пункт 1
}
}
// функция создаёт подменю третьего пункта главного меню
void show_start_menu3(int pos) {
int r = create_menu("data/start_menu.dat", "", 3, true, false, true, pos); // также как и раньше, только берём третью строку из файла
switch(r){
case -1: exit(); break;
case 0: show_start_menu(3); break; // если нажата "эскейп", то возвращаемся в главное меню, на пункт 3
case 1: show_start_menu3(1); break; // здесь можно обработать результат и мы запускаем снова эту же функцию с активным пунктом 1
case 2: show_start_menu3(2); break; // и здесь мы запускаем саму себя, но активным пункт уже будет 2
// поясню выше сказанное, мы сделали такое меню, где нажатие на пункт как бы ничего не делает, вернее возвращает результат и снова запускает себя, нужно это для настроек, если мы в меню настройки на пункте нажимаем энтер, то можем переключить что-то, и при этом останемся тамже и можем переключать энтером сколько душе угодно
case 3: show_start_menu(1); break; // если нажат пункт "назад" то открываем главное меню.
}
}
Конец кодинга.
Уф. вобщем если не понятно или есть другие решения то пишем не стесняемся.
итог таков:
я беру модуль menu.bgt, не забываем его подключить кстати,
редактирую файл меню, как мне надо,
обрабатываю результаты в скрипте игры, запуская по несколько раз эти менюшки и подменюшки.
можно создавать меню глубже, но все надо будет обрабатывать.
обратите внимание:
если у нас строка в файле
menu=Старт,Об игре, Выход|Добропожаловать в игру
то результат который вернётся может быть от -1 до 3,
где -1 значит произошла ошибка
0 нажата клавиша эскейп
остальное номер пунктов меню.
На этом всё, всем творческих идей