Membuat AI Game Hoki Menggunakan Steering Behaviors: Menyerang
() translation by (you can also view the original English article)
Dalam tutorial ini, kami melanjutkan pengkodean kecerdasan buatan untuk
permainan hoki dengan menggunakan mesin steering
behaviors and finite state. Di
bagian seri ini, Anda akan belajar tentang AI (Kecerdasan Buatan) yang
dibutuhkan oleh entitas permainan untuk mengkoordinasikan sebuah serangan, yang
melibatkan penghadangan dan membawa puck
(bola Hoki) ke gawang lawan.
Beberapa Kata Tentang Menyerang
Mengkoordinasikan dan melakukan serangan dalam permainan olahraga kooperatif
adalah tugas yang sangat kompleks. Di dunia nyata, saat manusia bermain game
hoki, mereka membuat beberapa keputusan berdasarkan banyak variabel.
Keputusan tersebut melibatkan perhitungan dan pemahaman tentang apa yang sedang terjadi. Seorang manusia bisa tahu mengapa lawan bergerak berdasarkan tindakan lawan lawan lainnya, misalnya, "dia bergerak untuk berada dalam posisi strategis yang lebih baik." Tidaklah mudah untuk menerapkannya dalam sebuah komputer.
Sebagai konsekuensinya, jika kita mencoba kode AI untuk mengikuti semua nuansa dan persepsi manusia, hasilnya akan menjadi tumpukan kode yang besar dan menyeramkan. Selain itu, hasilnya mungkin tidak tepat atau mudah dimodifikasi.
Itulah alasan mengapa serangan AI kita akan mencoba meniru hasil sekelompok manusia yang bermain, bukan persepsi manusia itu sendiri. Pendekatan itu akan mengarah pada aproksimasi, namun kodenya akan lebih mudah dipahami dan di-tweak. Hasilnya cukup baik untuk beberapa kasus penggunaan.
Mengorganisir Serangan Dengan Kedudukan
Kami akan membagi prose penyerangan menjda beberapa-bagian, dengan masing-masing melakukan tindakan yang sangat spesifik. Bagiam-bagian itu adalah kedudukan dari mesin stack-based finite state. Seperti yang dijelaskan sebelumnya, masing-masing kedudukan akan menghasilkan tenaga kemudi yang akan membuat atlit melakukan hal yang benar.
Orkestrasi kedudukan-kedudukan tersebut dan kondisi pertukaran di antara mereka akan menentukan serangan tersebut. Gambar di bawah menyajikan FSM lengkap yang digunakan dalam proses ini:



Seperti yang terlihat pada gambar, kondisi untuk peralihan di antara kedudukan akan didasarkan pada jarak dan kepemilikan puck. Misalnya, ketika tim memiliki puck
atau puck yang berada terlalu jauh.
Proses penyerangan akan terdiri dari empat kedudukan idle (siaga)
, attack (menyerang)
, stealPuck (merebut Puck)
, and pursuePuck (Mengejar )
. Kedudukan idle
sudah diimplementasikan pada tutorial sebelumnya, dan ini adalah titik awal
dari prosesnya. Dari sana, seorang atlet akan beralih untuk attack
jika tim memiliki puck, dan menyerang jika tim lawan memiliki puck, atau akan memburu. puck
jika tidak seorangpun memiliki puck-nya
dan cukup dekat untuk diraih.
kedudukan attack
merupakan gerakan ofensif. Ketika berada pada kedudukan
tersebut itu, atlet yang membawa puck
(disebut leader
) akan berusaha mencapai gawang
lawan. Rekan tim akan bergerak, mencoba mendukung aksi tersebut.
Kedudukan
stealPuck
mewakili suatu berakan
antara gerakan defensif dan ofensif. Pada kedudukan ini, seorang atlet akan fokus mengejar lawan yang membawa puck-nya. Tujuannya adalah untuk mengembalikan puck-nya, sehingga tim bisa mulai menyerang lagi.
Pada
akhirnya, kedudukan persuePuck
itu
tidak terkait dengan serangan atau pertahanan; itu hanya akan membimbing para
atlet saat puck tidak memiliki
pemilik. Sementara pada kedudukan itu, seorang atlet akan mencoba untuk
mendapatkan puck yang bebas bergerak
di arena (misalnya, setelah dipukul oleh tongkat seseorang).
Memperbarui Kedudukan Idle (siaga)
Kedudukan idle
yang sebelumnya diimplementasikan tidak memiliki transisi.
Karena keadaan ini adalah titik awal bagi keseluruhan AI, marilah kita mperbarui
dan membuatnya dapat beralih ke kedudukan lainnya.
Kedudukan idle
memiliki tiga transisi:



Jika tim atlet memiliki puck, idle
harus muncul dari otak dan serangan
harus dilakukan. Begitu pula jika tim lawan memiliki puck, idle
harus diganti dengan stealPuck
(merebut puck). Transisi yang tersisa terjadi ketika tidak ada yang memiliki puck dan dekat dengan atlet; Dalam hal ini, persuePuck
usahakan agar bisa didorong masuk ke otak.
Versi terbaru dari idle
adalah sebagai berikut (kedudukan lainnya akan diimplementasikan nanti):
1 |
class Athlete { |
2 |
// (...)
|
3 |
private function idle() :void { |
4 |
var aPuck :Puck = getPuck(); |
5 |
|
6 |
stopAndlookAt(aPuck); |
7 |
|
8 |
// This is a hack to help test the AI.
|
9 |
if (mStandStill) return; |
10 |
|
11 |
// Does the puck has an owner?
|
12 |
if (getPuckOwner() != null) { |
13 |
// Yeah, it has.
|
14 |
mBrain.popState(); |
15 |
|
16 |
if (doesMyTeamHaveThePuck()) { |
17 |
// My team just got the puck, it's attack time!
|
18 |
mBrain.pushState(attack); |
19 |
} else { |
20 |
// The opponent team got the puck, let's try to steal it.
|
21 |
mBrain.pushState(stealPuck); |
22 |
}
|
23 |
} else if (distance(this, aPuck) < 150) { |
24 |
// The puck has no owner and it is nearby. Let's pursue it.
|
25 |
mBrain.popState(); |
26 |
mBrain.pushState(pursuePuck); |
27 |
}
|
28 |
}
|
29 |
|
30 |
private function attack() :void { |
31 |
}
|
32 |
|
33 |
private function stealPuck() :void { |
34 |
}
|
35 |
|
36 |
private function pursuePuck() :void { |
37 |
}
|
38 |
}
|
Mari kita lanjutkan dengan implementasi kedudukan lain.
Mengejar Puck
Sekarang
atlet telah mendapatkan beberapa persepsi tentang lingkungan dan dapat beralih
dari kedudukan idle
ke kedudukan manapun, mari
fokus untuk mengejar puck saat tidak
memiliki pemilik.
Seorang atlet akan beralih pursuePuck
untuk segera setelah pertandingan dimulai,
karena puck itu akan ditempatkan di
tengah arena tanpa pemilik. Kedudukan pursuePuck
(mengejar Puck) memiliki
tiga transisi:



Transisi pertama adalah puck yang terlalu jauh (puck is too far away)
, dan mencoba untuk mensimulasikan apa yang terjadi dalam permainan nyata mengenai mengejar puck itu. Untuk alasan strategis, biasanya atlet yang paling dekat dengan puckadalah yang mencoba menangkapnya, sementara yang lain menunggu atau mencoba membantu.
Tanpa beralih ke siaga idle
saat puck-nya
jauh, setiap atlet AI-dikontrol akan mengejar puckpada saat yang sama, bahkan jika mereka jauh dari itu. Dengan
mengecek jarak antara atlit dan puck-nya, pursuePuck
keluar dari otak dan mendorongnya pada kedudukan
siaga idle
saat puck-nya terlalu
jauh, yang berarti atlet itu "menyerah" mengejar puck-nya:
1 |
class Athlete { |
2 |
// (...)
|
3 |
|
4 |
private function pursuePuck() :void { |
5 |
var aPuck :Puck = getPuck(); |
6 |
|
7 |
if (distance(this, aPuck) > 150) { |
8 |
// Puck is too far away from our current position, so let's give up
|
9 |
// pursuing the puck and hope someone will be closer to get the puck
|
10 |
// for us.
|
11 |
mBrain.popState(); |
12 |
mBrain.pushState(idle); |
13 |
} else { |
14 |
// The puck is close, let's try to grab it.
|
15 |
}
|
16 |
}
|
17 |
|
18 |
// (...)
|
19 |
}
|
Saat puck sudah dekat, atlet harus mengejarnya, yang bisa dengan mudah diraih dengan seek behavior . Dengan menggunakan posisi puck sebagai tujuan pencarian, atlet akan dengan anggun mengejar puck-nya dan menyesuaikan lintasannya saat puck-nya bergerak:
1 |
class Athlete { |
2 |
// (...) |
3 |
private function pursuePuck() :void { |
4 |
var aPuck :Puck = getPuck(); |
5 |
|
6 |
mBoid.steering = mBoid.steering + mBoid.separation(); |
7 |
|
8 |
if (distance(this, aPuck) > 150) { |
9 |
// Puck is too far away from our current position, so let's give up |
10 |
// pursuing the puck and hope someone will be closer to get the puck |
11 |
// for us. |
12 |
mBrain.popState(); |
13 |
mBrain.pushState(idle); |
14 |
} else { |
15 |
// The puck is close, let's try to grab it. |
16 |
if (aPuck.owner == null) { |
17 |
// Nobody has the puck, it's our chance to seek and get it! |
18 |
mBoid.steering = mBoid.steering + mBoid.seek(aPuck.position); |
19 |
|
20 |
} else { |
21 |
// Someone just got the puck. If the new puck owner belongs to my team, |
22 |
// we should switch to 'attack', otherwise I should switch to 'stealPuck' |
23 |
// and try to get the puck back. |
24 |
mBrain.popState(); |
25 |
mBrain.pushState(doesMyTeamHaveThePuck() ? attack : stealPuck); |
26 |
} |
27 |
} |
28 |
} |
29 |
} |
Dua
transisi sisa di kedudukan persuePuck
, tim
memiliki puck (team has puck)
dan lawan memiliki puck
(opponent has the puck
,
terkait dengan puck yang tertangkap saat proses pengerjaan. Jika seseorang
menangkap kepingnya, atlet tersebut harus memunculkan kedudukan persuePuck
dan mendorong yang baru ke otak.
kedudukan yang harus didorong tergantung pada kepemilikan puck. Jika panggilan terhadap doesMyTeamHaveThePuck ()
mengembalikan
nilai true
, itu berarti
bahwa rekan setimnya mendapatkan puck,
jadi atlet harus mendorong serangan
, yang berarti sudah waktunya berhenti
mengejar puck dan mulai bergerak
menuju sasaran lawan. Jika lawan mendapat puck, atlet harus mendorongstealPuck
, yang akan membuat tim mencoba mengembalikan puck-nya.
Sebagai peningkatan kecil, atlet tidak boleh terlalu dekat satu sama lain
selama kedudukan pursuePuck
state, karena
gerakan mengejar yang "ramai" tidaklah wajar. Menambahkan separation ke kedudukan steering force (baris 6
pada kode di
atas) memastikan atlet akan menjaga jarak minimum di antara mereka.
Hasilnya adalah tim yang mampu mengejar puck-nya. Demi pengujian, dalam demo ini, puck ditempatkan di tengah arena setiap beberapa detik, untuk membuat atlet terus bergerak:
Menyerang Dengan Puck
Setelah mendapatkan puck-nya, seorang
atlet dan timnya harus bergerak menuju gawang lawan untuk mencetak gol. Itulah
tujuan dari serangan
tersebut:



Kedudukan menyerang (attack)
hanya memiliki dua transisi: lawan memiliki puck
dan puck tidak memiliki pemilik
.
Karena kedudukan semata-mata dirancang untuk membuat atlet bergerak menuju
gawang lawan, tidak ada gunanya tetap menyerang jika puck-nya tidak berada di bawah kepemilikan tim lagi.
Mengenai pergerakan menuju gawang lawan: atlit pembawa puck (leader) dan rekan tim yang membantunya harus bersikap berbeda. Leader harus mencapai gawang lawan, dan rekan tim harus membantunya di sepanjang jalan.
Hal ini dapat diimplementasikan dengan memeriksa apakah atlet yang menjalankan kode memiliki puck:
1 |
class Athlete { |
2 |
// (...)
|
3 |
private function attack() :void { |
4 |
var aPuckOwner :Athlete = getPuckOwner(); |
5 |
|
6 |
// Does the puck have an owner?
|
7 |
if (aPuckOwner != null) { |
8 |
// Yeah, it has. Let's find out if the owner belongs to the opponents team.
|
9 |
if (doesMyTeamHaveThePuck()) { |
10 |
if (amIThePuckOwner()) { |
11 |
// My team has the puck and I am the one who has it! Let's move
|
12 |
// towards the opponent's goal.
|
13 |
mBoid.steering = mBoid.steering + mBoid.seek(getOpponentGoalPosition()); |
14 |
|
15 |
} else { |
16 |
// My team has the puck, but a teammate has it. Let's just follow him
|
17 |
// to give some support during the attack.
|
18 |
mBoid.steering = mBoid.steering + mBoid.followLeader(aPuckOwner.boid); |
19 |
mBoid.steering = mBoid.steering + mBoid.separation(); |
20 |
}
|
21 |
} else { |
22 |
// The opponent has the puck! Stop the attack
|
23 |
// and try to steal it.
|
24 |
mBrain.popState(); |
25 |
mBrain.pushState(stealPuck); |
26 |
}
|
27 |
} else { |
28 |
// Puck has no owner, so there is no point to keep
|
29 |
// attacking. It's time to re-organize and start pursuing the puck.
|
30 |
mBrain.popState(); |
31 |
mBrain.pushState(pursuePuck); |
32 |
}
|
33 |
}
|
34 |
}
|
Jika amIThePuckOwner ()
memberikan nilai true
(baris 10), atlet yang
menjalankan kode memiliki puck. Dalam hal ini, dia hanya akan mencari posisi
gawang lawan. Cukup banyak logika yang sama yang digunakan untuk mengejar puck
pada keduukan persuePuck
Jika amIThePuckOwner ()
memberikan nilai
false
, atlet tidak memiliki puck,
jadi dia harus membantu pemimpinnya (leader). Membantu pemimpin adalah tugas
yang rumit, jadi kita akan menyederhanakannya. Seorang atlet akan membantu
pemimpin hanya dengan mencari posisi di depannya:



Ketika pemimpin bergerak, ia akan dikelilingi oleh rekan tim saat mereka mengikuti titik terdepan
. Ini memberi pemimpin beberapa pilihan untuk meloloskan puck itu jika ada masalah. Seperti dalam permainan nyata, rekan setimnya di sekitarnya juga harus tetap berada di luar jalur pimpinan.
Pola bantuan ini dapat dicapai dengan menambahkan sedikit modifikasi versi pemimpin mengikuti perilaku (baris 18). Satu-satunya perbedaannya adalah bahwa atlet akan mengikuti satu poin di depan pemimpin, bukan satu di belakangnya seperti yang pada awalnya diterapkan dalam perilaku itu.
Atlet yang membantu pemimpin juga harus menjaga jarak minimum antara satu sama lain. Itu diimplementasikan dengan menambahkan gaya pemisah (garis 19).
Hasilnya adalah sebuah tim mampu bergerak menuju gawang lawan, tanpa berkerumun dan saat mensimulasikan sebuah gerakan serangan yang dibantu:
Meningkatkan Dukungan Serangan
Implementasi kedudkan menyerang attack
saat
ini cukup baik untuk beberapa situasi, namun memiliki kekurangan. Saat
seseorang menangkap puck-nya, dia
menjadi pemimpin dan segera diikuti oleh rekan tim.
Apa yang terjadi jika sang pemimpin bergerak menuju gawangnya sendiri saat dia menangkap puck-nya? Lihatlah lebih dekat demo di atas dan perhatikan pola tidak wajar saat rekan tim mulai mengikuti pemimpinnya.
Ketika
pemimpin menangkap puck, perilaku
pencarian (seeking behavior) memerlukan beberapa waktu untuk memperbaiki
lintasan pemimpin dan secara efektif membuat dia bergerak menuju gawang lawan.
Bahkan ketika pemimpinnya "bermanuver", rekan tim akan mencoba untuk
mencari poin ahead
, yang berarti mereka akan
bergerak menuju gawang mereka sendiri (atau tempat yang dituju pemimpin).
Saat pemimpin akhirnya dalam posisi dan siap bergerak menuju gawang lawan, rekan tim akan "bermanuver" untuk mengikuti sang pemimpin. Pemimpin kemudian akan bergerak tanpa dukungan rekan setimnya selama yang lain menyesuaikan lintasan mereka.
Cacat ini bisa diperbaiki dengan memeriksa apakah rekan setimnya berada di depan pemimpin saat tim memulihkan puck-nya. Di sini, kondisi "depan" berarti "lebih dekat ke gawang lawan":
1 |
class Athlete { |
2 |
// (...)
|
3 |
private function isAheadOfMe(theBoid :Boid) :Boolean { |
4 |
var aTargetDistance :Number = distance(getOpponentGoalPosition(), theBoid); |
5 |
var aMyDistance :Number = distance(getOpponentGoalPosition(), mBoid.position); |
6 |
|
7 |
return aTargetDistance <= aMyDistance; |
8 |
}
|
9 |
|
10 |
private function attack() :void { |
11 |
var aPuckOwner :Athlete = getPuckOwner(); |
12 |
|
13 |
// Does the puck have an owner?
|
14 |
if (aPuckOwner != null) { |
15 |
// Yeah, it has. Let's find out if the owner belongs to the opponents team.
|
16 |
if (doesMyTeamHaveThePuck()) { |
17 |
if (amIThePuckOwner()) { |
18 |
// My team has the puck and I am the one who has it! Let's move
|
19 |
// towards the opponent's goal.
|
20 |
mBoid.steering = mBoid.steering + mBoid.seek(getOpponentGoalPosition()); |
21 |
|
22 |
} else { |
23 |
// My team has the puck, but a teammate has it. Is he ahead of me?
|
24 |
if (isAheadOfMe(aPuckOwner.boid)) { |
25 |
// Yeah, he is ahead of me. Let's just follow him to give some support
|
26 |
// during the attack.
|
27 |
mBoid.steering = mBoid.steering + mBoid.followLeader(aPuckOwner.boid); |
28 |
mBoid.steering = mBoid.steering + mBoid.separation(); |
29 |
} else { |
30 |
// Nope, the teammate with the puck is behind me. In that case
|
31 |
// let's hold our current position with some separation from the
|
32 |
// other, so we prevent crowding.
|
33 |
mBoid.steering = mBoid.steering + mBoid.separation(); |
34 |
}
|
35 |
}
|
36 |
} else { |
37 |
// The opponent has the puck! Stop the attack
|
38 |
// and try to steal it.
|
39 |
mBrain.popState(); |
40 |
mBrain.pushState(stealPuck); |
41 |
}
|
42 |
} else { |
43 |
// Puck has no owner, so there is no point to keep
|
44 |
// attacking. It's time to re-organize and start pursuing the puck.
|
45 |
mBrain.popState(); |
46 |
mBrain.pushState(pursuePuck); |
47 |
}
|
48 |
}
|
49 |
}
|
Jika pemimpin (pemilik puck) berada di depan atlet yang menjalankan kodenya, maka atlet harus mengikuti pemimpin seperti yang dia lakukan sebelumnya (baris 27 dan 28). Jika pemimpin berada di belakangnya, atlet harus memegang posisi saat ini, menjaga jarak minimum dengan atlit yang lain (baris 33).
Hasilnya sedikit lebih meyakinkan daripada implementasi serangan
awal:
Tips: Dengan menyesuaikan
perhitungan jarak jauh dan perbandingan dengan metode isAheadOfMe
(),
sangat memungkinkan untuk memodifikasi cara atlit memegang posisi mereka
saat ini.
Merebut Puck
kedudukan terakhir dalam proses penyerangan adalah merebut puck (stealPuck
), yang menjadi aktif
saat tim lawan memiliki puck. Tujuan
utama dari stealpuckk
adalah untuk
mencuri puck dari lawan yang
membawanya, sehingga tim bisa mulai menyerang lagi:



Karena gagasan di balik kedudukancini adalah mencuri puck dari lawan, jika puck itu dipulihkan oleh tim atau menjadi bebas (yaitu tidak memiliki pemilik), stealPuck
akan muncul dari otak dan mendorong kedudukan yang benar untuk mengatasi situasi baru:
1 |
class Athlete { |
2 |
// (...)
|
3 |
private function stealPuck() :void { |
4 |
// Does the puck have any owner?
|
5 |
if (getPuckOwner() != null) { |
6 |
// Yeah, it has, but who has it?
|
7 |
if (doesMyTeamHaveThePuck()) { |
8 |
// My team has the puck, so it's time to stop trying to steal
|
9 |
// the puck and start attacking.
|
10 |
mBrain.popState(); |
11 |
mBrain.pushState(attack); |
12 |
} else { |
13 |
// An opponent has the puck.
|
14 |
var aOpponentLeader :Athlete = getPuckOwner(); |
15 |
|
16 |
// Let's pursue him while mantaining a certain separation from
|
17 |
// the others to avoid that everybody will ocuppy the same
|
18 |
// position in the pursuit.
|
19 |
mBoid.steering = mBoid.steering + mBoid.pursuit(aOpponentLeader.boid); |
20 |
mBoid.steering = mBoid.steering + mBoid.separation(); |
21 |
}
|
22 |
} else { |
23 |
// The puck has no owner, it is probably running freely in the rink.
|
24 |
// There is no point to keep trying to steal it, so let's finish the 'stealPuck' state
|
25 |
// and switch to 'pursuePuck'.
|
26 |
mBrain.popState(); |
27 |
mBrain.pushState(pursuePuck); |
28 |
}
|
29 |
}
|
30 |
}
|
Jika puck memiliki pemilik dan dia termasuk dalam tim lawan, atlet harus mengejar pemimpin lawan dan mencoba mencuri puck-nya. Untuk mengejar pemimpin lawan, seorang atlet harus meramalkan di mana dia berada dalam waktu dekat, jadi dia bisa dicegat dalam lintasannya. Itu berbeda dengan hanya mencari pemimpin lawan.
Untungnya,
ini bisa dengan mudah diraih dengan pursue behavior (line 19). Dengan menggunakan kekuatan
pengejaran dalam kedudukan stealPuck
,
atlet akan mencoba mencegat pemimpin lawan, bukan hanya mengikutinya:
Mencegah Gerakan Merebut yang Penuh Sesak
implementasi saat ini dari stealPuck
bekerja, namun dalam permainan nyata hanya satu atau dua atlet yang mendekati
pemimpin lawan untuk mencuri puck-nya.
Anggota tim lainnya tetap berada di daerah sekitar untuk membantu, yang
mencegah pola pencurian yang penuh sesak.
Hal ini dapat diperbaiki dengan menambahkan pemeriksaan jarak (garis 17) sebelum pengejaran pemimpin lawan:
1 |
class Athlete { |
2 |
// (...)
|
3 |
private function stealPuck() :void { |
4 |
// Does the puck have any owner?
|
5 |
if (getPuckOwner() != null) { |
6 |
// Yeah, it has, but who has it?
|
7 |
if (doesMyTeamHaveThePuck()) { |
8 |
// My team has the puck, so it's time to stop trying to steal
|
9 |
// the puck and start attacking.
|
10 |
mBrain.popState(); |
11 |
mBrain.pushState(attack); |
12 |
} else { |
13 |
// An opponent has the puck.
|
14 |
var aOpponentLeader :Athlete = getPuckOwner(); |
15 |
|
16 |
// Is the opponent with the puck close to me?
|
17 |
if (distance(aOpponentLeader, this) < 150) { |
18 |
// Yeah, he is close! Let's pursue him while mantaining a certain
|
19 |
// separation from the others to avoid that everybody will ocuppy the same
|
20 |
// position in the pursuit.
|
21 |
mBoid.steering = mBoid.steering.add(mBoid.pursuit(aOpponentLeader.boid)); |
22 |
mBoid.steering = mBoid.steering.add(mBoid.separation(50)); |
23 |
|
24 |
} else { |
25 |
// No, he is too far away. In the future, we will switch
|
26 |
// to 'defend' and hope someone closer to the puck can
|
27 |
// steal it for us.
|
28 |
// TODO: mBrain.popState();
|
29 |
// TODO: mBrain.pushState(defend);
|
30 |
}
|
31 |
}
|
32 |
} else { |
33 |
// The puck has no owner, it is probably running freely in the rink.
|
34 |
// There is no point to keep trying to steal it, so let's finish the 'stealPuck' state
|
35 |
// and switch to 'pursuePuck'.
|
36 |
mBrain.popState(); |
37 |
mBrain.pushState(pursuePuck); |
38 |
}
|
39 |
}
|
40 |
}
|
Alih-alih
membabi buta mengejar pemimpin lawan, seorang atlet akan memeriksa apakah jarak
antara dia dan pemimpin lawan kurang dari, katakanlah, 150
. Jika itu true
,
pengejaran itu terjadi secara normal, tapi jika jaraknya lebih dari 150
, itu
berarti atlet terlalu jauh dari pimpinan lawan.
Jika itu terjadi, tidak ada gunanya terus mencoba mencuri puck-nya, karena terlalu jauh dan mungkin ada rekan setimnya yang
sudah mencoba melakukan hal yang sama. Pilihan terbaik adalah munculkan stealPuck
dari otak dan dorong kedudukan pertahanan
(yang akan dijelaskan di tutorial selanjutnya). Untuk saat ini, seorang atlet hanya akan memegang posisi saat ini jika pemimpin lawan terlalu jauh.
Hasilnya adalah pola perebutan yang lebih meyakinkan dan alami (tidak berkerumun):
Menghindari Lawan Ketika Menyerang
Ada satu trik terakhir yang harus dipelajari atlet agar bisa menyerang secara efektif. Saat ini, mereka bergerak menuju gawang lawan tanpa mempertimbangkan lawan di sepanjang jalan. Lawan harus dilihat sebagai ancaman, dan harus dihindari.
Dengan menggunakan perilaku penghindaran tabrakan (collision avoidance), atlet bisa menghindari lawan saat mereka bergerak:



Lawan akan dilihat sebagai rintangan melingkar. Sebagai hasil dari sifat dinamis dari perilaku kemudi, yang diperbarui dalam setiap loop permainan, pola penghindaran akan berjalan dengan anggun dan lancar untuk memindahkan hambatan (yang terjadi di sini).
Untuk membuat atlit menghindari lawan (rintangan), satu baris harus ditambahkan ke kedudukan penyerangan (baris 14):
1 |
class Athlete { |
2 |
// (...)
|
3 |
private function attack() :void { |
4 |
var aPuckOwner :Athlete = getPuckOwner(); |
5 |
|
6 |
// Does the puck have an owner?
|
7 |
if (aPuckOwner != null) { |
8 |
// Yeah, it has. Let's find out if the owner belongs to the opponents team.
|
9 |
if (doesMyTeamHaveThePuck()) { |
10 |
if (amIThePuckOwner()) { |
11 |
// My team has the puck and I am the one who has it! Let's move
|
12 |
// towards the opponent's goal, avoding any opponents along the way.
|
13 |
mBoid.steering = mBoid.steering + mBoid.seek(getOpponentGoalPosition()); |
14 |
mBoid.steering = mBoid.steering + mBoid.collisionAvoidance(getOpponentTeam().members); |
15 |
|
16 |
} else { |
17 |
// My team has the puck, but a teammate has it. Is he ahead of me?
|
18 |
if (isAheadOfMe(aPuckOwner.boid)) { |
19 |
// Yeah, he is ahead of me. Let's just follow him to give some support
|
20 |
// during the attack.
|
21 |
mBoid.steering = mBoid.steering + mBoid.followLeader(aPuckOwner.boid); |
22 |
mBoid.steering = mBoid.steering + mBoid.separation(); |
23 |
} else { |
24 |
// Nope, the teammate with the puck is behind me. In that case
|
25 |
// let's hold our current position with some separation from the
|
26 |
// other, so we prevent crowding.
|
27 |
mBoid.steering = mBoid.steering + mBoid.separation(); |
28 |
}
|
29 |
}
|
30 |
} else { |
31 |
// The opponent has the puck! Stop the attack
|
32 |
// and try to steal it.
|
33 |
mBrain.popState(); |
34 |
mBrain.pushState(stealPuck); |
35 |
}
|
36 |
} else { |
37 |
// Puck has no owner, so there is no point to keep
|
38 |
// attacking. It's time to re-organize and start pursuing the puck.
|
39 |
mBrain.popState(); |
40 |
mBrain.pushState(pursuePuck); |
41 |
}
|
42 |
}
|
43 |
}
|
Baris ini akan menambahkan kekuatan penghindaran tabrakan ke atlit, yang akan dikombinasikan dengan kekuatan yang sudah ada. Alhasil, atlit akan menghindari rintangan sekaligus mencari gawang lawan.
Berikut adalah demonstrasi atlet yang menjalankan kedudukan menyerang (attack
).
Lawan tidak bergerak untuk menyoroti perilaku penghindaran tabrakan:
Kesimpulan
Tutorial ini menjelaskan penerapan pola serangan yang digunakan oleh atlit untuk merebut dan membawa puck ke arah gawang lawan. Dengan menggunakan kombinasi perilaku kemudi (steering behavior), atlet kini mampu melakukan pola pergerakan yang kompleks, seperti mengikuti seorang pemimpin atau mengejar lawan dengan puck.
Seperti yang telah dibahas sebelumnya, implementasi serangan tersebut bertujuan untuk mensimulasikan apa yang dilakukan manusia, sehingga hasilnya merupakan perkiraan dari sebuah game yang sebenarnya. Dengan secara individual mengutak-atik bagian kedudukan yang menyusun serangan, Anda dapat menghasilkan simulasi yang lebih baik, atau yang sesuai dengan kebutuhan Anda.
Pada tutorial selanjutnya, Anda akan belajar bagaimana membuat atlet bertahan. AI akan menjadi fitur lengkap, mampu menyerang dan bertahan, menghasilkan kecocokan dengan 100% tim yang dikendalikan AI yang bermain satu sama lain.
Referensi
- Sprite: Hockey Stadium on GraphicRiver
- Sprites: Hockey Players by Taylor J Glidden