Самый простой чернильный скрипт — КиберПедия 

Поперечные профили набережных и береговой полосы: На городских территориях берегоукрепление проектируют с учетом технических и экономических требований, но особое значение придают эстетическим...

Механическое удерживание земляных масс: Механическое удерживание земляных масс на склоне обеспечивают контрфорсными сооружениями различных конструкций...

Самый простой чернильный скрипт

2021-11-25 16
Самый простой чернильный скрипт 0.00 из 5.00 0 оценок
Заказать работу

Писать чернилами

Введение

ink - это язык сценариев, построенный на идее разметки чистого текста с помощью потока для создания интерактивных сценариев.

По сути, его можно использовать для написания истории «Выбери свой стиль» или ветвящегося дерева диалога. Но его реальная сила в написании диалогов с большим количеством опций и большим количеством рекомбинации потока.

ink предлагает несколько функций, позволяющих нетехническим авторам часто переходить и воспроизводить последствия этих переходов, как второстепенными, так и основными, без суеты.

Сценарий нацелен на то, чтобы быть чистым и логически упорядоченным, поэтому разветвленный диалог можно проверить «на глаз». Поток описывается декларативным способом, где это возможно.

Это также разработано с учетом пересмотра; поэтому редактирование потока должно быть быстрым.

Часть первая: основы

Содержание

Самый простой чернильный скрипт

Самый простой скрипт для рукописного ввода - это просто текст в файле.ink.

Hello, world!

При запуске это выведет содержимое, а затем остановится.

Текст на отдельных строках создает новые абзацы. Сценарий:

Hello, world!

Hello?

Hello, are you there?

производит вывод, который выглядит одинаково.

Комментарии

По умолчанию весь текст в вашем файле будет отображаться в выходном контенте, если только он не размечен.

Самая простая разметка - это комментарий. Чернила поддерживают два вида комментариев. Для чтения кода используется тот тип, который компилятор игнорирует:

"What do you make of this?" she asked.

 

// Something unprintable...

 

"I couldn't possibly comment," I replied.

 

/*

  ... or an unlimited block of text

*/

и есть тип, используемый для напоминания автору, что он должен делать, который компилятор печатает во время компиляции:

TODO: Write this section properly!

Теги

Текстовый контент из игры будет отображаться «как есть» при запуске двигателя. Тем не менее, иногда полезно пометить строку контента дополнительной информацией, чтобы сообщить игре, что делать с этим контентом.

Чернила обеспечивают простую систему для пометки строк содержимого с помощью хэштегов.

A line of normal game-text. # colour it blue

Они не отображаются в основном текстовом потоке, но могут быть прочитаны игрой и использованы по вашему усмотрению. См. RunningYourInk для получения дополнительной информации.

Выбор

Ввод предлагается игроку с помощью выбора текста. Выбор текста обозначается *символом.

Если никакие другие инструкции потока не даны, после того как выбор сделан, выбор переместится в следующую строку текста.

Hello world!

*  Hello back!

   Nice to hear from you!

Это производит следующую игру:

Hello world

1: Hello back!

 

> 1

Hello back!

Nice to hear from you.

По умолчанию текст выбора снова появляется в выводе.

Подавление выбора текста

Некоторые игры отделяют текст выбора от его результата. В краске, если текст выбор даются в квадратных скобках, текст выбора не будет печататься в ответ.

Hello world!

*  [Hello back!]

   Nice to hear from you!

производит

Hello world

1: Hello back!

 

> 1

Nice to hear from you.

Узлы

Части контента называются узлами

Чтобы позволить игре разветвляться, нам нужно пометить разделы контента именами (как это делает старомодная книга игр со своим «Параграфом 18» и т.п.).

Эти разделы называются «узлами» и являются основной структурной единицей содержания чернил.

Написание узла

Начало узла обозначается двумя или более знаками равенства, как показано ниже.

=== top_knot ===

(Знаки равенства в конце необязательны; имя должно быть одним словом без пробелов.)

Начало узла - заголовок; содержание, которое следует, будет в том узле.

=== back_in_london ===

 

We arrived into London at 9.45pm exactly.

Продвинутый: узелок "Привет, мир"

Когда вы запускаете файл чернил, содержимое вне узлов будет запускаться автоматически. Но сучки не будут. Поэтому, если вы начнете использовать узлы для хранения своего контента, вам нужно будет указать игре, куда идти. Мы делаем это с помощью стрелки ->, которая будет рассмотрена в следующем разделе.

Самый простой запутанный скрипт:

-> top_knot

 

=== top_knot ===

Hello world!

Однако чернила не любят незакрепленные концы и выдают предупреждение о компиляции и / или времени выполнения, когда считают, что это произошло. Сценарий выше производит это при компиляции:

WARNING: Apparent loose end exists where the flow runs out. Do you need a '-> END' statement, choice or divert? on line 3 of tests/test.ink

и это при беге:

Runtime error in tests/test.ink line 3: ran out of content. Do you need a '-> DONE' or '-> END'?

Следующее проигрывается и компилируется без ошибок:

=== top_knot ===

Hello world!

-> END

-> ENDявляется маркером как для автора, так и для компилятора; это означает, что «поток истории должен прекратиться».

Диверты

Узлы отвлекаются на узлы

Вы можете рассказать историю, чтобы перейти от одного узла к другому, используя ->«стрелку отклонения». Переадресация происходит немедленно без какого-либо участия пользователя.

=== back_in_london ===

 

We arrived into London at 9.45pm exactly.

-> hurry_home

 

=== hurry_home ===

We hurried home to Savile Row as fast as we could.

Дайверты невидимы

Переадресация должна быть бесшовной и даже произойти в середине предложения:

=== hurry_home ===

We hurried home to Savile Row -> as_fast_as_we_could

 

=== as_fast_as_we_could ===

as fast as we could.

производит ту же строку, что и выше:

We hurried home to Savile Row as fast as we could.

Клей

Поведение по умолчанию вставляет разрывы строк перед каждой новой строкой содержимого. В некоторых случаях, однако, контент должен настаивать на отсутствии переноса строки, и это может быть сделано с помощью <>или «склеивания».

=== hurry_home ===

We hurried home <>

-> to_savile_row

 

=== to_savile_row ===

to Savile Row

-> as_fast_as_we_could

 

=== as_fast_as_we_could ===

<> as fast as we could.

также производит:

We hurried home to Savile Row as fast as we could.

Вы не можете использовать слишком много клея: несколько клеев рядом друг с другом не имеют никакого дополнительного эффекта. (И нет никакого способа «отрицать» клей; если линия липкая, она заклеится.)

Ветвление потока

Основное ветвление

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

=== paragraph_1 ===

You stand by the wall of Analand, sword in hand.

* [Open the gate] -> paragraph_2

* [Smash down the gate] -> paragraph_3

* [Turn back and go home] -> paragraph_4

 

=== paragraph_2 ===

You open the gate, and step out onto the path.

 

...

Ветвление и присоединение

Используя переадресацию, писатель может разветвлять поток и снова присоединять его, не показывая игроку, что поток присоединился.

=== back_in_london ===

 

We arrived into London at 9.45pm exactly.

 

*  "There is not a moment to lose!"[] I declared.

   -> hurry_outside

       

*  "Monsieur, let us savour this moment!"[] I declared.

   My master clouted me firmly around the head and dragged me out of the door.

   -> dragged_outside

 

*  [We hurried home] -> hurry_outside

 

       

=== hurry_outside ===

We hurried home to Savile Row -> as_fast_as_we_could

 

 

=== dragged_outside ===

He insisted that we hurried home to Savile Row

-> as_fast_as_we_could

 

 

=== as_fast_as_we_could ===

<> as fast as we could.

Поток истории

Узлы и отводы объединяются, чтобы создать основной сюжетный поток в игре. Этот поток «плоский» - нет стека вызовов, а переадресация «не возвращается» из.

В большинстве рукописных сценариев поток истории начинается сверху, подпрыгивает в спагетти-подобном беспорядке и в конечном итоге, как мы надеемся, достигает a -> END.

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

Дополнительно: петли

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

См. Разделы «Изменение текста и условные параметры» для получения дополнительной информации.

О, а следующее законно и не очень хорошая идея:

=== round ===

and

-> round

Включает в себя и швы

Швы имеют уникальные имена

Стежок может быть перенаправлен на использование его «адреса».

*  [Travel in third class]

   -> the_orient_express.in_third_class

 

*  [Travel in the guard's van]

   -> the_orient_express.in_the_guards_van

Первый стежок по умолчанию

Отклонение к узлу, который содержит стежки, переключится на первый стежок в узле. Так:

*  [Travel in first class]

   "First class, Monsieur. Where else?"

   -> the_orient_express

такой же как:

*  [Travel in first class]

   "First class, Monsieur. Where else?"

   -> the_orient_express.in_first_class

(... если мы не переместим порядок стежков внутри узла!)

Вы также можете включить содержимое в верхней части узла вне любой строчки. Тем не менее, вы должны помнить, чтобы отклониться от него - движок не будет автоматически вводить первый стежок, как только он проработает содержимое заголовка.

=== the_orient_express ===

 

We boarded the train, but where?

*  [First class] -> in_first_class

*  [Second class] -> in_second_class

 

= in_first_class

  ...

= in_second_class

  ...

Местные отводы

Изнутри узла вам не нужно использовать полный адрес для стежка.

-> the_orient_express

 

=== the_orient_express ===

= in_first_class

   I settled my master.

   *  [Move to third class]

          -> in_third_class

 

= in_third_class

   I put myself in third.

Это означает, что стежки и узлы не могут иметь общие имена, но несколько узлов могут содержать одно и то же имя стежка. (Так что и у Восточного Экспресса, и у Монголии СС может быть первый класс.)

Компилятор предупредит вас, если используются неоднозначные имена.

Резервный выбор

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

> 1

The man with the briefcase looks disgusted as you stumble past him.

You search desperately for a friendly face in the crowd.

 

Runtime error in tests/test.ink line 6: ran out of content. Do you need a '-> DONE' or '-> END'?

Мы можем решить это с помощью «запасного варианта». Резервные варианты никогда не отображаются игроку, а «выбираются» игрой, если других вариантов не существует.

Резервный выбор - это просто «выбор без выбора текста»:

*  -> out_of_options

И, слегка нарушив синтаксис, мы можем сделать выбор по умолчанию с содержанием в нем, используя «стрелку выбора, затем»:

* ->

   Mulder never could explain how he got out of that burning box car. -> season_2

Пример резервного варианта

Добавление этого в предыдущий пример дает нам:

=== find_help ===

 

   You search desperately for a friendly face in the crowd.

   *  The woman in the hat[?] pushes you roughly aside. -> find_help

   *  The man with the briefcase[?] looks disgusted as you stumble past him. -> find_help

   *  ->

          But it is too late: you collapse onto the station platform. This is the end.

          -> END

и производит:

You search desperately for a friendly face in the crowd.

 

1: The woman in the hat?

2: The man with the briefcase?

 

> 1

The woman in the hat pushes you roughly aside.

You search desperately for a friendly face in the crowd.

 

1: The man with the briefcase?

 

> 1

The man with the briefcase looks disgusted as you stumble past him.

You search desperately for a friendly face in the crowd.

But it is too late: you collapse onto the station platform. This is the end.

Липкий выбор

Конечно, поведение «один раз» - это не всегда то, что мы хотим, поэтому у нас есть второй выбор: «липкий» выбор. Липкий выбор - просто тот, который не используется и отмечен +маркером.

=== homers_couch ===

   +  [Eat another donut]

          You eat another donut. -> homers_couch

   *  [Get off the couch]

          You struggle up off the couch to go and compose epic poetry.

          -> END

Выбор по умолчанию тоже может быть липким.

=== conversation_loop

   *  [Talk about the weather] -> chat_weather

   *  [Talk about the children] -> chat_children

   +  -> sit_in_silence_again

Условный выбор

Вы также можете включить или выключить выбор вручную. В чернилах достаточно много логики, но простейшие тесты - «видел ли игрок определенную часть контента».

Каждый узел / строчка в игре имеет уникальный адрес (поэтому его можно перенаправить на), и мы используем тот же адрес, чтобы проверить, был ли виден этот фрагмент контента.

*  { not visit_paris } [Go to Paris] -> visit_paris

+ { visit_paris  }        [Return to Paris] -> visit_paris

 

*  { visit_paris.met_estelle } [ Telephone Mme Estelle ] -> phone_estelle

Обратите внимание, что тест knot_nameверен, если какой-либо шов внутри этого узла был замечен.

Также обратите внимание, что условные выражения не переопределяют однократное поведение параметров, поэтому вам все равно понадобятся липкие параметры для повторяющихся вариантов.

Переменный текст

Текст может отличаться

Пока что весь контент, который мы видели, был статичным, фиксированным фрагментом текста. Но содержимое также может меняться в момент печати.

Types of alternatives

Sequences (the default):

A sequence (or a "stopping block") is a set of alternatives that tracks how many times its been seen, and each time, shows the next element along. When it runs out of new content it continues the show the final element.

The radio hissed into life. {"Three!"|"Two!"|"One!"|There was the white noise racket of an explosion.|But it was just static.}

 

{I bought a coffee with my five-pound note.|I bought a second coffee for my friend.|I didn't have enough money to buy any more coffee.}

Cycles (marked with a &):

Cycles are like sequences, but they loop their content.

It was {&Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday} today.

Один раз (помечено!):

Разовые альтернативы похожи на последовательности, но когда у них заканчивается новый контент для отображения, они ничего не отображают. (Вы можете думать о единственной альтернативе как о последовательности с пустой последней записью.)

He told me a joke. {!I laughed politely.|I smiled.|I grimaced.|I promised myself to not react again.}

Перемешивание (помечено ~):

Перемешивание производит случайный вывод.

I tossed the coin. {~Heads|Tails}.

Особенности альтернатив

Альтернативы могут содержать пустые элементы.

I took a step forward. {!|Then the lights went out. -> eek}

Альтернативы могут быть вложенными.

The Ratbear {&{wastes no time and |}swipes|scratches} {&at you|into your {&leg|arm|cheek}}.

Альтернативы могут включать отклоняющиеся заявления.

I {waited.|waited some more.|snoozed.|woke up and waited more.|gave up and left. -> leave_post_office}

Их также можно использовать внутри текста выбора:

+ "Hello, {&Master|Monsieur Fogg|you|brown-eyes}!"[] I declared.

(... с одним предупреждением; вы не можете начать текст опции с {, так как он будет выглядеть как условный.)

(... но у предостережения есть предостережение, если вы выйдете из пробела, \ прежде чем ваши {чернила распознают его как текст.)

Примеры

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

Вот версия "удар-моль" с одним узлом. Обратите внимание, что мы используем однократные опции и запасной вариант, чтобы крот не перемещался, и игра всегда заканчивалась.

=== whack_a_mole ===

   {I heft the hammer.|{~Missed!|Nothing!|No good. Where is he?|Ah-ha! Got him! -> END}}

   The {&mole|{&nasty|blasted|foul} {&creature|rodent}} is {in here somewhere|hiding somewhere|still at large|laughing at me|still unwhacked|doomed}. <>

   {!I'll show him!|But this time he won't escape!}

   * [{&Hit|Smash|Try} top-left] -> whack_a_mole

   * [{&Whallop|Splat|Whack} top-right] -> whack_a_mole

   * [{&Blast|Hammer} middle] -> whack_a_mole

   * [{&Clobber|Bosh} bottom-left] -> whack_a_mole

   * [{&Nail|Thump} bottom-right] -> whack_a_mole

   * [] Then you collapse from hunger. The mole has defeated you!

          -> END

производит следующую «игру»:

I heft the hammer.

The mole is in here somewhere. I'll show him!

 

1: Hit top-left

2: Whallop top-right

3: Blast middle

4: Clobber bottom-left

5: Nail bottom-right

 

> 1

Missed!

The nasty creature is hiding somewhere. But this time he won't escape!

 

1: Splat top-right

2: Hammer middle

3: Bosh bottom-left

4: Thump bottom-right

 

> 4

Nothing!

The mole is still at large.

1: Whack top-right

2: Blast middle

3: Clobber bottom-left

 

> 2

Where is he?

The blasted rodent is laughing at me.

1: Whallop top-right

2: Bosh bottom-left

 

> 1

Ah-ha! Got him!

И вот небольшой совет по образу жизни. Обратите внимание на липкий выбор - приманка телевизора никогда не угаснет:

=== turn_on_television ===

I turned on the television {for the first time|for the second time|again|once more}, but there was {nothing good on, so I turned it off again|still nothing worth watching|even less to hold my interest than before|nothing but rubbish|a program about sharks and I don't like sharks|nothing on}.

+  [Try it again]          -> turn_on_television

*  [Go outside instead] -> go_outside_instead

 

=== go_outside_instead ===

-> END

Предварительный просмотр Sneak: многострочные альтернативы

У чернил есть и другой формат для создания альтернатив различных блоков содержимого. См. Раздел «Многострочные блоки» для получения подробной информации.

Условный текст

Текст также может варьироваться в зависимости от логических тестов, так же как и параметры.

{met_blofeld: "I saw him. Only for a moment." }

и

"His real name was {met_blofeld.learned_his_name: Franz|a secret}."

Они могут отображаться в виде отдельных строк или в разделе содержимого. Они даже могут быть вложенными, поэтому:

{met_blofeld: "I saw him. Only for a moment. His real name was {met_blofeld.learned_his_name: Franz|kept a secret}." | "I missed him. Was he particularly evil?" }

может производить либо:

"I saw him. Only for a moment. His real name was Franz."

или:

"I saw him. Only for a moment. His real name was kept a secret."

или:

"I missed him. Was he particularly evil?"

Game Queries and Functions

ink provides a few useful 'game level' queries about game state, for use in conditional logic. They're not quite parts of the language, but they're always available, and they can't be edited by the author. In a sense, they're the "standard library functions" of the language.

The convention is to name these in capital letters.

CHOICE_COUNT()

CHOICE_COUNT returns the number of options created so far in the current chunk. So for instance.

*  {false} Option A

* {true} Option B

* {CHOICE_COUNT() == 1} Option C

produces two options, B and C. This can be useful for controlling how many options a player gets on a turn.

TURNS()

This returns the number of game turns since the game began.

TURNS_SINCE(-> knot)

TURNS_SINCE returns the number of moves (formally, player inputs) since a particular knot/stitch was last visited.

A value of 0 means "was seen as part of the current chunk". A value of -1 means "has never been seen". Any other positive value means it has been seen that many turns ago.

*  {TURNS_SINCE(-> sleeping.intro) > 10} You are feeling tired... -> sleeping

* {TURNS_SINCE(-> laugh) == 0} You try to stop laughing.

Note that the parameter passed to TURNS_SINCE is a "divert target", not simply the knot address itself (because the knot address is a number - the read count - not a location in the story...)

TODO: (requirement of passing -c to the compiler)

Sneak preview: using TURNS_SINCE in a function

The TURNS_SINCE(->x) == 0 test is so useful it's often worth wrapping it up as an ink function.

=== function came_from(-> x)

   ~ return TURNS_SINCE(x) == 0

The section on functions outlines the syntax here a bit more clearly but the above allows you to say things like:

* {came_from(-> nice_welcome)} 'I'm happy to be here!'

* {came_from(-> nasty_welcome)} 'Let's keep this quick.'

... and have the game react to content the player saw just now.

SEED_RANDOM()

В целях тестирования часто бывает полезно починить генератор случайных чисел, чтобы чернила давали одинаковые результаты при каждой игре. Вы можете сделать это путем «заполнения» системы случайных чисел.

~ SEED_RANDOM(235)

Число, которое вы передаете функции начального числа, является произвольным, но предоставление различных начальных значений приведет к различным последовательностям результатов.

Часть 2: Плетение

До сих пор мы создавали разветвленные истории самым простым способом с «опциями», которые ссылаются на «страницы».

Но для этого необходимо, чтобы мы однозначно назвали каждое место назначения в истории, что может замедлить написание и препятствовать незначительному переходу.

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

Этот формат называется «переплетением», и он построен на основе базового синтаксиса контента / опций с двумя новыми функциями: меткой сбора -и вложением вариантов и сборок.

Собирает

Варианты и собирает формы цепочки контента

We can string these gather-and-branch sections together to make branchy sequences that always run forwards.

=== escape ===

I ran through the forest, the dogs snapping at my heels.

 

   * I checked the jewels[] were still in my pocket, and the feel of them brought a spring to my step. <>

       

   * I did not pause for breath[] but kept on running. <>

 

   *  I cheered with joy. <>

 

- The road could not be much further! Mackie would have the engine running, and then I'd be safe.

 

   *  I reached the road and looked about[]. And would you believe it?

   * I should interrupt to say Mackie is normally very reliable[]. He's never once let me down. Or rather, never once, previously to that night.

 

-  The road was empty. Mackie was nowhere to be seen.

This is the most basic kind of weave. The rest of this section details additional features that allow weaves to nest, contain side-tracks and diversions, divert within themselves, and above all, reference earlier choices to influence later ones.

The weave philsophy

Weaves are more than just a convenient encapsulation of branching flow; they're also a way to author more robust content. The escape example above has already four possible routes through, and a more complex sequence might have lots and lots more. Using normal diverts, one has to check the links by chasing the diverts from point to point and it's easy for errors to creep in.

With a weave, the flow is guaranteed to start at the top and "fall" to the bottom. Flow errors are impossible in a basic weave structure, and the output text can be easily skim read. That means there's no need to actually test all the branches in game to be sure they work as intended.

Weaves also allow for easy redrafting of choice-points; in particular, it's easy to break a sentence up and insert additional choices for variety or pacing reasons, without having to re-engineer any flow.

Nested Flow

The weaves shown above are quite simple, "flat" structures. Whatever the player does, they take the same number of turns to get from top to bottom. However, sometimes certain choices warrant a bit more depth or complexity.

For that, we allow weaves to nest.

This section comes with a warning. Nested weaves are very powerful and very compact, but they can take a bit of getting used to!

Options can be nested

Consider the following scene:

- "Well, Poirot? Murder or suicide?"

*  "Murder!"

* "Suicide!"

-  Ms. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed.

The first choice presented is "Murder!" or "Suicide!". If Poirot declares a suicide, there's no more to do, but in the case of murder, there's a follow-up question needed - who does he suspect?

We can add new options via a set of nested sub-choices. We tell the script that these new choices are "part of" another choice by using two asterisks, instead of just one.

- "Well, Poirot? Murder or suicide?"

   *  "Murder!"

      "And who did it?"

          * * "Detective-Inspector Japp!"

          * * "Captain Hastings!"

          * * "Myself!"

   * "Suicide!"

   -  Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed.

(Note that it's good style to also indent the lines to show the nesting, but the compiler doesn't mind.)

And should we want to add new sub-options to the other route, we do that in similar fashion.

- "Well, Poirot? Murder or suicide?"

   *  "Murder!"

      "And who did it?"

          * * "Detective-Inspector Japp!"

          * * "Captain Hastings!"

          * * "Myself!"

   * "Suicide!"

          "Really, Poirot? Are you quite sure?"

          * * "Quite sure."

          * *       "It is perfectly obvious."

   -  Mrs. Christie lowered her manuscript a moment. The rest of the writing group sat, open-mouthed.

Now, that initial choice of accusation will lead to specific follow-up questions - but either way, the flow will come back together at the gather point, for Mrs. Christie's cameo appearance.

But what if we want a more extended sub-scene?

Advanced: What gathers do

Gathers are hopefully intuitive, but their behaviour is a little harder to put into words: in general, after an option has been taken, the story finds the next gather down that isn't on a lower level, and diverts to it.

The basic idea is this: options separate the paths of the story, and gathers bring them back together. (Hence the name, "weave"!)

Tracking a Weave

Sometimes, the weave structure is sufficient. But when it's not, we need a bit more control.

Scope

Inside the same block of weave, you can simply use the label name; from outside the block you need a path, either to a different stitch within the same knot:

=== knot ===

= stitch_one

   - (gatherpoint) Some content.

= stitch_two

   *  {stitch_one.gatherpoint} Option

or pointing into another knot:

=== knot_one ===

-  (gather_one)

   * {knot_two.stitch_two.gather_two} Option

       

=== knot_two ===

= stitch_two

   - (gather_two)

          *  {knot_one.gather_one} Option

Advanced: Loops in a weave

Labelling allows us to create loops inside weaves. Here's a standard pattern for asking questions of an NPC.

- (opts)

   *  'Can I get a uniform from somewhere?'[] you ask the cheerful guard.

          'Sure. In the locker.' He grins. 'Don't think it'll fit you, though.'

   *  'Tell me about the security system.'

          'It's ancient,' the guard assures you. 'Old as coal.'

   *  'Are there dogs?'

          'Hundreds,' the guard answers, with a toothy grin. 'Hungry devils, too.'

   // We require the player to ask at least one question

   *  {loop} [Enough talking]

          -> done

- (loop)

   // loop a few times before the guard gets bored

   { -> opts | -> opts | }

   He scratches his head.

   'Well, can't stand around talking all day,' he declares.

- (done)

   You thank the guard, and move away.

Part 3: Variables and Logic

So far we've made conditional text, and conditional choices, using tests based on what content the player has seen so far.

ink also supports variables, both temporary and global, storing numerical and content data, or even story flow commands. It is fully-featured in terms of logic, and contains a few additional structures to help keep the often complex logic of a branching story better organised.

Global Variables

Самая мощная переменная, и, возможно, самая полезная для истории, - это переменная для хранения некоторого уникального свойства о состоянии игры - от суммы денег в кармане главного героя до значения, представляющего состояние главного героя. разум.

Этот тип переменных называется «глобальным», потому что к нему можно получить доступ из любой точки истории - как установить, так и прочитать. (Традиционно, программирование пытается избежать такого рода вещей, поскольку позволяет одной части программы связываться с другой, не связанной частью. Но история - это история, а истории - о последствиях: то, что происходит в Вегасе, редко остается там.)

Переменные печати

Значение переменной может быть напечатано как содержимое с использованием встроенного синтаксиса, аналогичного последовательностям, и условного текста:

VAR friendly_name_of_player = "Jackie"

VAR age = 23

 

My name is Jean Passepartout, but my friend's call me {friendly_name_of_player}. I'm {age} years old.

Это может быть полезно при отладке. Для более сложной печати, основанной на логике и переменных, см. Раздел о функциях.

Оценка строк

Можно заметить, что выше мы упоминали, что переменные могут содержать «содержимое», а не «строки». Это было преднамеренно, потому что строка, определенная чернилами, может содержать чернила - хотя она всегда будет соответствовать строке. (Yikes!)

VAR a_colour = ""

 

~ a_colour = "{~red|blue|green|yellow}"

 

{a_colour}

... производит красный, синий, зеленый или желтый.

Note that once a piece of content like this is evaluated, its value is "sticky". (The quantum state collapses.) So the following:

The goon hits you, and sparks fly before you eyes, {a_colour} and {a_colour}.

... won't produce a very interesting effect. (If you really want this to work, use a text function to print the colour!)

This is also why

VAR a_colour = "{~red|blue|green|yellow}"

is explicitly disallowed; it would be evaluated on the construction of the story, which probably isn't what you want.

Logic

Obviously, our global variables are not intended to be constants, so we need a syntax for altering them.

Since by default, any text in an ink script is printed out directly to the screen, we use a markup symbol to indicate that a line of content is intended meant to be doing some numerical work, we use the ~ mark.

The following statements all assign values to variables:

=== set_some_variables ===

   ~ knows_about_wager = true    

   ~ x = (x * x) - (y * y) + c

   ~ y = 2 * x * y

and the following will test conditions:

{ x == 1.2 }

{ x / 2 > 4 }

{ y - 1 <= x * x }

Mathematics

ink supports the four basic mathematical operations (+, -, * and /), as well as % (or mod), which returns the remainder after integer division. There's also POW for to-the-power-of:

{POW(3, 2)} is 9.

{POW(16, 0.5)} is 4.

If more complex operations are required, one can write functions (using recursion if necessary), or call out to external, game-code functions (for anything more advanced).

RANDOM(min, max)

Ink can generate random integers if required using the RANDOM function. RANDOM is authored to be like a dice (yes, pendants, we said a dice), so the min and max values are both inclusive.

~ temp dice_roll = RANDOM(1, 6)

 

~ temp lazy_grading_for_test_paper = RANDOM(30, 75) 

 

~ temp number_of_heads_the_serpent_has = RANDOM(3, 8)

The random number generator can be seeded for testing purposes, see the section of Game Queries and Functions section above.

String queries

Oddly for a text-engine, ink doesn't have much in the way of string-handling: it's assumed that any string conversion you need to do will be handled by the game code (and perhaps by external functions.) But we support three basic queries - equality, inequality, and substring (which we call? for reasons that will become clear in a later chapter).

The following all return true:

{ "Yes, please." == "Yes, please." }

{ "No, thank you."!= "Yes, please." }

{ "Yes, please"? "ease" }

Switch blocks

And there's also an actual switch statement:

{ x:

- 0: zero

- 1: one

- 2: two

- else: lots

}

Multiline blocks

There's one other class of multiline block, which expands on the alternatives system from above. The following are all valid and do what you might expect:

// Sequence: go through the alternatives, and stick on last

{ stopping:

   -  I entered the casino.

   - I entered the casino again.

   - Once more, I went inside.

}

 

// Shuffle: show one at random

At the table, I drew a card. <>

{ shuffle:

   - Ace of Hearts.

   - King of Spades.

   - 2 of Diamonds.

          'You lose this time!' crowed the croupier.

}

 

// Cycle: show each in turn, and then cycle

{ cycle:

   - I held my breath.

   - I waited impatiently.

   - I paused.

}

 

// Once: show each, once, in turn, until all have been shown

{ once:

   - Would my luck hold?

   - Could I win the hand?

}

Advanced: modified shuffles

The shuffle block above is really a "shuffled cycle"; in that it'll shuffle the content, play through it, then reshuffle and go again.

There are two other versions of shuffle:

shuffle once which will shuffle the content, play through it, and then do nothing.

{ shuffle once:

-  The sun was hot.

- It was a hot day.

}

shuffle stopping will shuffle all the content (except the last entry), and once its been played, it'll stick on the last entry.

{ shuffle stopping:

- A silver BMW roars past.

-  A bright yellow Mustang takes the turn.

- There are like, cars, here.

}

Temporary Variables

Functions

The use of parameters on knots means they are almost functions in the usual sense, but they lack one key concept - that of the call stack, and the use of return values.

ink includes functions: they are knots, with the following limitations and features:

A function:

  • cannot contain stitches
  • cannot use diverts or offer choices
  • can call other functions
  • can include printed content
  • can return a value of any type
  • can recurse safely

(Some of these may seem quite limiting, but for more story-oriented call-stack-style features, see the section of Tunnels.)

Return values are provided via the ~ return statement.

Examples

For instance, you might include:

=== function max(a,b) ===

   { a < b:

          ~ return b

   - else:

          ~ return a

   }

 

=== function exp(x, e) ===

   // returns x to the power e where e is an integer

   { e <= 0:

          ~ return 1

   - else:

          ~ return x * exp(x, e - 1)

   }

Then:

The maximum of 2^5 and 3^3 is {max(exp(2,5), exp(3,3))}.

produces:

The maximum of 2^5 and 3^3 is 32.

Constants

Global Constants

Interactive stories often rely on state machines, tracking what stage some higher level process has reached. There are lots of ways to do this, but the most conveninent is to use constants.

Sometimes, it's convenient to define constants to be strings, so you can print them out, for gameplay or debugging purposes.

CONST HASTINGS = "Hastings"

CONST POIROT = "Poirot"

CONST JAPP = "Japp"

 

VAR current_chief_suspect = HASTINGS

 

=== review_evidence ===

   { found_japps_bloodied_glove:

          ~ current_chief_suspect = POIROT

   }

   Current Suspect: {current_chief_suspect}

Sometimes giving them values is useful:

CONST PI = 3.14

CONST VALUE_OF_TEN_POUND_NOTE = 10

And sometimes the numbers are useful in other ways:

CONST LOBBY = 1

CONST STAIRCASE = 2

CONST HALLWAY = 3

 

CONST HELD_BY_AGENT = -1

 

VAR secret_agent_location = LOBBY

VAR suitcase_location = HALLWAY

 

=== report_progress ===

{ secret_agent_location == suitcase_location:

   The secret agent grabs the suitcase!

   ~ suitcase_location = HELD_BY_AGENT 

       

- secret_agent_location < suitcase_location:

   The secret agent moves forward.

   ~ secret_agent_location++

}

Constants are simply a way to allow you to give story states easy-to-understand names.

Advanced: Game-side logic

There are two core ways to provide game hooks in the ink engine. External function declarations in ink allow you to directly call C# functions in the game, and variable observers are callbacks that are fired in the game when ink variables are modified. Both of these are described in Running your ink.

Tunnels

The default structure for ink stories is a "flat" tree of choices, branching and joining back together, perhaps looping, but with the story always being "at a certain place".

But this flat structure makes certain things difficult: for example, imagine a game in which the following interaction can happen:

=== crossing_the_date_line ===

*  "Monsieur!"[] I declared with sudden horror. "I have just realised. We have crossed the international date line!"

-  Monsieur Fogg barely lifted an eyebrow. "I have adjusted for it."

*  I mopped the sweat from my brow[]. A relief!

* I nodded, becalmed[]. Of course he had!

* I cursed, under my breath[]. Once again, I had been belittled!

...but it can happen at several different places in the story. We don't want to have to write copies of the content for each different place, but when the content is finished it needs to know where to return to. We can do this using parameters:

=== crossing_the_date_line(-> return_to) ===

...

-  -> return_to

 

...

 

=== outside_honolulu ===

We arrived at the large island of Honolulu.

- (postscript)

   -> crossing_the_date_line(-> done)

- (done)

   -> END

 

...

 

=== outside_pitcairn_island ===

The boat sailed along the water towards the tiny island.

- (postscript)

   -> crossing_the_date_line(-> done)

- (done)

   -> END

Both of these locations now call and execute the same segment of storyflow, but once finished they return to where they need to go next.

But what if the section of story being called is more complex - what if it spreads across several knots? Using the above, we'd have to keep passing the 'return-to' parameter from knot to knot, to ensure we always knew where to return.

So instead, ink integrates this into the language with a new kind of divert, that functions rather like a subroutine, and is called a 'tunnel'.

Tunnels run sub-stories

The tunnel syntax looks like a divert, with another divert on the end:

-> crossing_the_date_line ->

This means "do the crossing_the_date_line story, then continue from here".

Inside the tunnel itself, the syntax is simplified from the parameterised example: all we do is end the tunnel using the ->-> statement which means, essentially, "go on".

=== crossing_the_date_line ===

// this is a tunnel!

...

- ->->

Note that tunnel knots aren't declared as such, so the compiler won't check that tunnels really do end in ->-> statements, except at run-time. So you will need to write carefully to ensure that all the flows into a tunnel really do come out again.

Tunnels can also be chained together, or finish on a normal divert:

...

// this runs the tunnel, then diverts to 'done'

-> crossing_the_date_line -> done

...

 

...

//this runs one tunnel, then another, then diverts to 'done'

-> crossing_the_date_line -> check_foggs_health -> done

...

Tunnels can be nested, so the following is valid:

=== plains ===

= night_time

   The dark grass is soft under your feet.

   +  [Sleep]

          -> sleep_here -> wake_here -> day_time

= day_time

   It is time to move on.

       

=== wake_here ===

   You wake as the sun rises.

   +  [Eat something]

          -> eat_something ->

   +  [Make a move]

   -  ->->

 

=== sleep_here ===

   You lie down and try to close your eyes.

   -> monster_attacks ->

   Then it is time to sleep.

   -> dream ->

   ->->

... and so on.

Threads

So far, everything in ink has been entirely linear, despite all the branching and diverting. But it's actually possible for a writer to 'fork' a story into different sub-sections, to cover more possible player actions.

We call this 'threading', though it's not really threading in the sense that computer scientists mean it: it's more like stitching in new content from various places.

Note that this is definitely an advanced feature: the engineering stories becomes somewhat more complex once threads are involved!

Uses of threads

In a normal story, threads might never be needed.

But for games with lots of independent moving parts, threads quickly become essential. Imagine a game in which characters move independently around a map: the main story hub for a room might look like the following:

CONST HALLWAY = 1

CONST OFFICE = 2

 

VAR player_location = HALLWAY

VAR generals_location = HALLWAY

VAR doctors_location = OFFICE

 

== run_player_location

   {

          - player_location == HALLWAY: -> hallway

   }

 

== hallway == 

   <- characters_present(HALLWAY)

   *  [Drawers] -> examine_drawers

   * [Wardrobe] -> examine_wardrobe

   * [Go to Office] -> go_office

   -  -> run_player_location

= examine_drawers

   // etc...

 

// Here's the thread, which mixes in dialogue for characters you share the room with at the moment.

 

== characters_present(room)

   { generals_location == room:

          <- general_conversation

   }

   { doctors_location == room:

          <- doctor_conversation

   }

   -> DONE

       

== general_conversation

   *  [Ask the General about the bloodied knife]

          "It's a bad business, I can tell you."

   -  -> run_player_location

 

== doctor_conversation

   *  [Ask the Doctor about the bloodied knife]

          "There's nothing strange about blood, is there?"

   -  -> run_player_location

Note in particular, that we need an explicit way to return the player who has gone down a side-thread to return to the main flow. In most cases, threads will either need a parameter telling them where to return to, or they'll need to end the current story section.

Basic Lists

The basic unit of state-tracking is a list of states, defined using the LIST keyword. Note that a list is really nothing like a C# list (which is an array).

For instance, we might have:

LIST kettleState = cold, boiling, recently_boiled

This line defines two things: firstly three new values - cold, boiling and recently_boiled - and secondly, a variable, called kettleState, to hold these states.

We can tell the list what value to take:

~ kettleState = cold

We can change the value:

*  [Turn on kettle]

   The kettle begins to bubble and boil.

   ~ kettleState = boiling

We can query the value:

*  [Touch the kettle]

   { kettleState == cold:

          The kettle is cool to the touch.

   - else:

      The outside of the kettle is very warm!

   }

For convenience, we can give a list a value when it's defined using a bracket:

LIST kettleState = cold, (boiling), recently_boiled

// at the start of the game, this kettle is switched on. Edgy, huh?

... и если обозначения для этого выглядят немного избыточными, есть причина для этого в несколько подразделов времени.

List Values

When a list is defined, the values are listed in an order, and that order is considered to be significant. In fact, we can treat these values as if they were numbers. (That is to say, they are enums.)

LIST volumeLevel = off, quiet, medium, loud, deafening

VAR lecturersVolume = quiet

VAR murmurersVolume = quiet

 

{ lecturersVolume < deafening:

   ~ lecturersVolume++

       

   { lecturersVolume > murmurersVolume:

          ~ murmurersVolume++

          The murmuring gets louder.

   }

}

The values themselves can be printed using the usual {...} syntax, but this will print their name.

The lecturer's voice becomes {lecturersVolume}.

Multivalued Lists

The following examples have all included one deliberate untruth, which we'll now remove. Lists - and variables containing list values - do not hav


Поделиться с друзьями:

Автоматическое растормаживание колес: Тормозные устройства колес предназначены для уменьше­ния длины пробега и улучшения маневрирования ВС при...

Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

1.178 с.