Почему я должен отделять объекты от рендеринга?

11

Отказ от ответственности: я знаю, что такое шаблон сущности и не использую его.

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

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

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

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

(возможно, с примерами, я не очень хорошо разбираюсь в абстрактных речах)

башмак
источник
1
Было бы также полезно, если бы вы предоставили пример того, что вы имеете в виду, когда говорите, что не используете шаблон системы сущностей, и как, по вашему мнению, это связано с тем, следует ли вам отделить задачу рендеринга от проблемы сущности / игровая логика.
Майкл Бартнетт
@ michael.bartnett, я не разделяю объекты на небольшие повторно используемые компоненты, которые обрабатываются системами, как это делает большинство реализаций шаблонов. Мой код - скорее попытка шаблона MVC. Но это не имеет значения, так как вопрос не зависит ни от какого кода (даже от языка). Я поместил дисклеймер, потому что я знал, что некоторые попытались бы убедить меня в использовании ECS, который, кажется, излечивает рак. И, как вы видите, это все равно произошло.
Чистка

Ответы:

13

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

Итак, ваша сцена выглядит так:

class Scene {
    World* world
    Player* player
    Enemy* boss
    Camera* camera
}

(По крайней мере, это основные данные . Как вы их содержите, зависит от вас.)

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

State* gameState = new State();
gameState->addScene(scene);

Теперь в вашем игровом состоянии есть сцена. Далее вы хотите запустить логику на сцене и визуализировать сцену. Для логики, вы просто запускаете функцию обновления.

State::update(double delta) {
    scene->update(delta);
}

Таким образом, вы можете сохранить всю игровую логику в Sceneклассе. И просто для справки, система компонентов сущности может сделать это следующим образом:

State::update(double delta) {
    physicsSystem->applyPhysics(scene);
}

В любом случае, теперь вам удалось обновить свою сцену. Теперь вы хотите отобразить это! Для чего мы делаем что-то похожее на вышеперечисленное:

State::render() {
    renderSystem->render(scene);
}

Вот и ты. RenderSystem считывает информацию со сцены и отображает соответствующее изображение. Упрощенно, метод рендеринга сцены может выглядеть так:

RenderSystem::renderScene(Scene* scene) {
    Camera* camera = scene->camera;
    lookAt(camera); // Set up the appropriate viewing matrices based on 
                    // the camera location and direction

    renderHeightmap(scene->getWorld()->getHeightMap()); // Just as an example, you might
                                                        // use a height map as your world
                                                        // representation.
    renderModel(scene->getPlayer()->getType()); // getType() will return, for example "orc"
                                                // or "human"

    renderModel(scene->getBoss()->getType());
}

Действительно упрощенный, вам все равно придется, например, применять ротацию и перевод в зависимости от того, где находится ваш игрок и куда он смотрит. (Мой пример - 3D-игра, если вы пойдете с 2D, это будет прогулка в парке).

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

И вам не нужно прикреплять информацию рендеринга к сцене. Этого должно быть достаточно, чтобы визуализатор знал, что ему нужно визуализировать орка. Вы уже загрузили модель орка, которую рендер будет знать для отображения.

Это должно удовлетворить ваши требования. Графическое представление и логика связаны , потому что они оба используют одни и те же данные. Все же они отделены , потому что ни один не полагается на другого!

РЕДАКТИРОВАТЬ: И просто чтобы ответить, почему кто-то сделал бы это так? Потому что это проще, это самая простая причина. Вам не нужно думать о том, "что-то и такое произошло, теперь я должен обновить графику". Вместо этого вы заставляете вещи происходить, и каждый кадр игры смотрит на то, что происходит в данный момент, и интерпретирует это некоторым образом, давая вам результат на экране.

Fault
источник
7

Ваш заголовок задает другой вопрос, нежели содержание вашего тела. В заголовке вы спрашиваете, почему логика и рендеринг должны быть разделены, а в теле вы спрашиваете о реализации систем логики / графики / рендеринга.

Второй вопрос был рассмотрен ранее , поэтому я сосредоточусь на первом вопросе.

Причины разделения логики и рендеринга:

  1. Широко распространенное мнение, что объекты должны делать одно
  2. Что если вы хотите перейти с 2D на 3D? Что если вы решите перейти с одной системы рендеринга на другую в середине проекта? Вы не хотите пролистывать весь код и вносить огромные изменения в логику игры.
  3. Скорее всего, у вас есть причина повторять фрагменты кода, что обычно считается плохой идеей.
  4. Вы можете создавать системы для управления потенциально огромными полосами рендеринга или логики без индивидуального общения небольшими частями.
  5. Что делать, если вы хотите назначить драгоценный камень игроку, но система замедляется из-за того, сколько граней у камня? Если вы достаточно хорошо абстрагировали свою систему рендеринга, вы можете обновлять ее с разной скоростью, чтобы учитывать дорогостоящие операции рендеринга.
  6. Это позволяет вам думать о вещах, которые действительно имеют значение для того, что вы делаете. Зачем крутить мозг вокруг матричных преобразований, смещений спрайтов и координат экрана, когда все, что вам нужно, - это реализовать механизм двойного прыжка, взять карту или снарядить меч? Вам не нужен спрайт, представляющий ваш снаряженный меч, ярко-розовый только потому, что вы хотели переместить его из правой руки в левую.

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

Dragonsdoom
источник
6

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

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

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

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

введите описание изображения здесь

Упомянутые вами пункты не являются откатами, они отстают только в том случае, если существует больше зависимостей, чем должно быть в движке, иными словами, системы видят части слона больше, чем должны были. А это значит, что двигатель не был «правильно» сконструирован.

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

В противном случае, естественным способом решения такого случая является проектирование системы ввода / вывода. Обновление выполняет логику и выводит результат. Рендеринг подается только с результатами обновления. Вам действительно не нужно все подвергать. Вы только выставляете Интерфейс, который связывается между двумя стадиями. Связь между различными частями движка должна осуществляться через абстракции (интерфейсы) и / или сообщения. Никакая внутренняя логика или состояния не должны быть разоблачены.

Давайте рассмотрим простой пример графа сцены, чтобы объяснить идею.

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

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

concept3d
источник