Переосмысление SOLID: почему традиционные принципы проектирования не работают при разработке игр

Принципы SOLID (Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion) - это общепризнанные рекомендации, разработанные для улучшения сопровождаемости и читаемости исходного кода программного обеспечения. Однако их применение в сфере разработки игр часто оказывается сложным из-за уникальных особенностей этой области. Чтобы полностью оценить контекст, мы погрузимся в эти сложности и изучим альтернативные парадигмы проектирования, которые лучше подходят для динамичной природы разработки игр.

Проблемы, связанные с принципами SOLID при разработке игр

Принцип единой ответственности (SRP)

SRP диктует, что класс должен иметь только одну ответственность. Это часто противоречит взаимосвязанной природе разработки игр.
Рассмотрим персонажа ролевой игры (RPG). Он может обладать такими возможностями, как перемещение, атака, взаимодействие с окружающей средой и отображение анимации. Придерживаясь SRP, можно было бы разделить эти функциональные возможности на отдельные классы, такие как CharacterMovement, CharacterCombat, CharacterInteraction и CharacterAnimation. Такой подход, хотя и является чистым с точки зрения SRP, может привести к обилию классов. Поведение персонажа становится разбросанным по всей кодовой базе, что усложняет понимание, поддержку и отладку.
Принцип открытости-закрытости (OCP)
Согласно OCP, программные объекты должны быть открыты для расширения, но закрыты для модификации. Этот принцип оказывается сложным в контексте разработки игр - области, характеризующейся итеративной и динамичной природой.
Например, оружие в игре-шутере от первого лица может изначально включать в себя базовый пистолет. По результатам игрового тестирования разработчикам может понадобиться добавить к оружию новые функции, например, лазерный прицел или увеличенный магазин. Такие модификации часто требуют внесения изменений в существующий код, что противоречит принципу OCP.
Принцип замещения Лискова (LSP)
LSP - это еще один принцип, который часто противоречит реалиям разработки игр. Этот принцип утверждает, что объекты суперкласса должны иметь возможность быть заменены объектами подкласса без ущерба для корректности программы.
В игре, однако, это часто не так. Например, рассмотрим врага-босса, который является подклассом класса общего врага. Враг-босс может обладать уникальными способностями или поведением, не присущими общим врагам. Функция, разработанная для работы с общими врагами, может работать некорректно при замене врага-босса, что нарушает LSP.
Принцип разделения интерфейсов (ISP)
ISP утверждает, что множество интерфейсов, специфичных для клиента, лучше, чем один интерфейс общего назначения. Однако этот принцип может привести к фрагментации и ненужной сложности при разработке игр.
Игровой объект, например, персонаж игрока, может взаимодействовать с предметами, врагами, игровым окружением и пользовательским интерфейсом игры. Если для каждого типа взаимодействия требуется свой интерфейс, как предполагает ISP, код может быстро стать разрозненным, запутанным и сложным в управлении.

Принцип инверсии зависимостей (DIP)
Наконец, DIP подчеркивает зависимость от абстракций, а не от конкретных реализаций. Однако в высокопроизводительной, ресурсоемкой области разработки игр это часто может оказаться непрактичным.
Например, в гоночной игре объекту автомобиля может потребоваться прямое управление его графическим представлением или прямой доступ к его физическим атрибутам для обнаружения столкновений. Строгое следование DIP может привнести ненужную сложность, накладные расходы на производительность и затруднить отладку, что противоречит практическим потребностям разработки игр.
Учитывая эти проблемы с принципами SOLID, становится очевидным, что разработка игр требует альтернативных парадигм проектирования. Со временем появилось несколько таких парадигм, предлагающих более гибкие и эффективные подходы к созданию игр.

Альтернативные парадигмы в разработке игр

Архитектура на основе компонент

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

Например, в платформере персонаж игрока может быть композицией MoveComponent, JumpComponent и CollisionComponent. Это уменьшает необходимость в больших монолитных классах, упрощает сложные объекты и предлагает гибкий ответ на вызовы, поставленные SRP и LSP.

Entity-Component-System (ECS)

Парадигма Entity-Component-System (ECS) является дальнейшим развитием архитектуры на основе компонентов. Здесь каждый игровой объект - это сущность (идентификатор или контейнер компонентов), компоненты хранят данные, а системы обеспечивают поведение, оперируя сущностями с определенными комбинациями компонентов.

В космическом шутере вражеский корабль может представлять собой объект с компонентами PositionComponent, MovementComponent и AttackComponent. Затем отдельные системы обрабатывают движение, атаку и рендеринг на основе этих компонентов. Такой подход повышает производительность, развязывает данные и поведение и предлагает решения проблем, связанных с OCP, LSP и ISP.

Дизайн, основанный на данных
Дизайн, основанный на данных, - еще одна важная стратегия в разработке игр. Этот подход способствует гибкости, позволяя дизайнерам легко настраивать элементы игры, используя данные о параметрах, не углубляясь в кодовую базу.
Например, в файтинге атрибуты персонажа, такие как здоровье, сила и скорость, могут храниться во внешних конфигурационных файлах. Это позволяет разработчикам изменять баланс персонажа без необходимости модификации кода, решая проблемы, связанные с OCP.
Техники AI
Машины состояний и деревья поведения помогают управлять сложностью поведения персонажей. Они представляют состояния и переходы персонажей, делая сложное поведение управляемым при сохранении модульности и возможности повторного использования.
Например, в стелс-игре поведение вражеского ИИ может быть представлено в виде машины состояний с такими состояниями, как "Патруль", "Расследование" и "Преследование". Это помогает управлять сложностью поведения ИИ, сохраняя при этом четкое разделение задач.
Паттерны проектирования
Несмотря на проблемы с принципами SOLID, другие паттерны проектирования хорошо подходят для разработки игр. Например, паттерн Observer используется для обработки событий, паттерн Prototype - для порождения объектов, а паттерн Flyweight - для эффективного управления ресурсами.

Заключение
Хотя принципы SOLID трудно строго соблюдать при разработке игр из-за их уникальных требований, проблемы, которые они представляют, проложили путь для альтернативных парадигм. Такие подходы, как архитектура на основе компонентов, ECS, проектирование на основе данных, методы искусственного интеллекта и конкретные паттерны проектирования, обеспечивают надежные решения возникающих проблем, предлагая эффективные и гибкие способы решения захватывающих задач разработки игр. Целью, как всегда, остается создание запоминающихся, увлекательных игровых впечатлений.

7.1K7.1K показов
5.1K5.1K открытий
11 репост
55 комментариев

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

LSP (он имени Барбары Лисков, а не Лискова) - он в первую очередь про соблюдение предусловий и постусловий.
Пример с боссом не в ту степь, но очевидно, что иерархия классов тут некорректная. Должно быть не Враг -> РядовойВраг -> Босс, а Враг -> РядовойВраг и Враг -> Босс.

ISP про то, что не должно быть огромного интерфейса IPlayer, который может всё, с единственной реализацией в классе Player. Должны быть мелкие интерфейсы навроде ICanMove, ICanInteract и так далее.

Ответить

ISP про то, что не должно быть огромного интерфейса IPlayer, который может всёМожно сделать фасад который буквально "может всё", внутри это будут те-же мелкие интерфейсы.

Ответить

для в LSP я обосрался хорошо что мне скинули статью

Ответить

Статья сама себе противоречит.
Там где единственная ответственность куча классов (CharacterMovement, CharacterCombat, CharacterInteraction и CharacterAnimation) это безусловно каша и плохо, а там где компоненты та же куча тех же классов только сбоку (MoveComponent, JumpComponent и CollisionComponent, и это только замена CharacterMovement) это уже благо и хорошо.

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

Ответить

Возможно ты не правильно понял, потому что в случаее единственной отвественности для каждого моба мы будем создавать GoblinMovement, GoblinCombat,GoblinInteraction ,GoblinAnimation
TrollMovement, TrollCombat, TrollInteraction , TrollAnimation
и т.д.
В случае компонентов 1 компонет MoveComponent используется для всех противников и персонажей.

Ответить

Очень сомнительная статья.
Попытка переосмыслить SOLID, когда его не понимаешь невозможно.

К тому же, попытка сравнивать разные сущности (SOLID, ECS, Data Driven Design, Design Patterns), априори глупая. Можно сравнивать сущности в рамках одной категории, а они изначально находятся на разных уровнях.

Изучите глубже темы, SOLID (примеры использования в геймдеве/вне геймдева), разберитесь, что такое парадигмы, принципы на более общем уровне, подходы проектирования.

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

Ответить

p.s, на хабре за такую статью, вас бы утопили в минусах

Ответить