Тактика переміщення логіки візуалізації з класу GameObject


10

Створюючи ігри, ви часто створюєте такий ігровий об’єкт, від якого успадковуються всі сутності:

public class GameObject{
    abstract void Update(...);
    abstract void Draw(...);
}

Отже, в циклі оновлення ви повторюєте всі об’єкти гри і надаєте їм можливість змінити стан, а потім у наступному циклі малювання ви повторно повторюєте всі об’єкти гри та надаєте їм можливість намалювати себе.

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

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

Кращим способом, можливо, було б взагалі видалити метод Draw з класу GameObject і створити клас Renderer. GameObject все ще повинен містити деякі дані про його візуальні зображення, як, наприклад, з якою моделлю представляти його та які текстури слід намалювати на моделі, але про те, як це зробити, залишається рендерінгу. Однак часто буває багато прикордонних випадків при візуалізації, хоча, хоча це призведе до усунення жорсткого з’єднання з GameObject до Renderer, Renderer все одно повинен був би знати всі ігрові об'єкти, які зробили б його жирним, все знаючи і щільно з'єднані. Це порушило б чимало належних практик. Можливо, орієнтований на дані дизайн може зробити свою справу. Ігрові об'єкти, безумовно, були б даними, але як би рендері цим керували? Я не впевнений.

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

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

  • У об’єкті гри немає логіки візуалізації
  • Вільне з'єднання між ігровими об'єктами та візуалізацією
  • Не всі знаючі рендері
  • Переважно перемикання між двигунами візуалізації

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

Цей мислительний похід почався, коли я почув, як Джон Кармак говорив на Twitter, що у нього є така система, що він може змінювати двигуни візуалізації під час роботи і навіть може сказати своїй системі використовувати як рендери (програмний рендер, так і апаратний прискорений рендер) в той же час, щоб він міг перевірити відмінності. Системи, які я запрограмував поки що, навіть не такі гнучкі

Відповіді:


7

Швидкий перший крок до роз'єднання:

Ігрові об’єкти посилаються на ідентифікатор того, що їх візуальні зображення, але не дані, скажімо, щось просте, як рядок. Приклад: "human_male"

Renderer несе відповідальність за завантаження та підтримку посилань на "human_male" та передачу назад до об'єктів, що використовуються для обробки.

Приклад у жахливому псевдокоді:

GameObject( initialization parameters )
  me.render_handle = Renderer_Create( parameters.render_string )

- elsewhere
Renderer_Create( string )

  new data handle = Resources_Load( string );
  return new data handle

- some time later
GameObject( something happens to me parameters )
  me.state = something.what_happens
  Renderer_ApplyState( me.render_handle, me.state.effect_type )

- some time later
Renderer_Render()
  for each renderable thing
    for each rendering back end
        setup graphics for thing.effect
        render it

- finally
GameObject_Destroy()
  Renderer_Destroy( me.render_handle )

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

  • У ігровому об’єкті відсутня логіка візуалізації (зроблено, всі об'єкти знають, що це ручка, щоб він міг застосувати ефекти до себе)
  • Вільне з'єднання між ігровими об'єктами та візуалізацією (зроблено, весь контакт здійснюється за допомогою абстрактної ручки, констатує, що можна застосувати, а не що робити з цими станами)
  • Не всі знаючі рендері (зроблено, знає лише про себе)
  • Переважно перемикання між двигунами візуалізації (це робиться на етапі Renderer_Render (), вам потрібно записати обидва зворотні кінці)

Ключові слова, за якими можна шукати, щоб вийти за рамки простого рефакторингу класів, були б "сутність / компонентна система" та "ін'єкція залежності" та потенційно "MVC" графічний інтерфейс, лише щоб отримати старі передачі мозку.


Це надзвичайно відрізняється від того, що я робив раніше, звучить так, що він має досить великий потенціал. На щастя, мене не обмежує жоден існуючий двигун, тому я можу просто подумати. Я також шукаю ті терміни, які ви згадали, хоча ін'єкція залежності завжди робить мені головний мозок: P.
Рой Т.

2

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

  • ModuleSprite - малювання спрайтів
  • ModuleWeapon - вогнепальні рушниці
  • ModuleScriptingBase - сценарій
  • ModuleParticles - ефекти частинок
  • ModuleCollision - виявлення та реагування на зіткнення

Тож у мене є Playerклас і Bulletклас. Обидва походять від GameObjectта додаються до Scene. Але Playerмає такі модулі:

  • МодульSprite
  • МодульWeapon
  • Частини модуля
  • Модульне зіткнення

І Bulletмає ці модулі:

  • МодульSprite
  • Модульне зіткнення

Такий спосіб організації речей дозволяє уникнути "Діаманта смерті", де у вас є " Vehicleа" VehicleLandі "", VehicleWaterі тепер ви хочете "VehicleAmphibious . Натомість у вас є, Vehicleі воно може мати ModuleWaterі а ModuleLand.

Доданий бонус: ви можете створювати об'єкти, використовуючи набір властивостей. Все, що вам потрібно знати, - це базовий тип (Player, Enemy, Bullet тощо), а потім створити ручки для потрібних модулів для цього типу.

У своїй сцені я роблю наступне:

  • Телефонуйте Update всі GameObjectручки.
  • Зробіть перевірку зіткнення та реакцію на зіткнення для тих, хто має ModuleCollision рукоятку.
  • Телефонуйте UpdatePostдля всіхGameObject ручки, щоб повідомити про своє остаточне положення після фізики.
  • Знищуйте об'єкти, на яких встановлено прапор.
  • Додайте до m_ObjectsCreatedсписку нові об’єкти зі m_Objectsсписку.

І я міг би це організувати далі: за модулями, а не за об'єктом. Тоді я б візуалізував список ModuleSprite, оновив купу ModuleScriptingBaseі зіткнувся зі списком ModuleCollision.


Звучить як композиція до максимуму! Дуже хороший. Я не бачу тут багато конкретних підказок. Як ви вирішите це, просто додаючи різні модулі?
Рой Т.

О, так. Це є недоліком цієї системи: якщо у вас є конкретні вимоги щодо GameObject(наприклад, способу зробити "змію" Sprites), вам потрібно буде створити дитину ModuleSpriteдля цієї конкретної функціональності ( ModuleSpriteSnake) або додати новий модуль взагалі ( ModuleSnake). На щастя, вони є лише вказівниками, але я бачив код, де GameObjectробив буквально все, що може зробити об'єкт.
лицар666
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.