Nick Chung gửi vào
- 19448 lượt xem
Hẳn là các bạn ít nhiều cũng đã từng có một tuổi thơ dữ dội cùng với trò xếp gạch, xe tăng trên các máy chơi game đen trắng cầm tay, phá đảo thế giới ảo cùng với Contra, Super Mario, MU…Và khi lớn lên, chúng ta lại thích thú trong việc làm sao để tạo ra các chuyển động như vậy, nói đúng hơn là làm game.. Bài viết này sẽ giúp bạn hiểu hơn cách tạo ra các hiệu ứng chuyển động hình ảnh trong lập trình đồ họa nói chung và game nói riêng. Tất nhiên là bằng board mạch Arduino cùng với LCD ST7565.
MOTIONS
Việc đọc một cuốn truyên tranh,xem một bộ phim hoạt hình, phim chiếu bóng thời xưa đến việc chơi những game từ Mario cho đến cả Asphalt 8. Tuy phương tiện để truyền tải hình ảnh đến mắt người là khác nhau. Nhưng con người vẫn cảm thấy nó thực sự sống động và thú vị trước mắt họ. Để làm được điều đó, những phương tiện truyền tải đang tồn tại một điểm chung để mê hoặc người xem ,đó là tạo ra các chuyển động.
Xét đến sự chuyển động của một vật là chúng ta đang quan tâm đến các thuộc tính của vật đó theo thời gian.
Hiệu ứng đồ họa cũng phải tuân theo nguyên tắc này, máy tính cần phải tính toán rồi cho màn hình hiện lên theo từng khung hình, đi vào bản chất của quá trình tạo hình thì đó là việc xếp chồng các hình ảnh liên tiếp theo thời gian, chúng ta có thể mô hình hóa theo một chu trình như sau:
Và kết quả ta được 1 thước phim đáng yêu như thế này:
Hãy cùng hiện thực hóa nguyên tắc đó trên arduino cùng với lcd st7565 nào!
Gỉa sử chúng ta có một đối tượng là hình tròn với bán kính r=8pixel.
Và chuyển động là di chuyển từ trái qua phải.
Giải thuật là dùng vòng lặp for để tang đần hoành độ của đối tượng
#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(){ for( byte x=0; x<127; x+=5){ lcd.fillcircle( x,30,8,BLACK); lcd.display(); delay(100); lcd.fillcircle( x,30,8,DELETE); } }
Nhìn vào hàm loop chúng ta thấy chúng có cấu trấu giống với nguyên lý tạo hình:
lcd.fillcircle( x,30,8,BLACK); | là phần tính toán của máy tính |
lcd.display(); | là phần cho phép hiển thị |
delay(100); lcd.fillcircle( x,30,8,DELETE); | cho dừng khung hình rồi xóa đối tượng |
Tất cả được đặt trong hàm for để thay đổi thuộc tính (hoành độ x) của đối tượng, tất cả công việc được lặp lại và liên tục theo thời gian.
INTERACTION
Đã là game thì phải có sự tương tác giữa người chơi với đối tượng trong game. Giờ thì chúng ta sẽ sử dụng nút ấn để điều khiển đối tượng trên. Cụ thể khi nhấn nút “Left” thì nó sang trái, “right” đi sang phải.
Nối nút ấn với arduino
Đây là cách nối dây phù hợp với code ví dụ bên dưới.
Chúng ta sẽ sử dụng các chân Analog để nhận tín hiệu từ nút nhấn. Chú ý nút nhấn được nối theo kiểu Pullup nhé (một đầu nối xuống GND).
Dùng hàm Pullup_4 có sẵn trong thư viện để kiểm tra xem nút nào đang được nhấn
byte Pullup_4(byte right_pin, byte up_pin, byte left_pin, byte down_pin);
Hàm sẽ trả về chỉ số của nút tương ứng khi chúng được nhấn:
- =0 nếu không có nút nào được nhấn
- =1 right
- =2 up
- =3 left
- =4 down
Tính theo chiều dương của 4 góc phần tư
_______[2]
___[3]_____[1]
_______[4]
Sau khi nối xong hãy copy và chạy trên chương trình để test nhé.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { Serial.begin(9600); pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } void loop(){ byte value; value=lcd.Pullup_4(A3, A2, A1, A0); Serial.println(value);// mở cửa sổ monitor để xem }
Test hoàn thiện, Giờ thì cùng quay lại chủ đề nào
Vậy thì thuật toán sẽ ra sao để khi nhấn nút thì nó (đối tượng hình tròn bên trên) sẽ di chuyển trái phải.
Tìm thuật toán đơn giản nhất chính là cách tư duy trong lối phát biểu của chúng ta, mình sẽ phát biểu thế này :
- Nếu (Nhấn nút Left)
- {Thì hoành độ giảm xuống; }
- Nếu (Nhấn nút Right)
- {Thì hoành độ tăng lên; }
- Nếu không nút nào nhấn thì thôi, giữ nguyên vị trí;
Triển khai thuật toán:
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { Serial.begin(9600); pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } int x=60;//hoành độ khảo sát byte button; void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); if (button==3) { x--;// sang trái } else if(button==1) { x++;//sang phải } Serial.println(x); } //Hãy mở cửa sổ monitor để xem hoành độ thay đổi ra sao khi nhấn hoặc không nhấn.
Vậy là ok, thuộc tính (x) đã phản ứng theo đúng ý ta.
Giờ thì cần thay đổi phương thức từ xem X qua cửa sổ monitor thành phương thức di chuyển trên màn hình.
Đơn giản là mình thay dòng Serial.println() với đoạn code đã viết để xử xí chuyển động hình tròn:
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); //thêm hai dòng này khi cần dùng đến màn hình pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } int x=60;//hoành độ khảo sát byte button; void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); if(button==3){ x-=5;// sang trái 5 }else if(button==1){ x+=5;//sang phải 5 } lcd.fillcircle( x,30,8,BLACK); lcd.display(); delay(100); lcd.fillcircle( x,30,8,DELETE); }
Vậy là ta đã điều khiển được một đối tượng rồi, từ đây bạn có thể tự tin hơn rồi đấy
Tự tin hơn thì nghịch nghịch tý nhỉ:
Bây giờ còn tung độ của hình tròn thì chúng ta thay nốt xem sao:
Thuật toán:
- Nếu (Nhấn nút Left)
- {Thì x giảm; }
- Nếu (Nhấn nút Right)
- {Thì x tăng ; }
- Nếu (Nhấn nút Up)
- {Thì y giảm; }
- Nếu (Nhấn nút Down)
- { Thì y tăng;}
- Nếu không nút nào nhấn thì thôi, giữ nguyên vị trí;
Chú ý lại quy tắc hệ trục tọa độ tại bài viết này nhé:
http://arduino.vn/tutorial/1319-st7565-huong-dan-su-dung-glcd-st7565-homephone-va-chia-se-thu-vien
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); //thêm hai dòng này khi cần dùng đến màn hình pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } int x=60;//hoành độ khảo sát int y=30;// tung độ khảo sát byte button; void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){// dùng switch thay cho if case 1: x+=5; break; case 2: y-=5; break; case 3: x-=5; break; case 4: y+=5; break; } lcd.fillcircle( x,y,8,BLACK); lcd.display(); delay(100); lcd.fillcircle( x,y,8,DELETE); }
Tìm hiểu các chức năng còn lại của Pullup_4 xem sao
Ngoài 0,1,2,3,4 nó còn trả về các số sau:
- 1*2=20 right và up
- 1*3=30 right và left
- 1*4=40 right và down
- 2*3=60 up và left
- 2*4=80 up và down
- 3*4=120 left và down
Bây giờ mình muốn nó di chuyển chéo thì sao nhỉ:
Thuật toán:
- Nếu (Nhấn nút Left )
- {Thì x giảm;}
- Nếu (Nhấn nút Right)
- {Thì x tăng ;}
- Nếu (Nhấn nút Up)
- {Thì y giảm; }
- Nếu (Nhấn nút Down)
- { Thì y tăng;}
- Nếu (Nhấn nút Left + up)
- {x và y giảm;}
- Nếu (Nhấn nút Right+up)
- {x tăng và y giảm;}
- Nếu (Nhấn nút Left + down)
- {x giảm và y tăng}
- Nếu (Nhấn nút Right +down)
- {x tăng và y tăng}
- Nếu không nút nào nhấn thì thôi, giữ nguyên vị trí;
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); //thêm hai dòng này khi cần dùng đến màn hình pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } int x=60;//hoành độ khảo sát int y=30;// tung độ khảo sát byte button; void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: x+=5; break; case 2: y-=5; break; case 3: x-=5; break; case 4: y+=5; break; case 20: x+=5; y-=5; break; case 40: x+=5; y+=5; break; case 60: x-=5; y-=5; break; case 120: x-=5; y+=5; break; } lcd.fillcircle( x,y,8,BLACK); lcd.display(); delay(100); lcd.fillcircle( x,y,8,DELETE); }
Giờ mình cần giới hạn chuyển động
Không cho đối tượng chạy mất khỏi màn hình.
Thuật toán
Kiểm tra xem: nếu đối tượng nằm trong biên thì cho phép di chuyển, ngược lại thì không cho phép di chuyển.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); //thêm hai dòng này khi cần dùng đến màn hình pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } int x=60;//hoành độ khảo sát int y=30;// tung độ khảo sát byte button; void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: x+=5; break; case 2: y-=5; break; case 3: x-=5; break; case 4: y+=5; break; case 20: x+=5; y-=5; break; case 40: x+=5; y+=5; break; case 60: x-=5; y-=5; break; case 120: x-=5; y+=5; break; } if(x<5) {x=5;} if(y<5) {y=5;} if(x>122) {x=122;} if(y>59) {y=59;} lcd.fillcircle( x,y,8,BLACK); lcd.display(); delay(100); lcd.fillcircle( x,y,8,DELETE); }
Tiếp tục thực thể hóa đối tượng
- Hình tròn là một khuôn mẫu, giờ cần thay bằng một ảnh bitmap (một thực thể):
- Nó có hình là mũi tên con trỏ chuột:
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); //thêm hai dòng này khi cần dùng đến màn hình pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } const static unsigned char __attribute__ ((progmem)) mouse8x10[]= { 0x0,0xff,0x7e,0xfc,0xf8,0xb0,0x20,0x0, 0x0,0x0,0x0,0x0,0x1,0x1,0x0,0x0, }; int x=60;//hoành độ khảo sát int y=30;// tung độ khảo sát byte button; void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: x+=5; break; case 2: y-=5; break; case 3: x-=5; break; case 4: y+=5; break; case 20: x+=5; y-=5; break; case 40: x+=5; y+=5; break; case 60: x-=5; y-=5; break; case 120: x-=5; y+=5; break; } if(x<5) {x=5;} if(y<5) {y=5;} if(x>122) {x=122;} if(y>59) {y=59;} lcd.bitmap( x,y,8,10,mouse8x10,BLACK); lcd.display(); delay(100); lcd.fillrect( x,y,8,10,DELETE);// vẽ hình chữ nhật để xóa }
Rồi lại thực thể nó thành xe tăng
Sử dụng một ảnh bitmap xe tank và dùng Plus_bitmap cho nó gọn nhẹ.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); //thêm hai dòng này khi cần dùng đến màn hình pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } const static unsigned char __attribute__ ((progmem)) tank10x10[]= { 0xff,0xb5,0xb7,0xfd,0xb7,0x85,0xff,0x30,0x30,0x78, 0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x0,0x0,0x0, }; int x=60;//hoành độ khảo sát int y=30;// tung độ khảo sát byte button; int angle;// góc xoay ảnh void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: x+=2;angle=0; break;// right case 2: y-=2;angle=90; break;// up case 3: x-=2;angle=180; break;//left case 4: y+=2;angle=270; break; //down default : break; } if(x<0) {x=0;} if(y<0) {y=0;} if(x>117) {x=117;} if(y>53) {y=53;} lcd.plus_bitmap( x,y,10,10,tank10x10,angle,NO_MIRROR,BLACK); lcd.display(); delay(100); lcd.fillrect( x,y,10,10,DELETE);// vẽ hình chữ nhật để xóa }
Motions+inerections
Vậy là chúng ta đã có một bước khởi đầu tốt đẹp
- Tóm lại quy tắc trong chuyển động một đối tượng sẽ là :Tính toán thuộc tính thay đổi theo thời gian rồi biểu diễn đối tượng từ các thuộc tính đó.
- Còn quy tắc hiển thị sẽ là một vòng tròn: …->Tính toán-> Vẽ->Đợi->Xóa->Tính toán->Vẽ->Đợi->Xóa->.....
Còn đây là một vài quy tắc trong làm phim hoạt hình
1. Squash and Stretch(Sự co và giãn của chuyển động)
2:Timing and Spacing (Quản lý thời gian và Khoảng cách của khung hình ảnh thay đổi trong từng khoảnh khắc)
3. Exaggeration (Cường điệu hoá)
4: Solid Drawing (Mọi hình ảnh hiển thị cần tạo phối cảnh trong khung hình điều này có nghĩa bạn hãy cố gắng tạo ra nhiều góc độ không gian hơn khi trình diễn )
EVENT
Các sự kiện xuất hiện trong game sẽ xuất hiện theo cách tự nhiên hoặc theo chu trình
- Theo tự nhiên: Xảy ra do một móc nối logic có điều kiện. Ví dụ khi ta chơi game xe tăng, khi bắn chúng quân địch thì sự kiện cộng điểm sẽ được thực thi...
- Theo chu trình: Nó là một sự kiện chắc chắn sẽ xảy ra với một lời gọi tự động. Ví dụ như hết thời gian chơi, cập nhật dữ liệu người chơi thường xuyên để tránh thất lạc thông tin khi có sự cố kết nối...
Bây giờ với yêu cầu khi xe tăng chạm vào biên của màn hình thì sẽ có vụ nổ sảy ra, đồng thời viết lên màn hình dòng thông báo
Giải thuật: So sánh tọa độ của xe với biên , chạm biên thì vẽ hình vụ nổ tại đó, dùng kiểu vẽ kí tự để thông báo.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); //thêm hai dòng này khi cần dùng đến màn hình pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } const static unsigned char __attribute__ ((progmem)) tank10x10[]= { 0xff,0xb5,0xb7,0xfd,0xb7,0x85,0xff,0x30,0x30,0x78, 0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x0,0x0,0x0, }; int x=60;//hoành độ khảo sát int y=30;// tung độ khảo sát byte button; int angle;// góc xoay ảnh void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: x+=2;angle=0; break;// right case 2: y-=2;angle=90; break;// up case 3: x-=2;angle=180; break;//left case 4: y+=2;angle=270; break; //down default : break; } if(x<0){x=0;} if(y<0){y=0;} if(x>117){x=117;} if(y>53){y=53;} if((x<=0)||(x>=117)||(y<=0)||(y>=53)){ lcd.fillrect(x-5,y-5,20,20,BLACK); lcd.asc_string( 1,1,Asc(" Turn around!"),BLACK); }else{ lcd.fillrect(1,1,80,8,DELETE);// xóa vùng ảnh của chữ nếu không còn sự kiện } lcd.plus_bitmap( x,y,10,10,tank10x10,angle,NO_MIRROR,BLACK); lcd.display(); delay(100); lcd.fillrect( x,y,10,10,DELETE);// vẽ hình chữ nhật để xóa }
MORE ...
Vẫn còn nhiều điều với game, nhưng hôm nay thế này là đủ với cả mình và bạn. Sẽ có những phần tiếp theo hay ho hơn nữa. Hi vọng các bạn thích bài viết này. Mình cũng có một thử thách nho nhỏ cho bạn đọc, đề là: hãy cho đối tượng di chuyển theo hình chữ nhật một cách tự động.(không có điều khiển nha)
Gọi là để lại dấu chân khi khám phá các vùng đất mới ý mà.
Mình sẽ không sẽ không đưa ra code. Bạn chỉ cần khoe chiến tích hoặc làm nhiều trò hơn thế rồi chụp ảnh vô bình luận là ok rồi.
Tác giả: Thái Sơn.