Tiếp theo câu chuyện của phần 1
Trong phần 1 – Lập trình game khó hay dễ?, mình đã hướng dẫn các bạn cách di chuyển 1 đối tượng trên màn hình console bằng ngôn ngữ C. Nếu các bạn có hứng thú với bài viết của mình thì thời gian tới, mình sẽ hướng dẫn các bạn làm 1 game gì đó hay xíu với Unity.
Chắc các bạn cũng không xa lạ gì với game Snake hay còn gọi với tên khác là Rắn săn mồi, theo mình thấy game này khá dễ lập trình đặc biết là hạn chế khi làm trên màn hình console cũng khá phù hợp với game này. Mình cũng nói luôn là game lập trình trên màn hình console, nếu các bạn sử dụng lệnh xóa màn hình thì hiện tượng giật lag là bình thường, để giải quyết vấn đề này mình sẽ hướng dẫn các bạn sau (cách mình sử dụng là sử dụng một mảng 2 chiều để vẽ, hiển thị).
Lập trình game Snake
Ở phần này mình sẽ sử dụng hàm cho các bạn dễ hiểu.
1. Phần khởi tạo
//định nghĩa màn hình console, ở đây mình định nghĩa màn hình game (giới hạn chạy của game) thay vì màn hình console
#define consoleWidth 41
#define consoleHeight 26
//tạo 1 enum trạng thái di chuyển
enum TrangThaiDC{ UP, RIGHT, LEFT, DOWN };
//khởi tạo tọa độ
struct ToaDoXY{
int x; //hoành độ
int y; //tung độ
};
//khởi tạo thức ăn cho rắn mang giá trị là tọa độ xuất hiện
struct ThucAn{
ToaDoXY td;
};
//khởi tạo con rắn
struct Snake{
ToaDoXY dot[100]; //tọa độ của con rắn
int n; // số đốt của con rắn(độ dài)
TrangThaiDC tt; //trạng thái di chuyển
};
Bây giờ là hàm khởi tạo giá trị đầu cho các đối tượng
void KhoiTao (Snake &snake, ThucAn &ta)
{
//khởi tạo snake
snake.n=0; //số đốt ban đầu bằng 1
snake.dot[0].x=0; //hoành độ của đốt đầu tiên
snake.dot[0].y=0; //tung độ của đốt đầu tiên
snake.tt=RIGHT; //trạng thái di chuyển mặc định là sang phải
//khởi tạo giá trị đầu của thức ăn
ta.td.x=5; //tung độ
ta.td.y=10; //hoành độ
}
2. Hiển thị
//hàm hiển thị
void HienThi( Snake snake, ThucAn ta, int score) //có thêm score để tính điểm
{
clrscr(); //xóa màn hình
//vẽ khung cho game
//vẽ biên dưới
for(int i=0; i<consoleWidth; i++)
{
TextColor(8); //màu khung
gotoXY(i; consoleHeight-1); //tọa độ vẽ
putchar(219); //tham khảo thêm hàm này trên internet (in kí tự thứ 219 trong ASCII)
}
//vẽ biên bên phải
for(i=0; i<consoleHeight; i++)
{
gotoXY(consoleWidth, i);
putchar(219);
}
//thiết lập tọa độ nhảy, màu sắc và in thức ăn
TextColor(4);
gotoXY(ta.td.x, ta.td.y); // tọa độ thức ăn
putchar(3);
//vẽ con rắn
//vẽ phần đầu
TextColor(4);
gotoXY (snake.dot[0].x , snake.dot[0].y ); //tọa độ của đầu snake.dot[0]
putchar(2);
//vẽ phần thân
for(int i=1; i<snake.n; i++)
{
TextColor(4);
gotoXY ( snake.dot[i].x, snake.dot[i].y); // tọa độ con rắn(phần thân)
putchar('o');
}
//hiển thị điểm số(lưu ý: hiển thị bên ngoài khung game)
TextColor(10);
gotoXY(consoleWidth + 2, 8);
printf("SCORE : %d",score);
//hiển thị thêm các thứ phụ khác
gotoXY(consoleWidth + 2, consoleHeight - 2);
printf("A : LEFT S : DOWN D : RIGHT W : UP");
}
3. Điều khiển, di chuyển
//viết hàm điều khiển, di chuyển
void DieuKhien( Snake &snake )
{
//cách di chuyển của con rắn là tọa độ đốt sau thế chỗ vị trí tọa độ đốt trước, nên để mô phỏng cách di chuyển cách di chuyển của snake thì chỉ cần truyền tọa độ của đốt trước cho đốt cũ, phần đầu snake.dot[0] đóng vai trò là nguồn truyền trạng thái di chuyển
for (int i= snake.n -1 ; i>0; i--)
{
snake.dot[i] = snake.dot[i-1];
}
//điều đầu snake(phần thân đã đi theo đầu nên chỉ cần điều khiển phần đầu)
if (kbhit()) //phát hiện có phím nhấn vào
{
int key = _getch(); // lưu kí tự nhấn vào
// thiết lập lệnh di chuyển
if ( key == 'A' || key == 'a' )
snake.tt= LEFT;
else if ( key == 'S' || key == 's')
snake.tt= DOWN;
else if ( key == 'D' || key == 'd')
snake.tt= RIGHT;
else if ( key == 'W' || key == 'w')
snake.tt= UP;
}
//thiết lập cách di chuyển chủa các trạng thái di chuyển UP, DOWN, LEFT, RIGHT
if ( snake.tt == UP ) // UP : giảm tung độ (đi lên)
snake.dot[0].y--;
else if ( snake.tt == DOWN ) //DOWN : tăng tung độ (đi xuống)
snake.dot[0].y++;
else if ( snake.tt == LEFT ) //LEFT: giảm hoành độ (sang trái)
snake.dot[0].x--;
else if ( snake.tt == RIGHT ) //RIGHT: tăng hoành độ (sang phải)
snake.dot[0].x++;
}
4. Xử lí
//hàm xử lí các tình huống game
void XuLi(Snake &snake, ThucAn &ta , int &speed, int &score)
{
//khi con rắn ăn hoa quả (tức là tọa độ snake.dot[0] bằng tọa độ của thức ăn) cần xử lí các chức năng sau: số đốt tăng thêm 1, điểm tăng lên, thức ăn xuất hiện ở vị trí khác, tốc độ game (nhịp game) tăng lên để tăng độ khó,...(có thể thêm tùy các bạn)
//tăng thêm đốt, tọa độ đốt trước bằng tọa độ đốt sau
if(snake.dot[0].x == ta.td.x && snake.dot[0].y == ta.td.y)
{
for(int i = snake.n; i>0 ; i--)
{
snake.dot[i] = snake.dot[i-1]; //tọa độ đốt trước bằng tọa độ đốt sau
}
snake.n++; //tăng số đốt lên 1
//tốc độ tăng lên sau khi ăn mồi
speed-=-5;
if(tocdo<=0)
{
tocdo = 0; // tốc độ đạt tối đa
}
//điểm tăng lên 1
score+=1;
//trạng thái di chuyển của snake sau khi ăn mồi
if ( snake.tt == UP )
snake.dot[0].y--;
else if ( snake.tt == DOWN )
snake.dot[0].y++;
else if ( snake.tt == LEFT )
snake.dot[0].x--;
else if ( snake.tt == RIGHT )
snake.dot[0].x++;
//tọa độ mới của thức ăn sau khi con rắn ăn(xuất hiện ngẫu nhiên)
ta.td.x = rand() % (consoleWidth-1);
ta.td.y = rand() % (consoleHeight-1);
}
}
- Mình sẽ viết 1 hàm xử lí khi con rắn chạm tường(ở đây mình sẽ cho con rắn đi xuyên tường thay vì chạm tường chết) và chạm thân
//xử lí khi con rắn chạm tường, tự cham thân mình
int XuyenTuong( Snake &snake)
{
//đi xuyên tường
if (snake.dot[0].x < 0)
snake.dot[0].x = consoleWidth - 2;
else if ( snake.dot[0].x >= consoleWidth-1 )
snake.dot[0].x = 0;
else if (snake.dot[0].y < 0)
snake.dot[0].y = consoleHeight-2;
else if (snake.dot[0].y >= consoleHeight-1)
snake.dot[0].y = 0;
//xử lí chạm thân
for (int i=1; i < snake.n ; i++)
{
if ( snake.dot[0].x == snake.dot[i].x && snake.dot[0].y == snake.dot[i].y)
{
return 1; // trẩ về 1 (game over)
}
}
return 0; //trả về 0 nếu chưa thua
}
5. Chương trình chính
// chương trình chính thì không có gì, chỉ cần gọi các hàm đã tạo vào vòng lặp game là được
int main()
{
srand(time(NULL)); //khởi tạo bộ sinh số ngẫu nhiên
Snake snake; //khai báo
ThucAn ta;
int score =0; //khởi tạo điểm =0
int speed=300;
//vòng lặp game
while(1)
{
//HIỂN THỊ
HienThi(snake, ta, score);
//ĐIỀU KHIỂN
DieuKhien(snake);
//XỬ LÍ
XuLy(snake, ta , speed, score);
XuyenTuong(snake);
//thoát vòng lặp game nếu thua
if(XuyenTuong(snake) == 1)
{
TextColor(6);
gotoXY( consoleWidth + 2, 15);
printf("Game Over @.@"); //in ra GameOver
gotoXY( consoleWidth + 2, 17);
printf("NHAN SPACE !!!!"); //in hướng dẫn
while(_getch() != 32); //nhấn SPACE để thoát vòng lặp
clrscr();
gotoXY( consoleWidth + 2, 20);
printf("ENTER : EXIT!!"); // hiển thị hướng dẫn exit game
gotoXY(40, 2);
printf("THANK YOU FOR PLAYING!!!!!");
while ( _getch() != 13); //vòng lặp nhấn ENTER để thoát
break; //thoát vòng lặp game (thoát game)
}
Sleep (tocdo); //điều khiển tốc độ game
}
return 0;
}
Đoạn code trên là ví dụ điển hình của 1 game đơn giản và tất nhiên không phải do mình làm mà là từ kênh Youtube của anh Nguyễn Trung Thành – người đã truyền cảm hứng cho mình làm game, các bạn có thể xem để hiểu rõ hơn.
Thực hành
Như các bạn thấy, để lập trình 1 game gọi là hay thì không hề đơn giản 1 chút nào, nó đòi hỏi năng lực, tư duy, sáng tạo rất lớn của người lập trình nên sẽ rất dễ gây nản cho những bạn không có đam mê với nghề này. Sau đây là đoạn code do mình làm sau khi xem video, hi vọng các bạn có thể hình dung được đoạn code của mình ở dưới đây Tải về
Các bạn có thể cải tiến thêm đoạn code trên các chức năng khác như: thêm các vật phẩm, chướng ngại vật, các cách xử lí khác,… tùy sở thích mà các bạn có thể làm rất nhiều thứ thú vị (mình thì thêm các chướng ngại vật di chuyển, thêm chức năng chọn độ khó, các chức năng pause, exit, hiển thị thức ăn lớn nếu ăn 5 thức ăn nhỏ,…). Ở game này thì mình cảm thấy phần khó khăn nhất là phần test game, bởi vì để test và tìm ra lỗi với game nhiều yếu tố ngẫu nhiên là một việc cực kì khó khăn và tốn thời gian, mình đã test rất nhiều lần và xuất hiện rất nhiều vấn đề cần giải quyết, giải quyết xong rồi thì phải xem đã khắc phục được chưa,.. nói chung rất khổ. Nên các bạn đừng nghĩ làm tester là dễ, hãy thử làm và cảm nhận đi.
Như đã nói ở đầu bài, đoạn code hướng dẫn do sử dụng lệnh xóa màn hình clrscr() nên sẽ không tránh khỏi giật lag và tất nhiên trong đoạn code game của mình đã khắc phục điều đó, cũng từ video hướng dẫn của anh Thành thôi, nếu các bạn cần hoặc chưa hiểu lắm thì lần sau mình sẽ hướng dẫn các bạn cụ thể hơn về đoạn code của mình, các bạn có thể test và cho mình xin đánh giá.
Chúc các bạn thành công!
Nguồn: PhamAnChi