У коментарях я опублікував стисну версію старої відповіді своєї відповіді:
Використання 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
.)