Nick Chung gửi vào
- 10752 lượt xem
Đây là phiên bản thứ 2 của Game hứng trứng đơn giản, cùng với sự xuất hiện của cô gà mái, hi vọng đây sẽ là sản phẩm Game mang lại nhiều niềm vui trong những ngày đầu năm Đinh Dậu.
Là phiên bản nâng cấp >++
Đây là phiên bản đơn giản trước đó:
Làm game hứng trứng đơn giản - phiên bản 1
Còn ở phiên bản thứ 2 này:
Một chú gà đi trên mái kèo, là một nhân vật hư cấu, chú vừa đi vừa đẻ trứng. Để trứng không bị vỡ khi rơi, 2 nhân vật Cả Mẹo và Dần Béo sẽ phải phối hợp nhịp nhàng hứng những quả trứng vào rổ.
Phân vai:
- Cả Mẹo – có cái đầu đinh: cẩm chiếc rổ để hứng trứng.
- Dần Béo – mập ú và rất vụng về là sở đoản của cậu ta: tuy nhiên vì chỉ có một chiếc rỏ , cậu ấy bèn phải dùng chiếc vợt để đỡ những quả trứng đang rơi, trứng sẽ nảy lên khi rơi vào chiếc vợt đó.
Tính Điểm:
- Trứng rơi thẳng vào rổ: cộng 1 điểm.
- Trứng rơi và được Dần Béo đỡ bằng vợt rồi rơi vào rổ: cộng 2 điểm.
- Trứng rơi và được Dần Béo đỡ bằng vợt nhưng không vào rổ: trừ 2 điểm.
- Rơi thẳng xuống đất: Trừ 1 điểm.
Điều khiển:
- Sử dụng 2 nút di chuyển trái phải để di chuyển nhân vật.
- Nhấn nút ”OK” để lựa chọn nhân vật.
- Mũi tên sẽ chỉ vào nhân vật đang được chọn.
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
|
Gà mái
|
Di chuyển qua lại ở phía trên màn hình. Đẻ trứng một cách ngẫu nhiên.
|
Tọa độ, hướng.
|
Dần và Mẹo.
|
Hứng và đỡ trứng đang rơi.
|
Tọa độ, hướng nhìn.
|
Trứng
|
Rơi thẳng từ trên xuống, tọa độ bắt đầu là tọa độ của gà. Bay lên theo đường parabol khi rơi xuống chiếc vợt của Dần Béo.
|
Sự tồn tại, tọa độ.
|
Bạn hãy đọc các bài trước về quản lí các đối tượng này nhé !
Di chuyển gà
#include "ST7565_homephone.h" // add a bitmap library of a 16x16 fruit icon #include "bmps.h" ST7565 lcd(3, 4, 5, 6); const static unsigned char __attribute__((progmem)) ga_mai_16x16[] = { 0x20, 0xD8, 0x2C, 0x0C, 0x9E, 0x72, 0x42, 0xC0, 0x40, 0x40, 0x60, 0xFC, 0xCE, 0x3B, 0x7B, 0x01, 0x00, 0x06, 0x09, 0x10, 0x30, 0x48, 0x44, 0x4E, 0x59, 0x71, 0x39, 0x1F, 0x0E, 0x00, 0x00, 0x00 }; /* Qủa trứng (6x7) pixel * */ const static unsigned char __attribute__((progmem)) trung_6x7[] = { 0x1C, 0x22, 0x41, 0x41, 0x22, 0x1C, }; byte Xmin, Ymin, Xmax, Ymax; //biên // mảng cho 2 đối tượng gà(0) và trứng(1) byte object[2][4]; //object[i][0]: tồn tại hay không //object[i][1]: tọa độ x //object[i][2]: tọa độ y //object[i][3]: hướng (1 phải, 2 trên , 3 trái, 4 dưới) // // Chương trình chạy 1 lần void setup() { lcd.ON(); lcd.SET(23, 0, 0, 0, 4); Xmin = 0; Xmax = 125; Ymin = 0; Ymax = 63; } void ve_ga_di() { byte x_cu, y_cu, huong_cu; byte x_moi; x_cu = object[0][1]; y_cu = object[0][2]; huong_cu = object[0][3]; //lấy thông tin if (huong_cu == 1) { //gà quay về bên phải x_moi = x_cu + 1; lcd.Plus_Bitmap(x_moi, y_cu, 16, 16, ga_mai_16x16, 0, MIRROR, BLACK); // xoay bitmap (mirror) // } if (huong_cu == 3) { //gà quay về bên trái x_moi = x_cu - 1; lcd.Plus_Bitmap(x_moi, y_cu, 16, 16, ga_mai_16x16, 0, NO_MIRROR, BLACK); // không xoay (non mirror) } //ghi lại thông tin object[0][1] = x_moi; lcd.display(); } //vẽ gà void xoa_ga(byte x, byte y) { lcd.FillRect(x, y, 16, 16, DELETE); // lcd.display(); } //xóa gà void ga_di_va_de_trung() { // gà đi và đẻ trứng //////////// XỬ LÍ GÀ//////////// //b1: cấp giấy khai sinh if (object[0][0] == 0) { // nếu không tồn tại gà thì phải tạo gà // cấp thuộc tính cho đối tượng gà object[0][1] = Xmin + 5; //hoành độ ban đầu object[0][2] = Ymin; // tung độ ban đầu object[0][3] = 1; //hướng sang bên phải object[0][0] = 1; //cấp quyền tồn tại } //b2: gà đi và quay đầu //lấy thông tin; if (object[0][1] <= Xmin) { // nếu gà đến biên trái thì phải quay đầu về bên phải object[0][3] = 1; //ghi lại thông tin } if (object[0][1] >= Xmax) { // nếu gà đến biên phải thì phải quay đầu về bên trái object[0][3] = 3; //ghi lại thông tin } ///////////GÀ ĐANG ĐI///////// // lấy tt xoa_ga(object[0][1], object[0][2]); //xóa ảnh cũ ve_ga_di(); // vẽ ảnh mới } void loop() { ga_di_va_de_trung(); delay(100); }
Kết quả chạy thử
Di chuyển gà và đẻ trứng
#include "ST7565_homephone.h" // add a bitmap library of a 16x16 fruit icon #include "bmps.h" ST7565 lcd(3, 4, 5, 6); const static unsigned char __attribute__((progmem)) ga_mai_16x16[] = { 0x20, 0xD8, 0x2C, 0x0C, 0x9E, 0x72, 0x42, 0xC0, 0x40, 0x40, 0x60, 0xFC, 0xCE, 0x3B, 0x7B, 0x01, 0x00, 0x06, 0x09, 0x10, 0x30, 0x48, 0x44, 0x4E, 0x59, 0x71, 0x39, 0x1F, 0x0E, 0x00, 0x00, 0x00 }; /* Qủa trứng (6x7) pixel * */ const static unsigned char __attribute__((progmem)) trung_6x7[] = { 0x1C, 0x22, 0x41, 0x41, 0x22, 0x1C, }; byte Xmin, Ymin, Xmax, Ymax; //biên // mảng cho 2 đối tượng 1 gà(0) và 3 trứng(1,2.3) byte object[1 + 3][4]; //object[i][0]: tồn tại hay không //object[i][1]: tọa độ x //object[i][2]: tọa độ y //object[i][3]: hướng (1 phải, 2 trên , 3 trái, 4 dưới) // // Chương trình chạy 1 lần void setup() { lcd.ON(); lcd.SET(23, 0, 0, 0, 4); Xmin = 0; Xmax = 125; Ymin = 0; Ymax = 63; } //=========================================================== ////////////////DI CHUYỂN GÀ//////////////////// void di_chuyen_ga() { byte x_cu, y_cu, huong_cu; byte x_moi; x_cu = object[0][1]; y_cu = object[0][2]; huong_cu = object[0][3]; //lấy thông tin if (huong_cu == 1) { //gà quay về bên phải x_moi = x_cu + 1; lcd.Plus_Bitmap(x_moi, y_cu, 16, 16, ga_mai_16x16, 0, MIRROR, BLACK); // xoay bitmap (mirror) // } if (huong_cu == 3) { //gà quay về bên trái x_moi = x_cu - 1; lcd.Plus_Bitmap(x_moi, y_cu, 16, 16, ga_mai_16x16, 0, NO_MIRROR, BLACK); // không xoay (non mirror) } //ghi lại thông tin object[0][1] = x_moi; lcd.display(); } //vẽ gà void xoa_ga(byte x, byte y) { lcd.fillrect(x, y, 18, 18, DELETE); lcd.display(); } //xóa gà void ga_di_va_de_trung() { // gà đi và đẻ trứng //////////// XỬ LÍ GÀ//////////// //b1: cấp giấy khai sinh if (object[0][0] == 0) { // nếu không tồn tại gà thì phải tạo gà // cấp thuộc tính cho đối tượng gà object[0][1] = Xmin + 5; //hoành độ ban đầu object[0][2] = Ymin; // tung độ ban đầu object[0][3] = 1; //hướng sang bên phải object[0][0] = 1; //cấp quyền tồn tại } //b2: gà đi và quay đầu //lấy thông tin; if (object[0][1] <= Xmin) { // nếu gà đến biên trái thì phải quay đầu về bên phải object[0][3] = 1; //ghi lại thông tin } if (object[0][1] >= Xmax) { // nếu gà đến biên phải thì phải quay đầu về bên trái object[0][3] = 3; //ghi lại thông tin } ///////////GÀ ĐANG ĐI///////// // lấy tt xoa_ga(object[0][1], object[0][2]); //xóa ảnh cũ di_chuyen_ga(); // vẽ ảnh mới } //=============================================================== ////////////////////TRỨNG RƠI////////////////////// void ve_trung(byte x, byte y) { lcd.bitmap(x, y, 6, 7, trung_6x7, BLACK); lcd.display(); } void xoa_trung(byte x, byte y) { //xóa = hình chữ nhật màu trắng lcd.fillrect(x, y, 7, 7, DELETE); lcd.display(); } void di_chuyen_trung(byte huong_cu, byte i) { byte x_cu, y_cu, x_moi, y_moi, ton_tai, huong_moi; //lây tt x_cu = object[i][1]; y_cu = object[i][2]; ton_tai = object[i][0]; if ((huong_cu == 4) && (ton_tai == 1)) { //trứng rơi xuống dưới; y_moi = y_cu + 1; x_moi = x_cu; huong_moi = huong_cu; ve_trung(x_moi, y_moi); } //ghi tt object[i][1] = x_moi; object[i][2] = y_moi; object[i][3] = huong_moi; } void ve_trung_roi() { for (byte i = 1; i <= 3; i++) { // xét lần lượt 3 quả trứng /* if((object[i][3]==3)||(object[i][3]==1)){ // nếu trứng i này đang bay theo parabol //thì phải nhảy sang trứng khác */ //vẽ trứng rơi if (object[i][0] == 0) { // nếu không tồn tại trứng thì phải tạo trứng //b1: chuẩn bị sẵn tọa độ bằng random randomSeed(millis() % 5000); byte X_random; X_random = random(Xmin, Xmax); // lưu thông tin object[i][1] = X_random; //chương trình chỉ chạy 1 lần mà thôi } //khi gà đi đến vị trí chuẩn bị sẵn này, trứng bắt đầu được tạo và rơi if (object[i][1] == object[0][1]) { // thỏa mãn trùng tọa độ //b2: cấp giấy khai sinh // cấp thuộc tính cho đối tượng trứng //(nên nhớ đã có hoành độ Random) object[i][2] = Ymin + 10; // tung độ ban đầu object[i][3] = 4; //hướng xuống dưới object[i][0] = 1; //cấp quyền tồn tại } //b2 : trứng roi //lấy thông tin xoa_trung(object[i][1], object[i][2]); //xóa ảnh cũ di_chuyen_trung(object[i][3], i); // vẽ ảnh mới //b3: Trứng chạm đất và vỡ // hàm test nếu tung độ quá biên thì mất quả trứng đó if (object[i][2] >= Ymax) { object[i][0] = 0; // xóa quyền tồn tại } } //for } //======================================================== void loop() { ga_di_va_de_trung(); ve_trung_roi(); delay(100); }
Kết quả test thử
Di chuyển nhân vật
#include "ST7565_homephone.h" // add a bitmap library of a 16x16 fruit icon #include "bmps.h" ST7565 lcd(3, 4, 5, 6); #ifdef __AVR__ #include <avr/io.h> #include <avr/pgmspace.h> #endif /** Nhân vật Cả Mẹo (22x18)pixel */ const static unsigned char __attribute__((progmem)) ca_meo_22x18[] = { 0x60, 0x9C, 0x62, 0x01, 0x01, 0xC1, 0x26, 0x29, 0x29, 0x4B, 0x4E, 0x48, 0x70, 0x00, 0xC0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xC0, 0x00, 0x00, 0x01, 0x01, 0x02, 0xE4, 0xFF, 0xFE, 0xFE, 0xFE, 0xFC, 0x1C, 0x0E, 0x06, 0x1F, 0x21, 0x52, 0x44, 0x48, 0x52, 0x21, 0x1F, 0x00, 0x00, 0x64, 0x70, 0x2F, }; /** Nhân vật Dần Béo (23x19)pixel */ const static unsigned char __attribute__((progmem)) dan_beo_23x19[] = { 0x00, 0x00, 0x80, 0x43, 0xB2, 0x0F, 0x86, 0x44, 0x5C, 0xA2, 0xA6, 0x5C, 0x50, 0xB0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0xE3, 0xF4, 0xFA, 0xF0, 0xF7, 0x28, 0x78, 0xD8, 0xA8, 0xA8, 0xA4, 0x90, 0x58, 0x3E, 0x0A, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04, 0x07, 0x07, 0x06, 0x06, 0x05, 0x07, 0x07, 0x04, }; /* mũi tên * */ const static unsigned char __attribute__((progmem)) mui_ten_5x4[] = { 0x01, 0x07, 0x0F, 0x07, 0x01, }; // mảng cho 2 nhân vật Cả mẹo(0) và Dần béo(1) byte object2[2][4] = { 0 }; //object[i][0]: tồn tại hay không //object[i][1]: tọa độ x //object[i][2]: tọa độ y //object[i][3]: hướng (1 phải, 2 trên , 3 trái, 4 dưới) // byte chon_nv_A; byte chon_nv_B; //lựa chọn nhân vật, mặc định A được chọn trước byte Xmin, Ymin, Xmax, Ymax; //biên //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 // Chương trình chạy 1 lần void setup() { Serial.begin(9600); 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); Xmin = 0; Xmax = 125; Ymin = 0; Ymax = 63; chon_nv_A = 1; chon_nv_B = 0; } ////////ĐIỀU KHIỂN 2 NHÂN VẬT////////////////////// void ve_nhan_vat(byte x, byte y, byte huong, byte i) { switch (i) { case 0: // vẽ Cả Mẹo if (huong == 1) { //quay về bên phải lcd.Plus_Bitmap(x, y, 22, 18, ca_meo_22x18, 0, NO_MIRROR, BLACK); } if (huong == 3) { //quay về bên trái lcd.Plus_Bitmap(x, y, 22, 18, ca_meo_22x18, 0, MIRROR, BLACK); } lcd.display(); break; case 1: if (huong == 1) { //quay về bên phải lcd.Plus_Bitmap(x, y, 23, 19, dan_beo_23x19, 0, NO_MIRROR, BLACK); } if (huong == 3) { //quay về bên trái lcd.Plus_Bitmap(x, y, 23, 19, dan_beo_23x19, 0, MIRROR, BLACK); } lcd.display(); //vẽ Dần Béo break; default: break; } //switch } // ve nhân vật void xoa_nhan_vat(byte x, byte y) { lcd.fillrect(x, y - 7, 24, 19 + 7, DELETE); lcd.display(); } // xóa nhân vật void ve_mui_ten(byte x, byte y) { // mũi tên sẽ chỉ vào nhân vật i lcd.bitmap(x, y, 5, 4, mui_ten_5x4, BLACK); lcd.display(); } // vẽ mũi tên void xoa_mui_ten(byte i) { lcd.fillrect(object2[i][1] - 5, object2[i][2] - 7, 20, 5, DELETE); lcd.display(); } //xóa mũi tên void di_chuyen_nhan_vat(byte i) { byte x_cu, y_cu, huong_cu; byte x_moi; //lấy thông tin x_cu = object2[i][1]; y_cu = object2[i][2]; huong_cu = object2[i][3]; if (huong_cu == 1) { //đi về bên phải x_moi = x_cu + 4; ve_nhan_vat(x_moi, y_cu, huong_cu, i); ve_mui_ten(x_cu + 5, y_cu - 7); } if (huong_cu == 3) { //đi về bên trái if (x_cu >= Xmin + 4 * 2) { //chống quá biên x_moi = x_cu - 4; } else { x_moi = x_cu; } ve_nhan_vat(x_moi, y_cu, huong_cu, i); ve_mui_ten(x_cu + 5, y_cu - 7); } //lưu thông tin object2[i][1] = x_moi; } //di chuyển nhân vật void xu_li_nhan_vat() { //b0: điều khiển lựa chọn nhân vật if (digitalRead(fight_b) == 0) { //fight được nhấn if (chon_nv_A == chon_nv_B) { // đề phòng A bằng B chon_nv_A = 1; chon_nv_B = 0; } if (chon_nv_A != chon_nv_B) { //để dảm bảo A khác B chon_nv_A = !chon_nv_A; chon_nv_B = !chon_nv_B; //đổi } } //if //b1: chọn nhân vật byte i = 0; if ((chon_nv_A == 1) && (chon_nv_B == 0)) { i = 0; // nhân vật Cả Mẹo } if ((chon_nv_A == 0) && (chon_nv_B == 1)) { i = 1; // nhân vật Dần Béo } if (digitalRead(fight_b) == 0) { //fight được nhấn xoa_mui_ten(!i); ve_mui_ten(object2[i][1] + 5, object2[i][2] - 7); } //b2: cấp giấy khai sinh (chỉ dùng khi start game) if (object2[i][0] == 0) { // nếu không tồn tại gà thì phải tạo gà // cấp thuộc tính cho đối tượng gà object2[i][1] = Xmin + 50 * i; //hoành độ ban đầu object2[i][2] = Ymax - 19; // tung độ ban đầu object2[i][3] = 1; //hướng về bên phải object2[i][0] = 1; //cấp quyền tồn tại } //b4: vẽ 2 nhân vật for (byte j = 0; j <= 1; j++) { ve_nhan_vat(object2[j][1], object2[j][2], object2[j][3], j); } //b5: điều khiển hướng nhân vật i byte read_button; read_button = lcd.Pullup_4(right_b, up_b, left_b, down_b); if (read_button == 1) { // nút right //ghi lại tt hướng object2[i][3] = 1; xoa_mui_ten(!i); xoa_nhan_vat(object2[i][1], object2[i][2]); //xóa ảnh cũ //lấy thông tin tọa độ để xóa di_chuyen_nhan_vat(i); // vẽ ảnh mới } if (read_button == 3) { // nút left object2[i][3] = 3; xoa_mui_ten(!i); xoa_nhan_vat(object2[i][1], object2[i][2]); //xóa ảnh cũ //lấy thông tin tọa độ để xóa di_chuyen_nhan_vat(i); // vẽ ảnh mới } } //xử lí nhân vật //================================================== void loop() { xu_li_nhan_vat(); delay(50); }
Kết quả
Xử lí nảy trứng
Giải phương trình quỹ đạo bay Parabol.
Trong đoạn code này
byte X0; // tọa độ đỉnh parabol f_y_moi = 1.0 + sq((float(X0) - float(x_moi)) / 4.0); //phương trình
Với quy ước màn hình, trục tung hệ tọa độ có chiều hướng xuống dưới. Kết quả ta được phương trình của chuyển động ném xiên.
Bay từ trái sang phải
#include "ST7565_homephone.h" ST7565 lcd(3, 4, 5, 6); //RST, SCLK, A0, SID void setup() { lcd.ON(); lcd.SET(23, 0, 0, 0, 4); } void loop() { float y; byte X0 = 60; // hoành độ đỉnh parabol for (byte x = 30; x < 100; x += 1) { y = 1.0 + sq((float(X0) - float(x)) / 4.0); //phương trình lcd.FillCircle(x, byte(y), 4, BLACK); lcd.Display(); delay(1); lcd.Clear(); // xóa toàn màn hình } }
Bay từ phải sang trái
#include "ST7565_homephone.h" ST7565 lcd(3, 4, 5, 6); //RST, SCLK, A0, SID void setup() { lcd.ON(); lcd.SET(23, 0, 0, 0, 4); } void loop() { float y; byte X0 = 60; // hoành độ đỉnh parabol for (byte x = 100; x > 30; x -= 1) { y = 1.0 + sq((float(X0) - float(x)) / 4.0); //phương trình lcd.FillCircle(x, byte(y), 4, BLACK); lcd.Display(); delay(1); lcd.Clear(); // xóa toàn màn hình } }
Kết
Code đã dài, sau khi kết hợp các module với nhau, ta được Game hoàn chỉnh :
https://drive.google.com/file/d/0BzMEcyRK_uUFU2EyWWF0dmpaZlk/view?usp=sharing (mirror)
Chúc bạn có hứng khởi cùng các dự án về Game. Hẹn gặp lại ở các project game khác.
< Thái Sơn >