Аксессоры к структурам данных и массивам в GameMaker: Studio

Перевод: Dmi7ry
Оригинал: James Foreman

Последнее обновление (начиная с версии 1.2.1261]) GameMaker: Studio добавляет интересный дополнительный функционал для некоторых структур данных и обычным массивам, называемый “аксессоры” (Accessors, методы доступа). Эти аксессоры являются простыми логическими выражениями, которые позволяют вам добавлять или изменять значения в этих структурах и записываются таким образом, будто вы работаете с массивами, с тем отличием, что используется специальный идентификационный символ перед первым аргументом, который говорит GameMaker: Studio, что вы работаете со структурой данных (заранее созданной), либо напрямую с исходным массивом.

Вы можете добавлять, заменять и получать значения и ключи для следующих типов структур данных: ds_maps, ds_lists и ds_grids, каждый из которых имеет свой собственный символ для доступа, изменения или установки значения, как показано ниже. Для массивов этот функционал даёт вам прямой доступ к исходному массиву, передаваемому в скрипт, взамен стандартного поведения, когда передаётся копия массива, которое обычно и ожидается (это объясняется в конце статьи).

Также обратите внимание, что символы, выбранные для обозначения метода доступа, были выбраны в соответствии с их мнемонической природой (например, # – это идентификатор для ds_grid), что делает их более лёгкими для опознавания и запоминания.

ds_list [| ]

Синтаксис для списков следующий:

list_index[| index]

После создания списка с помощью ds_list_create(), вы можете использовать индекс списка, который у вас сохранён в переменной, для обращения к нему. Значение index здесь указывает на позицию элемента в списке, который вы хотите добавить или установить. Например, следующий код создаёт список и добавляет 10 записей, каждая из которых будет содержать случайное число от 0 до 9:

ds = ds_list_create();

var index = 0;

repeat(10)
{
    ds[| index++] = irandom(9);
}

После создания вашего списка и заполнения его данными, чтобы получить значение из списка, нужно сделать что-нибудь типа этого:

value = ds[| 5];

Этот код получает значение из элемента 5 (но это шестая позиция в списке, так как нумерация начинается с нуля) и сохраняет его в переменную. Обратите внимание, что если вы используете выражение для добавления ссылки на элемент, который уже имеет значение, то предыдущее значение будет заменено новым, а не просто добавится в список. Чтобы добавить дополнительные элементы, нужно узнать размер структуры данных (ds_list_size) и добавить их в её конец. Ещё нужно отметить, что вы также можете задать индекс элемента больше, чем их количество в списке – в этом случае установится заданный элемент, одновременно увеличивая список и заполняя все предыдущие элементы нулём.

ds​_map [? ]

Синтаксис для карт следующий:

map_index[? key]

После создания вашей карты с помощью ds_map_create(), вы должны использовать индекс карты, сохранённый в переменной, чтобы обращаться к ней. Значение “key” указывает на ключ карты, который необходимо записать или прочитать. Например, следующий код создаёт карту и после добавляет несколько записей, используя этот синтаксис:

ds = ds_map_create();

ds[? "Name"] = "Hamish";

ds[? "Company"] = "MacSeweeny Games";

ds[? "Game"] = "Catch The Haggis";

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

value = ds[? "Name"];

Этот код получает значение из ключа “Name” и сохраняет его в переменную. Обратите внимание, что если при записи значения, в карте уже содержится запись с таким ключом, то предыдущее значение будет заменено новым.

ds_grid [# ]

Синтаксис для сеток следующий:

grid_index[# xpos, ypos]

После создания вашей сетки с помощью ds_grid_create(), вы должны использовать индекс сетки, сохранённый в переменной, чтобы обращаться к ней. Значения “xpos” и “ypos” указывают на элемент сетки, который необходимо записать или прочитать. Например, следующий код создаёт сетку, очищает её нулём и после добавляет несколько записей в неё:

ds = ds_grid_create();

ds_grid_clear(ds, 0);

var gw = ds_grid_width(ds) - 1;

var gh = ds_grid_height(ds) - 1;

repeat(10)

{
    var xx = irandom(gw);

    var yy = irandom(gh);

    if ds[# xx, yy] == 0 ds[# xx, yy] = 1;
}

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

value = ds[# mouse_x div 16, mouse_y div 16];

Этот код получает значение из сетки, основываясь на координатах мыши (поделенных на размер “клетки” в комнате, чтобы получить корректную позицию).

Массивы.

Обычный метод работы GameMaker: Studio с массивами – “копирование при записи”, означающий, что при передаче массива (как пример) в скрипт, передаётся ссылка на оригинальный массив, но при изменении любого значения массива, будет создана копия массива. Это означает, что для того, чтобы получить новые значения, которые вы установили, вы должны вернуть массив из скрипта и переназначить его исходной переменной массива. Например:

//CREATE EVENT
a[9] = 0; //initialise a 10 value array

​
//CALL A SCRIPT WITH THE ARRAY
a = scr_Set_Array(a);

 
//SCRIPT
var temp_a = argument0;

temp_a[3] = 10;

return temp_a;

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

array[@ val]; //1D array

array[@ val, val]; //2D array

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

//CREATE EVENT
a[9] = 0; //initialise a 10 value array


//CALL A SCRIPT WITH THE ARRAY
a = scr_Set_Array(a);


//SCRIPT
var temp_a = argument0;

temp_a[@ 3] = 10;

Обратите внимание, что нам теперь не нужно возвращать массив как его копию, потому что мы записали новое значение непосредственно в исходный массив.

Расширения для iOS и Android в GameMaker: Studio

Перевод статьи iOS & Android Extensions
Автор: James Foreman
Перевод: Dmi7ry

Буду благодарен сообщениям об опечатках и улучшениях перевода.

 

Обновление версии 1.3 вносит в GameMaker: Studio много новых и интересных функций: SWF-импорт, Push-уведомления, переработанную систему внутриигровых покупок. Тем не менее, возможно, что самой важной частью этого обновления является открытие системы расширений, что позволит использовать сторонние SDK и нативный код в Ваших играх.

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

Стоит отметить, что расширения для платформы iOS должны быть написаны с использованием Objective C++, в то время как расширения для Android должны писаться на Java. В данной статье предполагается, что если Вы пишете, либо используете нативные расширения, то у вас уже есть практические знания нативного языка для выбранной платформы.

 

Приступаем

Создание расширений в GameMaker: Studio просто и требует от вас клика правой кнопкой мыши на папке Extensions в дереве ресурсов и последующего клика на “Create Extension”.

После этого появится окно Extension Package Properties (свойства пакета расширений), в котором, во вкладке General, Вам нужно заполнить поля “name” (имя), “author” (автор) и другие, для расширения, которое Вы создаёте. Сделав это, Вам нужно заполнить детали формы для выбранной платформы в соответствующей вкладке. Обратите внимание, что пакет расширений может содержать расширения для нескольких платформ. Поэтому, если у вас есть стороннее SDK с файлами для Android и iOS, Вы можете создать один пакет, который будет содержать и то и другое. Затем в своей конфигурации нужно установить, чтобы экспортировалось только то, что Вам нужно (подробнее об этом позже).

Каждый тип расширений требует, чтобы Вы указали ClassName (имя класса), идентифицирующий его, и Source Directory (исходная папка). Это исходная папка, так как расширения для платформ iOS и Android не требуют, чтобы Вы добавляли файлы непосредственно в GameMaker: Studio, как расширения JS или DLL (хотя для корректной работы они, в настоящее время, требуют файл-болванку, как описано далее в этой статье). Вместо этого, во вкладке Вы должны указать на Ваши папки со сторонними расширениями или пользовательскими файлами – тогда GameMaker: Studio скомпилирует требуемые файлы из них в Вашу игру.

Если Вы создаёте расширение iOS, есть некоторые дополнительные поля, которые, возможно, Вам потребуется заполнить: Mac Source Directory, необходимый для любых SDK, требующих symlinks, Linker Flags, так как некоторые фреймворки и сторонние SDK требуют добавление дополнительных флагов в компоновщик и, наконец, любые системные и сторонние фреймворки, которые Вам необходимы.

Если Вы работаете с Android, то есть только два поля, которые может понадобиться заполнить: разрешения, требуемые Вашему расширению (Permissions) и любые дополнительные записи для манифеста (Android Manifest XML). Необходимые Вам разрешения будут полностью зависеть от того, что использует расширение и Вам нужно смотреть документацию Google, либо документацию, поставляемую с выбранным SDK, чтобы найти те, которые Вам нужны. Что касается Android Manifest, здесь Вы добавляете любую дополнительную информацию к манифесту, которая может требоваться для Вашего расширения, которое будет внедрено (добавлено), когда Ваша игра будет собираться для тестирования или финального релиза.

 

Создание нативного расширения – Общие

Ниже приведены основные шаги создания iOS или Android расширения и вызов функции, использующей нативный код. В данном случае мы добавим в GameMaker: Studio одну новую функцию, которая будет использовать Objective C++ на iOS для вывода информации в консоль iPhone Configuration Utility на Вашем рабочем Mac, а на Android отправляет ту же самую информацию в ADB-консоль.

Основы

Прежде чем идти дальше, Вы должны сначала создать новую комнату, новый объект и новый спрайт. Чтобы не усложнять, мы просто сделаем спрайт кнопки, который можно присвоить объекту, а объект поместим в комнату. Эта “кнопка” будет вызывать нашу функцию нативного расширения.

Создание расширения

Теперь нужно создать наше расширение. Для этого нужно кликнуть правой кнопкой мыши на папке Extensions в дереве ресурсов и выбрать “Create Extension”, после чего откроется окно Extension Package Properties и здесь (для нашего простого примера) Вам нужно заполнить только общую информацию (General information), затем нажать “Okay” для продолжения.

Добавление файла-болванки.

Для JS, GML и DLL расширений, Вам требуется файл, содержащий необходимый код. Однако для iOS и Android этот файл не используется, но GameMaker: Studio по-прежнему требуется файл для “группировки” Ваших функций расширения и констант (это может измениться в будущих обновлениях). Поэтому Вам необходимо создать файл-болванку, который может иметь любой формат, кроме .js, .gex или .dll и добавить его (для нашего примера назовём его как-нибудь типа “extension.ext”).

Для добавления этого файла, нужно кликнуть правой кнопкой мыши на новое расширение и выбрать “Добавить файл”, а затем найти свой созданный файл и нажать кнопку “Открыть” в нижней части.

 

Создание нативного расширения – iOS

Теперь, когда Вы подготовили свой файл для группировки функций и констант, которые Вы собираетесь использовать в Вашей тестовой игре, мы должны добавить файлы с исходным Objective C++ кодом, которые собирается использовать расширение. Для этого сделайте двойной щелчок по папке Extension Package и кликните по вкладке iOS, чтобы открыть свойства для этой платформы.

Для такого простого примера, Вам нет необходимости волноваться ни о каких других деталях, кроме исходного каталога (Source Directory) и имени класса (Class Name), поэтому просто установим их сейчас. Папка – место, куда мы собираемся сохранить необходимые исходные файлы, а имя класса может быть чем-нибудь простым, вроде “GenericExtension”.

Теперь, в проводнике Windows, перейдите в каталог, который Вы выбрали для исходного кода Вашего расширения и создайте папку с именем “Source” (если назвать любым другим именем, кроме “Source“, то GameMaker: Studio просто не найдёт его). В этой новой папке Вы должны создать два файла для своего кода – .mm и .h файлы. Для этого примера мы назовем их GenericExt.mm и GenericExt.h. В файл GenericExt.h добавьте строку

@interface YourClassName :NSObject

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

-(void) genexttestdummy_Function1:(char *)arg1 Arg2:(double)arg2 Arg3:(char *)arg3;

Обратите внимание, что первый параметр следует непосредственно после двоеточия и ему не требуется имя, но последующие параметры должны быть с именем “ArgN“, за которым следует двоеточие, затем тип (либо double, либо char *) в скобках и, наконец, имя, которое будет использоваться в определении функции – arg2, arg3 и т.д.

После объявления членов своих функций, добавьте “@end”, чтобы закрыть объявление интерфейса.

В .mm файле мы сначала подключим .h:

#import “GenericExt.h”

затем напишем реализацию функции, которую мы хотим добавить, начав с этого:

@implementation GenericExtClass

Добавьте свои функции как в заголовочном файле, но, вместо того, чтобы закончить строку точкой с запятой, добавьте изогнутые фигурные скобки, содержащие код, который Вы хотите вызвать. Для нашего примера, мы вызываем NSLog, чтобы вывести на экран вызов функции и параметры на консоль, которую мы будем в состоянии просмотреть из iPhone Configuration Utility на Mac, с которым соединено наше устройство на iOS.

 

Создание нативного расширения – Android

С расширениями для Android все несколько проще… Мы действительно должны создать только исходный файл Java, который собирается использовать расширение и “указать” GameMaker: Studio на него. Для нашего простого примера, Вы не должны волноваться о деталях манифеста и разрешениях. Таким образом, в окне свойств Extension Package, на вкладке Android, просто задайте имя класса (типа “GenericExtension“) и укажите папку, в которой Вы собираетесь хранить свой Java файл для использования.

Теперь, в проводнике Windows перейдите в каталог, который Вы выбрали, чтобы найти исходные файлы Вашего расширения и создайте папку “Java” (и опять, если назвать любым другим именем, кроме “Java“, то GameMaker: Studio не сможет найти его).

В этой новой папке необходимо создать файл с Java-кодом. Для нашего примера просто создайте файл с именем “GenericExtension.java”, не забывая включить следующую строку в верхней части Вашего кода Java:

package ${YYAndroidPackageName};

Теперь Вы можете создать свое определение класса, которое включает функцию, которую Вы хотите вызвать. В нашем примере Вы должны будете создать класс “GenericExtension” с функцией, названной genextestdummy_Function1, принимающей три параметра – String, double, string. Для простоты, в этом примере просто сделайте вывод параметров в ADB-консоль, чтобы проверить, что функция была вызвана правильно.

Создание GML-функции

Затем Вы должны добавить объявление функции к нашему расширению в GameMaker: Studio так, чтобы Ваш код мог вызвать Вашу новую Java-функцию. Чтобы сделать это, нажмите правой кнопкой мыши по файлу группы, который Вы добавили (файл-болванку), и выберите “Add Function”. Откроется новое окно, где Вы можете добавить свою функцию и присвоить ей параметры и свойства.

В соответствии с код, созданным ранее, Вы должны указать имя функции (Function Name) как “genextestdummy_Function1“, и Вы должны также определить внешнее имя, которое будет тем же самым. Теперь установите три параметра, которые принимает функция (string, double, string), и нажмите кнопку “Okay“, чтобы сохранить изменения.

Вызов функции нового расширения

Всё, что осталось сделать – добавить код в наш объект-кнопку, вызывающий новую функцию. В нашем примере есть только объект object0, в событии отпускания левой кнопки мыши которого мы будем делать вызов:

genexttestdummy_Function1("Hello", 100, "World");

При тестировании игры на выбранном устройстве, нажатие и отпускание этой кнопки должно приводить к отображению переданных параметров в консоли Configuration Utility iPhone на Mac или в консоли ADB на Вашем PC.

Принадлежность точки многоугольнику

Скрипт проверки вхождения точки в многоугольник (то есть проверяем, внутри многоугольника точка, или снаружи), с учётом попадания на грани и вершины многоугольника. Здесь реализован метод трассировки луча (учёт числа пересечений) Сама проверка состоит из последовательного вызова скрипта для каждой пары вершин многоугольника. Скрипт проверяет, пересекает ли луч, направленный вправо, отрезок между указанными вершинами. Если пересекает, то возвращается результат -1, если нет, то 1. Если же точка лежит на отрезке, то вернётся 0. В скрипт нужно передать шесть координат – координаты X, Y вершин многоугольника и координаты X, Y проверяемой точки. Все вершины многоугольника должны проверяться последовательно. При этом можно перебирать их как по часовой стрелке, так и против часовой стрелки. Вызов скрипта может происходить, например, так:

r1 = check_point(x1, y1, x2, y2, x0, y0)
r2 = check_point(x2, y2, x3, y3, x0, y0)
r3 = check_point(x3, y3, x4, y4, x0, y0)
r4 = check_point(x4, y4, x1, y1, x0, y0)

res = r1*r2*r3*r4

Собственно, скрипт проверки точки:

s_ax = argument0
s_ay = argument1
s_bx = argument2
s_by = argument3
s_mx = argument4
s_my = argument5

ax = s_ax - s_mx
ay = s_ay - s_my
bx = s_bx - s_mx
by = s_by - s_my

s = sign(ax * by - ay * bx)
if (s == 0 && (ay == 0 || by == 0) && ax * bx <= 0)
    return 0
if ((ay < 0) ^ (by < 0))
{
    if (by < 0)
        return s
    return -s
}      
return 1

Тут пример, показывающий работоспособность (все вершины можно перемещать мышкой). Действие происходит в событии draw контроллера. Использован “китайский” код, но исключительно для того, чтобы не усложнять понимание принципа работы. Например, в Студии можно все вершины передавать массивом – таким образом можно реализовать проверку с динамичным числом вершин. В примере используется многоугольник из 5 вершин, но на практике их может быть любое количество.