Game Loop
Bagaimana caranya agar ular bisa
bergerak? Caranya, adalah dengan membuat infinite loop untuk
me-render ulang
layar setiap putarannya. Dengan demikian, setiap ada perubahan situasi (state) pada arraysnake, entah
itu jumlah element (length) atau nilai x dan y nya, perubahan itu akan
langsung tercermin di layar.
Mari kita taruh bagian rendering
tadi ke dalam infinite loop (lihat
baris 11-20).
1. /**
2. Program utama
3. */
4. int main() {
5. // Pertama-tama, push segment (node) ke kanan
6. // sebanyak 3 segment (sesuai nilai variable snake_size)
7. for (int i = 0; i < snake_size; i++) {
8. push(i, 0);
9. }
10.
11. // Game loop. Bagian di dalam while akan dieksekusi terus menerus
12. while (true) {
13. // Tampilkan kondisi permainan saat ini di layar...
14.
15. // Bersihkan layar
16. system("cls");
17.
18. // Cetak (render) snake di layar
19. display();
20. }
21.
22. getchar();
23. return 0;
24. }
Untuk menggerakkan ular ke kanan
setiap 200ms, pertama-tama, di dalam game loop kita
menghitung berapa waktu yang sudah terlewati, jika waktu yang berlalu sudah
lebih atau sama dengan 200ms, maka kita geser ular. Sama dengan sebelumnya,
agar nilai 200 ini mudah diubah-ubah, kita simpan dalam variabel global snake_speed.
1. // Kecepatan gerakan snake dalam ms
2. int snake_speed = 200;
Untuk menghitung interval waktu
yang berlalu, kita gunakan fungsi ftime() untuk
mendapat kan penanda waktu.
Cara menggeser ular, adalah
dengan melakukan pop(), lalu push() kembali
di posisi koordinat headdengan nilai x ditambah 1 karena saat ini
kepala ular mengarah ke kanan.
( Lihat baris 6-8, 17-40)
1. /**
2. Program utama
3. */
4. int main() {
5.
6. // Untuk menyimpan penanda waktu saat snake bergerak
7. struct timeb last_timestamp;
8. ftime(&last_timestamp); // Set nilai awal
9.
10. // Pertama-tama, push segment (node) ke kanan
11. // sebanyak 3 segment (sesuai nilai variable snake_size)
12. for (int i = 0; i < snake_size; i++) {
13. push(i, 0);
14. }
15. // Game loop. Bagian di dalam while akan dieksekusi terus menerus
16. while (true) {
17. // Ambil penanda waktu saat ini
18. struct timeb current_timestamp;
19. ftime(¤t_timestamp);
20.
21. // Selisih waktu terakhir dengan waktu sekarang dalam ms
22. int interval = 1000 * (current_timestamp.time - last_timestamp.time) + (current_timestamp.millitm - last_timestamp.millitm);
23.
24. // Snake bergerak setiap 200 ms (sesuai nilai variable snake_speed)
25. // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu
26. // terakhir kali snake bergerak.
27. if (interval >= snake_speed) {
28. // Tentukan posisi x,y ke mana snake akan bergerak.
29. int x, y;
30. x = snake[0].x + 1;
31. y = snake[0].y;
32.
33. // Pop ekor, lalu push segment ke depan head sehingga
34. // snake tampak bergerak maju.
35. pop();
36. push(x, y);
37.
38. // Perbarui penanda waktu
39. last_timestamp = current_timestamp;
40. }
41.
42. // Tampilkan kondisi permainan saat ini di layar...
43.
44. // Bersihkan layar
45. system("cls");
46.
47. // Cetak (render) snake di layar
48. display();
49. }
50.
51. ...
52. }
Coba jalankan lagi. Sekarang ular sudah bisa bergerak!
Tapi layar tampaknya
berkedip-kedip. Hal ini terjadi karena program mencoba mengosongkan layar
dengan system(“cls”); sebelum
menggambar lagi. Umumnya pembuat game akan melakukan teknik double buffering untuk menghindari layar berkedip (flicker). Namun untuk menyederhanakan tutorial ini,
kita akan lakukan pendekatan lain, yaitu dengan me-render ulang layar hanya ketika ular
bergerak. Sehingga rendering hanya
terjadi setiap 200ms sekali (5 FPS).
Caranya mudah, kita pindahkan
baris-baris rendering ke
dalam blok if(interval >= snake_speed) { } (lihat baris
30-37).
1. /**
2. Program utama
3. */
4. int main() {
5. ...
6.
7. // Game loop. Bagian di dalam while akan dieksekusi terus menerus
8. while (true) {
9. // Ambil penanda waktu saat ini
10. struct timeb current_timestamp;
11. ftime(¤t_timestamp);
12.
13. // Selisih waktu terakhir dengan waktu sekarang dalam ms
14. int interval = 1000 * (current_timestamp.time - last_timestamp.time) + (current_timestamp.millitm - last_timestamp.millitm);
15.
16. // Snake bergerak setiap 200 ms (sesuai nilai variable snake_speed)
17. // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu
18. // terakhir kali snake bergerak.
19. if (interval >= snake_speed) {
20. // Tentukan posisi x,y ke mana snake akan bergerak.
21. int x, y;
22. x = snake[0].x + 1;
23. y = snake[0].y;
24.
25. // Pop ekor, lalu push segment ke depan head sehingga
26. // snake tampak bergerak maju.
27. pop();
28. push(x, y);
29.
30. // Tampilkan kondisi permainan saat ini di layar...
31.
32. // Bersihkan layar
33. system("cls");
34.
35. // Cetak (render) snake di layar
36. display();
37.
38. // Perbarui penanda waktu
39. last_timestamp = current_timestamp;
40. }
41. }
42.
43. ...
44. }
Mengontrol
Arah Gerakan Ular
Untuk bisa mengontrol arah
gerakan ular, kita membuat sebuah variabel global tambahan bernamadir. Variabel ini memberitahu arah push() berikutnya, apakah ke kanan, bawah,
kiri, atau atas. Arah ini akan ditentukan berdasarkan input tombol panah yang
ditekan.
Pertama-tama, buat variabel
global dir, dengan nilai awal ke arah kanan. VK_RIGHT
adalah konstanta berisi kode untuk tombol panah kanan.
1. // Arah kepala saat awal permainan
2. int dir = VK_RIGHT;
Sekarang kita modifikasi
penentuan nilai x dan y untuk melakukan push() berdasarkan
variabel dir. Lalu di dalam game loop,
dilakukan juga pengecekan tombol yang sedang ditekan. Jika merupakan salah satu
dari empat tombol panah di keyboard, maka ubah nilai dir (lihat baris 17-37, 56-73).
1. /**
2. Program utama
3. */
4. int main() {
5. ...
6.
7. // Game loop. Bagian di dalam while akan dieksekusi terus menerus
8. while (true) {
9.
10. ...
11.
12. // Snake bergerak setiap 200 ms (sesuai nilai variable snake_speed)
13. // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu
14. // terakhir kali snake bergerak.
15. if (interval >= snake_speed) {
16. // Tentukan posisi x,y ke mana snake akan bergerak.
17. // Posisi dilihat dari koordinat segment kepala (head)
18. // dan arah (variable dir)
19. int x, y;
20. switch (dir) {
21. case VK_LEFT:
22. x = snake[0].x - 1;
23. y = snake[0].y;
24. break;
25. case VK_RIGHT:
26. x = snake[0].x + 1;
27. y = snake[0].y;
28. break;
29. case VK_UP:
30. x = snake[0].x;
31. y = snake[0].y - 1;
32. break;
33. case VK_DOWN:
34. x = snake[0].x;
35. y = snake[0].y + 1;
36. break;
37. }
38.
39. // Pop ekor, lalu push segment ke depan head sehingga
40. // snake tampak bergerak maju.
41. pop();
42. push(x, y);
43.
44. // Tampilkan kondisi permainan saat ini di layar...
45.
46. // Bersihkan layar
47. system("cls");
48.
49. // Cetak (render) snake di layar
50. display();
51.
52. // Perbarui penanda waktu
53. last_timestamp = current_timestamp;
54. }
55.
56. // Ubah arah jika tombol panah ditekan
57. if (GetKeyState(VK_LEFT) < 0) {
58. dir = VK_LEFT;
59. }
60. if (GetKeyState(VK_RIGHT) < 0) {
61. dir = VK_RIGHT;
62. }
63. if (GetKeyState(VK_UP) < 0) {
64. dir = VK_UP;
65. }
66. if (GetKeyState(VK_DOWN) < 0) {
67. dir = VK_DOWN;
68. }
69.
70. // Keluar dari program jika menekan tombol ESC
71. if (GetKeyState(VK_ESCAPE) < 0) {
72. return 0;
73. }
74. }
75.
76. ...
77. }
Kita juga bisa menambahkan pengecekan untuk keluar dari
program jika pemain menekan tombol ESC.
Coba jalankan lagi program, sekarang kita bisa
menggerakan ular dengan bebas
Collision Detection
Salah satu aspek yang penting
dalam permainan ini adalah pengecekan apakah kepala ular bertabrakan dengan
dinding atau dirinya sendiri. Di sini kita bisa melakukan pengecekan saat
program memeroleh posisi x dan y yang baru, sebelum melakukan pop() dan push().
Jika posisi x berada di luar
batasan 0-79 (panjang console) atau posisi y berada diluar batasan 0-24 (tinggi
console), maka ular telah menabrak dinding, dan permainan berakhir. Sama
seperti sebelum-sebelumnya, untuk nilai panjang dan lebar console bisa kita
simpan di variabel global console_widthdan console_height.
1. // Panjang console
2. int console_width = 80;
3.
4. // Tinggi console
5. int console_height = 25;
Pengecekan berikutnya yaitu
mengecek apabila posisi x dan y sama dengan posisi salah satu node, yang artinya
ular menabrak dirinya sendiri. Untuk mengeceknya, kita buat fungsi check_collision().
1. /**
2. Memeriksa apakah terdapat salah satu segment
3. snake (array) di koordinat x,y.
4. Return 0 artinya tidak bertumpuk, 1 artinya bertumpuk.
5. */
6. int check_collision(int x, int y) {
7. for(int i = 0; i < length; i++) {
8. if (snake[i].x == x && snake[i].y == y) {
9. return 1;
10. }
11. }
12. return 0;
13. }
Berikut ini baris-baris yang
ditambahkan di main() untuk
melakukan pengecekan tadi, serta tambahan baris yang dilakukan di luar game loop, setelah
permainan berakhir (game over) (lihat baris 18-32,
44-48).
1. /**
2. Program utama
3. */
4. int main() {
5. ...
6.
7. // Game loop. Bagian di dalam while akan dieksekusi terus menerus
8. while (true) {
9.
10. ...
11. // Snake bergerak setiap 200 ms (sesuai nilai variable snake_speed)
12. // Dihitung dengan membandingkan selisih waktu sekarang dengan waktu
13. // terakhir kali snake bergerak.
14. if (interval >= snake_speed) {
15.
16. ...
17.
18. // Jika posisi kepala (head) menabrak tembok pembatas,
19. // maka permainan berakhir (keluar dari game loop)
20. if (x < 0 || x >= console_width || y < 0 || y >= console_height) {
21. break;
22. }
23.
24. // Jika posisi kepala (head) menabrak dirinya sendiri
25. // (posisi sama dengan salah satu segment), maka permainan
26. // berakhir (keluar dari game loop)
27. if (check_collision(x, y) == 1) {
28. break;
29. }
30.
31. // Jika tidak terjadi tabrakan (collision), maka snake
32. // boleh bergerak maju..
33. // Pop ekor, lalu push segment ke depan head sehingga
34. // snake tampak bergerak maju.
35. pop();
36. push(x, y);
37.
38. // Tampilkan kondisi permainan saat ini di layar...
39. ...
40. }
41. ...
42. }
43.
44. // Setelah keluar dari game loop, berarti permainan berakhir (game over)
45. system("cls");
46. printf("GAME OVER\n");
47.
48. printf("Press ENTER to exit...");
49. getchar();
50. return 0;
51. }
Jalankan program sekali lagi,
lalu coba arahkan ular ke dinding. Untuk pengetesan tabrakan terhadap diri
sendiri, bisa dilakukan dengan mengubah snake_size dengan
nilai yang lebih besar, agar ular cukup panjang untuk menabrak dirinya sendiri.