Как сравнивать цвета и сделать из этого казуальную игру?
Привет, Хабр! В этой статье я хочу рассказать о разработке своей небольшой игры, в основу которой легло сравнение цветов.
Об идее
Идея возникла ещё в эпоху флеш-игр, когда я играл в какую-то казуальную игру, основная идея которой заключалась в уравновешивании чаш весов с заданной точностью, при помощи создания объектов нужной массы.
Пока писал эту статью, нашел скрин той игры:
Тогда мне хотелось придумать что-то своё и я подумал, почему бы не сделать аналогичную игру, но где нужно смешивать цвета, чтобы получить нужный?!
С того момента прошлого много лет, и вот у меня появилось свободное время и желание заняться геймдевом. Что из этого вышло, я расскажу ниже
Реализация
До момента создания игры я ничего не знал про сравнение цветов в RGB пространстве, поэтому первая идея, которая пришла мне в голову – это находить расстояние между двумя точками в координатах RGB. Но чем больше я погружался в вопрос, тем отчетливей начинал понимать, что такой подход неверен и стоит использовать формулу цветового отличия. Оказалось, что похожую задачу уже решал международный комитет по освещению и для сравнения двух цветов понадобится LAB пространство. Тогда отличие двух цветов можно будет вычислить по формуле
где значение ≈ 2.3 примерно соответствует минимально различимому для человеческого глаза отличию между цветами (это формула 1976 года, которую я решил использовать в своей игре, ещё есть формулы 1994 и 2000 года, которые тут не упоминаются).
Теперь оставалось только перевести цвета из RGB в Lab пространство.
Для этого для начала придется конвертировать RGB в XYZ, а затем XYZ в Lab :
RGB в XYZ
//sR, sG and sB (Standard RGB) input range = 0 ÷ 255
//X, Y and Z output refer to a D65/2° standard illuminant.
var_R = ( sR / 255 )
var_G = ( sG / 255 )
var_B = ( sB / 255 )
if ( var_R > 0.04045 ) var_R = ( ( var_R + 0.055 ) / 1.055 ) ^ 2.4
else var_R = var_R / 12.92
if ( var_G > 0.04045 ) var_G = ( ( var_G + 0.055 ) / 1.055 ) ^ 2.4
else var_G = var_G / 12.92
if ( var_B > 0.04045 ) var_B = ( ( var_B + 0.055 ) / 1.055 ) ^ 2.4
else var_B = var_B / 12.92
var_R = var_R * 100
var_G = var_G * 100
var_B = var_B * 100
X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
XYZ в Lab
//Reference-X, Y and Z refer to specific illuminants and observers.
//Common reference values are available below in this same page.
var_X = X / Reference-X
var_Y = Y / Reference-Y
var_Z = Z / Reference-Z
if ( var_X > 0.008856 ) var_X = var_X ^ ( 1/3 )
else var_X = ( 7.787 * var_X ) + ( 16 / 116 )
if ( var_Y > 0.008856 ) var_Y = var_Y ^ ( 1/3 )
else var_Y = ( 7.787 * var_Y ) + ( 16 / 116 )
if ( var_Z > 0.008856 ) var_Z = var_Z ^ ( 1/3 )
else var_Z = ( 7.787 * var_Z ) + ( 16 / 116 )
CIE-L* = ( 116 * var_Y ) - 16
CIE-a* = 500 * ( var_X - var_Y )
CIE-b* = 200 * ( var_Y - var_Z )
Теперь, когда мы научились сравнивать цвета, можно приступать к реализации. В качестве игрового движка был выбран Unity. Я понимаю, что для такой игры его использование - оверхэд, но с Unity я был знаком, а для написания на "чистом" HTML/JS мне пришлось бы подтянуть их до должного уровня, поэтому выбор остановился на нём.
Геймплей
Было несколько идей того, каким должен быть геймплей, в итоге я остановился на том, что с верхней части экрана будут спускаться шарики разных цветов, а в нижней части экрана будет три слайдера с зеленым, красным и синим цветами, которые могут принимать значения от 0 до 255, а чуть выше капсула, где тут же отображается получаемый цвет. На изображении ниже можно увидеть, как это выглядит
Сложность игры и игровые достижения
Как и во многих играх, чтобы поддерживать интерес к игре я решил, что нужно повышать сложность по мере игрового процесса. Повышение сложности осуществил следующим образом:
в начале игры цвета нужно подбирать с точностью в 65% и выше, а с каждой следующей 1000 очков точность повышается на 5% до 95%
каждые 1000 очков немного увеличивается скорость шариков-целей
первые пару тысяч очков генерируются простые цвета, которые можно получить комбинацией 1-2 слайдеров, например, голубой – это G = 255 + B = 255
игровые достижения. Будучи и сам геймером, который любит получать ачивки, я решил добавить игровые достижения. Реализованы они были следующим образом: изначально каждому достижению установлен черно-белый спрайт. При переходе на экран достижений в PlayerPrefs проверяется наличие записи по каждому достижению, если такая запись есть, то спрайт заменяется на его цветную версию
после того, как я выбрал площадку для публикации игры, используя методы предоставляемой SDK был добавлен лидерборд для авторизованных пользователей, чтобы подстегнуть соревновательный дух
Отладка и баги
Будучи по профессии QA-инженером, было интересно какие ошибки могут возникнуть в таком маленьком проекте. Вот некоторые из них:
В начале первой игры отображается экран с обучением, в этот момент, чтобы остановить время и генерацию целей я использовал свойство Time.timeScale = 0. Оказалось, что в значении 0 останавливается движение объектов, течение времени, но не инициализации префабов(игровых объектов). Поэтому, при клике мимо кнопки закрытия окна с обучением, каждый раз создавался снаряд, что приводило к следующему:
Для задания движения целям я использовал компонент Rigidbody2D, который придает объекту свойства физического тела(масса, твердость и т.п.). Из-за этого при столкновении двух объектов они до момента разрушения успевали передать импульс соседним объектам, которые в последствии улетали за пределы экрана
Публикация игры
В качестве платформы для сборки я выбрал WebGL, как преемника Flash.
Не знаю как так вышло, но арендовать VPS и отлаживать игру на нем мне оказалось проще, чем отлаживать локально. К тому же это позволило делиться игрой с друзьями, чтобы узнать их мнение. Когда я закончил создании игры, я решил опубликовать её на каких-нибудь площадках, чтобы посмотреть будет ли интересна эта игра кому-то ещё. И тут начались настоящие трудности.
Мне совершенно не приходило в голову, как можно назвать игру? Я даже пытался сгенерировать его через ChatGPT, а когда первое название было придумано, то оказалось, что игра с таким названием уже есть на площадке. В итоге я остановился на незамысловатом «Ловец Цвета».
Далее для публикации нужно было подготовить промо-материал, что вызывало у меня очередной ступор, т.к. мои навыки работы с графикой заканчивалось на создании скринов для баг-репортов.
Поэтому представляю вам обложку, которая у меня получилась и за которую мне стыдно, но пока лучше не придумал
Итоги
С момента публикации игры прошло несколько дней. За это время в игру сыграло чуть больше ста человек и на лидерборде появились игроки помимо меня. Сейчас я уже не могу оценить, насколько в получившуюся игру интересно играть, потому что за период отладки я сыграл в неё столько раз, что игра уже начинается казаться мне странной. Как это было, когда я писал игру-викторину под Android, но это уже совсем другая история.
Спасибо за внимание! Буду рад ответить на ваши вопросы.