Подробный гайд по новой системе цветов в майнкрафт

Руководство Подробный гайд по новой системе цветов в майнкрафт

Поддерживаемые версии
  1. 1.14
  2. 1.15
  3. 1.16
  4. 1.17
  5. 1.18
  6. 1.19
(Я не являюсь автором этой статьи, а лишь представляю её вольный перевод. Оригинал: https://www.spigotmc.org/threads/gradient-chat-particles.470496/)

После того как Minecraft представил поддержку всех видов цветов, стали возможны «Цветовые градиенты» что-то вроде этого:
1604336235050.png

Тут то и возникла некоторая путаница в том, как этого добиться, какие есть возможные методы и как достичь разных вещей, таких как градиенты, содержащие более двух цветов, различия между HSV / RGB и способы интерполирования между разными цветами. Я хочу рассмотреть все это в этой теме, конечно же вместе с примерами кода.

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

Начнём с интерполяции всего двух точек. Мы всегда будем определять эти две точки следующим образом:
Первая точка находится в (x, y) = (0, from), а вторая находится в точке (E-1, to). from и to - это два значения цвета, которые мы хотим соединить, N - длина строки или количество элементов, которые нужно раскрасить.

На рисунке выше используются два метода: линейная интерполяция и квадратичная интерполяция.
Наша цель - найти функцию f(e), проходящую через две точки. Если мы используем линейную интерполяцию, то получим уравнение:
f(e) = from + ((to - from) / (E - 1)) * e.

Если мы вставим 0, мы получим f(0) = from. Если мы вставим E-1, мы получим from + ((to - from) / (E-1)) * (E-1) = from + to - from = to.
Вот некоторые результаты с использованием линейной и квадратичной интерполяции.
1604335020805.png

Если мы используем квадратичную интерполяцию, уравнения станут несколько сложнее, поскольку у нас открывается гораздо больше свободы.
Можно сделать вывод о том, что вы можете использовать любую функцию, если она «плавная» и соответствует двум пунктам. Различные функции влияют на то, как цвет фактически «выцветает».
В коде я определю (функциональный) интерфейс, который позволит сгруппировать различные алгоритмы интерполяции. Я также предоставлю два метода: один для линейной интерполяции двух точек, а другой - для квадратичной:
Java:
@FunctionalInterface
interface Interpolator {

    double[] interpolate(double from, double to, int max);
}
Java:
private double[] linear(double from, double to, int max) {
    final double[] res = new double[max];
    for (int i = 0; i < max; i++) {
        res[i] = from + i * ((to - from) / (max - 1));
    }
    return res;
}
Java:
// mode == true: начинается "медленно" и становится "быстрее", см. оранжевую кривую
// mode == false: начинается "быстро" и становится "медленнее", см. желтую кривую
private double[] quadratic(double from, double to, int max, boolean mode) {
    final double[] results = new double[max];
    if (mode) {
        double a = (to - from) / (max * max);
        for (int i = 0; i < results.length; i++) {
            results[i] = a * i * i + from;
        }
    } else {
        double a = (from - to) / (max * max);
        double b = - 2 * a * max;
        for (int i = 0; i < results.length; i++) {
            results[i] = a * i * i + b * i + from;
        }
    }
    return results;
}
Переходим к нашим градиентам.

2. Цветовое пространство
Цвета могут быть представлены в разных цветовых пространствах. Самый распространенный (вероятно) - это RGB. Цвет может состоять из красного, зеленого и синего компонентов. Смешивание этих трех цветов вместе в конечном итоге даст другой цвет. Все эти компоненты (красный, зеленый и синий) могут иметь любое значение от 0 до 255.
Первый градиент, который я покажу, представляет собой линейную интерполяцию каждого из трех компонентов RGB (Пример 1). Для этого нам просто нужно применить линейную интерполяцию к каждому из этих компонентов.
Java:
private String rgbGradient(String str, Color from, Color to, Interpolator interpolator) {

    // интерполируем каждый компонент отдельно
    final double[] red = interpolator.interpolate(from.getRed(), to.getRed(), str.length());
    final double[] green = interpolator.interpolate(from.getGreen(), to.getGreen(), str.length());
    final double[] blue = interpolator.interpolate(from.getBlue(), to.getBlue(), str.length());

    final StringBuilder builder = new StringBuilder();

    // создаем строку, которая соответствует входной строке, но
    // к каждому символу применяется другой цвет
    for (int i = 0; i < str.length(); i++) {
        builder.append(ChatColor.of(new Color(
                (int) Math.round(red[i]),
                (int) Math.round(green[i]),
                (int) Math.round(blue[i]))))
                .append(str.charAt(i));
    }
}
Теперь это позволяет нам плавно интерполировать между двумя цветами. Однако это может показаться немного неудовлетворительным. При градиенте цвет становится темнее, а затем снова ярче. Однако что, если мы хотим оставаться на одной яркости? Для этого нам понадобится другая цветовая схема.

HSV разделяет цвет на оттенок, насыщенность и яркость. Если мы изменим значение оттенка, мы останемся на одном значении яркости. Для преобразования из RGB в HSV и наоборот мы можем использовать java.awt.Color:
Java:
private String hsvGradient(String str, Color from, Color to, Interpolator interpolator) {
    // возвращает массив с плавающей запятой, где hsv[0] = оттенок, hsv[1] = насыщенность, hsv[2] = значение / яркость
    final float[] hsvFrom = Color.RGBtoHSB(from.getRed(), from.getGreen(), from.getBlue(), null);
    final float[] hsvTo = Color.RGBtoHSB(to.getRed(), to.getGreen(), to.getBlue(), null);

    final double[] h = interpolator.interpolate(hsvFrom[0], hsvTo[0], str.length());
    final double[] s = interpolator.interpolate(hsvFrom[1], hsvTo[1], str.length());
    final double[] v = interpolator.interpolate(hsvFrom[2], hsvTo[2], str.length());

    final StringBuilder builder = new StringBuilder();

    for (int i = 0 ; i < str.length(); i++) {
        builder.append(ChatColor.of(Color.getHSBColor((float) h[i], (float) s[i], (float) v[i]))).append(str.charAt(i));
    }
    return builder.toString();
}

3. Многоцветные градиенты.
Наконец, я хочу посмотреть, что происходит, когда мы хотим использовать несколько цветов. В примерах 3, 4 и 5 мы переходим от красного к зеленому, а затем от зеленого к красному. В примерах 6-10 мы выполняем градиент от красного к зеленому, а затем обратно к красному, при этом каждому цвету присваивается разный интервал с различными методами интерполяции при использовании RGB, а также HSV.

Для этого мы можем просто повторно использовать предоставленные методы и интерполировать две последующие точки:
Java:
private String multiRgbGradient(String str, Color[] colors, @Nullable double[] portions, Interpolator interpolator) {
    final double[] p;
    if (portions == null) {
        p = new double[colors.length - 1];
        Arrays.fill(p, 1 / (double) p.length);
    } else {
        p = portions;
    }

    Preconditions.checkArgument(colors.length >= 2);
    Preconditions.checkArgument(p.length == colors.length - 1);

    final StringBuilder builder = new StringBuilder();
    int strIndex = 0;

    for (int i = 0; i < colors.length - 1; i++) {
        builder.append(rgbGradient(
                str.substring(strIndex, strIndex + (int) (p[i] * str.length())),
                colors[i],
                colors[i + 1],
                interpolator));
        strIndex += p[i] * str.length();
    }
    return builder.toString();
}
Массив частей позволяет нам контролировать, где должны быть размещены разные цвета. Особый случай «ноль» означает, что все цвета должны быть равномерно распределены. Примеры 3, 4 и 5 являются результатом этого метода. Во всех случаях цвета распределены равномерно. Пример 3 использует линейную интерполяцию, 4 и 5 квадратичную интерполяцию с двумя разными режимами, описанными выше.

Примеры 6, 7 и 8 являются примерами для цветов = {красный, зеленый, зеленый, красный} (что означает переход от красного к зеленому, от зеленого к зеленому и, наконец, от зеленого к красному), в то время как массив частей - это части = { 0,1, 0,8, 0,1}, что означает переход от красного к зеленому на 10% всего пространства, 80% пространства не должно изменяться, а оставаться зеленым и, наконец, переходить от зеленого к красному. В примере 6 используется линейная интерполяция, в примерах 7 и 8 используется квадратичная интерполяция.

Для оптимизации квадратичной интерполяции в примерах 9 и 10 показана версия, которая использует разные режимы для запуска и остановки и работает в цветовом пространстве HSV. Он несколько жестко запрограммирован, но показывает различные возможные варианты:
Java:
private String multiHsvQuadraticGradient(String str, boolean first) {
    final StringBuilder builder = new StringBuilder();

    builder.append(hsvGradient(
            str.substring(0, (int) (0.2 * str.length())),
            Color.RED,
            Color.GREEN,
            (from, to, max) -> this.quadratic(from, to, max, first)
    ));

    for (int i = (int) (0.2 * str.length()); i < (int) (0.8 * str.length()); i++) {
        builder.append(ChatColor.of(Color.GREEN)).append(str.charAt(i));
    }

    builder.append(hsvGradient(
            str.substring((int) (0.8 * str.length())),
            Color.GREEN,
            Color.RED,
            (from, to, max) -> this.quadratic(from, to, max, !first)
    ));

    return builder.toString();

}
Автор
Xezard
Просмотры
3 605
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок

Другие ресурсы пользователя Xezard

  • XGlow
    Для ядра XGlow
    Простое API на основе ProtocolLib для создания свечения на сущностях
  • XItemsRemover
    Для ядра XItemsRemover
    Простой плагин, который автоматически удаляет выпавшие предметы с отображением на них таймера.
  • XTextsCensor
    Платно Для ядра XTextsCensor
    Комплексное и тонко настраиваемое решение для фильтрации текстов на вашем сервере!

Поделиться ресурсом

Назад
Сверху Снизу