Gamedev Глеб Диденко
2 008

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

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

В закладки

Пользователи хотят получать удовольствие и выступают за честные шансы «выбивания» предметов — но порой это взаимоисключающие явления, считает продюсер и продакт-лид 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» посчитано, на какой попытке выпадет каждый предмет.

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

#геймдизайн

Статьи по теме
Подкаст «Как делают игры»: механики жанра батлеров
Школьная алгебра: вычисляем идеальную цену внутриигровых товаров
{ "author_name": "Глеб Диденко", "author_type": "self", "tags": ["\u0433\u0435\u0439\u043c\u0434\u0438\u0437\u0430\u0439\u043d"], "comments": 17, "likes": 30, "favorites": 12, "is_advertisement": false, "subsite_label": "gamedev", "id": 5652, "is_wide": false }
{ "id": 5652, "author_id": 5399, "diff_limit": 1000, "urls": {"diff":"\/comments\/5652\/get","add":"\/comments\/5652\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/5652"}, "attach_limit": 2, "max_comment_text_length": 5000 }

17 комментариев 17 комм.

Популярные

По порядку

Написать комментарий...

Eugen Dubovik

4

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

Ответить

Глеб Диденко

Eugen
3

Ну «честности» часто требуют пользователи, да.
Насчёт низких рейтов сразу вспоминается дроп карт в RO с шансом 0,01% — за время неудачных попыток выбить обычно можно было получить кучу лута, продать его и купить карту на рынке, не выбивая.

Ответить

Eugen Dubovik

Глеб
2

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

Ну когда мы говорим про"честность" в игровой механике, то подразумеваем выгоду для себя). А так уже много раз говорили что люди думают что 10% это 1 из 10 ).

Ответить

Сергей Пуговкин

Eugen
0

Помним-помним... :)

Ответить

Сергей Пуговкин

Eugen
0

Вкачать чара на одной локе до капа - это не только про РО, сравнительно недавно такой трюк устроил кто-то в ВоВ, прокачав пандарена, не уходя со стартовой локи.
А вот запороть чара, кинув очко не туда ещё до старта самой игры (т.е. на начальном распределении) - это да, в этом весь РО.

Ответить

Eugen Dubovik

Сергей
0

Ну у пандарена это была опция, а в ро это была суровая реальность, помнится в ро бедные монки на слиперах до упора, а сины на аллигаторах, ханты на котиках, визы на клоках. И да, рейты там изначально на всё были 0.01 - 0.02, потом помоему повысили. до 0.03 -0.06 на большинство карт.

Ответить

Сергей Пуговкин

Eugen
0

Не надо про клоков. Я их потом во сне видел...

Ответить

Юрий Елистратов

1

1-4. Игроки не дебилы, если дроп предмета будет распределен на 10-100 попыток и предмет передаваемый, то вы убьете социализацию - нафиг нужно торговать/договариваться если итак "гарантированый дроп", если дроп непередаваемый то какая нафиг вообще разница и соревнование между игроками, если все "в одинаковом" шмоте?

Ответить

Albert Alexandrovskiu

1

Отличная статья и отличный метод для раздачи предметов игрокам. Возьму на вооружение. Спасибо за статью! Пишите обязательно еще.

Ответить

Ilya Hinzburh

–2

А разве какая-то игра обещает игрокам "честные шансы"? Я о таком не слышал. Даже электронные казино этого не обещают :) Поэтому все правильно - разработчик игры имеет право вводить любое распределение вероятности.
Во многих играх я даже замечал, что после платежа вероятность получения хороших вещей резко растет. А почему нет? Платящих игроков надо прикармливать.

Ответить

Георгий Лешкашели

Ilya
0

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

Ответить

Ilya Hinzburh

Георгий
0

Это становится проблемой только в тех играх, где идет постоянная конкуренция между игроками со старта сервера. Во многих играх такой конкуренции нет, и новички играют на том же сервере, что и ветераны. Например, в Summoners' War один игрок может получить редкого монстра в первый день игры, а второй за полгода не получит. Правда, в этой игре зависимость удачи от оплаты не подтверждена, и я слышал, что некоторые тратили тысячи долларов, но все равно ничего не получали.
Если игра действительно хорошая - неплатящие игроки не уйдут, даже если узнают, что рандом "нечестный". В конце концов, в хороших играх рандомная удача еще не гарантирует победу. Практически во всех ККИ платящие игроки получают намного больше карт, чем неплатящие, но из них еще надо собрать хорошую колоду и правильно ею играть.

Ответить

Марик Омаргалиев

0

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

Ответить

Ilya Hinzburh

Марик
0

Почему "известно, что дроп будет 100%"? В Summoners' War, например, вероятность дропа монстра с натуральными 5* так низка, что мне первый выпал только через 8 месяцев игры. А некоторые и по году 5* не получали.
Нет, само собой, в конце концов игрок его получит, но если этот "конец концов" растянется лет на пять, ни у кого просто не хватит терпения так долго ждать.

Ответить

Николай Ксенофонтов

0

Если мы говорим о выпадении предметов в сессионках, то можно предложить ещё одну схему.
Игрок закачивает матч и его результаты - набранные фраги, MVP, помощь другим, позиция в рейтинге - влияли на изначальную формулу рандома, увеличивая шанс получения хороших предметов. Более того, это будет мотивировать игроков на хорошую игру. Конечно, чем выше уровень игрока, тем реже должен выпадать такой дроп, чтобы "хайлевелы" не наживались за счёт своего умения. Похожая схема, кстати, вроде как реализована в CS: GO.

Ответить

Сергей Бровков

Николай
0

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

Ответить

Николай Ксенофонтов

Сергей
0

Это, разумеется, зависит от самой игры. Можно организовать сложную зависимость от соотношения числа "личных" достижений, таких как фраги и позиции в рейтинге и достижений для команды. Лечение, прикрытие и всё, что считается за помощь другим. В таком случае нельзя будет просто стырить фраг перед носом напарника, увеличив себе дроп.
Игрок, убивший больше противников, но не принёсший пользу команде получит меньше шанса получить дроп.
Я думаю, это тема для отдельной аналитической статьи (одной из сотен).

Ответить

Комментарий удален

0

Прямой эфир

Подписаться на push-уведомления
[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "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" ], "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": "createAdaptive", "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-1134314964", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=clmf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudo", "p2": "ftjf" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvc" } } } ]