Gamedev LectureСтатьи

Лекция #33. Плюсы и минусы игровых "орхетектур". [Лектор - dDIMA] (4 стр)

Автор:

[22:49] <dDIMA> Есть универсальная реализация, есть стандартные решения построенные на базе этой реализации, есть игровые расширения, которые могут базироваться либо на HWND, либо на каких-то готовых классах, предоставляемых системой
[22:49] <dDIMA> В том случае, если у нас возникает вопрос с реализацией новой функциональности, мы почти сразу можем определить, куда именно это надо заталкивать
[22:49] <dDIMA> Потому что очевидно
[22:49] <dDIMA> Теперь берем какую-то хитрую вымученную иерархию, которую явно писали, руководствуясь  учебником по С++
[22:49] <dDIMA> http://www.ddima.ru/articles/v_lecture1/hier_3.jpg (http://www.gamedev.ru/files/images/?id=48040)
[22:49] <dDIMA> Давайте еще представим, что от AIPers растут бипеды и квадропеды, и сразу начинаются вопросы:
[22:50] <dDIMA> 1. Неочевидность иерархии. Мы с одной стороны жестко зафиксировали, что все четвероногие - это AI. Что делать, если появятся животные, которые не являются AI персонажами (в том смысле, которым мы уже нагрузили класс AIPers)
[22:50] <dDIMA> 2. Неочевидность вставки. Например, умение стрелять - это функция только солдат бипедов, или еще и каких-то животных? Куда вставлять новый функционал?
[22:50] <dDIMA> 3. Смешивание понятий. Геометрические (модель) классы и логические классы (AIPers) находятся в одной иерархии
[22:51] <dDIMA> Все это вместе порождает чудовищную головную боль
[22:51] <dDIMA> Не дай бог еще в этой схеме кто-то еще унаследуется от GameBaseObject, начнутся танцы с бубном с виртуальными наследованиями и т.п.
[22:51] <dDIMA> Давайте попробуем упростить эту схему и свести иерархию до минимума, заменив наследование агрегацией?
[22:51] <dDIMA> Чтобы сделать это, воспользуемся двумя принципами:
[22:51] <dDIMA> 1. Логика мировых связей.
[22:51] <dDIMA> 2. Тупость базовых классов. Чем более низкоуровневым является класс, тем меньше действий он умеет выполнять сам в автоматическом режиме.
[22:52] <dDIMA> Про логику мировых связей я уже писал в ЖЖ, поэтому повторяться не буду
[22:52] <dDIMA> http://ddima.livejournal.com/12415.html
[22:52] <dDIMA> Принцип тупости базовых классов определяет их самостоятельное поведение
[22:52] <dDIMA> Очень часто (даже слишком часто) бытует мнение, что геометрическая модель должна уметь сама себя рендерить
[22:52] <dDIMA> Сначала все просто замечательно.
[22:52] <dDIMA> Загрузили модель, оставили ее на произвол судьбы, и вот она на экране, вся из себя такая красивая.
[22:53] <dDIMA> Но что происходит дальше?
[22:53] <dDIMA> (как пример):
[22:53] <dDIMA> - Модели персонажей должны пропадать на расстоянии 500 метров, мелкие аптечки - на расстоянии 100 метров
[22:53] <dDIMA> - Трупы должны переставать рендериться  через 12 секунд после смерти. А некоторые - через 25
[22:53] <dDIMA> - Игрок по разному рендерится, если мы прикрутили к движку кат сцены
[22:53] <dDIMA> - Для пуль сделали контейнер на 500 объектов, чтобы не создавать пульки в рантайме, а брать их уже готовыми. Неактивные пули рендериться не должны
[22:53] <dDIMA> - Гильзы не должны рендериться в зеркалах
[22:53] <dDIMA> и т.п.
[22:54] <dDIMA> Подобное приводит к тому, что на первый взгляд красивый код GeomModel::Render() { ... } обрастает чудовищным количеством if(), причем две трети из них не имеют никакого отношения к свойствам модели
[22:54] <dDIMA> То есть надо вводить <разрез> - разделить решаемые задачи между универсальным кодом и кастомизированной game-specific частью
[22:55] <dDIMA> Отсюда возникает понятие <владельца> - того, кто должен полностью управлять поведением этой модели
[22:55] <dDIMA> Тем самым мы выносим модель из иерархии GameBaseObject - AIPers
[22:55] <dDIMA> Она будет агрегирована либо в  AIPers (если получится), либо (самое подходящее) - прямо в Fox :)
[22:55] <dDIMA> Именно логический обработчик Fox принимает решение - рендерить ли модель, процессировать ли ее и т.п.
[22:56] <dDIMA> Умная модель - это плохая модель :)
[22:56] <dDIMA> Она не должна принимать решения о своей судьбе!
[22:56] <dDIMA> На первый взгляд, получается дурацкая ситуация. Все персонажи будут испещрены кодом вида Fox::Render() { m_Model->Render(); }
[22:56] <dDIMA> Но на самом деле не спешите судорожно рефакторить код и объединять пары одинаковых строк
[22:56] <dDIMA> Подождите пару месяцев, и ваш красивый Fox::Render обрастет кучей условий (см. выше)
[22:57] <dDIMA> Т.е. получим в Fox - один набор, в Hero - другой, в BulletManager, у которого 500 пуль - третий
[22:57] <dDIMA> Вторая часть подхода. Что произойдет, если мы заменим модель четвероногой лисы на двуногого барсука?
[22:57] <dDIMA> В классе Fox - возможно, много чего изменится. Но это нас не сильно волнует - это чистый игровой класс.
[22:57] <dDIMA> На что еще повлияет квадропедность?
[22:57] <dDIMA> На логику поведения AI?
[22:57] <dDIMA> Вполне возможно, но поскольку AIPers оперирует преимущественно с логическими параметрами, задать обновленные данные труда не должно составить.
[22:57] <dDIMA> А вот на модель это может повлиять сильно.
[22:57] <dDIMA> Но
[22:58] <dDIMA> Корректировка модели (туловище, ИК на лапы и т.п.) - это чисто визуальные примочки, которые не имеют никакого отношения к логической части
[22:58] <dDIMA> Итого:
[22:58] <dDIMA> Иерархия заменяется на GameBaseObject - AIPers - Fox
[22:58] <dDIMA> QuadruPed получается либо наследником GeomModel, либо отдельным агрегированным классом в Fox
[22:58] <dDIMA> Структура становится существенно проще, если только вы не трясетесь над каждой строчкой, которая повторяется в нескольких файлах
[22:58] <dDIMA> В любом случае, линейный код читается, отлаживается и модифицируется быстрее, чем длинная иерархия, испещренная виртуальными вызовами
[22:58] <dDIMA> Еще одно замечание про получившуюся структуру -
[22:59] <dDIMA> с точки зрения подхода №6 к организации игрового цикла мы удачно разнесли логическую структуру с Fox и визуальную составляющую Model - QuadruPed
[22:59] <dDIMA> Обобщая все вместе - проектируя игровые классы, следует помнить о следующих аспектах тупости:
[22:59] <dDIMA> - чем проще (не с точки зрения кода, а с точки зрения иерархии) класс, тем от должен быть тупее. Он сам не принимает никаких решений, а полностью управляется своим владельцем
[23:00] <dDIMA> - базовый класс (в идеале) ничего не должен знать о своем владельце. Добавили хоть капельку такого знания - получите в лучшем случае if, в худшем - dynamic_cast или виртуальные вызовы в своего owner
[23:00] <dDIMA> - иерархия классов должна быть простой, не больше 3-4 наследований, любая, даже самая невозможная задача должна сразу очевидно ложиться в один из классов
[23:00] <dDIMA> - иерархия классов должна решать общие задачи. Не должны смешиваться в одной иерархии логические и геометрические классы
[23:01] <dDIMA> Еще раз повторюсь, это все сильно актуально только для схемы с разделением игра+движок. Если у вас большая свалка, можно на все это забить :)
[23:01] <dDIMA> Было замечание в привате, действительно выразился не совсем корректно
[23:01] <dDIMA>  <dDIMA> Очень часто (даже слишком часто) бытует мнение, что геометрическая модель должна уметь сама себя рендерить
[23:02] <dDIMA> Я имел в виду, что GeomModel не должна быть наследником GameObject и получать Render от системы автоматически во всех случаях
[23:02] <dDIMA> Т.е. модель имеет метод render (ну или подсистема рендера имеет метод render(m_model) - это непринципиально)
[23:03] <dDIMA> Но вот _вызов) рендера полностью обуславливается ее владельцем
[23:03] <dDIMA> Там впрочем есть еще один момент
[23:03] <dDIMA> То, что кроме описанных выше отсечек по рендеру есть еще и внутренние рендерные штуковины
[23:04] <dDIMA> Так что кроме проверок вида if (!active), if (dist() > 1000) и т.п. надо где-то еще вструнить проверку на попадание в FOV
[23:04] <dDIMA> Но это уже пошла тонкая оптимизация, которая на "линию разреза" влиять не должна
[23:05] <dDIMA> еще был вопрос в привате про типичные варианты иерархий
[23:05] <dDIMA> Я уже привел 2 примера максимальной глубины = 3 - это HWND - Button - User Class
[23:05] <dDIMA> И GameObject - AIPers - Fox
[23:06] <dDIMA> На самом деле сходу так не придумывается, можем устроить небольшой перерыв и попробовать обсудить
[23:06] <dDIMA> Какие-то варианты иерархий наверняка всплывут
[23:06] <dDIMA> Хотя, боюсь, вопросов будет очень много :)

[23:07] <IronPeter> э... Fox::Render() - оно что, прямо у класса гейм-логики?
[23:07] <dDIMA> IronPeter: ага :)
[23:07] <dDIMA> ну или на уровень выше
[23:07] <IronPeter> кто вызывает эту функцию?
[23:07] <IronPeter> она вызывается у всех объектов геймлогики?
[23:07] <dDIMA> Fox::Render()?
[23:07] <IronPeter> ну да.
[23:07] <dDIMA> Не обязательно у всех
[23:08] <IronPeter> ну а у кого?
[23:08] <dDIMA> Я уже говорил про танцы с FOV или PVS
[23:08] <dDIMA> Т.е. на верхнем уровне отсечка безусловно должна быть
[23:08] <IronPeter> так что, объект геймлогики автоматически имеет координаты?
[23:08] <IronPeter> bounding box там...
[23:08] <dDIMA> может иметь координаты
[23:08] <IronPeter> странный объект.
[23:09] <dDIMA> более того, я говорил про dist для рендера
[23:09] <_Winnie> Все GameObject имееют позицию и размер?
[23:09] <dDIMA> вотнесмотря на то, чот я это назвал game spec, такая вещь вполне выносится на системный уровень
[23:09] <IronPeter> dist для рендера - это внутреннее знание объекта, Бог с ним.
[23:09] <DDMZ> имхо ... fox->model->rener()
[23:09] <IronPeter> а вот позиция и размер - это довольно странно.

Страницы: 1 2 3 4 5 6 Следующая »

15 июня 2007 (Обновление: 13 ноя 2009)

Комментарии [10]