Сценарій та кінематограф без нарізки


12

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

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    alice.walk_to(bob)
    if bob.can_see(alice):
        bob.say("Hello again!")
    else:
        alice.say("Excuse me, Bob?")

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

Я розглядав, як кожна функція викликає дію - alice.walk_to(bob)підштовхне об'єкт до черги, яка вискочить після того, як Еліс дістається до Боба, де б не була. Це більш тонко зламано: ifгілка оцінюється негайно, тому Боб може привітати Алісу, навіть якщо його спина повернена до неї.

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


1
+1 за запитання, але особливо для "Python (він же псевдокод)" :)
Ricket

Python - це як мати наддержаву.
ojrac

Відповіді:


3

Так, як це робить Panda, це зворотній зв'язок. Замість блокування це було б щось на кшталт

def dramatic_scene(actors):
    alice = actors["alice"]
    bob = actors["bob"]

    def cb():
        if bob.can_see(alice):
            bob.say("Hello again!")
        else:
            alice.say("Excuse me, Bob?")
    alice.walk_to(bob, cb)

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

EDIT: Приклад JavaScript, оскільки в цьому стилі кращий синтаксис:

function dramatic_scene(actors) {
    var alice = actors.alice;
    var bob = actors.bob;
    alice.walk_to(bob, function() {
        if(bob.can_see(alice)) {
            bob.say('Hello again!');
        } else {
            alice.say('Excuse me, Bob?');
        }
     });
}

Це працює достатньо для +1, я просто думаю, що це неправильно для розробки контенту. Я готовий зробити додаткову роботу з моїм кінцем, щоб сценарії виглядали просто і зрозуміло.
ojrac

1
@ojrac: Насправді такий спосіб кращий, адже тут за одним і тим же сценарієм ви можете сказати декільком акторам почати ходити одночасно.
Барт ван Хекелом

Фу, хороший момент.
ojrac

Однак для покращення читабельності визначення зворотного виклику можна вкладати всередину виклику walk_to () або поставити після нього (для обох варіантів: якщо мова підтримує), щоб код, який отримав виклик пізніше, побачив пізніше у джерелі.
Барт ван Хекелом

Так, Python насправді не дуже підходить для такого роду синтаксису. Це виглядає набагато приємніше в JavaScript (див. Вище, тут не можна використовувати форматування коду).
кодерангер

13

Термін, який ви хочете шукати тут, - це " підпрограми " (і зазвичай це ключове слово або назва функції yield).

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

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

Я чую, що Lua має вбудовану підтримку для процедур. Так само і GameMonkey.

UnrealScript реалізує це за допомогою того, що він називає "станами" та "прихованими функціями".

Якщо ви використовуєте C #, ви можете подивитися цю публікацію в блозі Ніка Грейвліна.

Окрім того, ідея "ланцюгів анімації" - це не одне і те ж - є ефективним рішенням тієї ж проблеми. Nick Gravelyn також має C # реалізацію цього .


Хороший улов, Тетрад;)
Ендрю Рассел

Це дійсно добре, але я не впевнений, що він отримує мені 100% шляху. Схоже, що супроводи дозволяють вам поступатися методу виклику, але я хочу, щоб вийти зі скрипту Lua, аж до стека до коду C #, не записуючи while (walk_to ()! = Done) {return}.
ojrac

@ojrac: Я не знаю про Lua, але якщо ви використовуєте метод C # Ніка Грейвліна, ви можете повернути делегата (або об'єкта, що його містить), який умовно перевіряє ваш менеджер сценаріїв (код Ніка просто повертає час що неявно умовно). Ви навіть можете, щоб латентні функції самі повертали делегата, щоб ви могли написати: yield return walk_to();у своєму сценарії.
Ендрю Рассел

Вихід у C # є приголомшливим, але я оптимізую своє рішення для простого, складного сценарію. Мені буде легше пояснити зворотні дзвінки, ніж прибутковість, тому я прийму іншу відповідь. Я б +2, якби міг.
ojrac

1
Як правило, не потрібно пояснювати виклик прибутковості - ви можете, наприклад, включити це у функцію "walk_to".
Кілотан

3

розумно не збиратись.

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

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

Натомість, функція 'start_walk_to', ймовірно, буде робити щось на кшталт:

def start_cutscene_walk_to(actor,target):
    actor.ai.setbrain(cutscene_brain)
    actor.physics.nocoll = 1
    actor.anims.force_anim('walk')
    # etc.

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

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

сподіваюся, що це допомагає!


Мені подобається те, що ти збираєшся, але насправді я сильно відчуваю цінність glum.walk_to ([інший актор, фіксована позиція чи навіть функція, яка повертає позицію]). Моя мета - забезпечити прості, зрозумілі інструменти та вирішити всю складність, подалі від частини створення вмісту гри. Ви також допомогли мені зрозуміти, що все, що я дійсно хочу, - це спосіб ставитися до кожного сценарію як до машини з кінцевим станом.
ojrac

рада, що могла допомогти! Мені здалося, що моя відповідь була трохи поза темою :) Однозначно погоджуюся зі значенням функції актора.walk_to для досягнення ваших цілей, я з нетерпінням чекаю чути про вашу реалізацію.
Аарон Брейді

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