Суббота, 28 Декабря 2024, 09:19

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
  • Страница 1 из 1
  • 1
Модератор форума: Gnomov  
Движение объектов по ключевым точкам (waypoint)
QValidatorДата: Пятница, 25 Декабря 2015, 06:14 | Сообщение # 1
был не раз
Сейчас нет на сайте
Доброго времени суток. Помогите разобраться с проблемой smile
Алгоритм движения по точкам реализован на основе этой статьи: MOVING 2D OBJECTS BETWEEN WAYPOINTS. Он довольно прост в понимании и реализации на других языках, плюс ко всему: работает прекрасно, но с одним "НО" =[ он работает прекрасно только для фиксированного шага, не зависящего от времени затраченного на прорисовку/расчет логики.

Сейчас объясню: допустим есть таймер, который срабатывает каждые 10мсек, при срабатывании таймера объект движется на фиксированное кол-во пикселей - тут все ок. Теперь другая ситуация: таймер отсутствует, вместо него обычный луп while (true) { move(elapsedTime); } - вот в этом случае представленный алгоритм работает крайне нестабильно, потому что если объект почти достиг очередной точки и в игре произошел лаг, то следующее местоположение объекта окажется за точкой, в которую он следовал, при этом сам индекс (currentIndex) точек не изменится: объект отрисуется в неправильной позиции (перескочит через точку) и на следующем шаге продолжит свое движение в обратном направлении, в сторону точки, в которую он должен следовать согласно индексу. Все еще хуже, когда игра начинает лагать интенсивно: объект просто прыгает из стороны в сторону из-за того что он никак не может попасть в точку =)

Моя реализация выглядит так:
Код

void Unit::moveByWayPoints(qint64 time, qint64 elapsed)
{
    Q_UNUSED(time);

    // если движение не начиналось
    if(targetPointIndex == -1) {
        wayPoints = m_map->wayPoints(); // получаем маршрут (список индексов клеток игрового мира)
        wayPoints.takeFirst(); // удаляем стартовую точку (мы в ней)
        targetPointIndex = wayPoints.takeFirst(); // получаем индекс клетки в которую будем двигаться
    }

    // размер клетки игрового мира
    qreal cellSize = m_map->cellSize();

    // поучаем координаты центра клетки в которую движемся
    qreal deltac = cellSize/2 - width()/2;
    QVector2D target = QVector2D(m_map->cellAt(targetPointIndex)->col() * cellSize + deltac,
                    m_map->cellAt(targetPointIndex)->row() * cellSize + deltac);

    // текущие координаты объекта
    QVector2D current = QVector2D(x(), y());

    QVector2D diff = target - current;
    qreal distance = diff.length();

    // если не приблизились к точке достаточно близко, то движемся в ее сторону
    if(distance > 1.0f) {
        QVector2D direction = diff.normalized();
        qreal deltams = elapsed/1000.0f * cellSize * m_moveSpeed;

        qreal newx = x() + direction.x() * deltams;
        qreal newy = y() + direction.y() * deltams;
        
        /*
        QVector2D newPos(newx, newy);
        QVector2D newDirection = (target - newPos).normalized();
        
        if(!qFuzzyCompare(direction, newDirection)) {
            if(!wayPoints.isEmpty()) {
                targetPointIndex = wayPoints.takeFirst();
            }
        }
        */

        setPosition(QPointF(newx, newy));
    }
    // иначе получаем индекс следующей клетки маршрута
    else {
        if(!wayPoints.isEmpty()) {
            targetPointIndex = wayPoints.takeFirst();
        }
    }
}


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

Алгоритм работает только в случае прямолинейного движения объекта, но как только объекту нужно сменить направление (скажем, свернуть вправо), все ломается к чертям smile оно и понятно: объект рисуется в неправильной позиции и продолжает движение в новом направлении по диагонали (хотя такое как бы не предусмотрено "правилами" игры) из-за чего вектора направления никогда не совпадут.

Кто что может сказать по этому поводу? smile может посоветуете другой подход/алгоритм? Если игру получится доделать, то она возможно будет сетевой, поэтому наличие возможных лагов обеспечено, пока я их симулирую задержками в игровом цикле.


>=)
DaeloceДата: Пятница, 25 Декабря 2015, 07:14 | Сообщение # 2
был не раз
Сейчас нет на сайте
Тебе просто нужно изменить твою deltams в том случае, если вектор смещения оказался по длине больше чем вектор до конечной точки.

Код

if((direction * deltams).length() > diff.length() {
     deltams =  diff.length() / direction * deltams;
}


Минус такого решения, при приближении к waypoint'у будет происходить замедление, т.к. последний шаг всегда утыкается в точку маршрута, не зависимо от скорости и прошедшего времени. Если это критично, и существенное падение скорости перед этими точками не допустимо, можно сделать так. Выбираешь временной шаг который тебе удобен, например 10 мс, и все прошедшее между вызовами время делишь на кусочки по 10 мс, и для каждого временного отрезка делаешь смещения объекта, т.е. грубо говоря если тебе надо сдвинуться на 100 мс, ты двигаешься 10 раз по 10 мс, таким образом какой бы лаг по времени не был, ты каждый раз движешь удобными для тебя отрезками времени.
QValidatorДата: Пятница, 25 Декабря 2015, 07:47 | Сообщение # 3
был не раз
Сейчас нет на сайте
Первый вариант не подойдет, баловался с ним давно - замедление существенное, скорость движения стремится к нулю о-о-очень долго - это слишком критично smile движение должно быть равномерным (если не учитывать лаги, конечно).

Самым идеальным вариантом было бы сделать так же как в WarCraft III: если произошел лаг, то юниты двигаются не рывками, а просто ускоряется графика, как будто включили переметку вперед smile получаются, что юниты двигаются в ускоренном режиме, но не дергаются - сразу понятно что проблемы со скоростью передачи данных по сети, но это не сильно мешает воспринимать картинку. Реализация такой модели для меня остается загадкой.

Второй метод сейчас попробую впиндюрить, но сделаю привязку не ко времени, а к количеству допустимых шагов между клетками smile


>=)

Сообщение отредактировал QValidator - Пятница, 25 Декабря 2015, 07:48
DaeloceДата: Пятница, 25 Декабря 2015, 08:13 | Сообщение # 4
был не раз
Сейчас нет на сайте
Цитата QValidator ()
скорость движения стремится к нулю о-о-очень долго - это слишком критично


То ли я не понял что вы написали, то ли вы что-то не то сделали. Падение скорости будет только на последнем перед waypoint'ом шаге, и только на один этот шаг(шаг это тик по времени).

Цитата QValidator ()
а к количеству допустимых шагов между клетками


Тоже не ясно. Вам надо привязываться именно ко времени, потому что если между тиками прошло 400 мс, то вам и надо пройти на 400 мс. Деланьем вместо 1 шага на 400 мс 40 шагов на 10мс вы лишь избежите промахивание мимо цели и правильно отработаете передвижение.
QValidatorДата: Пятница, 25 Декабря 2015, 08:54 | Сообщение # 5
был не раз
Сейчас нет на сайте
Цитата Daeloce ()
то ли вы что-то не то сделали.

Да, вы правы smile это я перепутал. В том алгоритме про который я рассказывал скорость перемещения за один тик напрямую зависела от длины маршрута, а с каждым тиком длина уменьшается, значит и скорость на каждом тике уменьшалась.

Доработал ваше предложение так, чтобы учитывался остаток от измененной deltams на следующий шаг, вроде работает. Спасибо :)

Код

/* time - игровое время, мс с начала эпохи Unix
* elapsed - прошедшее время между тиками, мс
* residual - остаток предыдущего шага
*/
void Unit::moveByWayPoints(qint64 time, qint64 elapsed, qreal residual)
{
    Q_UNUSED(time);
    
    // если движение не начиналось
    if(targetPointIndex == -1) {
        wayPoints = m_map->wayPoints();
        wayPoints.takeFirst(); // remove start point
        targetPointIndex = wayPoints.takeFirst();
    }
    
    // размер клетки игрового мира
    qreal cellSize = m_map->cellSize();
    
    // получаем координаты центра клетки в которую движемся
    qreal deltac = cellSize/2 - width()/2;
    QVector2D target = QVector2D(m_map->cellAt(targetPointIndex)->col() * cellSize + deltac,
                    m_map->cellAt(targetPointIndex)->row() * cellSize + deltac);
    
    // текущие координаты объекта
    QVector2D current = QVector2D(x(), y());

    QVector2D diff = target - current;
    qreal distance = diff.length();
    
    // если не приблизились к точке достаточно близко, то движемся в ее сторону
    if(distance > 1) {
        QVector2D direction = diff.normalized();
        qreal deltams = elapsed/1000.0f * cellSize * m_moveSpeed + residual;

        qreal newx = x();
        qreal newy = y();
        
        // если вектор смещения оказался по длине больше
        // чем вектор до конечной точки, пересчитываем дельту
        // и вызываем следующий шаг
        if((direction * deltams).length() > diff.length()) {
            qreal newdeltams = diff.length() / (direction * deltams).length();
            qreal residual = deltams - newdeltams;

            newx += direction.x() * newdeltams;
            newy += direction.y() * newdeltams;
            setPosition(QPointF(newx, newy));

            if(!wayPoints.isEmpty()) {
                targetPointIndex = wayPoints.takeFirst();
                moveByWayPoints(time, 0, residual);
                return;
            }
        }

        newx += direction.x() * deltams;
        newy += direction.y() * deltams;

        setPosition(QPointF(newx, newy));
    }
    // иначе получаем индекс следующей клетки маршрута
    else {
        if(!wayPoints.isEmpty()) {
            targetPointIndex = wayPoints.takeFirst();
        }
    }
}


>=)

Сообщение отредактировал QValidator - Пятница, 25 Декабря 2015, 08:57
  • Страница 1 из 1
  • 1
Поиск:

Все права сохранены. GcUp.ru © 2008-2024 Рейтинг