Nick Chung gửi vào
- 11990 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 >



