ยินดีต้อนรับสู่โลกมหัศจรรย์ของการวางแผนการเคลื่อนไหวที่ไม่ใช่แบบองค์รวม ผมขอแนะนำให้ทำเช่นนี้โดยใช้การวางแผนเส้นทางตารางตาข่าย ทางเลือกอื่น ๆ รวมถึงRRT kinodynamicและการเพิ่มประสิทธิภาพของวิถี ระบบที่ไม่ใช่แบบองค์รวมประกอบด้วยรถยนต์เรือ unicycles หรืออะไรก็ตามที่ยานพาหนะไม่สามารถเดินทางไปในทิศทางใดก็ได้ตามที่ต้องการ การวางแผนสำหรับระบบเหล่านี้นั้นยากกว่าระบบแบบฮอโลโลคัลมากและจนถึง ~ 2000 อยู่บนขอบของการวิจัยทางวิชาการ ทุกวันนี้มีอัลกอริทึมมากมายให้เลือกใช้ซึ่งทำงานได้อย่างเหมาะสม
นี่คือวิธีการทำงาน
สถานะ
รถของคุณกำหนดค่าQเป็นจริงรัฐ 3D ที่มี x รถตำแหน่ง Y และปฐมนิเทศที โหนดในอัลกอริทึม A * ของคุณนั้นเป็นเวกเตอร์ 3 มิติ
class Node
{
// The position and orientation of the car.
float x, y, theta;
}
การปฏิบัติ
แล้วขอบล่ะ
มันยากกว่าเล็กน้อยเพราะรถของคุณสามารถเลือกจำนวนอนันต์เพื่อหมุนวงล้อได้ ดังนั้นเราสามารถทำให้เข้าถึงการวางแผนตารางตาข่ายโดยการ จำกัด จำนวนของการกระทำที่รถสามารถนำไปใช้เป็นชุดต่อเนื่อง เพื่อความเรียบง่ายสมมติว่ารถไม่เร่ง แต่สามารถเปลี่ยนความเร็วได้ทันที ในกรณีของเราAสามารถเป็นดังนี้:
class Action
{
// The direction of the steering wheel.
float wheelDirection;
// The speed to go at in m/s.
float speed;
// The time that it takes to complete an action in seconds.
float dt;
}
ตอนนี้เราสามารถสร้างชุดการกระทำที่ไม่ต่อเนื่องที่รถสามารถทำได้ตลอดเวลา ตัวอย่างเช่นสิทธิ์ที่ยากขณะกดแก๊สเต็ม 0.5 วินาทีจะมีลักษณะดังนี้:
Action turnRight;
turnRight.speed = 1;
turnRight.wheelDirection = 1;
turnRight.dt = 0.5;
การนำรถกลับด้านและสำรองจะมีลักษณะเช่นนี้:
Action reverse;
reverse.speed = -1;
reverse.wheelDirection = 0;
reverse.dt = 0.5;
และรายการการกระทำของคุณจะเป็นดังนี้:
List<Action> actions = { turnRight, turnLeft, goStraight, reverse ...}
คุณต้องมีวิธีการกำหนดวิธีการดำเนินการที่โหนดส่งผลให้โหนดใหม่ สิ่งนี้เรียกว่าพลวัตไปข้างหน้าของระบบ
// These forward dynamics are for a dubin's car that can change its
// course instantaneously.
Node forwardIntegrate(Node start, Action action)
{
// the speed of the car in theta, x and y.
float thetaDot = action.wheelDirection * TURNING_RADIUS;
// the discrete timestep in seconds that we integrate at.
float timestep = 0.001;
float x = start.x;
float y = start.y;
float theta = start.theta;
// Discrete Euler integration over the length of the action.
for (float t = 0; t < action.dt; t += timestep)
{
theta += timestep * thetaDot;
float xDot = action.speed * cos(theta);
float yDot = action.speed * sin(theta);
x += timestep * xDot;
y += timestep * yDot;
}
return Node(x, y, theta);
}
เซลล์กริดแบบแยก
ตอนนี้ที่จะสร้างตารางตาข่ายทั้งหมดที่เราต้องทำคือกัญชารัฐของรถที่เข้าสู่เซลล์ตารางต่อเนื่อง สิ่งนี้จะเปลี่ยนเป็นโหนดแบบไม่ต่อเนื่องที่สามารถตามด้วย A * สิ่งนี้สำคัญมากเพราะมิเช่นนั้น A * จะไม่มีทางรู้ว่ารถสองคันนั้นเหมือนกันจริง ๆ หรือเปล่าเพื่อเปรียบเทียบกับพวกเขา ด้วยการแปลงค่าเซลล์กริดเป็นจำนวนเต็มสิ่งนี้จะกลายเป็นเรื่องเล็กน้อย
GridCell hashNode(Node node)
{
GridCell cell;
cell.x = round(node.x / X_RESOLUTION);
cell.y = round(node.y / Y_RESOLUTION);
cell.theta = round(node.theta / THETA_RESOLUTION);
return cell;
}
ตอนนี้เราสามารถทำแผน A * โดยที่ GridCells เป็นโหนดการดำเนินการเป็นขอบระหว่างโหนดและการเริ่มต้นและเป้าหมายจะแสดงในแง่ของ GridCells Heuristic ระหว่างสอง GridCells คือระยะห่างใน x และ y บวกระยะทางเชิงมุมใน theta
ติดตามเส้นทาง
ตอนนี้เรามีเส้นทางในแง่ของ GridCells และการกระทำระหว่างพวกเขาแล้วเราสามารถเขียนผู้ติดตามเส้นทางสำหรับรถยนต์ เนื่องจากกริดเซลล์ไม่ต่อเนื่องกันรถจึงกระโดดขึ้นระหว่างเซลล์ ดังนั้นเราจะต้องทำให้การเคลื่อนที่ของรถราบรื่นตลอดเส้นทาง หากเกมของคุณใช้เครื่องมือฟิสิกส์คุณสามารถทำได้โดยการเขียนตัวควบคุมพวงมาลัยที่พยายามให้รถอยู่ใกล้กับเส้นทางให้มากที่สุด มิฉะนั้นคุณสามารถทำให้เคลื่อนไหวโดยใช้เส้นโค้ง bezier หรือเพียงแค่หาค่าเฉลี่ยของจุดที่ใกล้ที่สุดในเส้นทาง