Voreingenommener, konservativer Irrweg

13

Ich habe ein Sprite welches Velocityund Positionentweder als gespeichert hat Vector2. Bei jedem UpdateZyklus wird die Geschwindigkeit zur Position addiert.

Ich möchte dem Sprite einen dritten Vektor geben Target. Neue Ziele können bei jeder Iteration angegeben werden. Ich möchte, dass sich das Sprite im Wesentlichen in einem zufälligen Laufmuster bewegt, es müssen jedoch zwei Parameter verfügbar gemacht werden:

  1. Ein typischer zufälliger Gang vergrößert oder verkleinert gleichermaßen die Entfernung zu einem bestimmten Punkt Target(zuzüglich der geringen Wahrscheinlichkeit einer tangentialen Bewegung). Ich muss in der Lage sein, mein zufälliges Gehen so zu beeinflussen, dass die Richtung, in die das Sprite "entscheidet", eher dazu führt, dass es näher kommt, obwohl es noch zufällig ist Target.
  2. Der zufällige Gang sollte "glatt" sein - das Sprite sollte nicht schnell die Richtung ändern, da dies für den Spieler wie "Flackern" oder "Zittern" aussieht. Es sollte sich allmählich hin und her bewegen, sich zufällig bewegen und sich im Durchschnitt langsam nähern.

Was ist ein guter, einfacher Weg, dies zu tun? Wenn möglich, geben Sie die Antwort als Vector2 RandomWalk(Vector2 target)Methode an.

Ich habe bereits eine NextGaussian(mean, stdev)Methode zur Verfügung, wenn das hilfreich ist.

Super
quelle
Gibt es eine sehr kleine Chance, die Richtung für jeden Frame zu ändern? Und machen Sie diese Chance deutlich größer, wenn sie sich nicht in die gewünschte Richtung bewegt?
BlueRaja - Danny Pflughoeft
Das ist eine schöne Lösung. Ich würde es jedoch vorziehen, den plötzlichen, ruckartigen Richtungswechsel nach Möglichkeit zu vermeiden.
Super
1
Fügen Sie einfach einen Beschleunigungsfaktor hinzu. Anstatt also die Geschwindigkeiten zu ändern, ändern Sie die Beschleunigung, wodurch sich wiederum die Geschwindigkeit ändert. Dies sollte Jitter aus der Bewegung entfernen, und Sie können die Anwendung der Beschleunigung einfach optimieren, bis Sie einen reibungslosen Lauf erhalten
skeletalmonkey

Antworten:

4

Ich würde das "Wander" -Lenkverhalten (Quellcode finden Sie hier ) so anpassen, dass die Zufallszahlen auf Ihr Ziel ausgerichtet sind.

Blödmann
quelle
Ja, ich denke, Lenkverhalten ist der richtige Weg. Machen Sie einfach einen Wander + Seek und erhöhen Sie das Suchverhalten um ein geringes Gewicht.
krolth
6

Um einen reibungslosen, zufälligen Gang zu erzielen, können Sie Catmull-Rom-Splines verwenden . Diese Art von Spline nimmt eine Folge von Punkten auf und erzeugt glatte Kurven, die durch jeden Punkt verlaufen. So können Sie zufällige Wegpunkte erstellen, durch die sich das Sprite bewegen kann, und diese entlang eines Catmull-Rom-Splines durch die Wegpunkte animieren. Damit der Spline funktioniert, benötigen Sie insgesamt vier Wegpunkte: die vorherigen zwei und die nächsten zwei. Wenn das Sprite einen Wegpunkt erreicht, werfen Sie den ältesten aus Ihrer Vierergruppe weg und generieren Sie einen neuen. Fahren Sie dann mit der Animation entlang des Splines fort.

Eine Idee für die eventuelle Annäherung an das Ziel wäre es, die Verteilung des zufälligen Wegs zum Ziel zu verschieben. Wenn Sie beispielsweise in der Regel einen zufälligen Wegpunkt mit einer Gaußschen Verteilung auswählen, die auf der aktuellen Position Ihres Sprites zentriert ist, können Sie stattdessen die Mitte des Gaußschen um einen voreingestellten Abstand zum Ziel verschieben. Die relativen Größen des Versatzes und die Gaußsche Standardabweichung würden bestimmen, wie voreingenommen die Bewegung ist.

Nathan Reed
quelle
Ich habe darüber nachgedacht, und obwohl die Empfehlung nett ist, möchte ich, dass sich die Tendenz nach dem Standort des Spielers richtet. Da ich nach der Spline-Methode einen Teil des Pfades im Voraus generieren muss, wird die Fähigkeit, dem Spieler zu folgen, verzögert sein.
Superbest
@Superbest Wenn Sie eine Bezier-Kurve verwenden, brauchen Sie nur die nächsten zwei Punkte zu generieren - und Sie könnten den zweiten Punkt in der Zukunft in Richtung des Spielers machen, wenn dieser sich bewegt.
Jonathan Dickinson
2

Hier ist etwas, das ich in etwa 20 Minuten aufgeschlagen habe. Wir nehmen die Richtung vom Geher zum Ziel und wählen eine Richtung innerhalb eines bestimmten Grads von dieser Richtung aus (ein Betrag, der abnimmt, wenn der Geher sich seinem Ziel nähert). Dieser Algorithmus berücksichtigt auch die Entfernung zum Ziel, damit er nicht am Ziel vorbeigeht. Lange Rede kurzer Sinn, es wackelt im Grunde genommen eine kleine zufällige Menge nach links und rechts und landet auf dem Ziel, wenn es näher kommt.

Um diesen Algorithmus zu testen, platzierte ich den Walker auf (10, 0, 10) und das Ziel auf (0, 0, 0). Als der Algorithmus das erste Mal ausgeführt wurde, wählte er zufällig eine Position für den Fußgänger aus (3,73f, 0, 6,71f). Nachdem der Läufer diese Position erreicht hatte, wählte er (2.11f, 0, 3.23), dann (0.96f, 0, 1.68f), dann (0.50f, 0, 0.79f) und ging direkt zum Ziel, weil es sich innerhalb befand ein minimaler Toleranzabstand.

Aus der Vogelperspektive betrachtet würde der Pfad wie die Punkte im Bild unten aussehen, beginnend bei "W" (Gehhilfe) und endend bei "T" (Ziel). Wenn Sie eine natürlichere Bewegung wünschen, kalkulieren Sie einige Punkte im Voraus und erstellen einen Spline, der Ihnen viel mehr Punkte gibt, denen der Walker folgen kann. Ich habe geschätzt, wie dieser Pfad aussehen würde, nachdem er zu einem Spline verarbeitet wurde. Dies wird durch die Linie im Bild dargestellt.

Bildbeschreibung hier eingeben

Und hier ist der Beispielcode:

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

Abhängig von Ihrem spezifischen Spiel können Sie die Abstände, das Sichtfeld, die Zufälligkeit und die Häufigkeit der Ausführung anpassen, bis sie Ihren Anforderungen entsprechen. Ich bin sicher, der Algorithmus könnte ein bisschen aufgeräumt und optimiert werden, ich habe nicht viel Zeit damit verbracht, ich wollte nur, dass er einfach zu lesen ist.

Nic Foster
quelle