Nick Chung gửi vào
- 12251 lượt xem
PONG – một game đơn giản, funny và đòi hỏi chút kiên nhẫn. Hôm nay mình sẽ hướng dẫn các bạn làm game này trên lcd ST7565 bằng Arduino .
Phần cứng
Tải về thư viện đồ họa
Bạn hãy tải về thư viện tại bài viết
ST7565 | Hướng dẫn sử dụng glcd ST7565 homephone và chia sẻ thư viện
Chuẩn bị phần cứng
- Arduino chip atm328 trở lên, mình sử dụng arduino uno r3
- Lcd st7565 128x64 homephone spi
- Nút bấm: 6 nút
Nối mạch
Bạn hãy tham khảo cách nối mạch và nút bấm tại bài viết giới thiệu lcd nhé:
Quản lí đối tượng.
Bạn có thể cần đọc các bài viết về quản lí đối tượng Game :
- Phần 1: Hiệu ứng đồ họa Game trên lcd
- Phần 2: Chuyển động và lập trình cơ bản
- Phần 3: Quản lí các đối tượng Game
- Phần 4: Xử lí va chạm của của các đối tượng Game
Chương trình được lập trình theo hướng thủ tục.
Có 3 loại đối tượng cần quan tâm:
Đối tượng
|
Chức năng, nhiệm vụ
|
Thuộc tính
|
Thanh trượt
|
Người dùng điều khiển di chuyển trái phải.
|
Tọa độ
|
Viên đạn
|
Di chuyển theo 4 hướng cơ bản (chéo): hướng 45o – 135o – 235o – 315o. Chạm vào biên hoặc Thanh trượt thì phản xạ lại.Chạm vào gạch thì công phá gạch rồi phản xạ lại .
|
Tọa độ, Hướng bay
|
Gạch
|
Thứ để viên đạn công phá.
|
Tọa độ
|
- Sử dụng mảng 2 chiều quản lí 2 đối tượng chính “Thanh trượt và viên đạn”
- Đơn giản hóa việc quản lí “Gạch” : Sử dụng hàm “GetPixel” để kiểm tra điểm ảnh tọa độ (x,y) có được vẽ hay không, nếu có thì đó là “Gạch’, không thì thôi.
- Quy ước lại hướng bay: Để thuận tiện , hướng bay sẽ được quy ước giống như hướng địa lí.
Góc lệch vector
|
Hướng
|
45o
|
Đông bắc
|
135o
|
Tây bắc
|
235o
|
Tây nam
|
315o
|
Đông nam
|
- Quy ước phương thức phản ứng của viên đạn.
Bằng cách chơi lại game này, quan sát rất kĩ hành vi của viên đạn mình tổng kết hướng bay của viên đạn như sau.
Chạm tường
Chạm gạch
CODING
// GAME NÀY ĐƯỢC VIẾT BỞI :THÁI SƠN -12:38AM 7/9/2016 // GAME ĐƯỢC UP CHÍNH THỨC TẠI ARDUINO.VN //SỬ DỤNG 6 BUTTON NỐI PULLUP // VÀO HÀM setup, set_brick, set_begin ĐỂ CÀI ĐẶT THEO SỞ THÍCH //không được bấm quá nhiều nút cùng lúc #include "ST7565_homephone.h" // add a bitmap library of a 16x16 fruit icon #include "bmps.h" ST7565 lcd(3, 4, 5, 6); //cài đặt chân input #define fight_b A5 #define select_b A4 #define right_b A3 #define up_b A2 #define left_b A1 #define down_b A0 byte object[2][4]; /*mảng tĩnh object[2][3] i 0: thanh trượt i 1: vien đạn object[i][0] : tồn tại hay không, (dùng khi muốn tạo mới đối tượng) object[i][1] : hoành độ (x) object[i][2] : tung độ (y) object[i][3] : hướng: +đông bắc: hướng = 10 + tây bắc: hương = 20 +tây nam: hướng = 30 +đông nam: hướng = 40 */ byte danSize, width_cc, high_cc; byte Xmin, Xmax, Ymin, Ymax; unsigned int diem, timedelay; // Chương trình trượt 1 lần void setup() { lcd.ON(); lcd.SET(23, 0, 0, 0, 4); pinMode(left_b, INPUT_PULLUP); pinMode(down_b, INPUT_PULLUP); pinMode(right_b, INPUT_PULLUP); pinMode(up_b, INPUT_PULLUP); pinMode(fight_b, INPUT_PULLUP); pinMode(select_b, INPUT_PULLUP); danSize = 3; // 3*3 pixel width_cc = 20; // chiều dài con trượt high_cc = 3; //chiều cao con truot Xmin = 5; //biên trái Xmax = 120; //biên phải Ymin = 5; //biên trên Ymax = 60; //biên dưới //CHÚ Ý: nếu đạn vượt quá Ymax thì thua diem = 0; // điểm ban đàu timedelay = 80; //chu kì delay(làm chễ khung hình) /* Serial.begin(9600); */ } void set_brick() { //vẽ gạch //|||||||||||||||||||||||||||||||||||||||||| //Bạn có thể vẽ bất cứ thứ gì lên màn hình khi nó //start game vào đây int i1, i2; for (i1 = 0; i1 <= 50; i1++) { // khởi động-vẽ gạch lcd.drawline(Xmin, i1, Xmax, i1, BLACK); delay(10); lcd.display(); //hiển thị } // cả hình tròn nữa for (i2 = 2; i2 <= 50; i2 += 4) { lcd.circle(65, 30, i2, DELETE); // VÂN tròn màu trắng lcd.circle(65, 30, i2 + 1, DELETE); // VÂN tròn màu trắng delay(40); lcd.fillrect(8, 22, 118, 11, DELETE); lcd.rect(8, 22, 118, 11, BLACK); lcd.Asc_String(10, 24, Asc("A R D U I N O . V N"), BLACK); lcd.rect(58, 54, 70, 11, BLACK); lcd.Asc_String(60, 55, Asc("By Thai Son"), BLACK); lcd.display(); // nên nhớ cần đặt display sau lệnh delay //điều rất có ý nghĩa trong việc vẽ ở tốc đọ cao } //for //|||||||||||||||||||||||||||||||||||||||||| lcd.fillrect(0, 45, 128, 63, DELETE); lcd.display(); } void ve_dan(byte x, byte y) { lcd.fillrect(x, y, danSize, danSize, BLACK); lcd.display(); } // VẼ ĐẠN void xoa_dan(byte x, byte y) { lcd.fillrect(x, y, danSize, danSize, DELETE); lcd.display(); //vẽ chữ nhật màu trắng để xóa thanh trượt vị trí cũ } //XÓA ĐẠN void ve_con_truot(byte x, byte y, byte width, byte high) { lcd.fillrect(x, y, width, high, BLACK); lcd.display(); } // VẼ CON trượt void xoa_con_truot(byte x, byte y, byte width, byte high) { lcd.fillrect(x, y, width, high, DELETE); lcd.display(); } // void di_con_truot() { //lấy thông tin byte x, y, x_cu, y_cu; //cũ x_cu = object[0][1]; y_cu = object[0][2]; //đọc giá trị 4 button if (lcd.Pullup_4(right_b, up_b, left_b, down_b) == 1) { // nút right xoa_con_truot(x_cu, y_cu, width_cc, high_cc); //xóa con trượt cũ trên màn hình if (x_cu <= (Xmax - width_cc)) { // trong biên x = x_cu + 5; //cộng hoành độ lên 5 đơn vị } else { x = x_cu; } ve_con_truot(x, y_cu, width_cc, high_cc); object[0][1] = x; // ghi lại thông tin hoành độ } if (lcd.Pullup_4(right_b, up_b, left_b, down_b) == 3) { // nút left xoa_con_truot(x_cu, y_cu, width_cc, high_cc); //xóa con trượt cũ trên màn hình if (x_cu >= Xmin) { // trong biên x = x_cu - 5; //trừ hoành độ xuống 5 đơn vị } else { x = x_cu; } ve_con_truot(x, y_cu, width_cc, high_cc); object[0][1] = x; // ghi lại thông tin hoành độ } } //di chuyển con trượt void di_vien_dan(byte x_cu, byte y_cu, byte huong) { // viên đạn có 4 hướng đi byte x_moi, y_moi; //xóa ảnh cũ xoa_dan(x_cu, y_cu); //vẽ ảnh mới if (huong == 10) { //đông bắc x_moi = x_cu + 1; y_moi = y_cu - 1; } if (huong == 20) { //tây bắc x_moi = x_cu - 1; y_moi = y_cu - 1; } if (huong == 30) { //tây nam x_moi = x_cu - 1; y_moi = y_cu + 1; } if (huong == 40) { x_moi = x_cu + 1; y_moi = y_cu + 1; } //vẽ ve_dan(x_moi, y_moi); //ghi lại thông tin vào mảng object[1][1] = x_moi; object[1][2] = y_moi; } //di chuyển vien đạn byte test_huong(byte x, byte y, byte danSize, byte huong) { //x,y,danSize: 3 thuộc tính của viên đạn //kiểm tra vật thể ( gạch hoặc con trượt) ở hướng nào so với viên đạn //phải= 1 //trên= 2 //trái= 3 //dưới= 4 //chỉ cần kiểm tra về hướng đang di chuyển là đủ (không phải xung quanh) switch (huong) { case 10: // nếu đang đi hướng đông bắc for (byte y0 = y; y0 <= y + danSize; y0++) { //kiểm tra hướng đông if (lcd.getpixel(x + danSize + 2, y0) == 1) { return 1; } } for (byte x0 = x; x0 <= x + danSize; x0++) { // kiểm tra hướng bắc if (lcd.getpixel(x0, y - 2) == 1) { return 2; } } break; case 20: // nếu đang đi hướng tây bắc for (byte x0 = x; x0 <= x + danSize; x0++) { // kiểm tra hướng bắc if (lcd.getpixel(x0, y - 2) == 1) { return 2; } } for (byte y0 = y - 1; y0 <= y + danSize; y0++) { // kiểm tra hướng tây if (lcd.getpixel(x - 2, y0) == 1) { return 3; } } break; case 30: //nếu đang đi hướng tây nam for (byte y0 = y - 1; y0 <= y + danSize; y0++) { // kierm tra hướng tây if (lcd.getpixel(x - 2, y0) == 1) { return 3; } } for (byte x0 = x - 1; x0 <= x + danSize; x0++) { //kiểm tra hướng nam if (lcd.getpixel(x0, y + danSize + 1) == 1) { return 4; } } break; case 40: // nếu đang đi hướng đông nam for (byte x0 = x - 1; x0 <= x + danSize; x0++) { //kiểm tra hướng nam if (lcd.getpixel(x0, y + danSize + 1) == 1) { return 4; } } for (byte y0 = y; y0 <= y + danSize; y0++) { //kiểm tra hướng đông if (lcd.getpixel(x + danSize + 2, y0) == 1) { return 1; } } break; default: break; } //switch //nếu tất cả đều ko thỏa mãm return 0; } // kiểm tra hướng gạch void doi_huong(byte bien) { //biên switch (bien) { case 1: //biên phải_đông if (object[1][3] == 10) { object[1][3] = 20; } // ĐB>TB if (object[1][3] == 40) { object[1][3] = 30; } //ĐN>TN break; case 2: //biên trên_bắc if (object[1][3] == 20) { object[1][3] = 30; } // TB>TN if (object[1][3] == 10) { object[1][3] = 40; } //ĐB>ĐN break; case 3: //biên trái_tây if (object[1][3] == 30) { object[1][3] = 40; } // TN>ĐN if (object[1][3] == 20) { object[1][3] = 10; } //TB>ĐB break; case 4: //biên dưới(con trượt)_nam if (object[1][3] == 30) { object[1][3] = 20; } // TN>TB if (object[1][3] == 40) { object[1][3] = 10; } //ĐN>ĐB break; default: //no break; } //switch } //đổi hướng void BIGBANG(byte x, byte y) { //tạo vụ nổ công phá gạch = hcn màu trắng lcd.fillrect(x - danSize, y - danSize, danSize * 3, danSize * 3, DELETE); lcd.display(); } void bang_diem() { //THUA CUỘC // BẢNG ĐIỂM lcd.clear(); lcd.fillrect(0, 5, 128, 10, DELETE); lcd.Asc_String(0, 6, Asc(" DIEM CUA BAN LA:"), BLACK); lcd.Number_Ulong(98, 6, diem, ASCII_NUMBER, BLACK); lcd.Asc_String(60, 55, Asc("By Thai Son"), BLACK); lcd.display(); // nên nhớ cần đặt display sau lệnh delay lcd.display(); //Reset object[2][4] for (byte i = 0; i <= 1; i++) { for (byte j = 0; j <= 3; j++) { object[i][j] = 0; } } // reset diem diem = 0; } void lap_vo_han() { //LẶP CÓ ĐIỀU KIỆN //Nhấn nút fight để chơi int y = 55; lcd.Asc_String(10, y + 2, Asc("Fight!"), BLACK); lcd.display(); while (digitalRead(fight_b) != 0) { lcd.rect(8, 55, 40, 10, BLACK); lcd.display(); if (digitalRead(fight_b) == 0) { break; // thoát ngay } delay(250); lcd.rect(8, y, 40, 10, DELETE); lcd.display(); if (digitalRead(fight_b) == 0) { break; // thoát ngay } delay(250); } } void set_begin() { lcd.clear(); //làm sạch làm hình object[0][0] = 1; // cấp tồn tại cho con trượt object[0][1] = 60; // cấp hoành độ ban đầu object[0][2] = Ymax - high_cc; // cấp tung độ ban đầu object[1][0] = 1; // cấp tồn tại cho viên đạn object[1][1] = 60; // cấp hoành độ cho viên đạn object[1][2] = (Ymax - high_cc) - danSize; //cấp tung độ object[1][3] = 10; //hướng đông bắc /* //Bạn có thể gán thế này object[][]={ {1,60,Ymax-high_cc,0}, {1,60,(Ymax-high_cc)-danSize,10} }; */ } void loop() { //Block 0: start if ((object[0][0] == 0) && (object[1][0] == 0)) { set_begin(); //cấp dữ liệu set_brick(); //vẽ gạch } //Block 1: di chuyển con trượt di_con_truot(); //Block2: di chuyển đạn + kiểm tra cahmj biên byte x_cu, y_cu, huong_cu; // x cũ, y cũ, hướng cũ byte huong_moi; //mới //lấy thông tin x_cu = object[1][1]; y_cu = object[1][2]; huong_cu = object[1][3]; //kiểm tra chạm biên if (x_cu >= Xmax) { doi_huong(1); } if (y_cu <= Ymin) { doi_huong(2); } if (x_cu <= Xmin) { doi_huong(3); } if (y_cu >= ((Ymax - danSize * 2) - (high_cc))) { if (test_huong(x_cu, y_cu, danSize, huong_cu) == 4) { //kiểm tra hướng nam doi_huong(4); } } //lấy thông tin huong_moi = object[1][3]; //di chuyển di_vien_dan(x_cu, y_cu, huong_moi); //Block 3: phá gạch if (y_cu < ((Ymax - danSize * 2) - (high_cc))) { //nếu đạn ở phía trên con trượt byte kt_huong; kt_huong = test_huong(x_cu, y_cu, danSize, huong_moi); if (kt_huong != 0) { //phát hiện vật cản //b1: nổ BIGBANG(x_cu, y_cu); //b2: đỏi hướng giống như chạm biên doi_huong(kt_huong); //b3: cộng điểm diem++; } } //Block 4: Nếu quá biên dưới sẽ chết if (y_cu > Ymax) { bang_diem(); //in bảng điểm // nhấn nút fight để chơi lap_vo_han(); } //Block5: LÀM CHỄ if (digitalRead(fight_b) == 0) { //nút fight được nhấn delay(timedelay / 4); //tăng tốc gấp 4 } else { delay(timedelay); } //giữ nguyên /* Kiểm tra Serial.print(" Co vat can huong:"); Serial.println(test_huong(x_cu,y_cu, danSize, huong_moi)); Serial.println("______________ton_tai _____ X_______Y______huong"); for(byte i =0; i<=1; i++){ Serial.print(" Object("); Serial.print(i); Serial.print(") "); for(byte j=0; j<=3; j++){ Serial.print("_______"); Serial.print(object[i][j]); } Serial.println(""); } */ }
Một vài hàm set_brick() ;
Dưới đây là các thuật toán vẽ để tạo gạch trong game, bạn có thể copy và sửa lại hàm set_brick() trong game bằng các đoạn code dưới.
WALL
void set_brick() { //vẽ gạch //|||||||||||||||||||||||||||||||||||||||||| //Bạn có thể vẽ bất cứ thứ gì lên màn hình khi nó //start game vào đây int i1, i2; for (i1 = 0; i1 <= 45; i1++) { // khởi động-vẽ gạch lcd.drawline(Xmin, i1, Xmax, i1, BLACK); delay(10); lcd.display(); //hiển thị } // cả hình tròn nữa for (i2 = 2; i2 <= 40; i2 += 4) { lcd.drawline(Xmin, i2, Xmax, i2, DELETE); //ĐƯỜNG KẺ TRẮNG lcd.rect(58, 54, 70, 11, BLACK); lcd.Asc_String(60, 55, Asc("By Thai Son"), BLACK); lcd.display(); // nên nhớ cần đặt display sau lệnh delay //điều rất có ý nghĩa trong việc vẽ ở tốc đọ cao } //for //|||||||||||||||||||||||||||||||||||||||||| lcd.fillrect(0, 45, 128, 63, DELETE); lcd.display(); }
Brick matrix
void set_brick() { //vẽ gạch //|||||||||||||||||||||||||||||||||||||||||| //Bạn có thể vẽ bất cứ thứ gì lên màn hình khi nó //start game vào đây lcd.rect(58, 54, 70, 11, BLACK); lcd.Asc_String(60, 55, Asc("By Thai Son"), BLACK); lcd.display(); // nên nhớ cần đặt display sau lệnh delay //////////////////////////////////////////////// for (int x = 0; x < Xmax; x += 10) { for (int y = 0; y < 50; y += 10) { lcd.FillRect(x, y, 5, 5, BLACK); lcd.display(); delay(50); } } //|||||||||||||||||||||||||||||||||||||||||| lcd.fillrect(Xmin, Ymax - 15, Xmax, 63, DELETE); lcd.display(); }
Random brick
void set_brick() { //vẽ gạch //|||||||||||||||||||||||||||||||||||||||||| //Bạn có thể vẽ bất cứ thứ gì lên màn hình khi nó //start game vào đây lcd.rect(58, 54, 70, 11, BLACK); lcd.Asc_String(60, 55, Asc("By Thai Son"), BLACK); lcd.display(); // nên nhớ cần đặt display sau lệnh delay //////////////////////////////////////////////// int x, y, size_brick; for (int i = 0; i < 50; i++) { // 50 viên gạch randomSeed(millis()); // lấy random từ đồng hồ x = random(Xmin, Xmax); y = random(Ymin, Ymax - 15); size_brick = random(3, 10); lcd.FillRect(x, y, size_brick, size_brick, BLACK); lcd.display(); delay(30); } //|||||||||||||||||||||||||||||||||||||||||| lcd.fillrect(Xmin, Ymax - 15, Xmax, 63, DELETE); lcd.display(); }
Random circle
void set_brick() { //vẽ gạch //|||||||||||||||||||||||||||||||||||||||||| //Bạn có thể vẽ bất cứ thứ gì lên màn hình khi nó //start game vào đây lcd.rect(58, 54, 70, 11, BLACK); lcd.Asc_String(60, 55, Asc("By Thai Son"), BLACK); lcd.display(); // nên nhớ cần đặt display sau lệnh delay //////////////////////////////////////////////// int x, y, R; for (int i = 0; i < 25; i++) { // 50 viên gạch randomSeed(millis()); // lấy random từ đồng hồ x = random(Xmin, Xmax); y = random(Ymin, Ymax - 15); R = random(3, 10); lcd.FillCircle(x, y, R, BLACK); lcd.display(); delay(30); } //|||||||||||||||||||||||||||||||||||||||||| lcd.fillrect(Xmin, Ymax - 15, Xmax, 63, DELETE); lcd.display(); }
Kết
Hi vọng bạn có thật nhiều niềm vui khi chơi game và thử sức lập trình nó với ARDUINO. Chúc bạn tích lũy thêm nhiều kĩ năng hơn trong lập trình C/C++ hơn nữa.