Які мінуси використання DravableGameComponent для кожного примірника ігрового об’єкта?


25

Я багато разів читав, що DravableGameComponents слід зберігати для таких речей, як "рівні" чи якісь менеджери, замість того, щоб використовувати їх, наприклад, для символів або плиток (як тут каже цей хлопець ). Але я не розумію, чому це так. Я прочитав цю публікацію, і це мало для мене сенс, але це меншість.

Зазвичай я б не приділяв надто великої уваги таким речам, але в цьому випадку я хотів би знати, чому явна більшість вважає, що це не шлях. Можливо, мені чогось не вистачає.


Мені цікаво, чому через два роки ви зняли "прийняту" позначку з моєї відповіді ? Зазвичай мене це не дуже хвилює - але в цьому випадку це викликає розлючене викривлення моєї відповіді ВераШакллею вгору :(
Ендрю Рассел

@AndrewRussell Я недавно прочитав цю тему (через деякі відповіді та голоси) і подумав, що не маю кваліфікації давати думку про те, правильна ваша відповідь чи ні. Як ви кажете, однак, я не думаю, що відповідь VeraShackle повинна відповідати найбільшій кількості результатів, тому я знову відзначаю вашу відповідь правильною.
Кенджі Кіна

1
Я можу звести тут свою відповідь таким чином: " Використання DrawableGameComponentблокує вас в єдиний набір підписів методів і конкретна модель збережених даних. Це, як правило, спочатку неправильний вибір, а блокування значно перешкоджає еволюції коду в майбутньому. "Але дозвольте розповісти, що тут відбувається ...
Ендрю Рассел

1
DrawableGameComponentє частиною основної API XNA (знову ж таки: переважно з історичних причин). Новачки-програмісти використовують його, тому що він "є" і він "працює", і вони не знають нічого кращого. Вони бачать мою відповідь, яка говорить їм, що вони " неправильні " і - через характер людської психології - відкидають це і вибирають іншу "сторону". Що трапляється аргументованим невідповіддю ВераШакл; що трапилося набирати обертів, тому що я не викликав це негайно (див. ці коментарі). Я вважаю, що моє можливе спростування там залишається достатнім.
Ендрю Рассел

1
Якщо когось цікавить питання законності моєї відповіді на основі анонсів: хоча це правда, що (GDSE переповнюється з вищезгаданими новачками), відповідь VeraShackle встигла придбати ще пару нагород, ніж моя в останні роки, не забувайте що у мене є тисячі додаткових об'яв по всій мережі, в основному на XNA. Я реалізував API XNA на багатьох платформах. І я професійний розробник ігор, що використовує XNA. Родовід моєї відповіді бездоганний.
Ендрю Рассел

Відповіді:


22

"Ігрові компоненти" були дуже ранньою частиною дизайну XNA 1.0. Вони повинні були працювати як управління для WinForms. Ідея полягала в тому, що ви зможете перетягувати їх у свою гру за допомогою графічного інтерфейсу (такий як біт для додавання невізуальних елементів керування, як таймери тощо) та встановлювати на них властивості.

Отже, у вас можуть бути такі компоненти, як, можливо, камера чи лічильник FPS? ... чи ...?

Розумієш? На жаль, ця ідея насправді не працює для реальних ігор. Тип компонентів, які ви хочете для гри, насправді не можна використовувати багаторазово. У вас може бути компонент "player", але він буде сильно відрізнятися від "гравця" компонента гри інших.

Я думаю, що саме з цієї причини та простої нестачі часу графічний інтерфейс був висунутий до XNA 1.0.

Але API все ще доступний ... Ось чому ви не повинні його використовувати:

В основному, це зводиться до того, що найпростіше писати та читати, налагоджувати та підтримувати як розробник ігор. Виконайте щось подібне у своєму завантажувальному коді:

player.DrawOrder = 100;
enemy.DrawOrder = 200;

Набагато менш зрозуміло, ніж просто робити це:

virtual void Draw(GameTime gameTime)
{
    player.Draw();
    enemy.Draw();
}

Особливо, коли в реальній грі ви можете закінчити щось подібне:

GraphicsDevice.Viewport = cameraOne.Viewport;
player.Draw(cameraOne, spriteBatch);
enemy.Draw(cameraOne, spriteBatch);

GraphicsDevice.Viewport = cameraTwo.Viewport;
player.Draw(cameraTwo, spriteBatch);
enemy.Draw(cameraTwo, spriteBatch);

(Правда, ви могли поділитися камерою та spriteBatch, використовуючи подібну Servicesархітектуру. Але це також погана ідея з тієї ж причини.)

Ця архітектура - або її відсутність - настільки легка і гнучка!

Я можу легко змінити порядок малювання або додати кілька вікон перегляду або додати цільовий ефект візуалізації, просто змінивши кілька рядків. Для цього в іншій системі потрібно, щоб я подумав про те, що роблять усі ті компоненти - розповсюджені по декількох файлах, і в якому порядку.

Це щось на зразок різниці між декларативним та імперативним програмуванням. Виявляється, що для розвитку ігор імперативне програмування є набагато кращим.


2
У мене було враження, що вони стосуються програмування на основі компонентів , яке, очевидно, широко використовується для ігрового програмування.
BlueRaja - Danny Pflughoeft

3
Класи ігрових компонентів XNA насправді не застосовуються безпосередньо до цього шаблону. Ця закономірність стосується складання об'єктів з кількох компонентів. Класи XNA орієнтовані на один компонент на об'єкт. Якщо вони ідеально вписуються у ваш дизайн, то будь-якими способами користуйтеся ними. Але ви повинні НЕ будувати свій дизайн навколо них! Дивіться також мою відповідь тут - дивіться, де я згадую DrawableGameComponent.
Ендрю Рассел

1
Я погоджуюся, що архітектура GameComponent / Service не все так корисна і звучить, як поняття, схожі на Office або веб-додатки, примусові в ігрові рамки. Але я хотів би скористатися player.DrawOrder = 100;методом над імперативом для наведення порядку. Зараз я закінчую гру Flixel, яка малює об'єкти в тому порядку, в якому ви додаєте їх до сцени. Система FlxGroup полегшує щось із цього, але вбудоване сортування Z-індексу було б непогано.
michael.bartnett

@ michael.bartnett Зауважимо, що Flixel - це API в режимі збереженого режиму, тоді як XNA (за винятком, очевидно, системи ігрових компонентів) - API прямого режиму. Якщо ви використовуєте API в недержаному режимі або впроваджуєте свій власний , здатність змінювати черговий порядок на ходу безумовно важлива. Насправді, необхідність зміни черговості на ходу - це вагомий привід зробити щось утримуваному режимі (на розум приходить система вікон). Але якщо ви можете написати щось у режимі негайного режиму, якщо API платформи (як XNA) побудований саме так, то вам слід IMO.
Ендрю Рассел

Для подальшого ознайомлення: gamedev.stackexchange.com/a/112664/56
Kenji Kina

26

Хммм. Я боюсь виклик цього заради трохи балансу.

Здається, у відповіді Андрія є 5 звинувачень, тому я спробую розібратися з кожним по черзі:

1) Компоненти - це застарілий і покинутий дизайн.

Ідея шаблону дизайну компонентів існувала задовго до XNA - по суті, це просто рішення зробити об'єкти, а не надмірно архівним ігровим об’єктом, домом власного поведінки на оновлення та малювання. Для об'єктів у своїй колекції компонентів гра буде викликати ці методи (і пару інших) автоматично у відповідний час - головне, щоб ігровий об'єкт сам не повинен «знати» жоден із цього коду. Якщо ви хочете повторно використовувати свої ігрові класи в різних іграх (і, отже, різних ігрових об'єктах), ця розв'язка об'єктів все ще є фундаментально гарною ідеєю. Швидкий огляд останніх (професійно виготовлених) демонстрацій XNA4.0 з AppHub підтверджує моє враження, що Microsoft все ще (цілком справедливо, на мій погляд) міцно відстає від цього.

2) Ендрю не може придумати більше двох ігрових класів, які можна повторно використовувати.

Я можу. Насправді я можу думати про вантажі! Щойно я перевірив свою поточну спільну конфігурацію, і вона містить понад 80 компонентів та служб, які можна повторно використовувати Щоб надати вам аромат, ви можете:

  • Менеджери стану та екранів
  • Частичні випромінювачі
  • Мальовничі примітиви
  • AI контролери
  • Вхідні контролери
  • Контролери анімації
  • Білборди
  • Фізика та менеджери зіткнень
  • Камери

Будь-яка ідея гри потребуватиме щонайменше 5-10 речей із цього набору - гарантовано - і вони можуть бути повністю функціональними у абсолютно новій грі з одним рядком коду.

3) Контролювати порядок розіграшу за порядком явних зворотних викликів є кращим, ніж використання властивості DrawOrder компонента.

Ну, я в основному погоджуюся з Андрієм на цьому. Але, якщо ви залишите всі властивості DrawOrder вашого компонента за замовчуванням, ви все одно можете чітко контролювати їх порядок малювання у порядку, у якому ви додаєте їх до колекції. Це в дійсності ідентично з точки зору "видимості", як чітке впорядкування їх ручних розіграшів.

4) Викликати навантаження методу Draw об'єкта самостійно - це набагато легше, ніж викликати їх існуючим рамковим методом.

Чому? Плюс до всього, окрім самих тривіальних проектів, ваша логіка Update та Draw-код повністю затьмарить ваш метод виклику з точки зору ефективності в будь-якому випадку.

5) Розміщення коду в одному файлі є більш бажаним при налагодженні, ніж його у файлі окремого об'єкта.

Ну, по-перше, коли я налагоджую програму у Visual Studio, розташування точки перелому у відношенні файлів на диску майже непомітне в ці дні - IDE займається розташуванням без будь-якої драми. Але також на хвилину подумайте, що у вас є проблеми з малюнком Skybox. Невже перехід на метод малювання Skybox насправді складніше, ніж пошук коду малювання skybox в (імовірно, дуже тривалому) головному методі малювання в ігровому об’єкті? Ні, не дуже - насправді я б стверджував, що це зовсім навпаки.

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


2
Я тільки що помітив цю посаду, і я мушу висловити рішуче спростування: у мене немає проблем із класами для багаторазового використання! (У яких можуть бути методи малювання та оновлення тощо). У мене виникає конкретна проблема із використанням ( Drawable) GameComponentдля надання вашої архітектури гри через втрату явного контролю над порядком малювання / оновлення (з яким ви згодні в №3) жорсткістю їхніх інтерфейсів (до яких ви не звертаєтесь).
Ендрю Рассел

1
Ваш пункт №4 сприймає моє використання слова "легкий" для позначення продуктивності, де я фактично чітко маю на увазі архітектурну гнучкість. Ваші залишені бали №1, №2, а особливо №5, здаються, зводить безглузду людину з соломкою, яку, на мою думку, більшість / увесь код слід занести у ваш основний ігровий клас! Прочитайте тут мою відповідь для деяких моїх більш детальних думок про архітектуру.
Ендрю Рассел

5
Нарешті: якщо ви збираєтесь опублікувати відповідь на мою відповідь, сказавши, що я помиляюся, було б ввічливо також додати відповідь до своєї відповіді, щоб я побачив повідомлення про це, а не довелося спіткнутися на це через два місяці .
Ендрю Рассел

Я, можливо, неправильно зрозумів, але як це відповідає на питання? Чи варто використовувати GameComponent для своїх спрайтів чи ні?
Супербест

Подальше спростування: gamedev.stackexchange.com/a/112664/56
Kenji Kina

4

Я трохи не погоджуюся з Андрієм, але також думаю, що є час і місце для всього. Я б не використав Drawable(GameComponent)для чогось такого простого, як Спрайт або Ворог. Однак я б використав це для SpriteManagerабо EnemyManager.

Повертаючись до того, що Ендрю сказав про порядок малювання та оновлення, я думаю, це Sprite.DrawOrderбуло б дуже заплутано для багатьох спрайтів. Крім того, відносно легко створити DrawOrderі UpdateOrderдля невеликої (або розумної) кількості менеджерів, які є (Drawable)GameComponents.

Я думаю, що Microsoft покінчив би з ними, якби вони справді були там, де це жорстко, і ніхто їх не використовував. Але це не так, тому що вони працюють прекрасно, якщо роль правильна. Я сам використовую їх для розробки Game.Services, які оновлюються у фоновому режимі.


2

Я також думав про це, і мені спало на думку: Наприклад, вкрасти приклад у вашому посиланні, в грі RTS ви могли зробити кожен примірник ігрового компонента екземпляром. Гаразд.

Але ви також можете зробити компонент UnitManager, який зберігає список одиниць, малює та оновлює їх за необхідності. Я здогадуюсь, що причина, по якій ви хочете перетворити свої підрозділи в ігрові компоненти, в першу чергу - це розділення коду, щоб це рішення адекватно досягло цієї мети?


2

У коментарях я опублікував стисну версію старої відповіді своєї відповіді:

Використання DrawableGameComponentблокує вас в єдиний набір підписів методів і конкретної збереженої моделі даних. Зазвичай це неправильний вибір спочатку, і блокування значно перешкоджає еволюції коду в майбутньому.

Тут я спробую проілюструвати, чому це так, розмістивши якийсь код з мого поточного проекту.


Кореневий об’єкт, що підтримує стан ігрового світу, має Updateметод, який виглядає приблизно так:

void Update(UpdateContext updateContext, MultiInputState currentInput)

Існує ряд вхідних точок Update, залежно від того, працює гра в мережі чи ні. Наприклад, можливо, ігровий стан буде повернутий до попереднього моменту і потім повторно моделювати.

Існують також додаткові методи, схожі на оновлення, такі як:

void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)

У ігровому стані є список акторів, які потрібно оновити. Але це більше, ніж простий Updateдзвінок. Існують додаткові пропуски до та після занять фізикою. Також є кілька фантазійних матеріалів для відкладеного нересту / знищення акторів, а також рівневих переходів.

Поза ігровим станом існує система інтерфейсу, якою потрібно керувати самостійно. Але деякі екрани в інтерфейсі також повинні мати можливість працювати в мережевому контексті.


Для малювання, зважаючи на характер гри, існує спеціальна система сортування та відсікання, яка приймає дані від цих методів:

virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)

А потім, як тільки з'ясує, що малювати, автоматично дзвонить:

virtual void Draw(DrawContext drawContext, int tag)

tagПараметр необхідний , оскільки деякі учасники можуть тримати на інші актор - і тому повинні бути в змозі зробити як вище , так і нижче затриманий актор. Система сортування забезпечує, що це відбувається у правильному порядку.

Існує також система меню для малювання. І ієрархічна система для малювання інтерфейсу користувача, навколо HUD, навколо гри.


Тепер порівняйте ці вимоги з тим, що DrawableGameComponentпередбачено:

public class DrawableGameComponent
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
    public int UpdateOrder { get; set; }
    public int DrawOrder { get; set; }
}

Немає розумного способу потрапити з системи DrawableGameComponentв систему, яку я описав вище. (І це не незвичайні вимоги до гри навіть скромної складності).

Крім того, дуже важливо зазначити, що ці вимоги не просто з’явилися на початку проекту. Вони еволюціонували протягом багатьох місяців роботи з розвитку.


Що зазвичай роблять люди, якщо вони спускаються з DrawableGameComponentмаршруту, це те, що вони починають будувати хаки:

  • Замість того, щоб додавати методи, вони роблять потворний прийом вниз для власного базового типу (чому б просто не використовувати це безпосередньо?).

  • Замість заміни коду для сортування та виклику Draw/ Updateвони роблять кілька дуже повільних речей, щоб змінити номери замовлень на ходу.

  • І найгірше: замість того, щоб додавати параметри до методів, вони починають "передавати" "параметри" в місцях пам'яті, які не є стеком (використання Servicesдля цього популярне). Це суперечки, схильні до помилок, повільні, неміцні, рідко захищені від потоків, і, ймовірно, можуть стати проблемою, якщо ви додасте мережу, зробите зовнішні інструменти тощо.

    Це також робить код повторно використовувати складніше , тому що тепер ваша залежність прихована всередині «компоненти», а НЕ опублікована на кордоні API.

  • Або вони майже бачать, наскільки це все божевільно, і починають займатися "менеджером" DrawableGameComponent, щоб вирішити ці проблеми. За винятком того, що у них все ще є проблеми на кордонах "менеджера".

    Незмінно цих "менеджерів" можна було легко замінити рядом викликів методів у бажаному порядку. Все інше є надскладним.

Звичайно, щойно ви почнете використовувати ці хаки, після скасування цих хак потрібна справжня робота, як тільки ви зрозумієте, що вам потрібно більше, ніж те, що DrawableGameComponentпередбачено. Ви стаєте " замкненими ". І часто виникає спокуса продовжувати купувати хакі - що на практиці вас загрожує і обмежить види ігор, які ви можете зробити порівняно спрощеними.


Дуже багато людей, здається, досягають цієї точки і мають вісцеральну реакцію - можливо, тому, що вони використовують DrawableGameComponentі не хочуть визнати, що вони "неправильні". Тож вони зав'язують той факт, що принаймні можна змусити його "працювати".

Звичайно, це можна змусити "працювати"! Ми програмісти - змусити роботу працювати в незвичних обмеженнях - це практично наша робота. Але ця стриманість не потрібна, корисна чи раціональна ...


Особливо дбайливим є те, що напрочуд крокувати все це питання дивно . Тут я вам навіть дам код:

public class Actor
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
}

List<Actor> actors = new List<Actor>();

foreach(var actor in actors)
    actor.Update(gameTime);

foreach(var actor in actors)
    actor.Draw(gameTime);

(Просте додавання в сортуванні за DrawOrderі за UpdateOrder. Один простий спосіб - це підтримка двох списків та використання List<T>.Sort(). Це також тривіально для обробки Load/ UnloadContent.)

Не важливо, що таке код насправді є . Що важливо, це те, що я можу тривіально її змінити .

Коли я усвідомлюю, що мені потрібна інша система синхронізації gameTime, або мені потрібно більше параметрів (або цілого UpdateContextоб'єкта, який містить близько 15 речей), або мені потрібен зовсім інший порядок операцій для малювання, або мені потрібні додаткові методи для різних проходів поновлення, я можу просто йти вперед і робити це .

І я можу це зробити негайно. Мені не потрібно фальсувати, як вибирати DrawableGameComponentсвій код. Або ще гірше: зламати його таким чином, що зробить це ще складніше наступного разу, коли виникатиме така потреба.


Насправді існує лише один сценарій, коли це вдалий вибір DrawableGameComponent: і це, якщо ви буквально створюєте та публікуєте проміжне програмне забезпечення для стилю перетягування та перетягування компонентів для XNA. Яке було його первісне призначення. Яке - додам - ніхто не робить!

(Аналогічно, це справді має сенс використовуватиServices під час створення власних типів вмісту ContentManager.)


Хоча я згоден з вашими пунктами, я також не бачу нічого особливо неправильного у використанні речей, які успадковуються DrawableGameComponentдля певних компонентів, особливо для таких речей, як екрани меню (меню, багато кнопок, опцій та інших матеріалів) або накладки FPS / налагодження. ви згадали. У цих випадках вам зазвичай не потрібен тонкозернистий контроль над порядком розіграшу, і в кінцевому підсумку ви маєте менший код, який потрібно вручну записати (що добре, якщо вам не потрібно писати цей код). Але я здогадуюсь, що це майже сценарій перетягування, який ви згадували.
Гру
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.