[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjog" } } }, { "id": 10, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "create", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-250597-0", "render_to": "inpage_VI-250597-0-549065259", "adfox_url": "//ads.adfox.ru/228129/getCode?p1=bxeub&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid21=&puid22=&puid31=&puid32=&fmt=1&pr=" } } ]
{ "author_name": "Глеб Диденко", "author_type": "self", "tags": ["\u0433\u0435\u0439\u043c\u0434\u0438\u0437\u0430\u0439\u043d"], "comments": 17, "likes": 30, "favorites": 9, "is_advertisement": false, "section_name": "gamedev", "id": "5652" }
Глеб Диденко
1 891
Gamedev

Ложь во спасение: чем плох честный «рандом»

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

Поделиться

В избранное

В избранном

Пользователи хотят получать удовольствие и выступают за честные шансы «выбивания» предметов — но порой это взаимоисключающие явления, считает продюсер и продакт-лид 2RealLife, участник сообщества «Манжеты ГД», Григорий Чопоров. Свой взгляд на лучший способ реализации выдачи он изложил в статье для портала.

Хорошо ли быть с игроком честными до конца или нет?

Как часто пользователи рассказывают о том, что хотят максимально честного «рандома» в вашей игре? Часто? Очень часто? Так вот — люди врут. Напомню прописную истину геймдизайна: игроки хотят получать от игры удовольствие. Честность (если пользователи получают его в полной мере) заботит их в самую последнюю очередь.

Сегодня я предлагаю перестать верить игрокам и не делать честного «рандома», как минимум в тех случаях, когда необходимо совершить бинарный выбор (то есть определить, одержал игрок победу, или его постигла неудача). Например, если нужно определить, выпал пользователю предмет с убитого им монстра, или нет. О более сложных случаях (когда выбор производится из «n» вариантов) я расскажу в другой статье, хотя изложенный ниже метод можно применить и к ним.

Представим себе неискушённого в игровых механиках программиста, который по вашей просьбе реализует проверку вероятности. Как он поступит скорее всего? Возьмёт стандартную функцию (какую-нибудь «rand()», «random()» или «math.random()», в зависимости от того, на каком языке программирования пишет), получит случайное число от 0 до 1, умножит его на 100 и сравнит полученный результат с указанным вами шансом выпадения (например, с 10%). После чего со спокойной душой пойдёт дальше заниматься важными архитектурными задачами.

Устроит ли вас такой вариант? Если вы будете смотреть только на сводную статистику выпадения предмета — скорее всего да. Вы увидите, что примерно на каждые 10 попыток игроки (вот здесь будьте внимательны, не игрок, а игроки) получают один предмет (хотя скорее всего, конечно, на каждый миллион попыток пользователи получают 100 тысяч предметов). Что, казалось бы, хорошо, ведь этого вы и добивались. Однако вот отличная картинка, иллюстрирующая чувства, которые вы испытаете, если отвлечётесь от общей статистики и опуститесь до рассмотрения судеб отдельных игроков (а это в целом надо делать как можно чаще, — вторая прописная истина).

Рассматривая каждого игрока по отдельности, вы увидите, как много среди них тех, кто не получает предмет за 15 и даже 20 попыток, и тех, кому они выпадают каждые пять-шесть раз. Но, можете сказать вы, пройдёт время, и кривая «рандома» всех уравняет.

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

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

При этом подавляющее большинство вашей аудитории будет думать наоборот — если не повезло сейчас, значит с большей вероятностью повезёт потом; однако никакой гарантии, что это «потом» наступит, вы на самом деле дать не можете. Как исправить ситуацию?

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

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

  • current_chance = (max — goods) / (length — tries) ​

Здесь «max» и «length» — задаваемые вами константы, равные желаемому количеству получаемых игроком предметов («max») из желаемого количества попыток («length»). Например, «max = 10 предметов из length = 100 попыток» (что будет эквивалентно 10% шансу выпадения по схеме, реализованной вашим неискушённым программистом).

Две другие переменные («goods» и «tries») принимают значения, равные количеству уже выпавших на данный момент предметов (goods) из количества уже совершённых на данный момент попыток (tries). Как только «tries» становится равным «length», всё начинается заново, и так по кругу.

Несложно подсчитать (если вам сложно, смотрите в таблице ниже), что в таком варианте реализации шанс успешной попытки растёт при каждой неудачной, пока не достигнет единицы (или 100% — для самых редких неудачников), и возвращается к базовому, равному «max/length», при каждой следующей удачной попытке. В конечном итоге, настроенная таким образом выдача будет гарантировать получение игроком 10 предметов из 100 попыток (не больше и не меньше).

Всё стало на свои места, и ожидания игроков совпали с реальностью, отлично. Но этого должно быть мало для пытливого геймдизайнерского ума! Дополнительный вопрос — а сможем ли мы проконтролировать выдачу 10 предметов из 100 попыток так, чтобы игрок, например, чаще получал предметы во время начальных попыток (и радовался, что ему благоволит «рандом») и реже — во время конечных (чтобы не портить вам расчёты экономики)? Конечно, достаточно ввести в формулу магический коэффициент «k» следующим образом:

  • current_chance = ((max — goods) / (length — tries))*(k^(max — goods))

В итоге при «k = 1» ничего в выдаче не изменится (и она будет равнораспределённой), при «k > 1» игрок будет чаще получать предметы на начальных попытках, а при «0 < k < 1» — на конечных. Если результат деления равен или больше 1, то на «k^(max - good)» умножать не надо (чтобы самым неудачливым пользователям предмет выпал хотя бы на последней попытке).

Обманываем ли мы игрока? Конечно, обманываем. Становится ли ему от этого лучше? Конечно, становится. Получается ситуация, выигрышная для обеих сторон (побольше бы таких, вот последняя на сегодня прописная истина).

Ну а теперь примеры. Ниже вы найдёте таблицу, в которой для выпадения 10 предметов из 100 попыток и трёх разных значений коэффициента «k» посчитано, на какой попытке выпадет каждый предмет.

Надеемся, что с этой таблицей всё стало ещё понятнее!

#геймдизайн

Статьи по теме
Подкаст «Как делают игры»: механики жанра батлеров
Школьная алгебра: вычисляем идеальную цену внутриигровых товаров
Популярные материалы
Показать еще
{ "is_needs_advanced_access": false }

Комментарии Комм.

Популярные

По порядку

0

Прямой эфир

Узнавайте первым важные новости

Подписаться