Lập trình game khó hay dễ (P2) ?

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 @[email protected]");		//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 :https://www.youtube.com/watch?v=ScYWTDt.

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!