Готовый перевод Game Programming Patterns / Паттерны программирования в играх: 3.2 Subclass Sandbox

Цель

Создание различных видов поведения путём определения подклассов, которые используют операции, предоставляемые их общим базовым классом.

Мотивация

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

Игры, позволяющие нам стать супергероем, являются наиболее близким приближением.

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

Наш план состоит в том, что мы будем иметь базовый класс суперспособности Superpower.

Затем мы создадим производные классы для каждой суперспособности.

Мы поделим дизайн-документ между нашей командой программистов и поручим им писать код.

Когда всё будет готово, мы будем иметь сотню классов суперспособностей.

Когда вы столкнётесь с большим количеством подклассов, как в этом примере, часто это будет значить, что подход, основанный на управлении данными, будет лучшим.

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

Шаблоны Объект типа, Байт-код и Интерпретатор — все они могут помочь.

Мы хотим, чтобы наш игрок погрузился в мир, изобилующий разнообразием.

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

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

Не будет такого уголка в кодовой базе, который не был бы затронут этими подклассами.

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

Что произойдёт?

Появится много избыточного кода.

Хотя разные способности будут дико разнообразны, мы всё ещё можем ожидать значительного перекрытия функционала.

Многие из них будут порождать визуальные эффекты и проигрывать звуки одинаковым способом.

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

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

Каждая часть игрового движка будет связана с этими классами.

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

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

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

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

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

Будет трудно определить, каким инвариантам следуют все суперспособности.

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

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

Что мы хотим — так это дать каждому программисту игрового процесса, реализующему суперспособность, набор «игрушек», с которым он мог бы играться.

Вы хотите, чтобы ваша способность проиграла звук — вот вам функция playSound().

Хотите частиц?

Вот spawnParticles(). Мы обеспечим, чтобы эти операции покрыли все ваши нужды, так что вам не нужно будет включать случайные заголовочные файлы и совать свой нос в остальные части кодовой базы.

Мы сделаем это, сделав операции защищёнными методами в базовом классе Superpower.

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

Сделав их защищёнными (и, вероятно, невиртуальными),

мы тем самым сообщим, что они существуют специально для вызова из подклассов.

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

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

С учётом всего вышесказанного, для реализации нового вида способности вам будет необходимо:

Создать подкласс класса Superpower.

Переопределить метод activate().

Реализовать тело метода, вызывая защищённые методы, предоставляемые классом Superpower.

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

Когда мы видим код, дублирующийся во множестве подклассов, мы всегда можем выделить его в новую операцию в классе Superpower и далее использовать её.

Мы обратили нашу проблему связности путём помещения этой связности в одно место.

Сами суперспособности в конечном итоге, привязаны к различным игровым системам, но наши сто производных классов — нет.

Вместо этого, они связаны только с их базовым классом.

Когда изменится одна из этих игровых систем, может понадобиться изменить и класс Superpower, но не десятки его подклассов.

Этот шаблон подводит к архитектуре, где вы имеете не глубокую, но широкую иерархию классов.

Ваши цепочки наследования не глубокие, но есть множество классов, свисающих с Superpower.

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

Время и любовь, которые мы вкладываем в класс Superpower, могут принести выгоду широкому набору классов в игре.

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

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

Шаблон

Базовый класс определяет абстрактный метод песочницы и предоставляет несколько операций.

Пометка их как защищённых, даёт понять, что они нужны для использования в производных классах.

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

Когда использовать

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

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

Подкласс песочницы хорошо подходит, если:

Вы имеете базовый класс с рядом производных классов.

Базовый класс в состоянии предоставить все операции, которые могут понадобиться производному классу.

В подклассах есть перекрывающееся поведение и вы хотите упростить разделение кода между ними.

Вы хотите минимизировать связность между этими производными классами и остальной частью программы.

Имейте в виду

Он может сделать базовый класс чрезмерно тяжёлым.

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

Переведено на Нотабеноиде
http://notabenoid.org/book/51192/215223

Переводчики: Mingun, XDMaik

Настройки

Готово:

18.92% КП = 1.0

Непереведённые фрагменты будут пропущены.