Skip to content

Instantly share code, notes, and snippets.

@kkolyan
Created June 23, 2022 20:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kkolyan/03de66729a6a8443ae935050c0687be3 to your computer and use it in GitHub Desktop.
Save kkolyan/03de66729a6a8443ae935050c0687be3 to your computer and use it in GitHub Desktop.
Короче, под "я делаю что-то с использованием ООП" обычно называют это древнее нечто когда логику композируют путем наследования друг от друга классов с инкапсулированной логикой и данными, а также бесконечные слои абстракции. так делают, но получается мешанина, да. Поэтому и популярно мнение что ООП - фигня.
Но на самом деле, ООП не не обязательно использовать именно так. В умеренном использовании ООП очень даже полезно. Например для своих типов данных, контейнеров и других библиотечных случаев. Но не для дизайна игровой логики.
Юнити и C# не так уж и заточены под ООП.
C# достаточно хорошо поддерживает и лайтово-функциональный стиль и гибридно-процедурный.
А то, что предлагает Unity, это не столько ООП, сколько композитный подход. Идея архитектуры предлагаемой Unity в том, что игра состоит из абстрактный объектов (которые грубо говоря не имеют никаких свойств) к которым приаттачены компоненты, каждый из которых наделяет объект каким-то небольшим (в идеале) свойством.
Но делать игру с глубокой логикой чисто на композиции компонентов сложно. Лично я склонен считать что для сколь-либо сложных игр лучше использовать немного другой подход.
Во-первых, делим всю игру на слой симуляции и слой отображения.
Симуляцию в свою очередь делим на 2 части:
1.1. игровая модель данныех. это набор классов с одними только полями (можно делать только методы, которые не модифицируют состояние класса). Класс "Мир" с полями "монстры", "игровое поле", "очередь хода", "эффекты" и тому подобное. Критерий корректности - сериализация объекта "Мир" дает состояние сейв-файла. Здесь нет ничего об отображении - толкьо суть. Мир здесь описывается так, будто это реальный мир (пусть и со странными дсикретными клеточными правилами), а не чья-то игра.
1.2. поведение. это набор процедур, которые вызываясь каждый кадр пересчитывают состояние мира. Сдвигают летящие стрелы на "speed*Time.delta", декрементят кулдауны, обрабатывают отложенные в очередях события, реагируют на команды ввода (в терминах симуляции это решения принимаемые существами, а не какие-то там команды извне).
1.3. на самом деле, можно использовать классы и в классическом ООП стиле (приватные поля и методы, которые что-то делают умное), но очень важно это делать ТОЛЬКО для универсальных случаев - вектора, матрицы, контейнеры для особого индексирования. и каждый такой класс должен уметь делать только что-то одно. проверочный вопрос - "придется ли этот класс менять, если скопировать в другой проект в другом жанре?", и если ответ "да", то он сделан плохо и лучше расщепить его задачу на предыдущие два пункта.
(симуляция ничего не знает о Unity кроме векторной математики)
Отображение в свою очередь разделяется на 3 части: (а вот тут уже Unity полным ходом)
2.1. набор довольно простых и самодостаточных компонентов-MonoBehavior, которые так или иначе позволяют отображать данные из симуляции. Это куклы-марионетки и реквизит (а описанная выше игровая модель данныех - это сюжет спектакля).
2.2. набор процедур, которые выполняются каждый кадр и манипулируют состоянием сцены кукольного театра так, чтобы они соответствовали состоянию симулируемого мира. Это кукловоды.
2.3. обработка пользовательского ввода и передача команд в игровой мир.
На самом деле, то, что я описал отлично ложится на ECS (игровая модель (а в некоторых реализациях и модель сцены) = сущности+компоненты, а процедуры обоих словев - системы), но поначалу от ECS вы ничего не выиграете, только будете отвлекаться и писать лишний бойлерплейт.
А GoF-паттерны во многом устарели. Скорее для общего образования их стоит знать, но не как руководство к действию. Если у вас само по себе что-то получилось похожее на GoF - ок, вы теперь знаете как это называть, но не надо форсировать.
Понижение связности - полезная штука, но не нужно понимать ее как "прячте все за слоями абстракции". В игровой модели вообще на нее стоит забить - там гораздо важнее внятность.
При проектировании игры на чисто-композитном подходе (если игра очень неглубокая) - да, вот там можно и за интерфейсы друг от друга прятать разные компоненты, чтобы было легче сочетать разные компоненты друг с другом. Но в озвученной выше архитектуре компоненты сцены друг о друге знать и не должны - ими рулят процедуры-кукловоды
PS: В пошаговой игре очень значительная часть кода может быть вынесена из симуляции в отображение. Например упомянутый выше полет стрелы - это по большей части отображение. В игровой модели есть только факт выстрела и факт попадания (например, ивент, передаваемый из view в simulation, означающий что можно продолжать симуляцию).
PPS: В терминах упомянутого в треде MVVM, 1.1+1.2 - это Model, 2.1 - View, а 2.2 - ViewModel.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment