Як правильно використовувати одиночні кнопки в програмуванні двигунів на C ++?


16

Я знаю, що одинаки погані, мій старий ігровий движок використовував сингл-об'єкт «Гра», який обробляє все - від зберігання всіх даних до фактичного циклу гри. Зараз я роблю новий.

Проблема полягає в тому, щоб намалювати щось у SFML, яке ви використовуєте window.draw(sprite)там, де вікно sf::RenderWindow. Тут я бачу 2 варіанти:

  1. Створіть одиночний ігровий об’єкт, який отримує кожна суть у грі (що я раніше використовував)
  2. Зробіть це конструктором для сутностей: Entity(x, y, window, view, ...etc)(це просто смішно і дратує)

Який був би правильний спосіб зробити це, зберігаючи конструктор Entity лише x і y?

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


1
Ви можете передати вікно як аргумент функції 'render'.
дари

25
Однодольні непогані! вони можуть бути корисними, а іноді й необхідними (звичайно, це дискусійно).
ExOfDe

3
Не соромтеся замінювати одиночні кнопки простими глобальними. Немає сенсу створювати глобально необхідні ресурси "на вимогу", немає сенсу передавати їх навколо. Для юридичних осіб ви можете використовувати клас "рівень", щоб утримувати певні речі, що стосуються всіх них.
snake5

Я декларую своє вікно та інші залежності в своєму головному, а потім маю вказівники в інших своїх класах.
KaareZ

1
@JAB Легко виправляється з ручною ініціалізацією з main (). Ледача ініціалізація робить це в невідомий момент, що не є хорошою ідеєю для основних систем ніколи.
змія5

Відповіді:


3

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

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

Таким чином, цикл оновлення гри у вашому класі вищого рівня може виглядати так:

EntityList entities = mCurrentLevel.getEntities();
for(auto& i : entities){
  // Run game logic...
  i->update(...);
}
// Render all the entities
for(auto& i : entities){
  mRenderer->draw(i->getSprite());
}

3
Немає нічого ідеального в одиночному. Навіщо робити внутрішні програми впровадження загальнодоступними, коли цього не потрібно? Навіщо писати Logger::getInstance().Log(...)замість просто Log(...)? Навіщо ініціалізувати клас випадковим чином, коли вас запитують, чи можна це зробити вручну лише один раз? Глобальна функція, що посилається на статичні глобалі, просто набагато простіша у створенні та використанні.
змія5

@ snake5 Виправдання синглів на біржі стеків - це як співчуття Гітлеру.
Віллі Козел

30

Простий підхід - просто зробити те, що раніше було Singleton<T>глобальнимT . У глобальних людей теж є проблеми, але вони не представляють собою купу зайвої роботи та кодового коду для забезпечення тривіального обмеження. Це в основному єдине рішення, яке не передбачає (потенційно) дотику до конструктора сутності.

Більш складний, але, можливо, кращий підхід - передати свої залежності там, де вони вам потрібні . Так, це може включати передачу Window *купі об’єктів (наприклад, вашій особі) таким чином, що вони виглядають грубо. Той факт, що він виглядає валовим, повинен вам щось сказати: ваш дизайн може бути грубим.

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

Але це потенційно є великим починанням. Зробити це в системі після факту може бути дуже болісно. Для вас може бути набагато прагматичніше просто залишити свою систему в спокої з сингл-кодом (особливо якщо ви намагаєтеся фактично поставити гру, яка в іншому випадку спрацює нормально; гравці, як правило, не піклуються, якщо у вас є сингл або чотири там).

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

З огляду на те, що ви опублікували, я думаю, що великим кроком у напрямку "немає одиночки" було б уникнути необхідності у ваших організацій доступу до вікна чи виду. Це говорить про те, що вони малюють самі, і вам не потрібно, щоб вони мали самі малювати . Ви можете прийняти методологію, коли суб'єкти господарювання просто містять інформацію, яка дозволила б їх, щоб їх малювало якась зовнішня система (яка має вікно та перегляд посилань). Суб'єкт просто розкриває свою позицію, і спрайт, який він повинен використовувати (або якесь посилання на згаданий спрайт, якщо ви хочете кешувати фактичні спрайти у самому рендері, щоб уникнути повторюваних екземплярів). Відображувачу просто кажуть скласти певний список сутностей, через який він проходить цикл, зчитує дані з та використовує свій внутрішній об'єкт вікна для виклику drawспрайтом, який шукає для сутності.


3
Я не знайомий з C ++, але чи не існують зручні рамки введення залежності для цієї мови?
bgusach

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

1
Метод, який він описує як його створення, таким чином, суб'єкти не черпають їх самостійно, а зберігають інформацію, і єдина система обробляє малювання всіх сутностей використовується в багатьох популярних сьогодні ігрових двигунах.
Патрік У. Макмахон

1
+1 за "Той факт, що він виглядає валовим, повинен вам щось сказати: ваш дизайн може бути грубим".
Shadow503

+1 за надання ідеальної справи та прагматичної відповіді.

6

Спадщина від sf :: RenderWindow

SFML насправді спонукає вас успадкувати його класи.

class GameWindow: public sf::RenderWindow{};

Звідси ви створюєте функції малювання членів для об'єктів малювання.

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity);
};

Тепер ви можете зробити це:

GameWindow window;
Entity entity;

window.draw(entity);

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

class Entity: public sf::Sprite{};

Тепер sf::RenderWindowможна просто малювати Субстанції, і сутності тепер мають такі функції, як setTexture()і setColor(). Сутність може навіть використовувати позицію спрайта як власну позицію, що дозволяє використовувати setPosition()функцію як для переміщення Сутності, так і для її спрайту.


Зрештою , це дуже приємно, якщо у вас просто є:

window.draw(game);

Нижче наведено кілька швидких прикладів реалізації

class GameWindow: public sf::RenderWindow{
 sf::Sprite entitySprite; //assuming your Entities don't need unique sprites.
public:
 void draw(const Entity& entity){
  entitySprite.setPosition(entity.getPosition());
  sf::RenderWindow::draw(entitySprite);
 }
};

АБО

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity){
  sf::RenderWindow::draw(entity.getSprite()); //assuming Entities hold their own sprite.
 }
};

3

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

З цим з шляху, ви можете передати залежності безпосередньо як босі типів (як int, Window*, і т.д.) , або ви можете передати їх в одному або декількох призначених для користувача обгортка типів (наприклад EntityInitializationOptions).

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


3

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

Єдина вагома причина заміни одинака на глобальну - це умиротворення релігійних ненависників-одинаків.

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

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


1
Я релігійний ненависник одиноких і я також не вважаю глобальним рішенням. : S
Дан Комора

1

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

RenderSystem(IWindow* window);

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

Тепер він є більш тестовим, модульним, і він також відокремлений від конкретної реалізації. Це залежить лише від інтерфейсу, а не від конкретної реалізації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.