Композиция тяжелый ООП против чистых систем компонентов сущностей? [закрыто]

13

Признаюсь, я совершил грех, злоупотребляя и даже злоупотребляя наследством. Первый (текстовый) игровой проект, который я сделал, когда проходил курс обучения по ООП, дошел до «Запертой двери» и «Открытой двери» от «Дверь» и «Комната с одной дверью», «Комната с двумя дверями» и так далее из "Комнаты".

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

Я думал, что все еще делаю что-то не так, и, просмотрев его в Интернете, я обнаружил, что я не единственный, кто столкнулся с этой проблемой. Именно так я и обнаружил системы Entity после тщательного исследования (читай: googlefu)

Когда я начал читать его, я смог увидеть, насколько ясно он смог решить проблемы, которые у меня были с традиционной иерархией ООП с компонентами. Это было в первых чтениях однако. Когда я наткнулся на более… «радикальные» подходы ES, такие как на T-machine .

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

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

Следующий пример показывает, что я имею в виду (это вдохновлено примером, который я нашел в Game Engine Architecture, глава 14).

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

Тогда танк, подкласс транспортного средства, унаследует эти компоненты от него и получит свой собственный компонент «пушка».

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

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

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

Ваши мнения?

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

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

Мидори Рю
источник
2
Мне кажется, все в порядке. Есть ли конкретный пример в вашей иерархии или системе сущностей, который «пахнет» для вас? В конце концов, все дело в том, чтобы сделать игру больше, чем в некотором смысле чистоты.
drhayes
Я не знаю, но, как я уже сказал, у меня почти нет опыта, и я далеко не лучший судья для этого.
Midori Ryuu
3
Я проголосовал за то, чтобы закрыть этот вопрос, потому что он субъективен и открыт для широкого обсуждения. Я ценю, что это было задано с искренней позиции, но выбор того, как действовать дальше, полностью зависит от личного мнения. Единственный реальный недостаток фиксации ваших компонентов в подклассе заключается в том, что расположение компонентов не может быть изменено во время выполнения, но это, безусловно, должно быть очевидно для вас, и это ваш выбор.
Kylotan
4
Я тоже проголосовал за закрытие. Это хороший и хорошо написанный вопрос, но он не подходит для GDSE. Вы можете попробовать опубликовать это на GameDev.net.
Шон Мидлдитч
Как написано, с вопросом «Ваши мнения?», Это действительно открытый и не подлежащий обсуждению. Тем не менее, это отвечает , если вы ответить на него , как если бы вопрос был "Есть ли какие - зияющие ловушки , которые я мог бы попасть в не заметив? (По крайней мере, если вы думаете, что на самом деле есть какая-то эмпирическая разница между различными архитектурами.)
Джон Калсбек

Ответы:

8

Рассмотрим этот пример:

Вы делаете RTS. В полной логике вы решили создать базовый класс GameObject, а затем два подкласса BuildingиUnit . Конечно, это прекрасно работает, и в итоге получается что-то похожее на это:

  • GameObject
    • ModelComponent
    • CollisionComponent
    • ...
  • Building
    • ProductionComponent
    • ...
  • Unit
    • AimingAIComponent
    • WeaponComponent
    • ParticleEmitterComponent
    • AnimationComponent
    • ...

Теперь каждый Unitваш подкласс уже имеет все необходимые компоненты. И в качестве бонуса, у вас есть одно место , чтобы поместить все , что утомительный код настройки , что newS вверх WeaponComponent, соединяет его к AimingAIComponentи ParticleEmitterComponent-wonderful!

И, конечно же, потому что он все еще разбивает логику на компоненты, когда вы в конечном итоге решаете, что хотите добавить Tower класс, который является зданием, но имеет оружие, вы можете управлять им. Вы можете добавить AimingAIComponent, a WeaponComponentи a ParticleEmitterComponentк вашему подклассу Building. Конечно, тогда вам нужно пройти и выкопать код инициализации из Unitкласса и поместить его в другое место, но это не большая потеря.

И теперь, в зависимости от того, сколько у вас было предвидения, вы можете столкнуться с тонкими ошибками. Оказывается, в другом месте игры мог быть какой-то код, который выглядел так:

if (myGameObject instanceof Unit)
    doSomething(((Unit)myGameObject).getWeaponComponent());

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

WeaponComponent weapon = myGameObject.getComponent(WeaponComponent.class);
if (weapon)
    doSomething(weapon);

Намного лучше! После изменения этого вам приходит в голову, что любой из методов доступа, которые вы написали, может оказаться в этой ситуации. Поэтому самое безопасное, что нужно сделать, это раздеть их всех и получить доступ к каждому компоненту с помощью этого единственного getComponentметода, который может работать с любым подклассом. (В качестве альтернативы вы можете добавить каждый get*Component()суперкласс к суперклассу GameObjectи переопределить их в подклассах для возврата компонента. Впрочем, я приму первый.)

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

Если вы будете следовать этому до крайности, вы всегда делать из своих подклассов полностью инертные пакеты с компонентами, и в этот момент нет даже смысла иметь подклассы вокруг. Вы можете получить почти все преимущества наличия иерархии классов с иерархией методов, которая создает надлежащие компоненты. Вместо того, чтобы иметь Unitкласс, у вас есть addUnitComponentsметод, который делает AnimationComponentи также вызывает addWeaponComponents, который делает AimingAIComponent, а WeaponComponentи а ParticleEmitterComponent. В результате у вас есть все преимущества системы сущностей, все возможности повторного использования кода в иерархии и отсутствие соблазна проверить instanceofили привести к типу вашего класса.

Джон Калсбек
источник
Хорошо сказано. В дополнение к этому, я думаю, что идеальным решением является инициализация каждой сущности с помощью скрипта или файлов дескрипторов. Я использую текстовые файлы для инициализации объекта. Это быстро и позволяет не программистам тестировать различные параметры.
Койот
Ух ты, чтение твоего поста подарило мне кошмар! Конечно, я имею в виду, в хорошем смысле, видя, как вы открыли мне глаза на какой кошмар я мог бы закончить! Хотелось бы больше ответить на такой подробный ответ, но добавить особо нечего!
Мидори Рю,
По более простому пути. Вы заканчиваете, используя Наследство как образец фабрики бедного человека.
Zardoz89
2

Композиция над наследованием - это терминология ООП (по крайней мере, так, как я ее выучил / научил). Чтобы выбрать между композицией и наследованием, вы произносите вслух «Автомобиль - это тип колеса» (наследство) и «Автомобиль имеет колеса» (композиция). Один должен звучать более правильно, чем другой (в данном случае - композиция), и если вы не можете решить, по умолчанию используйте композицию, пока не решите иначе.

Даниэль Карлссон
источник
1

Когда дело доходит до интеграции систем на основе компонентов в существующие игры на основе игровых объектов, в ковбойском программировании есть статья http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/, которая может дать вам несколько советов по способ перехода может быть сделан.

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

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

Смешивание наследования игровых объектов с компонентным подходом дает вам некоторые преимущества компонентного подхода с недостатками обоих подходов.

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

PS написано на телефоне, поэтому мне сложно связываться с внешними ресурсами.

койот
источник
Спасибо за ваш ответ, хотя вы были на телефоне! Я понимаю, что вы имеете в виду. Вещи, основанные на компонентах перемещения, тем больше гибкости я должен изменить вещи. Это причина, почему я собираюсь пойти дальше и попробовать композиционный подход с минимальным наследованием. (Даже больше, чем тот, который я предложил.)
Midori Ryuu
@MidoriRyuu избегать любого наследования в объектах сущностей. Вам повезло, что вы используете язык со встроенным отражением (и самоанализом). Вы можете использовать файлы (txt или XML) для инициализации ваших объектов с правильными параметрами. Это не слишком много работы, и это позволит улучшить тонкую настройку игры без перекомпиляции. Но сохраняйте класс Entity, по крайней мере, для межкомпонентного взаимодействия и других служебных задач. PS еще на телефоне :(
Койот