Четверг, 24.06.2021, 06:22
Главная Регистрация RSS
Приветствую Вас, Гость
Поиск по сайту
Авторизация

Меню сайта
Game Maker
Если вы только-только начали изучать игрострой и еще даже не успели скачать сам Game Maker, предлагаем вам на выбор следующие версии программы:

Game Maker 8.1
Классика. Идеально подходит для создания простых 2D игр на PC. Требует активации.
Game Maker for Mac
Абсолютно та же программа, но только для пользователей Mac. Требует активации.
Game Maker Studio
Самая новая версия с возможностью кроссплатформенной разработки. Бесплатна.
Топ 5 игр
Агент Green 4.1 / 5.0
FeDo 4.1 / 5.0
To Mars: Sec... 4.1 / 5.0
Paintball 3.9 / 5.0
To Mars+MapE... 3.8 / 5.0

Топ игр составлен путем пользовательского голосования.

Если вы не согласны с какой-либо оценкой, примите участие и поставьте свой балл игре. Ваша оценка очень важна для нас ;)
Статистика



На сайте: 1
Гостей: 1
Пользователей: 0
Главная » Статьи » Уроки по Game Maker

Списки, мапы, очереди и стеки

Доброго времени суток, уважаемые читатели! В данной статье речь пойдет о такой полезной штуке в программировании, как структуры данных. И да, вы не поверите, они есть в Game Maker и были в нем уже очень давно. Но в исходниках и туториалах структуры данных почти никогда не встречаются. Впрочем, оно и не удивительно. ds_priority_create, ds_list_add, ds_map_find_value, ds_priority_find_max – я думаю, у многих сейчас мурашки по спине пробежали от названий этих функций. Однако на самом деле страшного в них ничего нет. Более того, структуры данных очень удобны. Кому доводилось реально писать код, со мной наверняка согласится. Надеюсь, что после прочтения данной статьи вы как минимум начнете уверенно использовать списки в своих играх, что значительно упростит вам жизнь и сделает ваш код более качественным.

Итак, что такое структуры данных? Самой простой структурой данных, наверное, является массив. Надеюсь, вы знаете, что это такое и как это использовать. Но на всякий случай напомню, что массив в GML выглядит примерно так:

Код arrayname[0] = "value";
где arrayname – имя массива, а 0 – индекс, по которому записано значение “value”.

В массив можно записать сколько угодно значений:

Код

n = 0;
arrayname[n] = "value1"; n += 1;
arrayname[n] = "value2"; n += 1;
arrayname[n] = "value"; n += 1;


А потом легко получить к ним доступ, просто обратившись к массиву по имени и индексу:

show_message(arrayname[0]);


Но есть одна проблема. Для того, чтобы взаимодействовать с массивом нам всегда придется хранить в памяти число n – количество ячеек в массиве. Иначе мы рискуем случайно обратиться к пустому полю. Кроме того, при удалении какой-либо записи из массива, нам придется вручную сдвигать оставшиеся записи:

var i, k;
k = 2; // индекс удаляемого элемента
for(i=k;i<n-1;i+=1) {
    arrayname[i]=arrayname[i+1];
}


Это могло бы сильно усложнить код, например, при написании инвентаря. Но на помощь приходят списки.

 

Списки

Списки сделают всю грязную работу за вас. Они всегда знают, сколько хранится полей в «массиве». Они умеют добавлять новые элементы, а также легко удалять их. Но выглядят в GML они достаточно уродливо. Вот простой пример создания списка:

list = ds_list_create(); // Инициируем список
ds_list_add(list,"First"); // Добавляем элемент First, его индекс будет = 0
ds_list_add(list,"Third"); // Добавляем элемент Third, его индекс = 1
ds_list_insert(list,1,"Second"); // Добавляем на позицию 1 элемент Second, тем самым смещая вправо оставшиеся элементы (т.е. индекс Third будет = 2)


// Теперь мы можем легко считать наши значения
show_message( ds_list_find_value(list,0) );
show_message( ds_list_find_value(list,1) );

// Есть более удобная запись для считывания значения из списка
// но она работает только в GMS и выше:
show_message( list[! 2] );

// а также мы можем узнать, сколько всего элементов в списке
show_message( ds_list_size(list) );


// Не забываем в событии Room End или Destroy (в зависимости от частного случая)
// удалить список
ds_list_destroy(list)

Кроме того, как я уже сказал выше, мы в любой момент можем безопасно удалить тот или иной элемент из списка по его индексу:

// Удаление элемента First из нашего предыдущего списка
ds_list_delete(list,0);


// Также мы можем полностью очистить список
ds_list_clear(list);

Списки могут хранить не только строки или числа, но также и объекты:

// Добавление объектом себя в список list
list = ds_list_create();
ds_list_add(list,id);


// Но будьте осторожны, ведь в списке могут быть данные только одного типа!
// Добавление переменной для объекта
text = "Hello World! ";


// Вызов переменной объекта через список
show_message(list[! 0].text);


// напоминаю, что до GMS вам придется юзать такой код:
// show_message(ds_list_find_value(list,0).text);

 

Как вы видите, списки – это безумно круто и очень юзабельно. Когда их использовать в вашей программе? Да всегда, когда вы имеете дело с однородными данными. В качестве примера вот вам исходник инвентаря. Не правда ли, очень удобно? В этом примере каждый предмет инвентаря представляет собой экземпляр объекта oitem с двумя полями: name и image.

Мапы

Продвинутые пользователи Game Maker наверняка знают, что массивы в GML – это и не массивы по сути. Доступ к ним на самом деле осуществляется не по индексу, а по ключу, которым может быть не только целочисленное значение, но и дробное или даже строчное:

notarray["field"] = "This is fine";
show_message(notarray["field"]);


Для таких случаев в GML есть специальная структура данных – мапы. Они позволяют так же отслеживать число элементов в структуре, а также удобным способом их перебирать:

map = ds_map_create();

// Добавляем записи
ds_map_add(map,"key0","value0");
ds_map_add(map,"key1","value1");
ds_map_add(map,"key2","value2");

// Выводим элемент с ключом key0
// (При этом не забываем убедиться, что он существует)
if(ds_map_exists(map, " key0")
show_message(ds_map_find_value(map,"key0"));


// Для GMS и выше действует удобное сокращение аналогичное тому,
// что мы уже видели в списках
show_message(map[? "key0"]);

// Мы можем легко перебрать все значения мапы
// Для этого находим первый ключ:
var key;
key = ds_map_find_first(map);
while(true) {
    // и от него поочередно двигаемся вперед по мапе
    // получая следующий ключ посредством функции ds_map_find_next
    key = ds_map_find_next(map, key);
    show_message(ds_map_find_value(map,key));


    // не забываем прервать цикл по достижении последнего ключа
    if(key == ds_map_find_last(map)) break;
}

ds_map_destroy(map);

Мапы удобно использовать также в случае необходимости хранения однородных данных. Но только с одной оговоркой – если к этим данным удобнее получать доступ по ключу, а не по индексу элемента. Например, мы пишем систему диалогов. И на старте заносим реплики в структуру данных. Согласитесь, будет не очень удобно в дальнейшем вызывать эти реплики по индексу (как в случае списка). Гораздо удобнее, если у нас будет понятный и читабельный ключ. Для этого мы можем воспользоваться мапой.


Скачав исходник, вы можете увидеть реализацию простенькой системы диалогов, основанную на списках и мапах. Ветки диалога представляют собой списки с репликами, которые удобно в дальнейшем перебирать. А для доступа к той или иной ветке используется мапа и доступ по ключу соответственно.

Очереди и стеки

Поговорим о других часто юзаемых структурах. Очереди. Очереди представляют собой что-то вроде списка. Но только добавляются элементы в этом списке всегда в конец, а считываются – из начала. Собственно, эта структура данных работает как и обычная очередь в какой-нибудь «Пятерочке».  Выглядит работа с очередями примерно так:

queue = ds_queue_create();

// Добавление элементов в очередь
ds_queue_enqueue(queue, "First");
ds_queue_enqueue(queue, "Second");

// Считывание элементов из начала очереди
show_message(ds_queue_dequeue(queue));
show_message(ds_queue_dequeue(queue));
// После считывания элемент удаляется из очереди

ds_queue_destroy(queue);

 

Аналогично работают стеки. Но с одним исключением. Если в очереди мы добавляем элементы в конец, то в стеке – новые элементы суются в начало.

stack = ds_stack_create();

// Добавление элементов в стек
ds_stack_push(stack, "First");
ds_stack_push(stack, "Second");

// Чтение элементов
// Обратите внимание, что в отличие от списка стек вернет значения в другом порядке
// то бишь сначала он вернет Second, а потом First, потому что
// элементы в нем добавляются в начало и считываются тоже из начала
show_message(ds_stack_pop(stack));
show_message(ds_stack_pop(stack));

ds_stack_destroy(stack);

 

По сути, очередь или стек всегда можно заменить списком. Но в ситуациях, когда нам нет необходимости в использовании функционала списка, вполне можно воспользоваться этими структурами данных, так как они работают гораздо быстрее. Например, при формировании команд в RTS. Пример вы можете скачать по ссылке. Здесь мы заносим в список команды для игрока в виде объектов oaction с полями x и y, а затем поочередно двигаемся к ним.

Приоритетные очереди

На мой взгляд, очень спорная структура данных в GML. Куда полезнее было бы иметь, например, упорядоченный список. Но имеем то, что имеем (впрочем, это вам не мешает пользоваться функцией ds_list_sort для простых списков). В официальной справке приводится пример использования приоритетных очередей для вывода таблицы лидеров:

[code]

q = ds_priority_create();

// Добавление новых значений
// Player1/2 – значение поля, 10/1 - приоритет
ds_priority_add(q, "Player 1", 10);
ds_priority_add(q, "Player 2", 1);

// Мы можем в любой момент изменить приоритет поля
ds_priority_change_priority(q, "Player 1", 0);


// Также мы можем в любой момент узнать приоритет поля
show_message(ds_priority_find_priority(q, "Player 1"));


// Но основная фича – получение максимального / минимального по приоритету значения
show_message(ds_priority_find_max(q));


ds_priority_destroy(q);

[/code]

 

Как вы видите, структуры данных – очень полезная и даже необходимая вещь при написании кода. К сожалению, в GML они выглядят довольно уродливо и отпугивают новичков одним своим видом. Поэтому в тысячах исходниках в сети интернет довольно сложно встретить их использование: код пишут в основном энтузиасты, часто малознакомые с современными практиками программирования и инстинктивно избегающие «сложных» функций.

К моменту выхода Game Maker Studio язык GML значительно проапгрейдили. Некоторые детали стали куда удобнее со времен восьмой версии, в том числе структуры данных. Если вы юзаете GMS и выше очень рекомендую использовать вместо таких функций, как ds_map_find_value и ds_list_find_value, сокращения map[? key] и list[! index]. Это сделает код более читаемым.

К сожалению, в рамках этой статьи я не затронул еще одну структуру данных в Game Maker – сетки (ds_grid). Кому интересно, советую ознакомиться с ними самостоятельно. Сетки представляют что-то вроде двумерного массива и довольно просты для понимания. Так что, если вы освоили материал данной статьи, вам не составит труда разобраться и с ними.

Не следует путать ds_grid с mp_grid. Вторые относятся к так называемым функциям планирования пути (motion planning). И как бы скептично я не относился к мувменту в Game Maker, они являются довольно юзабельными, так как позволяют реализовать простой поиск пути, основанный на популярном алгоритме A* pathfinding. Но об этом как-нибудь в другой раз.

Все исходники вы можете найти в архиве по ссылке ниже. Они написаны для Game Maker 8, но спокойно могут быть импортированы в Game Maker Studio и выше. Спасибо за внимание!

https://gamemaker.ucoz.com/files/tutorial.zip

Категория: Уроки по Game Maker | Добавил: BRESS (26.06.2020)
Просмотров: 535 | Комментарии: 1 | Рейтинг: 5.0/1
Всего комментариев: 1
0
1 BRESS   (26.06.2020 15:08) [Материал]
BRESS Кстати, в статьях теперь можно использвать BB-тег [ code], позволяющий выделить код. Комментарии подсвечиваются автоматически happy

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]