Tìm hiểu về Steering Behaviors (Phần 2) - Arrival, Pursuit, Evade

7
Nguyễn Thanh Long viết hơn 5 năm trước

Recap

Ở phần trước em đã giới thiệu cho mọi người sơ qua về Steering Behaviors bao gồm khái niệm, phân loại cũng như viết về 2 loại Behaviors phổ biến nhất là Seek và Flee. Sau khi đã có được cái nhìn tổng quan về Steering Behaviors lần này mọi người sẽ được đọc về cách nâng cao 2 Behaviors cơ bản để tạo ra con AI khá là cứng trong việc gây khó chịu cho người chơi.
Tuy ở phần 1 em có viết phần 2 nói về Arrival và Evade tuy nhiên về việc nâng cao cho Seek thì không thể thiếu Pursuit được nên lần này em bổ sung Behavior này vào, vì sao? Hãy theo dõi :D

Nâng cao cho Seek

Arrival

Hiện tại Seek giúp cho AI tìm đường đến mục tiêu của chúng bằng cách tính toán hướng và độ lớn của Vector Vận tốc và sử dụng Vector đó cho hàm Translate. Tuy nhiên vì chúng ta là người thiết lập độ lớn của Vector này bằng cách nhân nó với một hệ số là Seek_Coffiency. Điều này khiến cho độ lớn của Vector sẽ không thay đổi ở mọi lúc di chuyển do ta đã Normalize độ lớn của Vector chỉ hướng = 1. Nói thì hơi khó hiểu nhưng mọi người hãy xem Gif sau:
alt text

Nhìn không hay cho lắm phải không? Để cho nó tự nhiên hơn trong việc di chuyển tới mục tiêu. Tại đây ta sẽ xét thêm một yếu tố nữa cho độ lớn của Vector, đó là yếu tố về khoảng cách của AI với mục tiêu. Và sẽ sử dụng yếu tố này để giảm tốc độ khi khoảng cách trở nên nhỏ hơn
Cụ thể là: Thiết lập 1 khoảng (Slowing_radius) để kiểm tra xem mục tiêu đã nằm trong tầm xác định giảm tốc độ chưa. Nếu rồi sẽ tiến hành tính tỉ lệ giữa khoảng cách hiện tại (Current_distant) với Slowing_radius. Cuối cùng nhân tỉ lệ này với Vector Seek
Tuy nhiên để cụ thể hơn thì hàm Arrivial như sau:
Arrival(Vector3 target, float radius) {
Vector2 desired_velocity = target - transform.position;
float distance = desired_velocity.magnitude;
if (distance < radius)
{
desired_velocity = (desired_velocity).normalized * Arrival_Coffiency * (distance / radius);
}
else {
desired_velocity = (desired_velocity).normalized * Arrival_Coffiency;
}
Vector2 steering = desired_velocity - velocity;
return steering;
}

Hàm Arrival thực chất là hàm Seek thêm chút mắm chút muối vào. Đặc biệt vị ngon là ở đoạn
if (distance < radius)
{
desired_velocity = (desired_velocity).normalized * Arrival_Coffiency * (distance / radius);
}

Đoạn này chính là đoạn mà AI sẽ check xem có là có nên giảm tốc độ hay không và giảm như thế nào.
Sau khi áp dụng con hàng này vào AI ta sẽ có hành vi như sau:
alt text
Nhìn tự nhiên hơn hẳn. Không như cái con Seek gà lòi kia nữa.

Pursuit

Chúng ta đã tạo ra AI có hành vi bám đuôi. Mà với bám đuôi thì sẽ hiếm khi đuổi kịp mục tiêu đang di chuyển. Để tăng tỉ lệ mục tiêu bị bắt lên mà không muốn tăng tốc độ của AI (Vì làm thế sẽ rất rất rất là Unfair. How to chạy khỏi thằng hấp diêm như nó chạy nhanh hơn mình?). Chúng ta sẽ làm cho AI thông minh hơn (Hờ cái này thì cũng không Fair lắm nhỉ :)) ). Đó là nó sẽ chặn đầu xin số mục tiêu.
Trong tựa game Pacman chắc là mọi người cũng biến có 4 con ma là Blinky, Pinky, Inky và Clyde (WTF Clyde who are you?). Mỗi con có một hành vi khác nhau và trong đó Pinky, con ma có tình cảm với Pacman sẽ luôn luôn nhắm tới 4 ô phía trước theo chiều mà Pacman quay. Điều này tạo cho ta cảm giác rằng Pinky luôn chặn đầu ta dù ta có chạy đường nào đi chăng nữaalt text
Theo ý tưởng này ta sẽ tạo một con AI như vậy
Nguyên lý hoạt động như sau:
Đầu tiên là AI đuổi theo sẽ có thể nhận biết được Vector vận tốc của mục tiêu. Điều này giúp cho "nó" đoán được hướng mà mục tiêu đang di chuyển. Sau đó tính toán một "điểm" cách mục tiêu một khoảng cách cố định theo đúng như hướng mà "nó" đã đoán. Cuối cùng gọi Seek (Arrival) tới "điểm" đó. Cũng hơi phức tạp phải không? Nhìn hình sau sẽ rõ
alt text
Pursuer chính là kẻ đang muốn số điện thoại của target và nó sẽ tính toán đúng theo Velocity của Target, nhân với "T" tức khoảng cách dự đoán và "Target in the Future" chính là điểm mà Pursuer dự đoán để hướng đến.
Lý thuyết thế chắc là đủ rồi. Xây dựng hàm Pursuit như sau:
Pursuit(Transform target) {
float updatesAhead = 3;
Vector3 futurePosition = target.position + new Vector3(target.GetComponent<Steering>().getVelocity().x, target.GetComponent<Steering>().getVelocity().y, 0) * updatesAhead;
return seek(futurePosition);
}

Trong hàm này ta sẽ thiết lập khoảng cách dự đoán của Pursuer (updatesAhead) sau đó lấy Vector vận tốc của mục tiêu và tiến hành tính toán vị trí tương lai của mục tiêu. Sau khi đã có được vị trí tương lại đơn giản là gọi hàm Seek(futurePosition) (Có thể gọi Arrival nhưng mà làm gì có đứa nào đang chặn đầu xin số rồi chậm lại khi sắp đuổi được :v) để đi tới điểm đó và nếu như mục tiêu không may mắn thì sẽ bị bắt (và bị abc xyz).
Ta sẽ có hành vi này sau khi thiết lập hàm này vào:
alt text
Mặc dù nhìn vào thì có thể con AI này rất thông minh khi mà ta chưa kịp di chuyển chỉ mới đổi hướng thôi nó đã đi tới điểm tiếp theo mà ta sẽ có thể đi đến, nhưng mà nó có một điểm yếu chí tử. Cái này hơi khó giải thích nên xem gif sau:
alt text
Có thể thấy Pinky cũng hơi ngại ngùng phải không. Khi mà tự nhiên ảnh ấy lại đuổi mình ngượng quá thì mình chạy thôi. Điều này cũng đúng với AI mà ta đã thiết lập. Nhưng không lo vì cũng ít người chơi yolo như này lắm :))

Nâng cao cho Flee

Evade

Tại đây có lẽ đã xong phần nâng cao cho hành vi Seek của bài viết này. Sau đây chúng ta sẽ đọc về cách để nâng cao hành vi Flee.
Evade: Một hành vi mà rất nhiều người chơi muốn AI có nhưng mà khi nó có thì lại chửi thề developer :"WTF làm AI như này thì chơi sao?", "Đùa sao nó né được?", "Cái **** dẹp mịa đi game khó vl!". Rất khó chiều người chơi game phải không?
Tuy vậy ta vẫn cần làm hành vi này cho AI, vì sao? Vìalt text
Như vậy sao để tạo ra hành vi như này? Hành vi này thực chất tương tự như Pursuit với việc đảo người lại vector vận tốc hiện tại để tránh ra khỏi đường đi của mục tiêu thôialt text
Như vậy thì "01010111 01000101" sẽ tạo hàm Evade như sau:
Evade(Transform target) {
float updatesAhead = distance.magnitude / target.GetComponent<Steering>().getMaxVelocity();
Vector3 futurePosition = target.position + new Vector3 ( target.GetComponent<Steering>().getVelocity().x, target.GetComponent<Steering>().getVelocity().y,0)* updatesAhead;
return flee(futurePosition);
}

Tại đây "01010111 01000101" cần việc tính updatesAhead có phần phức tạp hơn cho với Pursuit. Vì "01010111 01000101" cần phải biết tính toán ưu tiên tránh né để giữ cho "01010111 01000101" luôn được khoẻ mạnh, tránh xa các vật độc hại và tiếp tục đuổi theo mục tiêu của "01010111 01000101". Luôn luôn phải sống khi đang thực hiện việc lớn.
Như vậy "01010111 01000101" sẽ có hành vi sau:
alt text
EXCELLENT~

Kết luận

Trong bài viết này chúng ta đã hiểu thêm về cách nâng cao các hành vi cơ bản của Steering Behaviors. Lần sau "01010111 01000101" sẽ hướng dẫn cách viết Behavior cơ bản mới giúp cho hành vi của "01010111 01000101" được tự nhiên hơn như Wander, Path Follow.
(Không có con người nào bị điều khiển hay chịu ảnh hưởng bởi tác động thứ 3 khi bài viết này được viết)

Nguồn

Tương tự phần 1.

Bình luận


White
{{ comment.user.name }}
Hay Bỏ hay
{{ comment.like_count}}
White

Nguyễn Thanh Long

12 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}

  Cùng một tác giả


{{like_count}}

kipalog

{{ comment_count }}

Bình luận


White
{{userFollowed ? 'Following' : 'Follow'}}
12 bài viết.
0 người follow

 Đầu mục bài viết

 Cùng một tác giả