LINUX.ORG.RU

Программирование анимации персонажа. Есть проблема

 , , , ,


0

1

Всех приветствую! Хочу двигать персонаж вправо на 10.0f ровно за свое заданное время: 2 сек, 0.5 сек и т.п.

Имеются следующие функции и глобальные переменные (очищенный от несущественного псевдокод):

float personage_X=-5.0f, personage_Y=-5.0f;
float distance_X=10.0, distance_Y=0.0;
float dX, dY;
double DELTA_T = 0.01667; //среднее значение delta_T даваемое программой 
                            //(по сути это 1000/60=16.6(6) мс/кадр)
double last_T_move;
double duration_T;
int duration_tick;
bool bmoveLeft  = false;
bool bmoveRight = false;



moveRight
{
   bmoveRight = true; // начинаю движение
   duration_T = 0.9;   // длительность в сек
   duration_tick = (int) (duration_T/DELTA_T); // кол-во тиков(кадров) для перемещения
   dX = distance_X/duration_tick;                   // шаг перемещения
   last_T_move = get_time();
}

update
{
  printf("update()\n");
  if (bmoveRight)
  {
      personage_X += dX;
      printf("now_T - last_T_move = %5.7f\n", now_T - last_T_move);
      if (now_T - last_T_move >= duration_T )
      {
         bmoveRight = false; // остановить движение
      }
    }
}

idle
{
  const double now_T = get_time();
  const double delta_T = now_T - last_T;
  last_T = now_T;
  printf("delta_T = %5.5f\n", delta_T);

  update(now_T);

  redisplay();
}

display
{
  printf("display()\n");
  render_personage();

  swap_buffers();
}

Вывод консоли работающей программы:

delta_T = 0.01669
update()
display()
delta_T = 0.01667
update()
display()
delta_T = 0.01667
update()
display()
delta_T = 0.01666
update()
display()
delta_T = 0.01667
update()
display()
delta_T = 0.01670
update()
display()
...

1) Теперь я начинаю движение, например за 2.2 сек вправо на 10:

personage coords: [-5.000, -5.000]
duration_tick = 131 // 131 кадр необходимо для достижения конечной точки перемещения 
dX = 0.07634        // шаг
now_T - last_T_move = 0.0156779
now_T - last_T_move = 0.0324781
now_T - last_T_move = 0.0491800
now_T - last_T_move = 0.0658500
now_T - last_T_move = 0.0825200
...
now_T - last_T_move = 2.1999640
now_T - last_T_move = 2.2166250
personage coords: [5.153, -5.000]  //  в идеале должно быть [5.000, -5.000]
                                   //  еще только маленькая погрешность

2) Движение за 0.9 сек вправо на 10:

personage coords: [-5.000, -5.000]
duration_tick = 53 // 53 кадра необходимо для достижения конечной точки перемещения 
dX = 0.18868       // шаг
now_T - last_T_move = 0.0135901
now_T - last_T_move = 0.0302720
now_T - last_T_move = 0.0469420
now_T - last_T_move = 0.0636110
...
now_T - last_T_move = 0.9139049
personage coords: [5.377, -5.000]  //  в идеале должно быть [5.000, -5.000]
                                   //  уже существенная погрешность

3) Движение за 0.2 сек вправо на 10:

personage coords: [-5.000, -5.000]
duration_tick = 11
dX = 0.90909
now_T - last_T_move = 0.0133729
now_T - last_T_move = 0.0302980
now_T - last_T_move = 0.0469708
now_T - last_T_move = 0.0636489
now_T - last_T_move = 0.0803308
...
now_T - last_T_move = 0.1970379
now_T - last_T_move = 0.2136910
personage coords: [6.818, -5.000]  // в идеале должно быть [5.000, -5.000]
                                   //  бешенейшая погрешность (!)
                                   // Alarm! Что делать?

Из трех экспериментов видно, что чем быстрее я хочу переместиться, то тем дальше перелетаю требуемую конечную точку.

Хотелось бы услышать компетентное мнение по устранению данной проблемы и/или возможного изменения подхода к анимации в принципе. Спасибо за помощь!


duration_tick = (int) (duration_T/DELTA_T); // кол-во тиков(кадров) для перемещения dX = distance_X/duration_tick;

нельзя переходить к интам при таких вычислениях. потеря точности в силу округлений. вообще вся физика, или геометрия считается либо в чисто плавающих, либо в «плавающих с фиксированной точкой», и переход к целым, если он нужен делается в самом конце, чтобы точность не терялась.

alysnix ★★★
()

Чёт дичь какая-то в коде. Я не только про то что выше написали, а вообще про саму логику в виде вызова функций. Это к какой-то либе/движку привязано или сами написали? Если сами то архитектура сильно неудобная. Так большую программу не сделать.

peregrine ★★★★★
()

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

ya-betmen ★★★★★
()
Ответ на: комментарий от alysnix

Сделал double вместо int :

double duration_tick;
...
duration_tick = duration_T/DELTA_T;
dX = 10.0/duration_tick;

Перескакивание немного уменьшилось, но это не решает проблему принципиально.

Точное перемещение получается только при больших временах. Вероятно надо менять сам способ анимации.

На какой - я пока не знаю.

Gyros
() автор топика
Последнее исправление: Gyros (всего исправлений: 2)

https://github.com/mobius3/tweeny

Tweeny is an inbetweening library designed for the creation of complex animations for games and other beautiful interactive software. It leverages features of modern C++ to empower developers with an intuitive API for declaring tweenings of any type of value, as long as they support arithmetic operations.

The goal of Tweeny is to provide means to create fluid interpolations when animating position, scale, rotation, frames or other values of screen objects, by setting their values as the tween starting point and then, after each tween step, plugging back the result.

dataman ★★★★★
()
Ответ на: комментарий от dataman

Спасибо за ссылку. Но ведь и в этих либах люди как-то реализовывают анимацию. Мне нужен свой код. В этом вся соль.

Gyros
() автор топика
Ответ на: комментарий от LINUX-ORG-RU
float lerp(float a, float b, float t)
{
    return a * (1.0 - t) + (b * t);
}

Т.е. мне надо не шагом dX прибавлять к координате, а линейную интерполяцию использовать? Надо подумать..

Gyros
() автор топика
Ответ на: комментарий от Gyros

Мне нужен свой код. В этом вся соль.

🆗, другим пригодится. :)

dataman ★★★★★
()

Меняете тип анимации на root motion, т. е. перемещение в пространстве зависит только от ввода контроллера, после чего настраиваете animation rate чтобы он визуально подходил пройденому пути. Почитайте про root motion animations например в доках по UE, они там на пальцах объясняют.

Obezyan
()
Последнее исправление: Obezyan (всего исправлений: 1)

Хочу двигать персонаж

не персонаж а персонажа

Из трех экспериментов видно, что чем быстрее я хочу переместиться, то тем дальше перелетаю требуемую конечную точку.

Выводи рядом с временем ещё и текущую координату и наглядно всё увидишь.

А вообще - ты делаешь два лишних шага, возьмём к примеру вот:

now_T - last_T_move = 2.1999640
now_T - last_T_move = 2.2166250

2.1999 это округлённое 2.2 и ты должен был остановиться уже к моменту наступления этого времени. Вместо этого ты считаешь, во-первых, что 2.199<2.2 и делаешь лишний шаг, а потом на 2.216 делаешь ещё один шаг т.к. у тебя сначала делается шаг и только потом проверяется время.

Соответственно, во-первых поставь проверку времени до того, как увеличиваешь координату, а не после, а во-вторых, чтобы разом убрать все ошибки округлений, считай всё в тиках сразу а не в секундах.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 3)
Ответ на: комментарий от alysnix

Ещё как можно. Только перейти надо было не только тут.

делается в самом конце, чтобы точность не терялась.

Наоборот, в самом начале, измеряем время в тиках и выкидываем всю нецелую логику.

firkax ★★★★★
()

ЯННП, но в любом случае - движение надо корректировать в процессе движения.

То есть на каждом шаге:

шаг_по_времени = min(максимальный шаг, оставшееся время)

смещение = шаг_по_времени*оставшееся_расстояние/оставшееся время.

AntonI ★★★★★
()

Есть проблема

Хочу двигать персонаж вправо на 10.0f ровно

t184256 ★★★★★
()
Ответ на: комментарий от firkax

Да, огромное спасибо firkax! Перевел все на тики. Проверка на время стала не нужна, вместо нее в update теперь:

if (current_Tick < duration_Tick )
{
       personage_X -= dX;
       current_Tick++;
}

Эта анимация (буду называть ее «шаговой», т.к. все время прибавляется или отнимается шаг: +/-dX) работает точно как часы.

Вот тут я сдвигаюсь влево на 10 за 0.25 сек:

personage coords: [-5.000, -5.000]
duration_Tick = 14 // 14 кадров необходимо для достижения конечной точки перемещения 
dX = 0.71429       // шаг 
current_Tick =  0  personage_X =  -5.000
current_Tick =  1  personage_X =  -5.714
current_Tick =  2  personage_X =  -6.429
...
current_Tick = 12  personage_X =  -13.571
current_Tick = 13  personage_X =  -14.286
current_Tick = 14  personage_X =  -15.000
elapsed time = 0.24698
personage coords: [-15.000, -5.000]

Теперь я хочу сделать анимацию не «шаговую», а с использованием интерполяции:

moveRight_WithLerp()
{
   bmoveRight = true;   // начинаю движение вправо
   duration_T = 0.45;   // длительность в сек
   duration_Tick = (int) (duration_T/DELTA_T);  // кол-во тиков(кадров) для перемещения
   old_personage_X = personage_X;          // начальная(где он сейчас) позиция персонажа
   new_personage_X = personage_X + 10.0f;  // конечная (будущая) позиция персонажа
   animationValue = 0.0f;          // параметр анимации (изменяется от 0 до 1)
        
   start_move_T = get_time();   // фиксирую время начала движения
}

update(double now_T)
{
  printf("update()\n");
  if (bmoveRight)
  {
      if ( animationValue < 1.0f )
      {
          printf("animationValue = %5.5f  personage_X =  %5.3f\n", animationValue, personage_X);
          animationValue += ?;
          personage_X = lerp(old_personage_X, new_personage_X, animationValue);
      }
      else
      {
          bmoveRight = false;    // остановить движение
          end_move_T = now_T;
          printf("elapsed time = %5.5f\n", end_move_T - start_move_T);
      }
    }
}

Тут проблема, что надо иметь шаг для animationValue, которое изменяется от 0 до 1.

Как получить этот шаг для такого нормированного времени?

Есть просто время double now_T.

Нужно его как-то нормировать с учетом duration_T = 0.45.

Какие будут предложения как это грамотно сделать?

Gyros
() автор топика
Последнее исправление: Gyros (всего исправлений: 2)
Ответ на: комментарий от firkax

Ну я даже не знаю, давай прокомментирую что-ли всё что мне не понравилось

float personage_X=-5.0f, personage_Y=-5.0f;//почему оно к персонажу прибито? Что это за графическая абстракция такая?
float distance_X=10.0, distance_Y=0.0; //snake case+camel case
float dX, dY; //camel case
double DELTA_T = 0.01667; //среднее значение delta_T даваемое программой (КАПС + snake case)
                            //(по сути это 1000/60=16.6(6) мс/кадр)
double last_T_move;
double duration_T;//что тут T означает? time?
int duration_tick;//а тут-то чего не T?
bool bmoveLeft  = false;//bmove - я даже не знаю что тут опять с codestyle
bool bmoveRight = false;



moveRight
{
   bmoveRight = true; // начинаю движение
   duration_T = 0.9;   // длительность в сек
   duration_tick = (int) (duration_T/DELTA_T); // кол-во тиков(кадров) для перемещения а куда округлять полтора землекопа?
   dX = distance_X/duration_tick;                   // шаг перемещения
   last_T_move = get_time();
}

update
{
  printf("update()\n");
  if (bmoveRight)
  {
      personage_X += dX;
      printf("now_T - last_T_move = %5.7f\n", now_T - last_T_move);
      if (now_T - last_T_move >= duration_T )
      {
         bmoveRight = false; // остановить движение
      }
    }
}

idle
{
  const double now_T = get_time();
  const double delta_T = now_T - last_T;
  last_T = now_T;
  printf("delta_T = %5.5f\n", delta_T);

  update(now_T);

  redisplay();//даже индусы так функции по перерисовке не называют, ну оно и понятно, у индусов английский вовсю используется redraw там или repaint я бы понял, но redisplay или там какая-то особая концепция display используется вокруг которой либа построена?
}

display//а почему не render/draw? Работы с дисплеем я тут не вижу
{
  printf("display()\n");
  render_personage();

  swap_buffers();
}
Исходя из неоднородности кода и кривоватых абстракций код написан студентом или любителем. Ладно бы одна две строки странные были бы, но тут больше половины странного.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 2)
Ответ на: комментарий от peregrine

Про названия переменных: код очевидно полностью черновой, а автор очевидно новичёк, ничего удивительного, и дичью это не называют. То что одна из низ капсом это наверно имелась ввиду константа, но по причине чернового кода недооформленная нормально. Даже более того, судя по «прототипам» функций это и не код вовсе, а псевдокод, так что претензий ещё меньше (а то можно ещё спросить почему там глобальные переменные слишком используются). Про название функции - это вообще не вопрос кода.

Про округление - ничего страшного, речь по сути про декорации игрового движка и главное чтоб выглядело красиво, а не точность математики. Красивость вида результата будет оцениваться вообще не по коду, а по картинке, которая получится на экране, на глаз при запуске игры, а устранение плавающих точек вполне полезно. И подозреваю что подобное выкидывание дробной части в игровых движках повсеместно применяется.

Вобщем, недостатки конечно есть но слово «дичь» мимо. Надо смотреть реальный проект а не кусок наброска, потому я и написал - слишком мало кода чтоб в нём дичь увидеть.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Ребят! Спасибо за критику. Но хотелось бы по существу вопроса, а не про названия переменных и мой опыт.

Здесь приведена только часть относящаяся к анимации, но это сама суть.

Хочу еще добавить некоторые виды easing-анимации. Потом оберну в классы.

Анимация нужна для этого героя в центре и других вредных персонажей:

https://postimg.cc/zHY9pMN4

Gyros
() автор топика
Ответ на: комментарий от Gyros

про конкретику я плясал всегда от аффинных преобразований и операций над матрицами, но то на чистом OpenGL/vulcan/sdl2 и соответственно вся логика вокруг очевидных для такого подхода методов строилась.

peregrine ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.