StarCraft: Триггеры: рандомизация

» Раздел: 1. История

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

Так как же моделируется случайность? Существует по крайней мере три различных подхода к созданию случайных событий, основанных на разных идеях, вот они:
  • Рандомизация с использованием хаотически двигающихся юнитов.
  • Рандомизация на основе случайных событий активирующихся игроками.
  • Рандомизация при помощи переключателей (Switches).Разберём подробнее каждый из подходов. Как обычно, сначала рассмотрим самые простые из них.

Рандомизация хаотическим движением.

Способ с использованием скрипта Junkyard Dog.


Вероятно, самый простой способ рандомизации, который основан на том, что юниты с применённым к ним скриптом Junkyard Dog (или Криттеры 9-12 Игроков, к которым по умолчанию применяется этот скрипт) двигаются непредсказуемым способом.
Мы выделим замкнутое пространство, куда поместим нейтрального Rhynadon'а. Далее полностью покроем всё это пространство несколькими локациями (в зависимости от того сколько случайных исходов мы хотим получить - в нашем примере их три) и подождем некоторое время. По истечении этого времени условием Bring проверим, на какой из локаций оказался наш Rhynadon и, в зависимости от этого, выполним необходимое действие.

 
**!!Conditions:!!**
 Elapsed scenario time is at least 10 game seconds.
 Player 12 brings at least 1 Rhynadon to '**Outcome 1**'.
**!!Actions:!!**
 Display text message for current player: 'Outcome 1'.
 Remove all Rhynadon for Player 12 at location 'Outcome 1'.
 
**!!Conditions:!!**
 Elapsed scenario time is at least 10 game seconds.
 Player 12 brings at least 1 Rhynadon to '**Outcome 2**'.
**!!Actions:!!**
 Display text message for current player: 'Outcome 2'.
 Remove all Rhynadon for Player 12 at location 'Outcome 2'.
 
**!!Conditions:!!**
 Elapsed scenario time is at least 10 game seconds.
 Player 12 brings at least 1 Rhynadon to '**Outcome 3**'.
**!!Actions:!!**
 Display text message for current player: 'Outcome 3'.
 Remove all Rhynadon for Player 12 at location 'Outcome 3'.

Способ с использованием "толкающихся" юнитов.


В некоторых картах используется другой метод. Выделяется специальное место, где на одной стороне располагаются различные юниты игрока (или юниты различных игроков). Всем этим юнитам отдается приказ двигаться в направлении другого конца комнаты. Посреди создается толпа нейтральных юнитов, патрулирующих вокруг и мешающих продвижению основных. Тот юнит (или игрок, во втором случае), который дошел первым до финиша активирует случайное событие. Я не буду приводить здесь триггеров в виду их простоты. Скажу лишь, что этот метод может быть удобен в случае, когда надо случайно выбрать одного игрока из нескольких (как в картах Phantom или The Thing например), и заранее неизвестно сколько игроков участвует в игре. При использовании данного способа их юниты просто не появятся в начале игры и, следовательно, эти игроки автоматически не смогут быть выбраны, тогда как при использовании других методов надо как-то контролировать такую ситуацию. Результата можно было бы добиться и другим способом: в замкнутом пространстве одновременно откопать большое число зерговских юнитов и посмотреть, куда в конце концов попадет выбранный юнит.

Достоинства:
+ Простота.
Недостатки:
  • Необходимо ждать заметное время.
  • Абсолютная случайнось реализации каждого события находится под вопросом.

Рандомизация на основе случайных событий.


В этом случае мы используем игрока для активации случайностей. При использовании этого подхода крайне важно что бы игрок не имел возможности контролировать выбор. Этого можно добиться следующим способом. Нам понадобится счётчик, в качестве которого можно использовать Deaths, Score и даже Ресурсы, однако использование Deaths более желательно. Мы будем постоянно изменять значения счётчика от единицы до n, где n - это количество случайных исходов, которое мы хотим получить. Когда нам нужна случайность, мы просто смотрим на значение этого счетчика.

Рассмотрим сказанное на примере. Допустим мы хотим сделать случайный респаун при гибели главного героя. В нашем примере в качестве героя будет выступать Марин, и будет три места для респауна. Соответственно будем использовать счетчик Deaths (моего любимого :) ) Rhynadon'а, который будет постоянно меняться от единицы до трех. Когда Марин погибает - смотрим на счетчик и респауним нового Марина на соответствующей локации. Нам понадобится пять триггеров: первые два - для добавления единицы к значению счетчика и сбрасыванию его значения, а остальные три - для респаунов.

 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Modify death counts for Current Player: **Add 1** for Rhynadon.
 Preserve Trigger.
 
**!!Conditions:!!**
 Current Player has suffered At Least 4 deaths of Rhynadon.
**!!Actions:!!**
 Modify death counts for Current Player: **Set to 1** for Rhynadon.
 Preserve Trigger.
 
**!!Conditions:!!**
 Current Player commands Exactly 0 Terran Marine.
 Current Player has suffered **Exactly 1** deaths of Rhynadon.
**!!Actions:!!**
 Create 1 Terran Marine at '**Respawn 1**' for Current Player.
 Preserve Trigger.
 
**!!Conditions:!!**
 Current Player commands Exactly 0 Terran Marine.
 Current Player has suffered **Exactly 2** deaths of Rhynadon.
**!!Actions:!!**
 Create 1 Terran Marine at '**Respawn 2**' for Current Player.
 Preserve Trigger.
 
**!!Conditions:!!**
 Current Player commands Exactly 0 Terran Marine.
 Current Player has suffered **Exactly 3** deaths of Rhynadon.
**!!Actions:!!**
 Create 1 Terran Marine at '**Respawn 3**' for Current Player.
 Preserve Trigger.

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

Для того, что бы все это работало, необходима случайность того события, которое активирует игрок (в нашем примере время смерти героя не предсказуемо). Игрок не видит значения счетчика и поэтому не может контролировать исход события, а при использовании Гипертриггеров, если бы даже и видел, то ему было бы очень сложно подгадать конкретное значение счётчика.

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

Этот способ может быть полезен для:
  • Выбрасывания контролируемых игроком игральных костей.
  • Систем урона в пошаговых сражениях.
  • Получения случайных Вещей/Ресурсов/Юнитов/Подсказок.
  • Случайных респаунов после смерти.
Тем не менее, с этим способом возникают проблемы при наличии большого числа случайных исходов, потому что чем больше исходов тем больше времени необходимо на то, что бы полностью "прокрутить" счётчик от 0 до n. Если придется получить два случайных исхода в течении двух секунд, и количество исходов больше чем 24 (с использованием Гипертриггеров триггреы выполняются 12 раз в секунду), то вы никогда не получите два одинаковых исхода.

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

Достоинства:
+ Не требуется так много условий в триггерах и рутинной работы, как при использовании переключателей.
Недостатки:
  • Требуется случайность инициирующего события.
  • Проблемы при необходимости бысто получать случайные исходы при большом их количестве.

Рандомизация на основе переключателей.


Для начала разберемся, что же такое переключатель (Switch)? Переключатель - это флаг или индикатор, который может находится в двух состояниях - Сброшенном (Cleared) и Установленном (Set). Всего в игре 256 переключателей, каждый из которых устанавливается в сброшенное состояние в начале игры. В условиях триггеров можно проверять, а в действиях устанавливать состояния переключателей.

При рандомизации на основе переключателей используется тот факт, что можно устанавливать состояние переключателей случайным образом при помощи действия 'Set Switch'.

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

 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 **Randomize** 'Switch 01'.
 
**!!Conditions:!!**
 Elapsed scenario time is at least 60 game seconds.
 'Switch 01' is **Cleared**.
**!!Actions:!!**
 Create 1 '**Terran Marine**' at 'Location' for current player.
 
**!!Conditions:!!**
 Elapsed scenario time is at least 60 game seconds.
 'Switch 01' is **Set**.
**!!Actions:!!**
 Create 1 '**Terran Firebat**' at 'Location' for current player.

К сожалению, мир не идеален и не часто происходят случайные события имеющие только два варианта случающиеся с одинаковым успехом. Здесь нам придёт на помощь комбинирование нескольких переключателей. Пусть мы используем два переключателя. Если взглянуть на них вместе, то они могут находиться в четырёх состояниях (Первый/Второй): Сброшен/Сброшен, Установлен/Сброшен, Сброшен/Установлен и Установлен/Установлен.

Пусть теперь мы делаем Мэднесс карту, где нам нужен респаун Гидралисков и Маринеров в 25% и Зилотов в 50% случаев. Для того, что бы получить Зилотов в 50% случаев мы объединим два состояния и будем создавать их в двух триггерах, отвечающих разным состояниям. Как и в предыдущем случае, нам понадобится один триггер для постоянной рандомизации переключателей и ещё четыре для каждого возможного состояния.

 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Randomize 'Random 1'.
 Randomize 'Random 2'.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random 1' is **Cleared**.
 'Random 2' is **Cleared**.
**!!Actions:!!**
 Create 1 '**Zerg Hydralisk**' at 'Spawn' for current player.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random 1' is **Set**.
 'Random 2' is **Cleared**.
**!!Actions:!!**
 Create 1 '**Terran Marine**' at 'Spawn' for current player.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random 1' is **Cleared**.
 'Random 2' is **Set**.
**!!Actions:!!**
 Create 1 '**Protoss Zealot**' at 'Spawn' for current player.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random 1' is **Set**.
 'Random 2' is **Set**.
**!!Actions:!!**
 Create 1 '**Protoss Zealot**' at 'Spawn' for current player.
 Preserve Trigger.

Наше решение не оптимально и обладает следующим плохим свойством: действия создающие Зилота дублируются в нескольких различных триггерах. Если нам потом захочется поменять Зилота на Зерглинга, то придется менять несколько триггеров и появится возможность случайно ошибится в одном из них. Заметим, что в случае, когда переключатель 'Random 2' установлен, переключатель 'Random 1' уже не влияет на результат. Таким образом мы можем заменить два триггера, создающие Зилотов, одним, вообще не проверяя значение первого переключателя 'Random 1':

 
**!!Conditions:!!**
 'Random 2' is **Set**.
**!!Actions:!!**
 Create 1 '**Protoss Zealot**' at 'Spawn' for current player.
 Preserve Trigger.

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

(**количество_исходов**)=2^(**количество_переключателей**).


В самом деле, для одного переключателя это - два исхода. Если некоторый набор переключателей даёт нам х исходов, то добавление ещё одного переключателя даст нам исходов, т.к. каждый из двух исходов добавляемого переключателя можно комбинировать со всеми х исходами переключателей исходного набора. Для двух переключателей получаем 2*2=4 исхода, для трёх - 4*2=8, для четырёх - 8*2=16 и так далее.

Получается, что нет такого набора переключателей, который дал бы в точности пятнадцать исходов. Хочу успокоить - это ещё не повод для расстройств. Если количество желаемых исходов не является точной степенью двойки, то выберайте следующую степень, которая полностью покроет это количество. В нашем случае - четыре переключателя с 16 (=2^4) исходами.

Следующий вопрос, который Вы спросите у себя: "Как определить все возможные комбинации?". Что бы упростить процесс, будем использовать двоичную запись чисел: каждый конкретный набор состояний переключателей будем кодировать числом, в котором за каждый переключатель отвечает свой разряд (переключатели в записи числа нумеруются справа налево, т.е. младшие разряды отвечают переключателям с меньшим номером), Единица означает, что переключатель Установлен, а Ноль, что Сброшен. Например, число 110 означает, что первый переключатель Сброшен, а второй и третий Установлены.

Таким образом запишем все варианты комбинации двух переключателей:

 
00
01
10
11

Из трех:

 
000
001
010
011
100
101
110
111

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

 
**0**000
**0**001
**0**010
**0**011
**0**100
**0**101
**0**110
**0**111
**1**000
**1**001
**1**010
**1**011
**1**100
**1**101
**1**110
**1**111

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

После того, как мы поняли, сколько понадобится переключателей и каковы все их комбинации, мы готовы написать триггеры. Как обычно, нам понадобится Рандомизатор - триггер, где будут рандомизироваться состояния всех четырёх переключателей. Проблема в том, что нам хотелось бы получить пятнадцать исходов, а не шеснадцать, однако мы не можем просто взять и не обращать внимания на одну из комбинаций переключателей, так как всегда есть возможность, что выпадет именно она. В этом случае, на место этой неиспользуемой комбинации мы помещаем заглушку Перерандомизатор - триггер который будет перерандомизировать значения переключателей. В этом случае мы говорим начать процесс рандомизации заново.

 
**!!Conditions:!!** // **Рандомизатор**
 Always
**!!Actions:!!**
 Randomize 'Random 1'.
 Randomize 'Random 2'.
 Randomize 'Random 3'.
 Randomize 'Random 4'.
 
**!!Conditions:!!**  // **Перерандомизатор**
 'Random 1' is **Cleared**.
 'Random 2' is **Cleared**.
 'Random 3' is **Cleared**.
 'Random 4' is **Cleared**.
**!!Actions:!!**
 Randomize 'Random 1'.
 Randomize 'Random 2'.
 Randomize 'Random 3'.
 Randomize 'Random 4'.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random 1' is **Set**.
 'Random 2' is **Cleared**.
 'Random 3' is **Cleared**.
 'Random 4' is **Cleared**.
**!!Actions:!!**
 Display text message for current player: 'Как называется апгрейд на скорость ультралиска?'.
 
// Далее идут триггеры, реализующие все оставшиеся 14 вопросов
...


Заметим, что Перерандомизатор несколько отличается от остальных триггеров. Вопервых, Вы должны сделать его повторяющимся (Preserve Trigger), так как даже независимо от того, что вы используете рандомизацию только один раз, есть возможность получить одинаковые результаты дважды подряд. Вовторых, так как триггеры выполняются сверху вниз, то в целях производительности Перерандомизатор лучше всего поставить перед всеми триггерами, реализующими остальные исходы: если Перерандомизатор запустится, то в большинстве случаев не придётся ждать следующего такта выполнения триггеров, потому что все исходы (кроме него самого) проверятся прямо за ним.
**
=== Построение триггеров в общем случае. ===
**
Рассмотрим нашу задачу в самом общем виде. Пусть надо получить t исходов с заданными вероятностями р1=m1/n1, р2=m2/n2, ... , рt=mt/nt (p1+p2+...+pt=1). Если доказательство правильности работы алгоритма построения триггеров (а ведь это и в самом деле алгоритм, позволяющий в любом случае получить требуемый результат) вызовет затруднения - пропустите его.

Предлагаю следующий алгоритм построения триггеров. Выберем наименьшее общее кратное (НОК) знаменателей n1, n2, ... , nt:

**НОК**(**n1**, **n2**, ... , **nt**) = **q**.

Количество переключателей s выберем равным наименьшей степени двойки такой, что q <= 2^s. Поставим r = 2^s - q комбинаций переключателей на перерандомизацию. Из оставшихся q комбинаций выделим m1(q/n1) под первое событие, m1(q/n2) - под второе, и так далее. Под последнее событие выделим mt(q/nt) комбинаций (заметим, что q делится нацело на все ni, как их НОК). Так как

**p1** + **p2** + ... + **pt** = 1,

то

**m1**(**q**/**n1**) + **m2**(**q**/**n2**) + ... + **mt**(**q**/**nt**) = **q**

и мы полностью исчерпаем все q комбинаций.

Доказательство правильности работы построенных триггеров.

Рассмотрим i-ый исход. Докажем, что вероятность появления i-ого события pi равна mi/ni. Рассмотрим два случая:
  1. r=2^s-q=0 (т.е. q=2^s) и Перерандомизаторов нет. Вероятность i-ого исхода равна количеству комбинаций благоприятствующих наступлению события разделенных на количество всех комбинаций:

**pi** = (**mi**(**q**/**ni**)) / (2^**s**) = (**mi**(**q**/**ni**)) / **q** = **mi**/**ni**.

  1. r не равно 0. Вероятность i-ого исхода складывается из просто вероятности возникновения одной из mi(q/ni) благоприятных комбинаций, вероятности возникновения одной перерандомизации и, после этого выпадения одной из mi(q/ni) благоприятных комбинаций, вероятности возникновения двух перерандомизаций и, выпадения одной из mi(q/ni) благоприятных комбинаций, и так далее:

**pi** = (**mi**(**q**/**ni**)/2^**s**) + (**r**/(2^**s**))(**mi**(**q**/**ni**)/2^**s**) + (**r**/(2^**s**))^2*(**mi**(**q**/**ni**)/2^**s**) + ...

(**mi**(**q**/**ni**)/2^**s**)*[**Сумма_по_k_от_0_до_бесконечности**]((**r**/2^**s**)^**k**).

Вычисляя сумму бесконечной геометрической прогрессии со знаменателем r/2^s = 1 - q/2^s < 1, получим:

**pi** = (**mi**(**q**/**ni**)/2^**s**)(1/(1-(1-**q**/2^**s**))) = (**mi**(**q**/**ni**)/2^**s**)(2^**s**/**q**) = **mi**/**ni**.

Что и требовалось.

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

Достоинства:
+ Возможность задания заранее заданного распределения вероятностей для всех событий.
Недостатки:
  • Много рутинной работы.
  • Иногда время на перерерандомизацию может оказаться заметно, особенно если много комбинаций занято под перерандомизацию. Здесь может быть интересна следующая формула, вычисляющая вероятность прохождения рандомизации p с первого раза:

**p** = (2^**n** - **r**)(2^**n** + 1)^**r** / 2^(**n**(**r**+1)),

где n - это количество используемых переключателей, r - количество Перерандомизаторов (0<=r<=2^n). Например для n=2, r=1 она даёт р=15/16, что приблизительно равно 0.94, а для n=4, r=7 приблизительно даёт p=0.86; в часности, если перерандомизаторов нет (r=0), то формула для любого n даёт p=1, а если число перерандомизаторов равно количеству всевозможных наборов (r=2^n), то p=0.

!!**Вариации на тему рандомизации: рандомизация на основе смешанных методов.**!!

========

**Счётчик и разная вероятность наступления событий.**


Разбирая рандомизацию с использованием счётчика, мы рассматривали только лишь равновозможные исходы. Если надо сделать некоторые исходы более вероятными, то просто отведите под них не одно, а несколько значений счётчика по аналогии с переключателями, где мы отводили под одно событие несколько их комбинаций. Для того, что бы этого добиться вовсе не обязательно дублировать триггеры для разных значений счётчика, достаточно использовать условия At Least и At Most для задания диапазонов. Вы можете задавать пересекающиеся диапазоны тем самым задавая вероятность их совместного выпадения.

**Счётчик плюс переключатель.**


У метода рандомизации с использованием одних только счётчиков есть один существенный недостаток: они не могут использоваться при наступлении периодически повторяющихся событий. Этот недостаток можно устранить, прокручивая счётчик случайным образом (так называемый Случайный счётчик). Будем постоянно рандомизировать переключатель и добавлять к счётчику единицу, если переключатель Установлен и двойку - если Сброшен. Это позволит счётчику прокручиваться быстрее и сделает его непредсказуемым, позволяя использовать его для периодических событий в противоположенность тем для которых требуется "ручная" активация.

 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Randomize 'Random'.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random' is **Set**.
**!!Actions:!!**
 Modify death counts for Current Player: **Add 1** for Rhynadon.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random' is **Cleared**.
**!!Actions:!!**
 Modify death counts for Current Player: **Add 2** for Rhynadon.
 Preserve Trigger.

Случайное время ожидания.


Используя счётчик и переключатель, как в предыдущем примере, можно задавать случайные временные интервалы, прокручивая его от нуля до некоторого значения. Пусть Вам захотелось сделать спаун предмета через случайное время: в среднем через минуту. Счётчик из предыдущего примера будет прокручиваться в среднем на 0.5*1+0.5*2=1.5 единицы каждый такт выполнения триггеров. Каждую секунду исполняется 12 тактов триггеров. Тогда в качестве верхней границы надо взять 60*12*1.5=540 единиц.

 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Randomize 'Random'.
 Preserve Trigger.
 
**!!Conditions:!!**
 Player 8 brings exactly 0 Data Disk to 'Item'.
 'Random' is **Set**.
**!!Actions:!!**
 Modify death counts for Current Player: **Add 1** for Rhynadon.
 Preserve Trigger.
 
**!!Conditions:!!**
 Player 8 brings exactly 0 Data Disk to 'Item'.
 'Random' is **Cleared**.
**!!Actions:!!**
 Modify death counts for Current Player: **Add 2** for Rhynadon.
 Preserve Trigger.
 
**!!Conditions:!!**
 Current Player has suffered **At Least 540** deaths of Rhynadon.
**!!Actions:!!**
 Create 1 Data Disk for Player 8 at 'Item'.
 Modify death counts for Current Player: **Set to 0** for Rhynadon.
 Preserve Trigger.

Можно не только прибавлять, но и отнимать значения счетчика. Например, если поставить вместо значений 1 и 2 величины -10 и 13, то счётчик по прежнему будет прокручиваться со средней скоростью -10*0.5+13*0.5=6.5-5=1.5. Однако в этом случае он будет набираться рывками, то далеко прорываясь вперёд, то откатываясь назад, и возможны редкие, но большие отклонения от среднего значения времени ожидания.

Несколько счётчиков.


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

Удобный способ работы с переключателями.


Здесь мы получим случайное число от нуля до 2^n-1. Такой способ получения чисел может быть использован для более удобной работы с переключателями, за счёт использования условий At Most и At Least и работы с привычными нам десятичными числами, а не наборами переключателей. Кроме того, этот способ даёт возможность получать свои собственные случайные числа каждому из игроков за счёт того, что у каждого игрока есть свой собственный индивидуальный счётчик, в противоположенность способу с одними только переключателями, где все они едины для всех игроков.

 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Randomize 'Random 1'.
 Randomize 'Random 2'.
 ...
 Randomize 'Random n'.
 Preserve Trigger.
 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Modify death counts for Current Player: **Set to 0** for Rhynadon.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random 1' is **Set**.
**!!Actions:!!**
 Modify death counts for Current Player: **Add 1** for Rhynadon.
 Preserve Trigger.
 
**!!Conditions:!!**
 'Random 2' is **Set**.
**!!Actions:!!**
 Modify death counts for Current Player: **Add 2** for Rhynadon.
 Preserve Trigger.
 
...
 
**!!Conditions:!!**
 'Random n' is **Set**.
**!!Actions:!!**
 Modify death counts for Current Player: **Add 2^(n-1)** for Rhynadon.
 Preserve Trigger.


После выполнения этих триггеров в счётчике смертей для Rhynadon'а будет получено случайное число от нуля до 2^n-1. Фактически мы как бы расшифровываем число, записанное в двоичной системе счисления при помощи переключателей. Все сказанное в разделе рандомизации при помощи переключателей справедливо и теперь, но далее вместо наборов переключателей можно просто использовать значения счётчика, что, согласитесь, намного удобнее. Кроме того мы могли бы вообще обойтись одним переключателем, рандомизируя и сразу же проверяя его состояние, а потом опять рандомизируюя в пределах одного игорока (см. следующий пример).

Рандомизация старт локаций.


Напоследок, хочу привести, придуманный мной, способ удобной рандомизации старт локаций. Пусть мы делаем что-то на подобие Melee карты для четырёх игроков и хотим случайно расставить игроков по стартовым позициям. При последовательной расстановке игроков может возникнуть такая проблема: что если место на которое мы хотим поместить очередного игрока уже занято? В этом случае понадобится перерандомизация. Однако при размещении четвертого игрока лишь одно место будет свободно и процесс расстановки может затянутся.

Я предлагаю другой метод, использующий гораздо меньше триггеров. Мы не будем размещать игроков по локациям, а жестко закрепим каждую локацию за каждым игроком, а вместо этого мы будем перемещать локации, причём не просто перемещать, а менять их местами. Для этого нам понадобиться вспомогательная локация (вспомните, как мы меняем местами переменные), которую мы назовём 'Tmp'. Нам так же понадобиться переключатель, который мы назовём 'Random'. Идея очень простая: что бы получить всевозможные комбинации стартовых позиций будем поочереди менять их местами с вероятностью 50% - сначала первый со вторым 1-2, потом второй с третим 2-3, затем третий с четвёртым 3-4 и, наконец, четвёртый с первым 4-1. Таким образом каждая локация может появиться на любом месте.

 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Randomize 'Random'.
 
**!!Conditions:!!**
 'Random' is Set.
**!!Actions:!!**
 Move location 'Tmp' on Not Exist at 'Start Location 1'.
 Move location 'Start Location 1' on Not Exist at 'Start Location 2'.
 Move location 'Start Location 2' on Not Exist at 'Tmp'.
 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Randomize 'Random'.
 
**!!Conditions:!!**
 'Random' is Set.
**!!Actions:!!**
 Move location 'Tmp' on Not Exist at 'Start Location 2'.
 Move location 'Start Location 2' on Not Exist at 'Start Location 3'.
 Move location 'Start Location 3' on Not Exist at 'Tmp'.
 
**!!Conditions:!!**
 Always
**!!Actions:!!**
 Randomize 'Random'.
 
**!!Conditions:!!**
 'Random' is Set.
**!!Actions:!!**
 Move location 'Tmp' on Not Exist at 'Start Location 3'.
 Move location 'Start Location 3' on Not Exist at 'Start Location 4'.
 Move location 'Start Location 4' on Not Exist at 'Tmp'.
 
**!!Conditions:!!**
 Always
**!!Actions:!!** 
 Randomize 'Random'.
 
**!!Conditions:!!**
 'Random' is Set.
**!!Actions:!!**
 Move location 'Tmp' on Not Exist at 'Start Location 4'.
 Move location 'Start Location 4' on Not Exist at 'Start Location 1'.
 Move location 'Start Location 1' on Not Exist at 'Tmp'.


Юнит Not Exist не должен находиться ни на одной из стартовых локаций, что бы они центровались точно одна по другой. Вся прелесть этого способа в том, что он может сработать в первый же такт выполнения триггеров, не требует перерандомизации и содержит намного меньше триггеров, чем если бы мы расставляли игроков один за другим. Таким образом рандомизация стартовых локаций абслютно моментальна, и игроки даже не заметили бы этого, если бы Fog of War от созданных или переданных юнитов включался мгновенно.

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

Мои добавления и исправления к переводу иностранных источников.

Просмотров: 7 791

Knight55 #1 - 12 лет назад 1
Как сделать управление героем с помощью клавиш.
Worm #2 - 12 лет назад 1
Если ты имеешь в виду управление при помощи горячих клавиш постройки юнитов (скажем из барака), то тебе понадобятся сетки. А вообще вопрос не по теме, создай топик и я отвечу более подробно.
TERAN #3 - 12 лет назад 0
Отличная статья, респект автору!
Aspid #4 - 12 лет назад 0
байан...но в новом оригинальном варианте...:)