В ГеймМейкере каждый ресурс ("ассет") - будь то спрайт, класс, файл или скрипт - имеет уникальный номер. Даже объекты (инстансы) имеют уникальный номер. Все эти номера никогда не совпадают между собой - даже номера скриптов с инстансами, классов с файлами. Я подозреваю, что номера уничтоженных объектов могут потом использоваться новосозданными объектами, но для наших рассуждений это не важно. Главное, что хотя в ГМ нельзя хранить ссылки на объекты, можно хранить их номера и обращаться к ним по номерам. Ещё один компонент нашего плана: то, что при вызове скрипта он получает тот же контекстный объект (грубо говоря, значение this), что и вызывающий его код. (Далее я использую русскоязычные названия переменных и ассетов, но только для ясности текста - скорее всего, ГеймМейкер не позволяет такие имена.)
Допустим, нам нужен класс "Снаряд", у которого будет метод "взрываться" - но каждый вид снаряда взрывается по-своему: кто-то обдаёт врагов краской, кто-то наносит урон, кто-то строит башню, а кто-то производит меньшие снаряды другого типа. В общем, у класса "Снаряд" есть потомки "РадужныйСнаряд" (со своими потомками "СинийСнаряд", "КрасныйСнаряд" и "ЖёлтыйСнаряд"), "Бомба", "СтроительныйСнаряд" (потомки по типам башен) и "ОсколочныйСнаряд", каждый из которых должен реализовывать метод "взрываться" по-своему.
Создаём скрипты "цветной_взрыв", "повреждающий_взрыв", "строительный_взрыв" и "осколочный_взрыв". Они просто висят в группе ассетов Scripts (или можно разместить по подпапкам, например, в папку "Снаряд/Взрывы"). Они не привязаны никак к классам, которые будут обслуживать.
При
создании объекта "РадужныйСнаряд" присваиваем его
переменной метод_взрыва значение
цветной_взрыв. Не строку "цветной_взрыв"! А просто -
цветной_взрыв. Потому что это обозначение в коде возвращает уникальный номер скрипта "цветной_взрыв", по которому его можно вызвать. Аналогично поступаем с остальными снарядами. У вторичных потомков "Снаряда" (например, "ЖёлтыйВзрыв") значение переменной "метод_взрыва" уже задано, поскольку реакция на событие "создание объекта" наследуется (если вы переопределяете его, не забудьте добавить сначала вызов родительской реакции).
Когда необходимо взорвать снаряд - например, при столкновении с целью или стенкой, при попадании в поле взрывания снарядов, при клике на снаряд, команде из консоли, да мало ли по каким причинам! - достаточно запустить код:
execute_script(метод_взрыва)
Не нужно кавычек, не нужно получать ссылку на метод по строковому названию - запись "метод_взрыва" является названием переменной, где уже содержится номер соответствующего скрипта. Главное, чтобы совпадал контекстный объект: нужно чтобы этот код считал за self собственно снаряд. Если же вызов происходит из другого контекста, следует использовать with. Допустим, если номер искомого снаряда находится в переменной "вот_его_взорвать", нужно записать:
with вот_его_взорвать execute_script(метод_взрыва)
Вот и всё: мы взрываем произвольный снаряд (полиморфизм) уникальным для него способом, являющимся для нас чёрным ящиком (инкапсуляция).
Недостатки способа
Поскольку это всё-таки не полноценное ООП, возникает несколько трудностей.
Во-первых, чтобы вызвать родительский метод, надо знать, что это за метод; или же держать в переменной не просто номер метода, а массив последовательных методов, обрабатывая каждый вызов специальным скриптом. Не знаю, как это скажется на производительности. С другой стороны - в Яваскрипте тоже надо знать родительский метод, чтобы вызвать его (вызов super() поддерживается не всеми браузерами).
Во-вторых, в предложенном варианте каждый отдельный объект (каждый инстанс!) хранит номера всех своих методов. Это позволяет изменять функционал отдельных объектов на лету, что также используется в таких динамических языках как Яваскрипт и Руби, но если мы подобной функции использовать не собираемся - то есть повод поволноваться о производительности. Не уверена, что ГеймМейкер оптимизирует начальные значения переменных у объектов одного типа, и может оказаться, что 100 пуль на экране - это 100 х 2 байт под хранение целого числа с номером одного и того же метода. Умножить на количество методов! Если будет заметно проседание производительности из-за этого, то придётся что-то думать. Как обычно, чем универсальнее решение и удобнее для изменения на лету, тем больше страдает оптимальность (это же и причина, почему ГеймМейкер не самый проворный движок).
Наконец, в ГеймМейкере нет такого понятия как "интерфейс", так что не получится следовать принципу
Loose coupling.