Найти в Дзене

Немного о наследовании в Game Maker

Оглавление

Добрый день, дорогие читатели! Сегодня я хотел бы немного рассказать о такой штуке, как наследование в движке Game Maker. Поговорить о том, что это такое, что такое родители и дети (наследники) в рамках этого движка и для чего оно вообще нужно. В общем, новичкам думаю будет полезно и интересно. Текста будет много, но попытался объяснить все и разжевать как можно понятнее. Погнали!

Для чего нужно наследование?

Давайте разберем на очень простом примере. Допустим у нас в игре есть несколько типов врагов и каждый из них может наносить урон герою при соприкосновении. Как это сделать? Да очень просто, скажете вы - достаточно прописать в объекте каждого врага, чтобы при столкновении его с героем у героя отнималась 1 ед. здоровья. Или же в объекте героя прописать код столкновения с каждым врагом.

Т.е. схематично это может выглядеть как-то так. Например, в игре есть следующие виды врагов:

Список врагов из условной игры
Список врагов из условной игры

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

Схематичное описание алгоритма уменьшения здоровья при столкновении героя с врагами
Схематичное описание алгоритма уменьшения здоровья при столкновении героя с врагами

А теперь давайте представим, что в игре 10 врагов! Нет, лучше 20! Нет, их там сотня!!! Представили сколько раз вам придется писать один и тот же код для столкновения героя с разными объектами?

Тут логичнее всего каким-то образом объединить всех врагов в какую-то условную субстанцию, условного врага и проверять столкновение только с ним один раз. Т.е. схематично как-то так:

Схематичное описание алгоритма столкновения с врагом
Схематичное описание алгоритма столкновения с врагом

Согласитесь, что гораздо приятнее и удобнее прописать только одно событие столкновения? Таким образом, сколько бы врагов вы после этого не добавляли, вам достаточно каждого врага просто включить в этот своего рода "контейнер" и игра будет работать, а вам даже не придется менять и дорабатывать код.

Так вот, этот самый контейнер с именем "ВРАГ" в примере выше и является родителем (Parent), а все объекты, которые в него входят, это его дети (Children). Иногда их еще называют "потомками", но в движке Game Maker их обозначают именно как "дети".

Т.е. если у нас есть потребность объединить несколько объектов по каким-то свойствам, то логичнее всего им назначить общего родителя.

Как в Game Maker назначить родительский объект?

Все, что я описал выше, в Game Maker делается очень просто. Например вы создали несколько объектов врагов и уже прописали им всю логику в коде событий. Т.е. считаем, что у вас в дереве ассетов уже есть несколько готовых объектов различных видов врагов:

Дерево ассетов с объектами врагов
Дерево ассетов с объектами врагов

Самое время объединить их. Создаем новый объект. Назовем его к примеру "obj_enemy". Данному объекту не назначаем никакой спрайт, не добавляем ему никаких событий и не прописываем никакой код, т.е. просто создаем пустой объект:

Созданный пустой объект, который будет родительским по отношению к объектам всех врагов
Созданный пустой объект, который будет родительским по отношению к объектам всех врагов

Далее поочередно заходим в каждый объект врага и выбираем ему родителем созданный только что нами объект:

Выбираем каждому объекту врага родителем созданный нами объект "obj_enemy"
Выбираем каждому объекту врага родителем созданный нами объект "obj_enemy"

После этого, открыв снова наш родительский объект "obj_enemy", во вкладке "Parent" будут показаны все его дочерние объекты, которые наследуются от него:

Список дочерних объектов в родительском объекте "obj_enemy"
Список дочерних объектов в родительском объекте "obj_enemy"

Как вы наверное уже успели заметить, в родительском объекте есть кнопка "+" (обвел ее красным на скриншоте выше), т.е. в принципе вы могли и не заходить в каждый дочерний объект врага и назначать ему родителя, а просто из родительского объекта через эту кнопку добавить дочерние объекты.

Все готово! Теперь вы можете у объекта героя проверять коллизию (столкновение) лишь с одним объектом obj_enemy (а не с десятком различных объектов врагов) и если столкновение имеет место быть, то отнимать единицу здоровья у героя. И несмотря на то, что сам этот объект отсутствует в уровнях, но так как он является родительским по отношению ко всем объектам врагов, то коллизии будут проверятся со всеми дочерними объектами в уровне.

Чем еще полезно наследование?

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

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

Давайте рассмотрим очень простой пример, где у героя есть пушка, которая стреляет различными снарядами. Попробуем подумать, что схожего у всех снарядов?

  • при появлении из ствола, все они летят вперед
  • при попадании в стену все они уничтожаются
  • пры вылете за границы экрана все они уничтожаются
  • при попадании во врага все они наносят урон врагу

Теперь давайте подумаем, а чем же они могут отличаться?

  • все они могут иметь различную скорость полета и различные эффекты при полете (например оставлять разный след)
  • при попадании в стену все они могут иметь различные эффекты
  • при попадании во врага они могут наносить ему различный урон
  • все они могут выглядеть по разному, т.е. иметь различные спрайты

Ок. С этим мы разобрались. Т.е. все схожие черты и действия мы будем помещать в родительский объект, а все, что их отличает уже будет дополнительно расписано по дочерним объектам. Это надо четко запомнить!

Т.е. для начала мы создаем родительский объект, например, под именем "obj_bullet". И в нем создаем несколько событий.

К примеру, в событии создания объекта (Create), мы создадим переменные скорости пули и урона. Что-то типа того:

spd = 1;
damage_value = 1;

Это будет скорость и урон всех снарядов по умолчанию. Но чуть позже в каждом дочернем объекте различных видов снарядов мы переназначим данные переменные.

В событии шага (Step) родительского объекта мы пропишем что-то типа того:

x = x + spd;

Т.е. каждый шаг будет изменяться x-координата на кол-во пикселей, равных переменной spd (она у нас равна пока 1).

Добавим событие столкновение (Collision) с объектом врага, в котором мы будем уменьшать здоровье врага на кол-во, равное переменной damage_value (оно у нас пока равно так же 1) и уничтожать сам объект пули:

other.hp - = damage_value;
instance_destroy();

Тут важно понимать (знать) что в событии столкновения можно обращаться к таким сущностям, как self и other, где self - это объект, участвующий в столкновении, с которого мы вызываем это событие, а other - это тот объект, с которым мы столкнулись. Так вот, мы берем переменную здоровья врага hp, с которым столкнулся снаряд, и уменьшаем ее на значение, равное damage_value. Переменную hp конечно нужно предварительно создать и назначить каждому врагу.

Так же в родительском объекте создадим событие столкновения (Collision) с объектом стены, где снаряд будет уничтожаться:

instance_destroy();

И создадим событие выхода снаряда за пределы комнаты (Outside Room), где пропишем аналогичный код уничтожения объекта.

Ок. Общий код для всех снарядов мы прописали. Теперь представим, что у нас есть снаряд лазера (obj_laser) и снаряд бластера (obj_blaster). Им обоим мы назначим родительским ранее созданный объект obj_bullet. Этим объектам мы уже назначаем различные спрайты (в отличии от родительского объекта obj_bullet, который спрайта не имеет). Таким образом эти снаряды у нас будут выглядеть по разному.

И вот мы хотим сделать так, чтобы снаряд лазера летел со скоростью 2 пикселя и на носил урон в 3 единицы, а снаряд бластера летел со скоростью в 3 пикселя и наносил урон в 5 единиц. Все, что нам нужно сделать, это переназначить событие создания (Create) данных объектов.

Т.е. у obj_laser создать это событие и прописать:

spd = 2;
damage_value = 3;

У а obj_blaster создать аналогичное событие и прописать:

spd = 3;
damage_value = 5;

Вот и все! Больше никаких событий в этих объектах создавать не нужно, при этом снаряды (дочерние объекты) будут лететь, уничтожаться при врезании в стену, наносить урон врагу и т.д., Т.е. они будут делать все то, что прописано в родительском объекте. Создавать при выстреле в игре конечно же нужно дочерние объекты, в зависимости от выбранного оружия создаем либо obj_laser либо obj_blaster.

Что такое переназначение?

Выше я упомянул, что у дочерних объектов мы переназначим переменные в событии создания (Create), но что это значит?

Дело в том, что если у родительского объекта есть какое-либо событие и такое же событие вы создаете у дочернего объекта, то по умолчанию весь код из такого события дочернего объекта перекрывает код такого же события родительского объекта, т.е. событие переназначается.

Может сложно объяснил, но если проще, то если у родителя есть какой-то код в событии шага (Step) и есть какой-то код у ребенка в том же событии шага (Step), то код в шаге родителя выполняться не будет, будет выполняться только код в событии шага ребенка. Короче, код родителя удаляется и переписывается кодом ребенка.

А если мы хотим чтобы в одном и том же событии работал как код родителя, так и код ребенка? Т.е. нас устраивает код родителя, но у ребенка мы хотим добавить какую-то пару строк кода так, чтобы код родителя при этом не игнорировался, а тоже выполнялся. И это возможно!

Давайте рассмотрим на примере выше. Помните у родительского объекта obj_bullet, мы в событии создания объекта задавали две переменные: скорости и уровня урона? Так вот, к примеру у одного из дочерних объектов мы хотим изменить только урон, а скорость оставить той, что прописана у родителя.

И если вы просто в дочерний объект добавите событие создания и пропишите код, меняющий переменную урона:

damage_value = 10;

То при запуске игры вам выдаст ошибку о том, что переменная скорости полета spd для этого дочернего объекта не задана. Почему? Да все просто, мы, создав событие Create в дочернем объекте, получается полностью перезаписываем это событие и родительское, где как раз задается скорость, уже просто игнорируется.

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

event_inherited();
damage_value = 10;

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

spd = 1;
damage_value = 1;
damage_value = 10;

Понимаете? Первые две строчки унаследованы от родителя функцией event_inherited(), а потом мы задаем значение переменной урона, равное 10. Так как код выполняется по порядку, то переменная урона в момент создания объекта сначала примет значение, равное 1, а потом тут же сменит его на 10.

Заключение

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

Я вам привел лишь самые простые примеры для удобства восприятия, чтобы вам проще было понять на пальцах, так сказать. Но на самом деле с помощью наследования делаются более глубокие и сложные вещи. Но если вы начнете пользоваться им хотя бы в таких, казалось бы простых случаях, то это уже сделает вас гораздо более продвинутым новичком по отношению к другим! ))

Спасибо, что дочитали статью до конца! Надеюсь она действительно окажется вам интересной и полезной. Ставьте лайки, оставляйте комментарии, подписывайтесь, делитесь - это все мотивирует меня писать дополнительные статьи на данном канале. Прошу прощения, что публикую что-то редко, как уже писал ранее - времени сейчас не так много. Но буду стараться находить несколько минут и рассказывать о чем-то интересном.