О чем этот пример

Работа с цветами — частая задача в игровой разработке, будь то плавные переходы интерфейса или динамическое освещение. Phaser предоставляет удобный инструментарий в классе `Phaser.Display.Color`. Однако один из методов может вести себя неочевидно и возвращать `undefined`, ломая логику игры, если не знать тонкости его работы. В этой статье мы разберем конкретный кейс из баг-трекера фреймворка, объясним причину ошибки и покажем, как правильно использовать интерполяцию цветов.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    constructor()
    {
        super({
            key: "example"
        });
    }

    create ()
    {
        let interpValue = 0.5;
        const colorA = Phaser.Display.Color.IntegerToColor(0x000000); //Black
        const colorB = Phaser.Display.Color.IntegerToColor(0xFFFFFF); //White
        const colorObject = Phaser.Display.Color.Interpolate.ColorWithColor(colorA, colorB, 1, interpValue);
        console.log(colorObject);
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#1d1d1d',
    parent: 'phaser-example',
    scale: {
        mode: Phaser.Scale.FIT,
        autoCenter: Phaser.Scale.CENTER_BOTH
    },
    scene: [ Example ]
};

const game = new Phaser.Game(config);

В чем проблема?

В представленном примере разработчик пытается получить промежуточный цвет между черным (0x000000) и белым (0xFFFFFF). Логика кажется простой: есть два цвета и значение интерполяции (в данном случае 0.5, что означает ровно середина). Однако при запуске этого кода в консоль выводится undefined, а не ожидаемый объект цвета серого оттенка.

Код, вызывающий проблему, выглядит так:

let interpValue = 0.5;
const colorA = Phaser.Display.Color.IntegerToColor(0x000000); //Black
const colorB = Phaser.Display.Color.IntegerToColor(0xFFFFFF); //White
const colorObject = Phaser.Display.Color.Interpolate.ColorWithColor(colorA, colorB, 1, interpValue);
console.log(colorObject); // Вывод: undefined

Разбираем аргументы метода ColorWithColor

Ключ к пониманию ошибки — в сигнатуре метода. Взглянем на официальную документацию или исходный код Phaser. Метод Phaser.Display.Color.Interpolate.ColorWithColor ожидает четыре аргумента в строгом порядке: 1. color1 (Phaser.Types.Display.ColorObject): Исходный цвет. 2. color2 (Phaser.Types.Display.ColorObject): Конечный цвет. 3. length (number): Общее количество шагов в интерполяции. Это целое число (integer), определяющее, на сколько частей разбивается переход. 4. index (number): Конкретный шаг, цвет для которого мы хотим получить. Должен быть в диапазоне от 0 до length.

Главное правило: index не может быть больше или равен length. Метод возвращает цвет для конкретного шага в дискретной последовательности.

Ошибка в исходном примере

Теперь становится ясно, что не так с кодом из баг-репорта. Разработчик передал length = 1 и index = 0.5.

// Неверные аргументы
const colorObject = Phaser.Display.Color.Interpolate.ColorWithColor(colorA, colorB, 1, 0.5);

При length = 1 существует только два валидных индекса: `0(полностью цвет A) и1(полностью цвет B). Любое дробное значение, такое как0.5, выходит за пределы этого дискретного набора шагов. Именно поэтому метод не может найти подходящий цвет и возвращаетundefined`. Это не баг, а строгое следование заложенной логике.

Как делать правильно: два подхода

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

**1. Дискретная интерполяция (для заранее известного числа шагов).** Если вам нужен, например, 5-й цвет из 10-шагового градиента, используйте целые числа для length и index.

// 10 шагов между цветами, получаем 5-й шаг (индекс 4)
const steps = 10;
const stepIndex = 4;
const correctColor = Phaser.Display.Color.Interpolate.ColorWithColor(colorA, colorB, steps, stepIndex);
console.log(correctColor); // Выведет объект цвета

**2. Плавная интерполяция (для любого дробного значения).** Для получения цвета по произвольному коэффициенту (например, от 0.0 до 1.0) используйте метод Phaser.Display.Color.Interpolate.RGBWithRGB. Он работает напрямую с компонентами цветов.

// Плавный переход по значению factor от 0.0 (colorA) до 1.0 (colorB)
let factor = 0.5;
const r = Phaser.Math.Interpolation.Linear([colorA.r, colorB.r], factor);
const g = Phaser.Math.Interpolation.Linear([colorA.g, colorB.g], factor);
const b = Phaser.Math.Interpolation.Linear([colorA.b, colorB.b], factor);

// Создаем новый объект цвета из полученных компонентов
const smoothColor = new Phaser.Display.Color(r, g, b);
console.log(smoothColor); // Объект серого цвета

Практическое применение в игре

Понимание этой разницы критично для геймдева.

* ColorWithColor идеален для ситуаций, где переход разбит на этапы. Например, индикатор здоровья, который меняет цвет по заранее заданным ступеням (зеленый -> желтый -> красный за 3 шага). * RGBWithRGB с линейной интерполяцией нужен для плавных, анимированных изменений. Например, для затемнения экрана (Tween) или динамического окрашивания спрайта в зависимости от времени суток в игре.

Вот как можно быстро получить плавный градиентный цвет, обернув логику в функцию:

function getInterpolatedColor(colorA, colorB, t) {
    // t - значение от 0 до 1
    const r = Phaser.Math.Interpolation.Linear([colorA.r, colorB.r], t);
    const g = Phaser.Math.Interpolation.Linear([colorA.g, colorB.g], t);
    const b = Phaser.Math.Interpolation.Linear([colorA.b, colorB.b], t);
    return new Phaser.Display.Color(Math.round(r), Math.round(g), Math.round(b));
}

const myGrey = getInterpolatedColor(colorA, colorB, 0.5);

Что попробовать дальше

Метод ColorWithColor — это инструмент для дискретной интерполяции, работающий с целочисленными шагами. Ошибка undefined возникает при передаче дробного индекса, выходящего за пределы заданного количества шагов (length). Для плавных переходов используйте комбинацию Interpolate.RGBWithRGB или линейной интерполяции компонентов. **Идеи для экспериментов:** Попробуйте создать индикатор, который плавно меняет цвет в зависимости от значения (заряда щита, уровня ярости). Или реализуйте систему времени суток, где цвет неба интерполируется между несколькими ключевыми оттенками (рассвет, день, закат, ночь) с помощью ColorWithColor и length, равному 4.