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

  • сравнивая с одним псевдослучайным числом (которое тоже можно получать по разному [1]);
  • сравнивая с наибольшим или наименьшим из двух псевдослучайных чисел;
  • сравнивая среднее из двух и более случайных чисел (среднее тоже можно считать по разному).


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

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

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

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

Манипуляции с меткостью


Обычно, когда игроку сообщается, что меткость у персонажа равна 60%, то он воспринимает эту информацию как: «из 10 выстрелов я могу рассчитывать на 6 попаданий». И если из 10 попаданий он будет наблюдать 1 попадание вместо 6, то он почти наверняка посчитает, что случайность в игре поломана. Особенно сильно он в этом уверится, если такое случится на отрезке из 100 выстрелов. Чтобы избежать таких неприятностей, разработчики часто скрытно манипулируют с реальной вероятностью попадания (либо вообще отказываются от элемента случайности).
Например, в игре Fire Emblem: The Binding Blade для определения попадания с меткостью сравнивалось не одно случайное число, а среднее из двух случайных чисел [2].


Результат этой манипуляции таков, что меткие атаки (>50%) попадают значительно чаще, чем им полагается при равномерном распределении, а неметкие (<50%) попадают значительно реже. Вот как меняется это распределение вероятностей:


Для примера, при 10% показываемой меткости фактическая при этом является 1.9%. А при 75% фактическая будет 87.24%.

Но, если уж на то пошло, то менять меткость можно и многими другими способами.

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


Искажение в результате этого становится еще более разительным: меткость 10% становится 0.41%; меткость 75% становится 92.69%.

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


Тут 10% превращаются в 4.83%, а 75% в 88.18%. Но, что самое интересное, переход происходит при 30%, а не при 50%, как в среднем арифметическом. Поэтому если Вам хочется уж точно быть уверенными, что у игрока всегда будет преимущество, то достаточно удостовериться, что его минимальная меткость никогда не будет ниже 30%, а у врагов она будет настолько низкой довольно часто (для врагов можно использовать и среднее арифметическое, или что-то еще более искаженное).

Среднее геометрическое из 3 чисел приводит примерно к тем же результатам, но только с большим разбросом:


Тут 10% превращаются в 1.95%, а 75% в 93.98%.

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


В результате этого вероятность попадания меняется очень сильно. Например, при выбирании наименьшего числа меткость в 10% преобразуется в 19.02%, а меткость в 75% — в 93.75%.
Такой способ, например, используется в игре Neverwinter Nights в навыке «Скользкий разум», при котором в случае провала проверки на спасение производится повторная проверка на силу воли [3].

Neverwinter Nights — Pixie Warrior [4]

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


В прошлой статье про манипуляции с вероятностями был показан метод для очень редких событий, где со временем увеличивалась вероятность события:
Разработчик Carsten Germer использует функцию контролируемой случайности для редких и не только событий [5]. Например, чтобы гарантировать периодическое выпадение особо редкого бонуса с шансом 1 к 10000, он после каждого «промаха» увеличивает шансы по порядку: 1 к 9900; 1 к 9800; 1 к 9700… и так до фиксации события. А чтобы гарантировать отсутствие частых редкостей, он ввел дополнительную переменную, блокирующую срабатывание на 100% в течение 10 следующих проверок после прошлого срабатывания.
Заблуждения игроков при оценке рисков [1]

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


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

Ниже я привожу также изменение 1% события, что оно произойдёт хотя бы раз по мере роста числа попыток. Алгоритм искажения: первые 10 попыток — гарантированный промах. Затем с каждым промахом вероятность повышается на 1% (0,1% и 0,01%) до попадания. Потом всё с начала.


Впрочем, если ваша основная цель, как разработчика, гарантировать то, что пользователь в 1% событии не попадёт на серию из 1000 промахов подряд, то легче и прозрачнее будет после 200 промахов подряд выставлять 100% попадание. Это избавит вас от редких недоумений игроков и позволит оставить программный код лаконичнее и дружелюбнее для будущих доработок.

Разные способы начисления бонусов к меткости


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





Если в игре планируется множество значительных бонусов к меткости, то, чтобы не переваливать за гарантированные 100% меткости, можно вычислять окончательную меткость как серию дополнительных бросков (чаще всего маловероятных по отдельности). Например, при меткости 70% и бонусе 50% окончательная вероятность попадания будет равна 85%, как проверка двух последовательных бросков, когда достаточно хотя бы одного попадания.




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

// newAccuracy - новая меткость в %
// baseAccuracy - изначальная меткость в %
// bonusAccuracy - бонус к меткости в %
let newAccuracy = 100 - ( (1 - baseAccuracy * 0.01) / (1 + bonusAccuracy * 0.01) ) * 100;

Гарантирует, что всегда будет вероятность промаха при любом бонусе. Благодаря этому игра может давать бонусы к «меткости» больше 100%, хоть 1000%. И всё равно будет оставаться вероятность промаха. Но игрока это скорее может запутать, а, значит, и разочаровать.



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

Распределение урона на основе дайсов


Данный пункт целиком основан на наглядных данных публикации «Probability and Games: Damage Rolls from Red Blob Games [6]». Здесь я кратко представляю некоторые примеры и выводы этой публикации. В оригинальной статье Вы получите более детальные выводы и интерактивные графики распределений, а также интерактивно настраиваемые функции для подбора разных уникальных комбинаций.

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

1d12 — одним кубиком с 12 гранями


2d6 — двумя кубиками с 6 разными гранями


3d4 — тремя кубиками с 4 разными гранями


4d3 — четырьмя кубиками с 3 разными гранями


6d2 — шестью кубиками с 2 разными гранями


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

Заключение


Таким образом, были рассмотрены следующие темы:

  • манипуляции с меткостью с помощью разных способов её проверки;
  • разные способы начисления бонусов к меткости;
  • различное распределение урона на основе дайсов.

Каждый из способов может быть полезен разработчику. Для игр с более казуальной аудиторией чаще выбирают щадящие алгоритмы, в которых вероятность успеха игрока завышается, а чтобы поднять азарт создаётся иллюзия маловероятной победы (подробнее в одной из прошлых статей: [7]).

Список литературы


1. Заблуждения игроков при оценке рисков. Контроль генератора случайных чисел в разработке.
2. Fire Emblem Wiki — Random Number Generator.
3. Neverwinter Nights 2 — Class Abilities — Slippery Mind.
4. Neverwinter Nights — Pixie Warrior.
5. «Not So Random Randomness» in Game Design and Programming.
6. Probability and Games: Damage Rolls from Red Blob Games.
7. Генерация Close call в играх: «На волосок от поражения» или «Чуть-чуть не победил».

Комментарии (32)


  1. 411
    04.01.2019 14:54
    +2

    Помнится среднее из трёх случайных чисел высмеивалось как пример г-нокода с комментариями вроде «true random». А тут даже его применение описано.


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


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

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


    1. qnok Автор
      04.01.2019 15:01

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

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


    1. JediPhilosopher
      04.01.2019 15:12
      +1

      Вроде в HOMM 5 была такая тема (я в комментарии к прошлой статье уже писал про нее). Призраки имеют большой (вроде 50% или около того) шанс уклониться от атаки, поэтому убивать их бывает очень сложно. Но разработчики предусмотрели, что не может быть трех промахов подряд, после двух промахов третий удар гарантированно попадает. Игроки им пользуются — сперва бьют призраков множеством слабых отрядов, дожидаются двух промахов подряд, а затем вкатывают им гарантированный урон от своего основного отряда.


      1. Nekto_Habr
        04.01.2019 16:36

        Ага. Я поэтому очень удивился, когда промахнулся в третий раз однажды. Правда, на игре стояли моды, но таки это всё равно был единственный случай за долгие годы практики в HoMM V


    1. SandroSmith
      04.01.2019 16:22
      +2

      Ну что вы! True random это

      return(4); //chosen by fair dice roll
                 //guaranteed to be random


  1. ua30
    04.01.2019 15:34
    +1

    А казалось бы — да что тут сложного. На первый взгляд…

    Скажите, пожалуйста, а почему не используют, скажем, медиану от 5-10 рандомных значений? Это должно быть крайне близко к истине. И не сложно.


    1. qnok Автор
      04.01.2019 15:47

      Я не понял, какого результата Вы хотите достичь, применяя медиану для 10 значений?
      Случайное число ведь генерируется в диапазоне 1..100 (или больший диапазон, если надо избавиться от ошибок малых чисел).
      А при использовании медианы будет получена просто случайное число, которому «посчастливилось» выпасть больше 1 раза.
      Может я неправильно Вас понял.


      1. ua30
        04.01.2019 17:23
        +1

        https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D0%B0_(%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%81%D1%82%D0%B8%D0%BA%D0%B0)

        Попробовал набросать немного кода. После 20 минут понял что первый абзац в моем комментарии выше надо умножить еще на 10. Как минимум. :)

        Потом начал думать что рандом не так уж и плохо. При шансе 60% попасть 1 раз из 10 можно. Кто знаком с теорией вероятностей понимает, что это нормально. Всякое бывает. Конечно, не уверен что для игры это хорошо.

        Накидал просто скрипт на PHP. Поставил в конфиге 1000 подходов по 10 повторений, вероятность попасть 60%, результаты:

        Array
        (
        [1] => 1
        [2] => 13
        [3] => 30
        [9] => 45
        [4] => 99
        [8] => 121
        [5] => 202
        [7] => 231
        [6] => 258
        )
        9

        Указано: [попаданий из 10 раз] => количество событий (из 1000 подходов), в конце — это максимум попаданий подряд (т.е. был случай, или несколько, когда в подходе из 10 ударов при вероятности попасть 60% было 9 попаданий подряд).

        В принципе, не все так плохо с чистым рандомом. Мы видим что диапазон 5-7 попаданий занимает: ((202 + 231 + 258) / 1000) * 100 = 69,1% всех случаев. 8 и 4 попаданий случаются примерно в 1 из 10 подходов по 10 ударов. Т.е. достаточно редко. Бывает и круто повезло (8), и не твой день (4). 9 раз удалось попасть лишь в 4,5% случаев. 10 раз ни разу. 2 раза — 1,3% и 1 раз попали в 1 из 1000 подходов по 10 ударов.

        А вот то же самое для 15%:

        Array
        (
        [5] => 3
        [4] => 55
        [3] => 134
        [0] => 185
        [2] => 286
        [1] => 337
        )
        3

        Больше 5 раз попасть не удалось, и то это случилось лишь в 3 из 1000 подходов. 95% лежат в пределах от 0 до 3 попаданий.

        Конечно, математика математикой, но тут надо брать реальную игру и проводить плей тесты. Я вполне согласен с тем, что при вероятности попасть 60% игроки будут ожидать попасть 6 раз из 10 ударов. А это, судя по всему, красиво сделать можно только проводя коррекцию новых значений на основе данных от предыдущих попыток. Впрочем, и чистый рандом, когда игрок иногда может попасть очень много или очень мало, тоже выглядит весьма интересно. Как по мне.


  1. amarao
    04.01.2019 15:38
    +2

    Мне нравится интуитивный подход к гарантированной вероятности. (60% = 6 из 10).

    Я бы реализовывал (в контексте «из 100») таким образом: вероятности генерируются заранее (последовательность рандома), как 60 «да» и 40 «нет», а дальше эта последовательность shuffle. Каждый следующий берёт элемент из этого пула. Спустя 100 ходов будет точно 60 «да» и точно 40 «нет». В принципе, для уменьшения предсказуемости, можно менять размер пула (100, 200, 300) случайным образом. Но всё время поддерживать рандомную справедливость на ограниченном окне бросков.


    1. qnok Автор
      04.01.2019 15:48

      Тоже хороший вариант. Не для всех случаев, но для логических/тактических может быть хорошим решением.


      1. amarao
        04.01.2019 15:51

        А вы его не можете проанализировать? Насколько оно от реального рандома будет отклоняться?


        1. qnok Автор
          04.01.2019 19:17

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


    1. vassabi
      04.01.2019 15:58

      … надо посмотреть, какая вероятность худшего случая, т.е. когда на предыдущей генерации было «все 60 да вначале пула», а на следующей «все 60 в конце пула» и образовалась «долина невезения» из «80 ходов нет».


  1. vassabi
    04.01.2019 16:03
    +1

    посмотрел на графики — чем-то на индекс Джини из статьи про "теорию счастья" походит.
    Только там вероятности событий нельзя напрямую в генераторе «подкрутить»…


  1. Xaliuss
    04.01.2019 17:36
    +2

    Когда говорят о подкрученной вероятности надо упомянуть пример псевдослучайности в таких играх, как Warcraft 3, League of Legend, Dota 2 и других, где итоговая вероятность после изменений остаётся неизменной, хотя на самом деле вероятность получить большие серии успехов/неудач заметно меньше, чем при полной случайности. Здесь описание реализации в Доте (вероятность успеха после успеха заметно меньше, чем заявлено, и после каждой неудачи растет, обычно линейно, пока не достигает 1). В большинстве методов из статьи этот подход можно тоже применить, решая уравнение, так что доля успехов соответствовала названной вероятности. Это особенно важно для онлайн игр, так как там серия критов/уворотов может решить исход схватки.


    1. 411
      04.01.2019 19:21
      +1

      Я бы не назвал вариант из доты хорошим. Судя по таблице работает неплохо это только для первых 20%, а дальше накапливается ошибка не в пользу игрока. Ещё одна вещь, которая не нравится, что наиболее вероятное N не всегда равно 1 / P. Хотя сама идея хорошая, просто надо лучше подстроить формулу.

      И насколько мне известно в Warcraft 3 такой механики не было.


      1. Xaliuss
        04.01.2019 21:38

        Просто считать надо точнее. Не знаю почему в доте так (погрешность для больших вероятностей), но там ссылку легче искать. В Warcraft 3 это работало для критов, уворотов и оглушения и подобных способностей, эта задача была решена для всех вероятностей кратных 5% (то есть все числа были уже в движке). Это кстати вызывало любопытную особенность — в редакторе можно было ставить вероятность кратную 1%, но она округлялась до ближайших 5%, и соответственно 1% становился 0… Конечно можно было использовать скрипты со своей реализацией случайности, но всё в игре работало по такому правилу, все разные источники работали независимо друг от друга. А в игре 1% от 0 легко не отличить. Эта механика перешла и в другие игры. И одно из развлечений в таких играх — проверка работы РНГ.


        1. 411
          04.01.2019 00:09

          Вспомнил про округление до кратных 5, точно, спасибо, память подвела. Её не было при использовании скриптованных способностей, что логично. А в стандартных была.


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


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


          1. DracoL1ch
            04.01.2019 10:51

            В доту2 их перенесли с изменениями, исправив искривление после 20%. Считается, что искривление было вызвано тем, что в варкрафте была всего пара абилок с вероятностью выше 20%, поэтому разработчики не запаривались с точностью более высоких шансов, создав гигантское расхождение на высоких значениях.


        1. DracoL1ch
          04.01.2019 10:49

          В ноль не округлялось, бралось как 5%. В древних версиях доты был хаос кнайт с 1% шанса крита на 11х, и оно работало. Не думаю, что тогда были подкованные знатоки, которые бы подсказали вписать в подсказку 1%, а в данные 5%.


          1. Xaliuss
            04.01.2019 16:53
            +1

            В Вики написано, что не работало. Я уже застал время (версии с 6.32 видел), когда все эти вероятности были кратными 5%, картоделы уже знали об этой фиче. Абсолютно уверенным быть не могу, не тестировал.


            1. DracoL1ch
              04.01.2019 17:13

              Верно, моя ошибка. Глянул еще раз на алгоритм, там не было защиты от подобного. Брался шанс и
              floor(chance * 0.2 + 0.5)
              для поиска по таблице вероятностей, к 5% попадали только шансы 2.5% и выше


  1. ptyrss
    04.01.2019 19:18
    +1

    Мне нравится такой вариант (используется в некоторых играх). Такой метод гарантирует стабильность (если попадание 80% то из 10 ударов 8 попадут).

    Предположим у нас вероятность попадания 40%. В начале мы генерируем число [0,100).
    При каждой проверке при прибавляем к числу вероятность попадания. Если число больше 100, то мы попали, вычитаем 100. Число 100 взято для целочисленных процентов, если проценты дробные, то вы поняли. Можно и дробное [0,1) генерировать.

    Пример:
    0. начальное значение — 50.
    1. 50+40 = 90 < 100 -> промах
    2. 90+40=130 > 100 -> попадание -> число 30
    3. 30+40 = 70 < 100 -> промах
    4. 70+40=110 > 100 -> попадание -> число 10.

    Таким образом у нас при вероятности в 40% никогда не будет 2 попаданий подряд, и при этом из 3 хотя бы 1 попадёт.


    1. qnok Автор
      04.01.2019 19:19

      В этом есть смысл, но

      Таким образом у нас при вероятности в 40% никогда не будет 2 попаданий подряд

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


    1. 411
      04.01.2019 19:40
      +1

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


      1. ptyrss
        04.01.2019 20:01
        +1

        Согласен, но ничего не злит больше, чем получение 2-3 ударов подряд при 50% уклонения. Из-за этого, при наличии условных вариантов увеличить уклонение и увеличить защиту, уклонение слишком большой риск. Подобная равномерность позволяет сбалансировать. Безусловно это подойдёт не любой игре. (механика используется в PoE если интересно).


        1. 411
          04.01.2019 20:22
          +1

          Согласен, эмоции это вызывает негативные, но нужно понимать, когда какая механика эффективнее.

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

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

          В общем случае они должны быть равны при прочих равных.

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


        1. AbstractGaze
          06.01.2019 14:53

          Злит не получение 2-3 ударов, злит то что большинство разработчиков скрывают формулы, и не дают игроку четко понять правила игры, в данном случае как работает точность или уклонение.
          Если бы игроку давали возможность понять как идет расчет — игрок бы уже сам принимал решение что ему делать, а обычно дают кота в мешке. А пое… в котором одна и та же механика не работает в однотипных ситуациях — это вообще убожество. В данной игре уже давно исключений больше чем правил.


          1. ptyrss
            06.01.2019 15:57

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


  1. mspain
    04.01.2019 11:18
    -2

    Какие-то вероятности без учета расстояния это несусветно. Даже во времена UFO1 выглядело как «результат г-кода».


    1. ua30
      04.01.2019 12:39
      +1

      Не путайте мух с котлетами. Расстояние — один из множества факторов, который может влиять на меткость. Еще это может быть усталость, окружающая обстановка, состояние экипировки, навыки персонажа, умения противника, и еще миллион с тележкой факторов.

      Речь не об определении числового % вероятности попасть. А о реализации алгоритма расчета самого события на основе этой вероятности. Или даже цепочки таких событий.


  1. chirkin
    04.01.2019 13:36

    Так мило смотреть, как в сообществе разработчиков витает идея, которая вот-вот должна сформироваться в последовательную научную теорию :)


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