Поділ малювання та логіки в іграх


11

Я розробник, який тільки зараз починає возитися з розвитком ігор. Я хлопець .Net, тому я поспілкувався з XNA і зараз граю з Cocos2d для iPhone. Моє запитання, правда, більш загальне.

Скажімо, я будую просту гру Pong. Я мав би Ballклас та Paddleклас. Виходячи з розвитку ділового світу, мій перший інстинкт - не мати жодного коду обробки малюнків або вводу в жодному з цих класів.

//pseudo code
class Ball
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Move(){}
}

Ніщо в класі балу не займається введенням або не займається малюванням. Тоді у мене був би інший клас, мій Gameклас, або мій Scene.m(у Cocos2d), який би поповнював м'яч, і під час ігрового циклу він маніпулював би м'ячем у міру необхідності.

Справа в тому, що в багатьох навчальних посібниках як для XNA, так і для Cocos2d я бачу такий зразок:

//pseudo code
class Ball : SomeUpdatableComponent
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Update(){}
   void Draw(){}
   void HandleInput(){}
}

Моє запитання: чи правильно це? Це шаблон, який люди використовують у розвитку ігор? Це якимось чином суперечить усьому, до чого я звик, щоб мій бальний клас все робив. Крім того, у цьому другому прикладі, де я Ballзнаю, як рухатись, як би я поводився з виявленням зіткнення з Paddle? Чи Ballпотрібно мати знання про це Paddle? У моєму першому прикладі Gameклас повинен містити посилання і на, Ballі на Paddle, а потім надсилати обидва з них до якогось CollisionDetectionменеджера чи чогось іншого, але як я маю справу зі складністю різних компонентів, якщо кожен окремий компонент робить все сам? (Я сподіваюся, що я маю сенс .....)


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

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

1
@sebf це не працює, якщо ви орієнтовані на дані, і це не працює, якщо вам подобається об'єктно-орієнтований дизайн. Дивіться тверді принципи
tenpn

@tenpn. Ця стаття та документ, пов'язані всередині, дуже цікаві, дякую!
sebf

Відповіді:


10

Моє запитання: чи правильно це? Це шаблон, який люди використовують у розвитку ігор?

Розробники ігор, як правило, використовують будь-які роботи. Це не суворо, але ей, це кораблі.

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

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

Зауважте, що виклик draw () може насправді не означає, що сутність викликає putPixel або все, що всередині себе; в деяких випадках це може просто означати оновлення даних, які згодом опитуються кодом, відповідальним за малювання. У більш розвинених випадках він «малює» себе, викликаючи методи, викриті рендером. У техніці, над якою я зараз працюю, об’єкт намалюватиме себе за допомогою викликів рендерінга, і тоді кожен кадр потоком візуалізації організовує всі виклики, зроблені всіма об'єктами, оптимізує для таких речей, як зміни стану, а потім виводить всю річ (все це відбувається на потоці, окремому від логіки гри, тому ми отримуємо асинхронну візуалізацію).

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

Крім того, у цьому другому прикладі, коли мій бал знає, як рухатись, як би я поводився з виявленням зіткнення з веслом? Чи повинен Балу мати знання про Весло?

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

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

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

У моєму першому прикладі клас Ігор міститиме посилання як на Ball, так і на Paddle, а потім надсилає обидва з них до якогось менеджера CollisionDetection або чогось іншого, але як я маю справу зі складністю різних компонентів, якщо кожен окремий компонент робить все самі?

Дивіться вище для дослідження цього. Якщо ви використовуєте мову, яка легко підтримує інтерфейси (наприклад, Java, C #), це легко. Якщо ви перебуваєте на C ++, це може бути ... цікаво. Я прихильник використання повідомлень для вирішення цих проблем (класи обробляють повідомлення, які вони можуть, ігнорують інші), деякі інші люди, як склад компонентів.

Знову ж, все, що працює і для вас найлегше.


2
О, і завжди пам’ятайте: YAGNI (вам це не знадобиться) тут дійсно важливий. Ви можете витратити багато часу, намагаючись придумати ідеальну гнучку систему для вирішення всіх можливих проблем, а потім ніколи нічого не зробите. Я маю на увазі, якби ви справді хотіли хорошого мультиплеєрного хребта для всіх можливих результатів, ви використовували б CORBA, так? :)
ChrisE

5

Нещодавно я зробив просту гру Space Invadors, використовуючи «сутність системи». Це закономірність, яка надзвичайно добре розділяє атрибути та поведінку. Мені знадобилося кілька ітерацій, щоб повністю зрозуміти це, але як тільки ви отримаєте кілька розроблених компонентів, це стає надзвичайно простим для складання нових об’єктів, використовуючи існуючі компоненти.

Ви повинні прочитати це:

http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/

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

Мої ітерації пройшли так:

Перша ітерація мала об'єкт "EntitySystem", як описав Адам; однак у моїх компонентів все ще були методи - мій компонент, що відводиться, мав метод paint (), а мій компонент позиції мав метод move () і т. д. Коли я почав витісняти сутності, я зрозумів, що мені потрібно почати передавати повідомлення між компоненти та замовлення виконання оновлень компонентів .... так само брудно.

Отже, я повернувся і перечитав блог T-машин. У нитках коментарів є багато інформації, і в них він дійсно підкреслює, що компоненти не мають поведінки - поведінка забезпечується системами сутностей. Таким чином, вам не потрібно передавати повідомлення між компонентами та замовляти оновлення компонентів, оскільки впорядкування визначається глобальним порядком виконання системи. Гаразд. Можливо, це занадто абстрактно.

У всякому разі для ітерації №2 це те, що я зібрав із блогу:

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

EntitySystem - кожна система по суті є лише методом, який працює на множині елементів. Кожна система використовуватиме компонент x, y і z сутності для завершення роботи. Таким чином, ви б запитали менеджера для об'єктів із компонентами x, y і z, а потім передаєте цей результат у систему.

Сутність - просто ідентифікатор, як довгий. Сутність - це те, що групує набір екземплярів компонентів разом у «сутність».

Компонент - набір полів .... немає поведінки! коли ви починаєте додавати поведінку, вона починає бруднитися ... навіть у простій грі Space Invadors.

Редагувати : до речі, "dt" - час дельта з часу останнього виклику основного циклу

Отже, моя основна петля загарбників:

Collection<Entity> entitiesWithGuns = manager.getEntitiesWith(Gun.class);
Collection<Entity> entitiesWithDamagable =
manager.getEntitiesWith(BulletDamagable.class);
Collection<Entity> entitiesWithInvadorDamagable = manager.getEntitiesWith(InvadorDamagable.class);

keyboardShipControllerSystem.update(entitiesWithGuns, dt);
touchInputSystem.update(entitiesWithGuns, dt);
Collection<Entity> entitiesWithInvadorMovement = manager.getEntitiesWith(InvadorMovement.class);
invadorMovementSystem.update(entitiesWithInvadorMovement);

Collection<Entity> entitiesWithVelocity = manager.getEntitiesWith(Velocity.class);
movementSystem.move(entitiesWithVelocity, dt);
gunSystem.update(entitiesWithGuns, System.currentTimeMillis());
Collection<Entity> entitiesWithPositionAndForm = manager.getEntitiesWith(Position.class, Form.class);
collisionSystem.checkCollisions(entitiesWithPositionAndForm);

Спочатку це виглядає трохи дивно, але неймовірно гнучко. Це також дуже просто оптимізувати; для різних типів компонентів ви можете мати різні резервні сховища даних для швидшого пошуку. Для класу "форма" ви можете мати його підкріпленим квадратом для швидкого доступу для виявлення зіткнень.

Ти мені подобаєшся; Я досвідчений розробник, але не мав досвіду писати ігри. Я провів деякий час на дослідженні моделей розробників, і цей потрапив мені в очі. Це жодним чином не єдиний спосіб робити речі, але я вважаю це дуже інтуїтивним та надійним. Я вважаю, що модель була офіційно обговорена в 6-й серії серії «Ігрові дорогоцінні програми» - http://www.amazon.com/Game-Programming-Gems/dp/1584500492 . Я сам не читав жодної книги, але чую, що вони фактично є посиланням для програмування ігор.


1

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

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

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

Ще раз рекомендую вам написати те, що, на вашу думку, є найбільш природним для вас.


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

Якщо бути справедливим, у бізнес-програмуванні, якщо ви звалите його, є два поїзди: бізнес-об’єкти, які завантажують, зберігаються та виконують логіку на себе, і DTO + AppLogic + Repository / Persitance Layer ...
Nate

1

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

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

Більшість людей використовують клас введення, результати якого доступні для всіх ігрових об’єктів через Сервіс або статичний клас.


0

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

У грі Dev world, як правило, хочеться думати з точки зору швидкості. Чим більше об’єктів у вас, тим більше контекстних комутаторів стане, що може сповільнити роботу залежно від поточного навантаження.

Це лише моя міркування, оскільки я розробник ігор Wannabe, але професійний розробник.


0

Ні, це не "правильно" чи "неправильно", це просто спрощення.

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

Ось приклад VB.NET, де "бізнес-логіка" (додавання числа) записана в обробці подій GUI - http://www.homeandlearn.co.uk/net/nets1p12.html

як я маю справу зі складністю різних компонентів, якщо кожен окремий компонент робить все самостійно?

Якщо і коли він вийде таким складним, то ви можете це визначити.

class Ball
{
    GraphicalModel ballVisual;
    void Draw()
    {
        ballVisual.Draw();
    }
};

0

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

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