Які існують способи відділення логіки гри від анімації та циклу малювання?


9

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

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

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

Відповіді:


6

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

Наприклад, у 2D іграх це може бути область прямокутника, яка позначає область, яка відображає поточну частину вашого спрайтового аркуша, яку потрібно намалювати (коли у вас є аркуш, що складається з, скажімо, 30 малюнків 80x80, що містять різні етапи вашого персонажа стрибати, сідати, рухатися тощо). Це також можуть бути будь-які дані, які вам не потрібні для візуалізації, але, можливо, для управління самими станами анімації, як час, що залишився до закінчення поточного кроку анімації, або назва анімації ("ходьба", "стоячи" і т.д.) Все це можна представити будь-яким способом. Ось логічна частина.

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

У коді (тут використовується синтаксис C ++):

class Sprite //Model
{
    private:
       Rectangle subrect;
       Vector2f position;
       //etc.

    public:
       Rectangle GetSubrect() 
       {
           return subrect;
       }
       //etc.
};

class AnimatedSprite : public Sprite, public Updatable //arbitrary interface for classes that need to change their state on a regular basis
{
    AnimationController animation_controller;
    //etc.
    public:
        void Update()
        {
            animation_controller.Update(); //Good OOP design ;) It will take control of changing animations in time etc. for you
            this.SetSubrect(animation_controller.GetCurrentAnimation().GetRect());
        }
        //etc.
};

Ось дані. Ваш рендер візьме ці дані та виведе їх. Оскільки і нормальні спрайти, і анімовані малюються однаково, ви можете використовувати тут поліморфію!

class Renderer
{
    //etc.
    public:
       void Draw(const Sprite &spr)
       {
           graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
       }
};

TMV:

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

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

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

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

Чи дозволяється вашому гравцю переміщатися між плитками самостійно (стиль Zelda)? Просто обробіть вхід і перемістіть програвач відповідно до кожного кадру. Або ви хочете, щоб гравець натискав "праворуч", і ваш персонаж автоматично переміщує одну плитку вправо? Нехай ваш клас RPGMap інтерполює позицію гравців до тих пір, поки він не прибуде до місця призначення, а тим часом заблокує все керування входом за допомогою клавіші руху.

Так чи інакше, якщо ви хочете спростити себе, всі ваші моделі матимуть методи Update (), якщо їм насправді потрібна певна логіка для оновлення (замість того, щоб просто змінювати значення змінних) - Ви не віддасте контролеру у шаблоні MVC таким чином, ви просто перемістіть код від "на один крок вище" (контролер) вниз до моделі, і все, що робить контролер, викликає цей метод оновлення () моделі (Контролер у нашому випадку буде RPGMap). Ви все ще можете легко поміняти код логіки - ви можете просто змінити код класу або, якщо вам потрібна зовсім інша поведінка, ви можете просто вивести з вашого модельного класу і лише замінити метод Update ().

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


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

Я придумав інший приклад. Скажімо, у вас RPG. Наприклад, вашій моделі, яка представляє карту світу, можливо, потрібно буде зберігати позицію персонажа у світі як координати плитки на карті. Однак, коли ви пересуваєте персонаж, вони проходять за кілька пікселів одночасно до наступного квадрата. Чи зберігаєте ви цю позицію "між плитками" в анімаційному об'єкті? Як ви оновлюєте модель, коли персонаж нарешті "прибув" до наступної координати плитки на карті?
TMV

Я змінив відповідь на ваше запитання, оскільки коментарі не дозволяють достатньо символів для цього.
TravisG

Якщо я все правильно зрозумів:
TMV

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

2

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

handle input

for every entity:
    update entity

for every entity:
    draw entity

У мене більше схожа система

handle input (well, update the state. Mine is event driven so this is null)

for every entity:
    update entity //still got game logic here

renderer.draw();

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

Для вашого прикладу я мав би клас GameBoard з низкою плиток. Кожна плитка, очевидно, знає своє положення, і я припускаю якусь анімацію. Коефіцієнт, який потрапляє в якийсь клас анімації, яким належить плитка, і передає йому посилання на клас Renderer. Там усі розділені. Коли ви оновлюєте Tile, він викликає оновлення на анімації .. або оновлює його сам. Коли Renderer.Draw()викликається, він малює анімацію.

Кадрова незалежна анімація не повинна занадто багато стосуватися циклу малювання.


0

Останнім часом я сам вивчав парадигми, тож якщо ця відповідь неповна, я впевнений, що хтось до неї додасть.

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

У більшості випадків ви хочете використовувати багатопотоковий підхід, якщо ви не знайомі з цією темою, це питання все своє, ось буквар вікі . По суті, ви хочете, щоб ваша логіка гри виконувалася в одному потоці, блокуючи змінні, до яких їй потрібно отримати доступ, щоб забезпечити цілісність даних. Якщо ваш логічний цикл неймовірно швидкий (супер-мега-анімований 3d-понг?), Ви можете спробувати виправити частоту виконання циклу, спаючи нитку за невеликі тривалості (на цьому форумі запропоновано 120 Гц для циклів фізики ігор). Одночасно інший потік перемальовує екран (60 гц запропоновано в інших темах) з оновленими змінними, знову запитуючи блокування змінних, перш ніж він звертається до них.

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

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

Сподіваюся, це допомагає :)

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


1
Я тут не згоден. У більшості випадків ви не хочете багатопотокових, особливо якщо це невелика гра.
Качка комуністична

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