Мне всегда было интересно, в какой степени объектная ориентация используется в видеоиграх, особенно в огромных трехмерных проектах. Я считаю, что было бы здорово, что оба troll
и werewolf
унаследовали его атрибуты enemy
и переписали некоторые из них. Но, может быть, занятия слишком тяжелы, чтобы быть практичными, я не знаю ...
18
Ответы:
Чтобы прийти к вашему примеру, я объясню свои мысли об Entites в видеоиграх. Я не буду обсуждать общее преимущество и недостатки объектов и классов, а также их роль в видеоиграх.
Сущности в небольших видеоиграх в основном определяются отдельными классами, в больших играх они часто определяются в файлах. Существует два основных подхода:
Расширение : в вашем примере
Troll
иWerewolf
будет расширятьсяEnemy
, что расширяетEntity
.Entity
заботится об основных вещах, таких как движение, здоровье и т. д., в тоEnemy
время как подклассы определяют как врагов и делают другие вещи (Minecraft следует этому подходу).Основанный на компонентах : Второй подход (и мой любимый) - один
Entity
класс, у которого есть компоненты. Компонент может бытьMoveable
илиEnemy
. Эти компоненты заботятся об одной вещи, как движение или физика. Этот метод разрывает длинные цепочки расширений и сводит к минимуму риск возникновения конфликта. Например: как вы можете создавать неподвижных врагов, если они наследуются от подвижного персонажа? ,В больших видеоиграх, таких как World of Warcraft или Starcraft 2, сущности - это не классы, а наборы данных, которые определяют всю сущность. Сценарии также важны, потому что вы определяете, что делает сущность не в классе, а в скрипте (если он уникален, а не в таких вещах, как перемещение).
В настоящее время я программирую RTS, и я использую компонентный подход с файлами дескрипторов и скриптов для Entites, Skills, Items и всего остального, динамического в игре.
источник
component
в этом контексте. Ссылка будет высоко ценится.К сожалению, такой подход к полиморфизму, популярный в 90-х годах, на практике оказался плохой идеей. Представьте, что вы добавили «волка» в список врагов - ну, он разделяет некоторые атрибуты с оборотнем, поэтому вы хотите объединить их в общий базовый класс, например. 'WolfLike. Теперь вы добавляете «Человека» в список врагов, но оборотни иногда являются людьми, поэтому они также имеют общие черты, такие как ходьба на двух ногах, способность говорить и т. Д. Вы создаете общую базу «Гуманоидов» для людей и оборотней? и нужно ли вам тогда останавливать оборотней, происходящих от WolfLike? Или вы умножаете наследование от обоих - в этом случае какой атрибут имеет приоритет, когда что-то в Humanoid конфликтует с чем-то в WolfLike?
Проблема в том, что попытка моделировать реальный мир как дерево классов неэффективна. Возьмите любые 3 вещи, и вы, вероятно, найдете 4 различных способа разделить их поведение на общие и не общие. Вот почему многие вместо этого обратились к модульной или основанной на компонентах модели, где объект состоит из нескольких меньших объектов, составляющих единое целое, и меньшие объекты можно поменять местами или изменить, чтобы создать желаемое поведение. Здесь у вас может быть только 1 класс Enemy, и он содержит подобъекты или компоненты для того, как он ходит (например, Bipedal против Quadrupedal), его методы атаки (например, укус, коготь, оружие, кулак), его кожа (зеленый для троллей, пушистых, для оборотней и волков) и т. д.
Это все еще полностью объектно-ориентированный, но понятие того, что является полезным объектом, отличается от того, что обычно преподавалось в учебниках. Вместо того, чтобы иметь большое дерево наследования различных абстрактных классов и несколько конкретных классов на кончиках дерева, у вас обычно есть только 1 конкретный класс, представляющий абстрактное понятие (например, «враг»), но который содержит более конкретные классы, представляющие более абстрактные понятия (например, приступы, тип кожи). Иногда эти вторые классы лучше всего реализовать через 1 уровень наследования (например, базовый класс Attack и несколько производных классов для конкретных атак), но глубокое дерево наследования пропало.
В большинстве современных языков классы не «тяжелые». Это просто еще один способ написания кода, и они обычно просто оборачивают процедурный подход, который вы бы в любом случае написали, за исключением более простого синтаксиса. Но во что бы то ни стало, не пишите класс, который будет выполнять функция. Классы по сути не лучше, только когда они делают код более простым в управлении или понимании.
источник
Я не уверен, что вы подразумеваете под «слишком тяжелым», но C ++ / OOP является языком разработки игр по тем же причинам, что и в других местах.
Разговор о взрывах кешей - это проблема дизайна, а не языковая особенность. C ++ не может быть слишком плохим, поскольку он использовался в играх последние 15-20 лет. Java не может быть слишком плохой, так как она используется в очень жестких условиях работы смартфонов.
Теперь перейдем к реальному вопросу: в течение многих лет иерархия классов углублялась и начинала страдать от проблем, связанных с этим. В более поздние времена иерархии классов сгладились, и составление и другие управляемые данными проекты являются скорее не логическим наследованием.
Это все ООП и все еще используется в качестве основы при разработке нового программного обеспечения, просто другой фокус на том, что воплощают классы.
источник
Классы не связаны с какими-либо накладными расходами во время выполнения. Полиморфизм во время выполнения просто вызывает через указатель на функцию. Эти издержки являются чрезвычайно минимальными по сравнению с другими затратами, такими как вызовы Draw, синхронизация параллелизма, дисковый ввод-вывод, переключатели режима ядра, плохая локальная память, чрезмерное использование динамического выделения памяти, плохой выбор алгоритмов, использование языков сценариев и список продолжается. Есть причина, по которой объектная ориентация по существу вытеснила предшествовавшее, и это потому, что она намного лучше.
источник
Следует помнить одну вещь: концепции объектно-ориентированного кода часто более ценны, чем их реализация в языках. В частности, необходимость выполнения 1000-х вызовов виртуального обновления для объектов в основном цикле может привести к путанице в кеше в C ++ на платформах, таких как 360 или PS3 - поэтому реализация может избежать использования «виртуальных» или даже «классных» ключевых слов ради скорости.
Хотя часто он все равно будет следовать ОО-концепциям наследования и инкапсуляции - просто реализуя их иначе, чем сам язык (например, любопытно рекурсивные шаблоны, композиция вместо наследования (метод на основе компонентов, описанный Марко)). Если наследование всегда может быть разрешено во время компиляции, тогда проблемы с производительностью исчезают, но код может стать немного проблематичным с помощью шаблонов, пользовательских реализаций RTTI (опять же, встроенные обычно непрактично медленны) или логики времени компиляции в макросы и т. д.
В Objective-C для iOS / Mac есть похожие, но более серьезные проблемы со схемой обмена сообщениями (даже с модным кешированием) ...
источник
Метод, который я буду пытаться использовать, смешивает наследование классов и компоненты. (Во-первых, я не делаю Enemy классом - я делаю это компонентом.) Мне не нравится идея использовать один универсальный класс Entity для всего, а затем добавлять необходимые компоненты, чтобы конкретизировать сущность. Вместо этого я предпочитаю, чтобы класс автоматически добавлял компоненты (и значения по умолчанию) в конструктор. Затем подклассы добавляют свои дополнительные компоненты в свой конструктор, чтобы у подкласса были свои компоненты и компоненты родительского класса. Например, класс Weapon добавляет базовые компоненты, общие для всех видов оружия, а затем подкласс Sword добавляет любые дополнительные компоненты, специфичные для мечей. Я до сих пор спорю о том, использовать ли активы text / xml для определения значений для сущностей (или конкретных мечей, например), или сделать все это в коде, или каков правильный микс. Это по-прежнему позволяет игре использовать информацию о типах и полиморфизме языка программирования и значительно упрощает ObjectFactories.
источник