Caminhada aleatória tendenciosa e conservadora

13

Eu tenho um sprite que tem Velocitye Position, ou armazenado como Vector2. A cada Updateciclo, a velocidade é adicionada à posição.

Eu gostaria de dar ao sprite um terceiro vetor Target,. Novos destinos podem ser dados a qualquer iteração. Gostaria que o sprite se movesse essencialmente em um padrão de caminhada aleatória, no entanto, dois parâmetros devem ser expostos:

  1. Uma caminhada aleatória típica tem a mesma probabilidade de aumentar ou diminuir a distância a qualquer ponto Target(mais a pequena chance de movimento tangencial). Devo ser capaz de influenciar minha caminhada aleatória de modo que, embora ainda seja aleatória, a direção na qual o sprite "decida" tenha maior probabilidade de aproximá-lo Target.
  2. A caminhada aleatória deve ser "suave" - ​​o sprite não deve mudar rapidamente de direção, pois isso parecerá "tremer" ou "tremer" para o jogador. Ele deve se virar gradualmente de uma maneira ou de outra, movendo-se aleatoriamente, enquanto se aproxima lentamente quando a média é calculada.

Qual é uma maneira boa e simples de fazer isso? Se possível, dê a resposta como um Vector2 RandomWalk(Vector2 target)método.

Eu já tenho um NextGaussian(mean, stdev)método disponível, se isso for útil.

Superbest
fonte
Dê uma chance muito pequena de mudar de direção a cada quadro? E torne essa chance significativamente maior se não estiver se movendo na direção que você deseja?
BlueRaja - Danny Pflughoeft
Essa é uma boa solução. No entanto, eu preferiria evitar a mudança repentina e repentina de direção, se possível.
Superbest
1
Basta adicionar um fator de aceleração; assim, em vez de alterar as velocidades, você altera a aceleração, que por sua vez altera a velocidade. Isso deve remover jitter de movimento, e você pode apenas ajustar a aplicação de aceleração até que você começa um passeio suave
skeletalmonkey

Respostas:

4

Eu pegaria o comportamento de direção "vagar" (o código-fonte pode ser encontrado aqui ) e o ajustaria de uma maneira que os números aleatórios fossem tendenciosos em direção ao seu alvo.

bummzack
fonte
Sim, acho que os comportamentos de direção são o caminho a percorrer. Basta fazer um Wander + Seek e adicionar um peso baixo ao comportamento de busca.
krolth
6

Para obter uma caminhada aleatória suave, você pode usar splines Catmull-Rom . Esse tipo de spline pega uma sequência de pontos e gera curvas suaves que passam por cada ponto. Assim, você pode gerar pontos de referência aleatórios para o sprite percorrer e animá-lo ao longo de um spline Catmull-Rom através dos pontos de referência. Para que o spline funcione, você precisará de um total de quatro waypoints: os dois anteriores e os próximos. Quando o sprite alcançar um waypoint, jogue fora o mais antigo do seu conjunto de quatro e gere um novo, depois continue animando ao longo do spline.

Quanto a seguir em direção ao alvo, uma idéia seria compensar a distribuição da caminhada aleatória em direção ao alvo. Por exemplo, se você normalmente escolher um waypoint aleatório usando uma distribuição gaussiana centralizada na posição atual do seu sprite, poderá deslocar o centro do gaussiano por alguma distância predefinida em direção ao alvo. Os tamanhos relativos do deslocamento e o desvio padrão do Gaussian determinariam quão tendencioso é o movimento.

Nathan Reed
fonte
Eu pensei sobre isso, e embora a recomendação seja boa, eu gostaria que o viés fosse direcionado para a localização do jogador. Como o método spline exige que eu gere parte do caminho com antecedência, haverá um atraso na capacidade de seguir o jogador.
Superbest
@Superbest, se você usar uma curva de Bezier, precisará gerar apenas os próximos dois pontos - e poderá fazer o segundo ponto no futuro avançar em direção ao jogador enquanto ele se move.
31912 Jonathan Dickinson
2

Aqui está uma coisa que preparei em cerca de 20 minutos. Tomamos a direção do caminhante até o alvo, escolhemos uma direção dentro de uma certa quantidade de graus dessa direção (uma quantidade que diminui à medida que o caminhante se aproxima do alvo). Esse algoritmo também é responsável pela distância do alvo, para que ele não passe do alvo. Para encurtar a história, basicamente oscila para a esquerda e para a direita uma pequena quantidade aleatória e se aproxima do alvo à medida que ele se aproxima.

Para testar esse algoritmo, coloquei o andador em (10, 0, 10) e o alvo em (0, 0, 0). Na primeira vez que o algoritmo foi executado, ele escolheu aleatoriamente uma posição para o caminhante caminhar até (3,73f, 0, 6,71f). Depois que o caminhante alcançou essa posição, ele escolheu (2.11f, 0, 3.23), depois (0.96f, 0, 1.68f), depois (0.50f, 0, 0.79f), depois caminhou direto para o alvo, porque estava dentro uma distância mínima de tolerância.

Representado graficamente da vista aérea, o caminho se pareceria com os pontos da imagem abaixo, começando em 'W' (andador) e terminando em 'T' (alvo). Se você deseja um movimento mais natural, pré-calcularia alguns pontos antes do tempo e criaria um spline, oferecendo muitos outros pontos para que o caminhante o siga. Estimei como seria esse caminho depois de ser transformado em um spline e isso é representado pela linha na imagem.

insira a descrição da imagem aqui

E aqui está o código de exemplo:

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;
}

Com base no seu jogo específico, você pode ajustar distâncias, FOV, aleatoriedade e frequência em que isso é executado, até que atenda às suas necessidades. Tenho certeza de que o algoritmo pode ser limpo e otimizado, não gastei muito tempo com isso, só queria que fosse fácil de ler.

Nic Foster
fonte