Предвзятая, консервативная случайная прогулка

13

У меня есть спрайт , который имеет Velocityи Position, либо хранится в виде Vector2. В каждом Updateцикле скорость добавляется к позиции.

Я хотел бы дать спрайту третий вектор Target. Новые цели могут быть заданы на любой итерации. Я бы хотел, чтобы спрайт по существу перемещался в произвольном порядке, однако необходимо указать два параметра:

  1. Типичная случайная прогулка в равной степени может увеличить или уменьшить расстояние до любого заданного Target(плюс небольшая вероятность тангенциального движения). Я должен быть в состоянии сместить мою случайную прогулку так, чтобы, хотя она все еще была случайной, направление, в котором «решает» спрайт, с большей вероятностью приблизило бы его Target.
  2. Случайное блуждание должно быть «плавным» - спрайт не должен быстро менять направление, поскольку это будет выглядеть как «мерцание» или «дрожание» игрока. Он должен постепенно отклоняться в ту или иную сторону, двигаясь случайным образом, и медленно приближаться при усреднении.

Какой хороший, простой способ сделать это? Если возможно, дайте ответ как Vector2 RandomWalk(Vector2 target)метод.

У меня уже есть NextGaussian(mean, stdev)метод, если он полезен.

Superbest
источник
Дайте ему очень маленький шанс изменить направление каждого кадра? И сделать этот шанс значительно больше, если он не движется в нужном вам направлении?
BlueRaja - Дэнни Пфлюгофт
Это хорошее решение. Однако я бы предпочел избегать резких, резких изменений направления, если это возможно.
Superbest
1
Просто добавьте коэффициент ускорения, поэтому вместо изменения скоростей вы изменяете ускорение, которое, в свою очередь, меняет скорость. Это должно устранить дрожание от движения, и вы можете просто настроить приложение ускорения, пока не получите плавный ход
skeletalmonkey

Ответы:

4

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

bummzack
источник
Да, я думаю, что рулевое поведение - это путь. Просто сделайте Wander + Seek и добавьте малый вес к поведению поиска.
Кролт
6

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

Что касается в конечном итоге движения к цели, то одной идеей будет компенсировать распределение случайного блуждания к цели. Например, если вы обычно выбираете случайную путевую точку, используя гауссово распределение с центром в текущей позиции вашего спрайта, вы можете вместо этого сместить центр гауссиана на некоторое заданное расстояние до цели. Относительные размеры смещения и стандартное отклонение Гаусса будут определять, насколько смещено движение.

Натан Рид
источник
Я думал об этом, и хотя рекомендация хороша, я хотел бы, чтобы уклон был к местоположению игрока. Так как метод сплайна требует, чтобы я заранее сгенерировал часть пути, у меня будет отставание в способности следовать за игроком.
Superbest
@Superbest, если вы используете кривую Безье, вам нужно только сгенерировать следующие две точки - и вы можете сделать так, чтобы вторая точка в будущем двигалась к игроку, когда он двигался.
Джонатан Дикинсон
2

Вот что-то, что я взбил за 20 минут. Мы берем направление от ходунка к цели, выбираем направление в пределах определенного количества градусов от этого направления (величина уменьшается, когда ходок приближается к своей цели). Этот алгоритм также учитывает расстояние до цели, чтобы он не проходил мимо цели. Короче говоря, он в основном колеблется влево и вправо на небольшую случайную величину и попадает в цель по мере приближения.

Чтобы проверить этот алгоритм, я поместил Уокера в (10, 0, 10), а цель в (0, 0, 0). В первый раз, когда алгоритм запускал его, случайным образом выбирал позицию, по которой должен идти ходок (3.73f, 0, 6.71f). После того, как ходок достиг этой позиции, он выбрал (2.11f, 0, 3.23f), затем (0.96f, 0, 1.68f), затем (0.50f, 0, 0.79f), затем он шел прямо к цели, потому что находился в пределах минимальное расстояние допуска.

На графике с высоты птичьего полета траектория будет выглядеть как точки на изображении ниже, начиная с «W» (ходок) и заканчивая «T» (цель). Если вы хотите более естественное движение, вы заранее рассчитаете несколько очков и создадите сплайн, который даст вам гораздо больше очков, за которыми вы можете следить за ходоком. Я оценил, как будет выглядеть этот путь после превращения в сплайн, и это представлено линией на изображении.

введите описание изображения здесь

А вот пример кода:

Vector3 WalkerPosition = new Vector3(10, 0, 10);
Vector3 TargetPosition = Vector3.Zero;

public Game1()
{
    // Each time you reach the next walk-to position, call this again.
    // Eventually you'll reach your target, assuming the target isn't moving away
    // from the walker faster than the walker can reach them.
    Vector3 NextWalkToPosition = PickRandomTarget();
}

public Vector3 PickRandomTarget()
{
    // For this code sample we'll assume that our two targets are on
    // the same horizontal plane, for simplicity.

    Vector3 directionToTarget = ( TargetPosition - WalkerPosition );
    float distance = directionToTarget.Length();
    directionToTarget.Normalize();

    float distanceThisIteration = distance * 0.5f;

    // We should never walk too little or too far, to make this more realistic
    // you could randomize the walking distance each iteration a bit.
    distanceThisIteration = MathHelper.Clamp(distanceThisIteration, 1.0f, 10.0f);

    // We're within minimum distance to the target, so just go straight to them
    if (distanceThisIteration > distance)
    {
        return TargetPosition;
    }

    directionToTarget *= distanceThisIteration; // Walk roughly halfway to the target            

    // Now we pick a new walking direction within an FOV that gets smaller as
    // we get closer to the target. We clamp the FOV between 0 and 90 degrees (45 degrees in either direction).
    const float walkerAggroRadius = 30.0f; // Walker aggros when within 30 units of target

    // Any distance outside of 30 we'll just treat as 30.
    float distanceMod = MathHelper.Clamp(distance, 0.0f, walkerAggroRadius);

    // We need a percentage value representing the current distance between the min 0, and max, 30
    float percentageAlongDistance = distanceMod / walkerAggroRadius;

    // We want FOV from center, so we cut the final FOV result in half
    float maxFOVAtThisDistance = MathHelper.Lerp(0.0f, MathHelper.PiOver2, percentageAlongDistance) * 0.5f;

    // Now we pick a random FOV from center within our maxFOV based on how far we are
    // from the target
    Random rand = new Random(System.DateTime.Now.Second);
    float randFOV = (float)(rand.NextDouble() * maxFOVAtThisDistance);

    // Right now our FOV value is an FOV from a vector pointing directly at our target, we now
    // need to randomly choose if we're going to aim to the left or right of the target. We'll
    // treat a result of 0 as left, and 1 as right
    int randDirection = rand.Next(2);
    if (randDirection == 0) // Left
    {
        // Rotate our direction vector left by randFOV radians
        return WalkerPosition + RotateAroundPoint(directionToTarget, Vector3.Zero, Vector3.UnitY, -randFOV);
    }
    else // Right
    {
        return WalkerPosition + RotateAroundPoint(directionToTarget, Vector3.Zero, Vector3.UnitY, randFOV);
    }
}

// Generic helper function to rotate a vector by a specific amount of degrees
public Vector3 RotateAroundPoint( Vector3 point, Vector3 originPoint, Vector3 rotationAxis, float radiansToRotate )
{
    Vector3 diffVect = point - originPoint;

    Vector3 rotatedVect = Vector3.Transform(diffVect, Matrix.CreateFromAxisAngle(rotationAxis, radiansToRotate));

    rotatedVect += originPoint;

    return rotatedVect;
}

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

Ник Фостер
источник