C++ Гайд Учимся делать анимации в ImGui.

Олдфаг
Статус
Оффлайн
Регистрация
4 Янв 2020
Сообщения
2,996
Реакции[?]
1,275
Поинты[?]
5K
Вы не ждали, а я вернулся, сегодня на повестке дня прекрасный гайдик, самое то для вечера за визуал студией. То, что никто до меня почему-то не делал. Гайд по анимациям в ImGui, самый простейший способ, который между прочим очень эффективен.
Перейдем к практической части, использовать будем std::map и ImGuiID.
Контейнер map, очень похож на остальные контейнеры, такие как
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
, но с небольшим отличием. В этот контейнер можно помещать сразу два значения. Если у Вас когда-то была мечта написать свой словарь, то лучше чем map, вам альтернативы не найти.
Каждый элемент в имгуи не имеет практической разницы, думаю вы заметили, что если дать любым элементам одинаковый лейбл, то они будут задействоваться одновременно, это все из-за ImGuiID, в дефолтных элементах он привязывается к лейблу элемента. Пример:
C++:
const ImGuiID id = win->GetID(label);
При определенном условии меняем значение переменной, благодаря ImGuiID анимация будет работать только в рамках нашего элемента.
Как выглядит вкратце сама анимация:
C++:
static std::map<ImGuiID, float> alpha_anim;
        auto it_alpha = alpha_anim.find(id);

        if (it_alpha == alpha_anim.end())
        {
            alpha_anim.insert({ id, 0.f });
            it_alpha = alpha_anim.find(id);
        }
        if (*v)
        {
            if (it_alpha->second < 1.f)
                it_alpha->second += 0.1f;
        }
        else
        {
            if (it_alpha->second > 0.f)
                it_alpha->second -= 0.1f;
        }
Вроде разобрались. Теперь посмотрим как эти две вещи помогут нам на практике.
Создаем функцию, думаю пояснять как это делать не надо. Задаем ей похожие аргументы:
C++:
void YourFirstAnimation(const char* label, float w, float h)
    ///
Лейбл будем использовать для ImGuiID, последующие аргументы для размеры нашей кнопки, кстати да, в этом примере мы будем делать кнопку и анимацию по альфе.
Создаем наши переменные, комментарии в коде все пояснят за меня.
C++:
ImGuiWindow* win = ImGui::GetCurrentWindow(); //указатель на класс окна
const ImGuiID id = win->GetID(label); //получаем id по лейблу
static std::map<ImGuiID, int> alpha_anim; //создаем контейнер map, указываем свой тип переменной
auto it_alpha = alpha_anim.find(id); //создаем переменную типа auto и задаем ей значение итератора find id
Заполним наш контейнер:
C++:
if (it_alpha == alpha_anim.end()) //если массив закончился
{
alpha_anim.insert({ id, 55 }); //заполняем контейнер, 55 - стартовая прозрачность
it_alpha = alpha_anim.find(id); //задаем значение
}
Теперь дополняем:
C++:
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, rgb_to_imvec4(55, 55, 55, (int)it_alpha->second)); //меняем цвета кнопки с помощью пушстайла
ImGui::PushStyleColor(ImGuiCol_Button, rgb_to_imvec4(55, 55, 55, (int)it_alpha->second)); //в последнем аргументе указываем нашу it_alpha с указанием на второй параметр нашего контейнера
ImGui::SetCursorPos(ImGui::GetCursorPos()); //задаем позицию по текущей позиции курсора
ImGui::Button(label, ImVec2(w, h)); // рендерим кнопку
ImGui::PopStyleColor(2); //возвращаем дефолтные колорстили
if (ImGui::IsItemHovered()) { //сама анимация при определенном условии, например IsItemHovered
   if (it_alpha->second < 255) //макс. прозрачность
        it_alpha->second += 8; //8 - скорость появления
}
 else { //Точно таже анимация, но плавное исчезновение
        if (it_alpha->second > 55) //прозрачность после анимации
            it_alpha->second -= 8; //8 - скорость исчезновения
        }
Важно отметить, что элемент нужно рендерить после анимации, если вы например рендерите всякие фигуры через дравлист, то есть сначала анимация, потом рендер, в особенности при нулевой прозрачности.
Что получилось в итоге:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Не забываем что мы можем использовать любой тип переменных и реализовать это так как захотим, т.е не только альфа для цвета например.
Таким методом можно изменять например размеры, да и вообще что захотите можно сделать плавно. Напомню скептикам, что это самый простой, но в тоже время эффективный способ, всем удачи!
 
I'm watching you
Участник
Статус
Оффлайн
Регистрация
7 Фев 2020
Сообщения
752
Реакции[?]
241
Поинты[?]
3K
Вы не ждали, а я вернулся, сегодня на повестке дня прекрасный гайдик, самое то для вечера за визуал студией. То, что никто до меня почему-то не делал. Гайд по анимациям в ImGui, самый простейший способ, который между прочим очень эффективен.
Перейдем к практической части, использовать будем std::map и ImGuiID.
Контейнер map, очень похож на остальные контейнеры, такие как
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
, но с небольшим отличием. В этот контейнер можно помещать сразу два значения. Если у Вас когда-то была мечта написать свой словарь, то лучше чем map, вам альтернативы не найти.
Каждый элемент в имгуи не имеет практической разницы, думаю вы заметили, что если дать любым элементам одинаковый лейбл, то они будут задействоваться одновременно, это все из-за ImGuiID, в дефолтных элементах он привязывается к лейблу элемента. Пример:
C++:
const ImGuiID id = win->GetID(label);
При определенном условии меняем значение переменной, благодаря ImGuiID анимация будет работать только в рамках нашего элемента.
Как выглядит вкратце сама анимация:
C++:
static std::map<ImGuiID, float> alpha_anim;
        auto it_alpha = alpha_anim.find(id);

        if (it_alpha == alpha_anim.end())
        {
            alpha_anim.insert({ id, 0.f });
            it_alpha = alpha_anim.find(id);
        }
        if (*v)
        {
            if (it_alpha->second < 1.f)
                it_alpha->second += 0.1f;
        }
        else
        {
            if (it_alpha->second > 0.f)
                it_alpha->second -= 0.1f;
        }
Вроде разобрались. Теперь посмотрим как эти две вещи помогут нам на практике.
Создаем функцию, думаю пояснять как это делать не надо. Задаем ей похожие аргументы:
C++:
void YourFirstAnimation(const char* label, float w, float h)
    ///
Лейбл будем использовать для ImGuiID, последующие аргументы для размеры нашей кнопки, кстати да, в этом примере мы будем делать кнопку и анимацию по альфе.
Создаем наши переменные, комментарии в коде все пояснят за меня.
C++:
ImGuiWindow* win = ImGui::GetCurrentWindow(); //указатель на класс окна
const ImGuiID id = win->GetID(label); //получаем id по лейблу
static std::map<ImGuiID, int> alpha_anim; //создаем контейнер map, указываем свой тип переменной
auto it_alpha = alpha_anim.find(id); //создаем переменную типа auto и задаем ей значение итератора find id
Заполним наш контейнер:
C++:
if (it_alpha == alpha_anim.end()) //если массив закончился
{
alpha_anim.insert({ id, 55 }); //заполняем контейнер, 55 - стартовая прозрачность
it_alpha = alpha_anim.find(id); //задаем значение
}
Теперь дополняем:
C++:
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, rgb_to_imvec4(55, 55, 55, (int)it_alpha->second)); //меняем цвета кнопки с помощью пушстайла
ImGui::PushStyleColor(ImGuiCol_Button, rgb_to_imvec4(55, 55, 55, (int)it_alpha->second)); //в последнем аргументе указываем нашу it_alpha с указанием на второй параметр нашего контейнера
ImGui::SetCursorPos(ImGui::GetCursorPos()); //задаем позицию по текущей позиции курсора
ImGui::Button(label, ImVec2(w, h)); // рендерим кнопку
ImGui::PopStyleColor(2); //возвращаем дефолтные колорстили
if (ImGui::IsItemHovered()) { //сама анимация при определенном условии, например IsItemHovered
   if (it_alpha->second < 255) //макс. прозрачность
        it_alpha->second += 8; //8 - скорость появления
}
else { //Точно таже анимация, но плавное исчезновение
        if (it_alpha->second > 55) //прозрачность после анимации
            it_alpha->second -= 8; //8 - скорость исчезновения
        }
Важно отметить, что элемент нужно рендерить после анимации, если вы например рендерите всякие фигуры через дравлист, то есть сначала анимация, потом рендер, в особенности при нулевой прозрачности.
Что получилось в итоге:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Не забываем что мы можем использовать любой тип переменных и реализовать это так как захотим, т.е не только альфа для цвета например.
Таким методом можно изменять например размеры, да и вообще что захотите можно сделать плавно. Напомню скептикам, что это самый простой, но в тоже время эффективный способ, всем удачи!
Всё хорошо, только в место
+= и -= лучше использовать ImClamp
 
money++
Разработчик
Статус
Оффлайн
Регистрация
14 Июн 2018
Сообщения
638
Реакции[?]
339
Поинты[?]
22K
Контейнер map, очень похож на остальные контейнеры, такие как
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
, но с небольшим отличием.
Дядя, пей таблетки. Это 4 абсолютно разных контейнера с абсолютно разными предназначениями.

В этот контейнер можно помещать сразу два значения
Нет, в мапе первое значение работает как ключ, а второе как значение. Если ты хочешь хранить по 2 (3+) значения, то создай массив пар (туплов).

Если у Вас когда-то была мечта написать свой словарь, то лучше чем map, вам альтернативы не найти.
Все словари используют std::unordered_map (обращение за О(1) в среднем, вместо O(logN) в std::map) или его аналоги в других языках...

Я бы и по оставшейся части поста прошелся, но писать с телефона очень неудобно. Если коротко - полная хуйня, зато работает. Пастеры сожрут. Но если вы шарите в кодинге напишите свои анимации пожалуйста, на это же больно смотреть...
 
bruh
Участник
Статус
Оффлайн
Регистрация
15 Апр 2017
Сообщения
1,298
Реакции[?]
365
Поинты[?]
0
прибавляя сырые значения +-8 ты рискуешь увидеть различные проблемы при низком/высоком фпс, а еще зачем то ты создал свою функцию rgb_to_imvec4, хотя у ImVec4 и так есть конструктор под ргб значения, на гайд это мало смахивает
 
Emton
Пользователь
Статус
Оффлайн
Регистрация
20 Мар 2021
Сообщения
115
Реакции[?]
40
Поинты[?]
0
прибавляя сырые значения +-8 ты рискуешь увидеть различные проблемы при низком/высоком фпс, а еще зачем то ты создал свою функцию rgb_to_imvec4, хотя у ImVec4 и так есть конструктор под ргб значения, на гайд это мало смахивает
а какие значения нужно, что бы не было проблем с низким\высоким фпс?
 
Эксперт
Статус
Оффлайн
Регистрация
30 Дек 2019
Сообщения
1,967
Реакции[?]
958
Поинты[?]
19K
Олдфаг
Статус
Оффлайн
Регистрация
4 Янв 2020
Сообщения
2,996
Реакции[?]
1,275
Поинты[?]
5K
не проще делать флоат?
мб тебе нужна будет не только альфа, а к примеру увеличить размер какого нибудь ректа
Да, вполне, просто изъявил такое желание когда решил сделать цвет в RGB, ни кого не принуждаю
 
Эксперт
Статус
Оффлайн
Регистрация
30 Дек 2019
Сообщения
1,967
Реакции[?]
958
Поинты[?]
19K
Да, вполне, просто изъявил такое желание когда решил сделать цвет в RGB, ни кого не принуждаю
так а в чём проблема сделать в ImVec4?
like this: ImVec4(28 / 255.f, 36 / 255.f, 133 / 255.f, it_alpha->second)
такой штукой можно и лёрп сделать, для красивой и плавной сменой цвета
 
money++
Разработчик
Статус
Оффлайн
Регистрация
14 Июн 2018
Сообщения
638
Реакции[?]
339
Поинты[?]
22K
а какие значения нужно, что бы не было проблем с низким\высоким фпс?
Обновлять в зависимости от пройденного времени между двумя обновлениями.
Я бы сделал абстрактный класс который вызывал внутри себя InternalUpdate и просто переопределял эту функцию в дочерних классах для разных анимации. Например такой:
C++:
using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
using TimeDiff = std::chrono::duration<float>;

template <typename T>
class AbstractAnimation {
public:
  explicit AbstractAnimation(T value) :
      value_(value), last_update_(std::chrono::system_clock::now()) {}

  T GetValue() const {
    return value_;
  }
  void SetValue(T value) {
    value_ = std::move(value);
  }

  void Update() {
    auto cur_time = std::chrono::system_clock::now();
    this->InternalUpdate(cur_time - last_update_);
    last_update_ = cur_time;
  }

protected:
  virtual void InternalUpdate(TimeDiff time_diff) = 0;

private:
  T value_;
  TimePoint last_update_;
};

template <typename T>
class AnimationLinearIncrement : public AbstractAnimation<T> {
public:
  AnimationLinearIncrement(T value, T delta_per_sec)
    : AbstractAnimation<T>(value), delta_per_sec_(delta_per_sec) {}

protected:
  void InternalUpdate(TimeDiff time_diff) override {
    this->SetValue(this->GetValue() + (delta_per_sec_ * time_diff.count()));
  }

private:
  T delta_per_sec_;
};

Если хочешь можешь потом сделать класс с "переключателем" направления анимации, например так:
C++:
template <typename T>
class AnimationLinearIncrementWithSwitcher : public AbstractAnimation<T> {
public:
  AnimationLinearIncrementWithSwitcher(T value, T delta_per_sec)
      : AbstractAnimation<T>(value), delta_per_sec_(delta_per_sec) {}

  void SetIncrementDirection(bool set_incrementing = true) {
    this->Update();
    is_incrementing_ = set_incrementing;
  }

protected:
  void InternalUpdate(TimeDiff time_diff) override {
    if (is_incrementing_) {
      this->SetValue(this->GetValue() + (delta_per_sec_ * time_diff.count()));
    } else {
      this->SetValue(this->GetValue() - (delta_per_sec_ * time_diff.count()));
    }
  }

private:
  T delta_per_sec_;
  bool is_incrementing_{true};
};

Еще можно не хранить значение в классе, а только указатель на значение и избавиться от лишних GetValue() при применении класса в коде
 
Олдфаг
Статус
Оффлайн
Регистрация
4 Янв 2020
Сообщения
2,996
Реакции[?]
1,275
Поинты[?]
5K
Обновлять в зависимости от пройденного времени между двумя обновлениями.
Я бы сделал абстрактный класс который вызывал внутри себя InternalUpdate и просто переопределял эту функцию в дочерних классах для разных анимации. Например такой:
C++:
using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
using TimeDiff = std::chrono::duration<float>;

template <typename T>
class AbstractAnimation {
public:
  explicit AbstractAnimation(T value) :
      value_(value), last_update_(std::chrono::system_clock::now()) {}

  T GetValue() const {
    return value_;
  }
  void SetValue(T value) {
    value_ = std::move(value);
  }

  void Update() {
    auto cur_time = std::chrono::system_clock::now();
    this->InternalUpdate(cur_time - last_update_);
    last_update_ = cur_time;
  }

protected:
  virtual void InternalUpdate(TimeDiff time_diff) = 0;

private:
  T value_;
  TimePoint last_update_;
};

template <typename T>
class AnimationLinearIncrement : public AbstractAnimation<T> {
public:
  AnimationLinearIncrement(T value, T delta_per_sec)
    : AbstractAnimation<T>(value), delta_per_sec_(delta_per_sec) {}

protected:
  void InternalUpdate(TimeDiff time_diff) override {
    this->SetValue(this->GetValue() + (delta_per_sec_ * time_diff.count()));
  }

private:
  T delta_per_sec_;
};

Если хочешь можешь потом сделать класс с "переключателем" направления анимации, например так:
C++:
template <typename T>
class AnimationLinearIncrementWithSwitcher : public AbstractAnimation<T> {
public:
  AnimationLinearIncrementWithSwitcher(T value, T delta_per_sec)
      : AbstractAnimation<T>(value), delta_per_sec_(delta_per_sec) {}

  void SetIncrementDirection(bool set_incrementing = true) {
    this->Update();
    is_incrementing_ = set_incrementing;
  }

protected:
  void InternalUpdate(TimeDiff time_diff) override {
    if (is_incrementing_) {
      this->SetValue(this->GetValue() + (delta_per_sec_ * time_diff.count()));
    } else {
      this->SetValue(this->GetValue() - (delta_per_sec_ * time_diff.count()));
    }
  }

private:
  T delta_per_sec_;
  bool is_incrementing_{true};
};

Еще можно не хранить значение в классе, а только указатель на значение и избавиться от лишних GetValue() при применении класса в коде
А еще можно просто использовать GetFrameTime из имгуи...
 
money++
Разработчик
Статус
Оффлайн
Регистрация
14 Июн 2018
Сообщения
638
Реакции[?]
339
Поинты[?]
22K
Emton
Пользователь
Статус
Оффлайн
Регистрация
20 Мар 2021
Сообщения
115
Реакции[?]
40
Поинты[?]
0
Обновлять в зависимости от пройденного времени между двумя обновлениями.
Я бы сделал абстрактный класс который вызывал внутри себя InternalUpdate и просто переопределял эту функцию в дочерних классах для разных анимации. Например такой:
C++:
using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
using TimeDiff = std::chrono::duration<float>;

template <typename T>
class AbstractAnimation {
public:
  explicit AbstractAnimation(T value) :
      value_(value), last_update_(std::chrono::system_clock::now()) {}

  T GetValue() const {
    return value_;
  }
  void SetValue(T value) {
    value_ = std::move(value);
  }

  void Update() {
    auto cur_time = std::chrono::system_clock::now();
    this->InternalUpdate(cur_time - last_update_);
    last_update_ = cur_time;
  }

protected:
  virtual void InternalUpdate(TimeDiff time_diff) = 0;

private:
  T value_;
  TimePoint last_update_;
};

template <typename T>
class AnimationLinearIncrement : public AbstractAnimation<T> {
public:
  AnimationLinearIncrement(T value, T delta_per_sec)
    : AbstractAnimation<T>(value), delta_per_sec_(delta_per_sec) {}

protected:
  void InternalUpdate(TimeDiff time_diff) override {
    this->SetValue(this->GetValue() + (delta_per_sec_ * time_diff.count()));
  }

private:
  T delta_per_sec_;
};

Если хочешь можешь потом сделать класс с "переключателем" направления анимации, например так:
C++:
template <typename T>
class AnimationLinearIncrementWithSwitcher : public AbstractAnimation<T> {
public:
  AnimationLinearIncrementWithSwitcher(T value, T delta_per_sec)
      : AbstractAnimation<T>(value), delta_per_sec_(delta_per_sec) {}

  void SetIncrementDirection(bool set_incrementing = true) {
    this->Update();
    is_incrementing_ = set_incrementing;
  }

protected:
  void InternalUpdate(TimeDiff time_diff) override {
    if (is_incrementing_) {
      this->SetValue(this->GetValue() + (delta_per_sec_ * time_diff.count()));
    } else {
      this->SetValue(this->GetValue() - (delta_per_sec_ * time_diff.count()));
    }
  }

private:
  T delta_per_sec_;
  bool is_incrementing_{true};
};

Еще можно не хранить значение в классе, а только указатель на значение и избавиться от лишних GetValue() при применении класса в коде
:NotLikeThis::NotLikeThis::NotLikeThis:
 
#define VOID void
Начинающий
Статус
Оффлайн
Регистрация
13 Май 2017
Сообщения
120
Реакции[?]
24
Поинты[?]
13K
прибавляя сырые значения +-8 ты рискуешь увидеть различные проблемы при низком/высоком фпс, а еще зачем то ты создал свою функцию rgb_to_imvec4, хотя у ImVec4 и так есть конструктор под ргб значения, на гайд это мало смахивает
800.0f / io.Framerate

Хуй знает почему, но при объявлении std::map в структурах или виджетах ImGui – крашит.
 
Сверху Снизу