Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Всем приветы. Я работаю над игровым проектом. В нем потребовалось реализовать сундуки. Все кто играл в игры, сталкивались так или иначе с сундуками. Открывали их. Забирали выпавшие награды. А как это реализовано? Так как мне нужно было реализовать уже, в целом, не новую механику, я начал много спрашивать и искать подходящие варианты. Меня интересовало , буквально все. От возможности создания сервера, до того, как пользователь будет забирать награду. Возможно, я очень плохо искал и/или до сих пор плохо гуглю, но я не нашёл достаточно описания и разъяснения механик сундуков. Вы сталкивались с такими механиками? Как они реализованы у вас?
Пришлось идти мучать гейм дизайнеров, чтобы понять, что они хотят от сундуков. Вырисовывалась следующая картина - гейм дизайнеры хотят создавать сундук и наполнять его различными наградами, типа золото, артефакты, геммы. Награды могут быть как обязательными, так и нет. В сундуке может быть несколько золотых наград, но которые будут выпадать с разным шансом. И это лишь начало, того что хотят наши ГД.
Окей, сундуки ушли на второй план. И нужно было определиться с тем как создавать награды, как хранить их в БД. Очень хотелось бы сделать достаточно гибкую систему, чтобы со временем была возможность добавить новые награды или выключить определенный тип наград. Первое что пришло в голову - сделать отдельную таблицу под каждую награду. Классная идея, под каждую таблицу есть свой репозиторий, достающий данные из БД, есть сервис обрабатывающий параметры награды и сервис по применению награды на игрока, когда он взял ее. И все работает отлично, пока не появляется необходимость собрать сундук из наград. Получается, что сущность Сундук, должна знать о всех наградах в системе. Добавление новой награды, вызывает изменение сущности сундук и сервисов, которые этот сундук открывают. Вообще это рабочий вариант, но принцип open-close говорит нам, что система должна быть открыта для расширения, но закрыта для изменения.
Схема, к слову сказать, получилась ужасной, что говорит нам о том, что вероятнее всего она неправильная.
Ищем дальше. Что если мы сделаем все награды в одной таблице, и добавим enum для определения типов наград? Мы сможем сделать лист наград в нашем сундуке и специальные обработчики для каждого типа награды. В этом случае у нас получится, со временем, огромный enum, со всеми типами наград. Работающий вариант, который снова приводит к изменениям системы.
Но в этом варианте нужно будет изменять только один enum и возможно, сервис наград, в который придётся добавлять “ифчик”. Так хочется начать кодить его. Здравый смысл взял надо мной верх и Тимлид надавал лещей, дав ещё время на изучение вопроса.
Как уйти от общего enum? В ORM моделях присутствует наследование, на моей практике, это не самый популярный кейс использования ORM. Кстати, мы используем hibernate. Так вот, можно сделать базовый класс “награда” с набором стандартных полей. От этого класса будут наследоваться другие типы наград, например награда золото и награда гемы. У каждой из этих наград могут быть специфичные поля, которые потребуются только этой награде. Hibernate даёт нам выбор. Мы можем положить все поля в одну таблицу, и у награды, которой не нужны какие то поля, они просто заполнятся null значениями. Или можно создать под каждый определенный тип награды отдельную таблицу и hibernate при сохранении награды будет доставать базовые поля из общей таблицы, а специфичные поля из дополнительной таблицы, специально созданной для этой награды. Этот вариант позволяет, не изменяя текущий код, добавлять новые награды в систему. Да, схему БД естественно придётся менять, но без этого не обойтись ни в одном из описанных выше вариантов. Так же, каждая награда будет возвращать свой уникальный тип, который потребуется нам в дальнейшем.
Отлично, награды удобно разместились в БД. Но каждая награда имеет свой уникальный тип расчёта. У какой-то награды может быть шанс выпадения, у дрогой лимит выпадения по времени. Как все рассчитать достаточно гибко? Можно попробовать добавить каждой сущности метод, который будет расчитывать текущую награду и приводить ее к определенному типу. Получается, что каждая награда сможет рассчитать себя сама, основываясь на своих параметрах. Звучит достаточно запутанно и сложно, но в коде все намного понятнее. Примерно так выглядит метод, который будет у каждой сущности.
List<?> rewardFor(Profile profile, ContentGenerator contentGenerator);
И один из вариантов реализации этого метода для золотой награды:
@Override
public List<?> rewardFor(Profile profile, ContentGenerator contentGenerator) {
int count = contentGenerator.generateCount(this.getMinCount(), this.getMaxCount(), this.getCount());
return List.of(new GoldCurrency(profile, count));
}
На бумаге у нас готов механизм расчёта каждой награды в системе. Осталось только применить эту награду для профиля игрока. Нам потребуются обработчики, которые принимают ДТО посчитанной награды и применяют эти данные на профиль игрока. Определять тип перехватчика нам позволит тип награды, про который говорилось выше.
Данный механизм позволил создавать и применять награды. Также стало понятно, что сундук это тоже по сути награда, в которой есть список наград. Если ГД захотят добавить награду артефакт, то придётся всего лишь добавить новую сущность и механизм применения этой награды на профиль. В следующей статье я детальнее и с примерами кода покажу, что у меня получилось.
Мне действительно безумно интересно, как у вас в компании реализован механизм наград? Насколько он гибкий и удобный? Будет круто услышать ваше мнение о том, что получилось у меня.