Принципы 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, проектирование на основе данных, методы искусственного интеллекта и конкретные паттерны проектирования, обеспечивают надежные решения возникающих проблем, предлагая эффективные и гибкие способы решения захватывающих задач разработки игр. Целью, как всегда, остается создание запоминающихся, увлекательных игровых впечатлений.

Комментарии (24)


  1. funca
    26.05.2023 22:47
    +13

    SOLID это набор принципов. ECS это паттерн. Принцип это высокоуровневая абстракция, задающая общий ход рассуждениям в процессе дизайна. Паттерн описывает конкретный способ реализации конкретной задачи в конкретном контексте. По смыслу это разные вещи.


    1. CptMarshmallow
      26.05.2023 22:47
      +2

      Прости, дружище. Нечайно вместо поста тебе минус кинул. :с В качестве компенсации кинул плюс отдельно на профиль.


    1. nin-jin
      26.05.2023 22:47

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


      1. funca
        26.05.2023 22:47

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

        Можно-ли обойтись без принципов? Скорее всего нет, если мы хотим дружить с логикой. Ведь логика формально это аксиоматическая система. Т.е. все свои утверждения можно разделить на два класса: аксиомы и теоремы. Теоремы выводятся из аксиом или других теорем. Аксиомы не выводятся ни от куда - это начала. Выделение аксиом даёт понимание структуры логики вашей деятельности, а минимизация их количества - к абстракции и упрощению.

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


  1. CptMarshmallow
    26.05.2023 22:47
    +17

    Дико извиняюсь, но булшит.

    Проблема вообще не в неподходящести SOLID. Проблема в 2 дураках - программистах и менеджерах.

    В UE, по крайней мере, несложно и даже отлично придерживаться этого паттерна, но посмотрите на рынок кадров - почти каждый первый это"любящий игры" чел самоучка/скилбоксер который даже имея пару лет опыта просто тупо не знает/понимает даже базовые принципы ООП, что уж там про солид, когда люди не понимают зачем нужны private/protected.

    Вторые - менеджеры которым нужно "здесь и сейчас" а то и вчера, в результате чего поставка фичи в ужатый срок это норма, а рефакторинг это "ну да, важно, но потом".

    Вот потому имхо мы и имеем 40 fps багованое говно на 4090.


    1. leonP4
      26.05.2023 22:47
      +4

      Подписываюсь под каждым словом.

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


      1. funca
        26.05.2023 22:47
        +5

        Подвергать сомнениям дедовские принципы это нормально, иначе они превращаются в догмы.

        Изначально SOLID возник в ходе переписки, когда ещё не было комментов на Хабре, и народ трындел в какой-то своей DLке на тему - какие у нас вообще есть принципы хорошего кода?

        Сперва насобирали пару десятков. Но потом пришел Дядя Боб и набросил, что этот склад мудрости овер дофига для простого разработчика - надо быть проще и оставил пять с хвостиком.

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

        Со временем SOLID мутировал под влиянием разных тенденций и трактовок. Та же Барбара Лисков спустя много лет на конференции как-то отметила в духе, что ее принцип подстановки в оригинале был вообще не о том (да и не она его придумала, а в последствии коллеги наши нестыковки применительно к своей задаче и его переделали), но довольна что кого-то это смогло вдохновить.

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


        1. hVostt
          26.05.2023 22:47
          +1

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

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


          1. leonP4
            26.05.2023 22:47

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


      1. nin-jin
        26.05.2023 22:47
        -2

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


        1. hVostt
          26.05.2023 22:47
          +1

          А что плохого в знаниях? Да, многое из накопленного опыта устарело, а некоторое как выяснилось даже вредно. Иногда, на поверку, "я не молюсь на ваши древние скрижали" оказывается просто отсутствием каких-либо знаний вообще. А чё, читаем туториал, берём библу и погнали. Знание, оно конечно для дураков.


  1. panzerfaust
    26.05.2023 22:47
    +6

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

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

    Martin defines a responsibility as a reason to change, and concludes that a class or module should have one, and only one, reason to be changed (e.g. rewritten).

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

    При этом принцип не заставляет вас создавать CharacterMovement, CharacterCombat, CharacterInteraction и CharacterAnimation. Это обычно получается само собой из соображений сложности поддержки и восприятия или если, опять же, изменения в движении персонажа начинают засаживать баги в боевую систему. А так весь код персонажа вполне может быть в одном классе.

    Также см. принцип "low coupling high cohesion", который идет рука об руку с SRP.


    1. sshikov
      26.05.2023 22:47
      +5

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


  1. sshikov
    26.05.2023 22:47
    +3

    строгой иерархии классов, предложенной SOLID

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


  1. aktuba
    26.05.2023 22:47
    +1

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

    Ясно, понятно.


  1. tzlom
    26.05.2023 22:47
    +2

    SRP это как бог - его нет, многие в него верят, объяснить не могут

    OCP не нарушается если принято решение полностью переделать класс. А вот добавлять пистолет с прицелом как отдельную от пистолета сущность не стоит (но возможно степень сродства не 100% и надо будет вводить класс стреляло)

    LSP это основа Entity framework ов, то, что ваш босс как то по другому беснуется не должно менять интерфейс каким именно образом мы ему передаём урон (а вот получит он урон или нет уже вопрос реализации)

    ISP если у вас каждый куст помимо коллизии еще и магазин реализует то вы что-то перемудрили

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


  1. NeoNN
    26.05.2023 22:47
    +8

    Не Лискова, а Лисков. Барбара Лисков. А принципы проектирования от того, игра это или нет, не изменяются. Иначе можно только игру "ЩИ!!! Симулятор жестокости" написать.


    1. Alexufo
      26.05.2023 22:47
      +1

      Хоть не Барбара Чоулз и на там спасибо


  1. dalerank
    26.05.2023 22:47
    +6

    Вам уже на dtf накидали минусов с указанием ошибок в рассуждениях, и общего качества материала, как будто написанного ЧатГпт. Пишите лучше код и игры, познавайте разные методы на практике, тогда на такое не останется времени ;)


  1. korsour
    26.05.2023 22:47
    +3

    Почему по твоему мнению, добавление лазерного прицела в пистолет должен менять кодовую базу, что противоречит OCP? Если код написан так, что стрельба меняется из-за модификации функционала, не относящегося к стрельбе, это значит что стрельба написана изначально некорректно. А OCP как раз и гласит, что мы открыты для расширения, а добавление лазерного прицела - отличная демонстрация расширения, которая не должна влиять на существующую механику стрельбу и прицеливания через мушку.


  1. Nialpe
    26.05.2023 22:47
    +2

    Приведенные вами примеры наоборот работают за SOLID, эти примеры в голос орут: "подумай, правильно ли спроектированы классы и их иерархия?" Именно для рефлексии с такой постановкой вопроса предназначен SOLID, чтобы разработчик задумался перед принятием решения. Поэтому маркеры в виде принципов я бы не назвал бесполезными. Разумеется конечное решение (и ответственность) за разработчиком.


  1. SadOcean
    26.05.2023 22:47
    +1

    Ох, Я прямо пригорел немножко, простите.

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

    SRP - он диктует не то, что класс должен делать одно, он диктует что класс должен изменяться в интересах одного актора. В контексте игр это значит, что хорошей будет декомпозиция в интересах игр. Например декомпозиция игрового персонажа на модель, контроллер игрового объекта и вью - хорошая с точки зрения SRP игровая декомпозиция. Модель отвечает за поведение и, возможно сохранение данных (или сериализацию и передачу по сети), контроллер - за взаимодействие с прочими сервисами, например вводом, визуалом и эффектами. А вью - за детали отрисовки, анимацию и т.д. Это соблюдение SRP для игр.

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

    LSP - тут в целом обычная проблема. Не только в играх, но вообще везде любят использовать наследование как инструмент переиспользования кода, а не как объекты с общим интерфейсом.
    В этом смысле, кстати, нарушения LSP нет, хотя ИМХО, код получается так себе, не стоит использовать глубокие иерархии.
    Но что тут важно - LSP будет нарушено, если в списке "бегающие юниты" окажется турель, то она не сможет выполнять все функции бегающих юнитов, и может сломать какую нибудь систему, которой это правдо важно.
    В принципе, это тоже не страшно.
    Самым важным следствием LSP Я бы назвал такое - если вы наследуете объект, например юнит, который умеет бегать, стрелять и терять здоровье, а потом заглушаете часть функций (не умеет бегать) - то вы можете получить хрупкий код. Вместо этого лучше разделить функции по аспектам или компонентам и использовать не один интерфейс для всех юнитов, а для юнитов, которые стреляют или бегают.

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

    DIP - вот тут соглашусь.
    Архитектура должна помогать, а не мешать, если при попытке делать чистую архитектуру получается херня, рулите в сторону практичного кода, а не чистой архитектуры. Либо подумайте еще.
    И да, в любой момент может потребоваться хак, обращающийся к чему то напрямую, имеющий не ООП организацию и т.д.
    Но вообще принцип неплох. Вообще Я рекомендую Чистую архитектуру того же Мартина, там неплохо написано, зачем.

    И дальше.
    Архитектура на основе компонент - это самое что ни на есть компонентно-ориентированное ООП
    Чистейшее, оно буквально направлено на реализацию ООП принципов.
    ООП - абстракция, ее можно реализовать по разному, наследование - не обязательное условие, через композицию тоже работает.

    ECS
    Ну тут еще интереснее.
    Я думаю написать статью про это.
    Тут как раз тот случай, когда ребята попытались изобрести другой вариант структуры в интересах производительностью.
    Штука отличная, попробовать можно, но нужно понимать - многим проектам она нафиг не нужна и принесет только проблемы.

    Data Driven Design - тоже никак не противоречит классическим рекомендациям разработки ПО.
    Собственно все крутые ребята топят за позднее связывание данных и конфигурацию.
    Вообще это полезная штука.

    В общем это выглядит немного как перпендикулярно тому, про что классические рекомендации разработки ПО.



  1. sibirier
    26.05.2023 22:47
    +2

    Очень похоже на сгенерированную статью. Не "в стиле gpt", а от "неё" полностью от начала и до конца.
    В этой статье, комментах на dtf и во второй статье на dtf очень похоже на то, что это какая-то нейросеть, которая обучается на примерах, задаёт вопросы, очень часто "признаёт ошибки", чтобы не выглядеть подозрительно (но при этом выглядит ещё подозрительнее, т.к. обычно новички, которые ринулись доказывать, что современные постулаты индустрии неверные, их неправильно понимают и что они только вредят и нужно меняться (предлагая взамен почти то же самое), имеют такое завышенное ЧСВ, что вряд ли знают и понимаю суть фразы "признавать ошибки"), не отвечает прямо на вопросы (в ответ выдаёт ещё более странные общие рассуждения, а не конкретику).
    А главное, что натолкнуло на такую мысль: в текстах в вышеуказанных источниках заметно нарушение причинно-следственных связей, слабая аргументация, отсутствие прямой логики. И самое главное: совершенно непонятна цель. Как будто промт был "критика SOLID", без указания какой должна быть цель поста.

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

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

    эмоции от статьи

    Поначалу читая статью, у меня начало пригорать. Потом когда дочитал "критику" SOLID подумал, что что-то не так.
    Дальше при предложении альтернатив увидел ровно то, против чего была первая часть статьи. Подумал "это какой-то троллинг? В хабах ничего такого не указано".
    Не заметив тут ни одного комментария от "автора", но почитав их обилие на dtf понял, что точно что-то не так.


    1. nin-jin
      26.05.2023 22:47

      Вы сейчас описали 90% комментариев на Хабре.