การเดินแบบสุ่มแบบเอนเอียงและอนุรักษ์นิยม


13

ฉันมีเทพดาซึ่งมีVelocityและทั้งเก็บไว้เป็นPosition Vector2ในแต่ละUpdateรอบความเร็วจะถูกเพิ่มเข้าไปในตำแหน่ง

ฉันอยากให้สไปรต์เวกเตอร์ตัวที่สาม, Target. อาจมีการกำหนดเป้าหมายใหม่ในการวนซ้ำใด ๆ ฉันต้องการให้ผีสไปรต์เคลื่อนไหวเป็นหลักในรูปแบบการเดินแบบสุ่ม แต่ต้องมีพารามิเตอร์สองพารามิเตอร์ดังนี้:

  1. การเดินสุ่มโดยทั่วไปมีแนวโน้มที่จะเพิ่มหรือลดระยะทางให้เท่ากันTarget(บวกกับโอกาสเล็กน้อยของการเคลื่อนไหวแบบสัมผัส) ฉันต้องสามารถอคติการเดินแบบสุ่มของฉันในขณะที่ยังคงสุ่มทิศทางที่สไปรต์ "ตัดสินใจ" ควรมีแนวโน้มที่จะนำมันเข้ามาใกล้Targetยิ่งขึ้น
  2. การเดินแบบสุ่มควร "ราบรื่น" - สไปรต์ไม่ควรเปลี่ยนทิศทางอย่างรวดเร็วเนื่องจากจะดูเหมือนว่า "ริบหรี่" หรือ "สั่น" กับผู้เล่น มันควรค่อย ๆ เปลี่ยนทิศทางด้วยวิธีนี้หรืออย่างนั้นเคลื่อนแบบสุ่มในขณะที่เข้าใกล้อย่างช้า ๆ เมื่อเฉลี่ย

อะไรคือวิธีที่ดีและเรียบง่ายในการทำสิ่งนี้? ถ้าเป็นไปได้ให้คำตอบเป็นVector2 RandomWalk(Vector2 target)วิธี

ฉันมีNextGaussian(mean, stdev)วิธีที่ใช้ได้อยู่แล้วหากเป็นประโยชน์


ให้โอกาสเล็กน้อยในการเปลี่ยนทิศทางทุกเฟรมหรือไม่ และทำให้โอกาสนั้นมีขนาดใหญ่ขึ้นอย่างมากหากไม่ได้เคลื่อนที่ไปในทิศทางที่คุณต้องการ?
BlueRaja - Danny Pflughoeft

นั่นเป็นทางออกที่ดี อย่างไรก็ตามฉันต้องการหลีกเลี่ยงการเปลี่ยนแปลงทิศทางฉับพลันหากเป็นไปได้
Superbest

1
เพียงเพิ่มปัจจัยเร่งความเร็วดังนั้นแทนที่จะเปลี่ยนความเร็วคุณเปลี่ยนความเร่งซึ่งจะเปลี่ยนความเร็ว สิ่งนี้จะช่วยกำจัดกระวนกระวายใจจากการเคลื่อนไหวและคุณสามารถปรับแต่งการเร่งความเร็วได้จนกว่าคุณจะเดินได้อย่างราบรื่น
skeletalmonkey

คำตอบ:


4

ฉันจะใช้พฤติกรรมการหมุน "เดิน" (ซอร์สโค้ดสามารถพบได้ที่นี่ ) และปรับแต่งเพื่อให้ตัวเลขสุ่มนั้นเอนเอียงไปยังเป้าหมายของคุณ


ใช่ฉันคิดว่าพฤติกรรมการขับขี่เป็นวิธีที่จะไป เพียงแค่แสวงหา Wander + แสวงหาและเพิ่มน้ำหนักเบาให้กับพฤติกรรมการแสวงหา
krolth

6

ที่จะได้รับการสุ่มเดินเรียบคุณสามารถใช้เส้นโค้ง Catmull-Rom เส้นโค้งประเภทนี้ใช้ลำดับของจุดและสร้างเส้นโค้งเรียบที่ผ่านแต่ละจุด ดังนั้นคุณสามารถสร้างจุดสุ่มเพื่อให้สไปรต์เคลื่อนที่ผ่านและเคลื่อนไหวไปตามแกน Catmull-Rom ผ่านจุดอ้างอิง เพื่อให้อิสระในการทำงานคุณจะต้องมีจุดรวมสี่จุดนั่นคือสองจุดก่อนหน้าและสองจุดถัดไป เมื่อเทพดาถึงจุดทางหนึ่งให้โยนชุดที่เก่าแก่ที่สุดของคุณในชุดที่สี่และสร้างขึ้นใหม่จากนั้นให้เคลื่อนไหวต่อไปตามเส้นโค้ง

ในที่สุดสำหรับการมุ่งสู่เป้าหมายความคิดหนึ่งก็คือเพื่อชดเชยการกระจายของการเดินสุ่มไปสู่เป้าหมาย ตัวอย่างเช่นหากคุณมักจะเลือกจุดสุ่มโดยใช้การแจกแจงแบบเกาส์ซึ่งมีศูนย์กลางอยู่ที่ตำแหน่งปัจจุบันของสไปรต์ของคุณคุณสามารถชดเชยจุดศูนย์กลางของเกาส์แทนด้วยระยะทางที่กำหนดล่วงหน้าไปยังเป้าหมาย ขนาดสัมพัทธ์ของการชดเชยและส่วนเบี่ยงเบนมาตรฐานของเกาส์เซียนจะกำหนดว่าการลำเอียงเคลื่อนที่อย่างไร


ฉันคิดเกี่ยวกับเรื่องนี้และในขณะที่การแนะนำนั้นดีฉันอยากให้อคติไปยังตำแหน่งของผู้เล่น เนื่องจากวิธีการ spline ทำให้ฉันต้องสร้างเส้นทางล่วงหน้าบางอย่างจะมีความล่าช้าในการติดตามผู้เล่น
Superbest

@ สุดยอดถ้าคุณใช้ Bezier curve คุณจะต้องสร้างจุดสองจุดถัดไป - และคุณสามารถทำการย้ายจุดที่สองในอนาคตที่มีต่อผู้เล่นในขณะที่เขาเคลื่อนที่ไปรอบ ๆ
Jonathan Dickinson

2

นี่คือบางสิ่งที่ฉันทำขึ้นในเวลาประมาณ 20 นาที เรานำทิศทางจากวอล์คเกอร์ไปยังเป้าหมายเลือกทิศทางภายในระดับหนึ่งของทิศทางนั้น (จำนวนที่ลดลงเมื่อวอล์คเกอร์เข้าใกล้เป้าหมายมากขึ้น) อัลกอริธึมนี้ยังคำนึงถึงระยะทางสู่เป้าหมายเพื่อที่จะไม่ผ่านเป้าหมาย เนื้อเรื่องย่อสั้น ๆ โดยทั่วไปจะโยกเยกไปทางซ้ายและขวาเล็กน้อยโดยสุ่ม

เพื่อทดสอบอัลกอริทึมนี้ฉันวางวอล์คเกอร์ที่ (10, 0, 10) และเป้าหมายที่ (0, 0, 0) ครั้งแรกที่อัลกอริทึมวิ่งสุ่มเลือกตำแหน่งสำหรับวอล์คเกอร์เพื่อเดินไปที่ (3.73f, 0, 6.71f) หลังจากวอล์คเกอร์ถึงตำแหน่งนั้นก็เลือก (2.11f, 0, 3.23) จากนั้น (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, การสุ่มและความถี่ที่เกมนี้รันจนกว่าจะเหมาะกับความต้องการของคุณ ฉันแน่ใจว่าอัลกอริทึมสามารถทำความสะอาดได้เล็กน้อยและปรับให้เหมาะสมฉันไม่ได้ใช้เวลามากกับเรื่องนั้นฉันแค่อยากให้มันอ่านง่าย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.