Nick Chung gửi vào
- 17779 lượt xem
Trong bài viết trước, chúng ta đã cùng tìm hiểu một vài nguyên tắc của hiệu ứng chuyển động trong đồ họa. Bài viết này sẽ nối tiếp nội dung còn dở dang của bài trước, hãy cùng đi tiếp nào. Tất nhiên là trên arduino cùng lcd st7565 rồi.
Stop_Motion
Đây là bài viết số 2 thuộc chuỗi bài “Chuyển động trong lập trình Game và đồ họa”.
Nếu chưa đọc bài viết trước, thì hãy đọc nó tại đây bạn nhé:
P1:Chuyển động trong lập trình game và đồ họa
Hãy cùng bắt đầu nhé
Chúng ta đã biết, mỗi khung hình cho một đối tượng ở một thời điểm là khác nhau (nếu giống nhau thì ta coi vật đó là tĩnh hay đứng im , nên ta sẽ không xét), vì vậy để hành động của đối tượng được mền mại, không quá cứng nhắc thì việc duy trì hành động với quỹ đạo theo thời gian càng mượt mà càng tốt.
Để làm được điều này, người lập trình lại phải quan tâm đến những chi tiết nhỏ hơn.
Đây cũng là nguyên tắc Arcs -Bất cứ một chuyển động nào cũng cần tạo ra đường cong của chuyển động từ điểm bắt đầu tới kết thúc.
"Ngày nay, với sự hỗ trợ của máy tính và phần mềm, trong lập trình game có đòi hỏi chất lượng tốt, thì nguyên tắc trên sẽ được khai khác một cách hiệu quả nhất. Phương pháp tối ưu nhất hiện nay để quản lý và điều khiển hình ảnh của đối tượng ảo là việc sử dụng một bản đồ các điểm nút , mà ở đó tập hợp các điểm sẽ cho khung của đối tượng , và việc tô màu đối tượng ảo sẽ là việc tô trên các mạng lưới nhỏ hơn cấu thành từ các điểm..'".
Tuy nhiên nguyên tắc này vẫn có thể bị phá vỡ dần khi người ta muốn cải thiện tốc độ và các thay đổi của chuyển động sẽ ở một mức chấp nhận được, giống như các tựa game đơn giản trên nền Mobi cấu hình thấp. Và điều này cũng phù hợp khi ta mong muốn tạo game trên Arduino.
Dưới đây là các stop_motion khi các động tác của nhân vật được tổng quát với những bản phác ở các góc độ khác nhau,nó hướng tới sự đơn giản hóa, điều này sẽ làm tăng đáng kể hiệu xuất của máy tính .
Stop_Motion trên arduino .
Chúng ta sẽ cùng quay trở lại với nhân vật Penguin mập ú cùng với sự hỗ trợ của Arduino và lcd ST7565
Đầu tiên là bản phác họa các hành động
Chuyển sang dạng ảnh bmp và lấy mã hex lưu trữ của ảnh
Để lấy mã hex, mình sử dụng ứng dụng java lấy trong thư viện của OPEN GLCD
- Trong file tải xuống tìm đến file glcdMakeBitmap.jad rồi mở nó
- Mở song song thư mục chứa file BMP của bạn.
- Nhấn giữ rồi kéo thả ảnh xuống cái cửa sổ chương trình glcdMakeBitmap.jad trên
- File hex sẽ nằm ngay ở thư mục lớn có chứa glcdMakeBitmap
- Mở file hex bằng NotePad , rồi copy đoạn nằm trong ngoặc { }, chú ý sửa lại ..
-
//ví dụ
25, // width 26, // height
//viết thành
// 25, width //26, height
- Dán vào code mẫu của bạn là xong. Bạn cũng cần biết sử dụng hàm vẽ ảnh Bitmap trong thư viện tải về nhé.
- Nếu có lỗi mở file .jad, thì bạn cần thiết lập máy tính có hỗ trợ môi trường JAVA, hãy lên google tham khảo hoặc sử dụng phần mềm khác thay thế.(Mình đã thử khá nhiều phần mềm, nhưng thấy ứng dụng java trên không ăn bớt mã hex của mình khi biên dịch , hãy cố gắng thiết lập môi trường java cho máy tính của bạn nha)
Tải ứng dụng tại đây.
Để có ảnh dưới tập tin BMP, hãy tham khảo bài viết của tác giả LeQuocChi nhé http://arduino.vn/result/1308-ve-anh-bitmap-tren-oled-i2c-rat-dep-ban-cam-nhan-thu-xem
Quay lại với chủ đề nào .
walk1 | walk2 | walk3 | walk4 |
jump1 | jump2 | jump3 | |
slide1 | slide2 | hurt | |
die1 | die2 | die3 |
Mình đã chuyển ảnh bitmap sang dạng code lưu trữ trong một tệp có tên bmp1.h
Tải về tại đây
Tư liệu đã sẵn sàng, cùng mở IDE lên và tạo mới một chương trình thôi!!
Chọn New Tab, và đặt tên file là “bmp1.h”, sau đó copy toàn bộ code trong file tải về dán vào, rồi nhớ ấn lưu.
Cuối cùng là thêm tệp "bmp1.h"
Ta có code sau để test như sau
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); #include "bmp1.h"// void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); } void loop() { lcd.bitmap(60,30,25,28,walk1,BLACK); lcd.display(); }
Kết quả khi test bằng đoạn code trên
Mô phỏng hành động
Bước đi
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); #include "bmp1.h" void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); } //Tạo một hàm có tên walk,với 3 tham số là x,y: tọa độ ; time_d: delay. void walk(int x, int y,unsigned int time_d){ //p1 lcd.Bitmap( x,y,25,28,walk1,BLACK); lcd.Display(); delay(time_d); lcd.fillrect(x,y,25,28,DELETE); //p2 lcd.Bitmap( x+3,y,25,28,walk2,BLACK); lcd.Display(); delay(time_d); lcd.fillrect(x+3,y,25,28,DELETE); //p3 lcd.Bitmap( x+6,y,25,28,walk3,BLACK); lcd.Display(); delay(time_d); lcd.fillrect(x+6,y,25,28,DELETE); //p4 lcd.Bitmap( x+9,y,25,28,walk4,BLACK); lcd.Display(); delay(time_d); lcd.fillrect(x+9,20,25,28,DELETE); lcd.Display(); } void loop(){ for(int x=0; x<100; x+=9){ walk(x,35,200); } }
Như bài trước, ta đã biết cách thức để vẽ một khung hình là một quy trình gồm các bước: tính toán->hiển thị->đợi->xóa.
Quy trình sẽ lặp lại với những khung hình tiếp theo. Một bước đi của nhân vật Penguin gồm 4 khung hình, do đó quy trình vẽ sẽ lặp lại 4 lần. Tất nhiên ta cần dùng đến hàm for để nhân bản hành động khi tăng dần hoành độ , tạo ra hành động đi bộ.
Tối ưu hóa code
Bất cứ dòng lệnh nào có tính lặp lại nhiều lần ta đều cần tối ưu nó.
Cụ thể là 4 khối code trên đã lặp lại, và mình sẽ cho vào hàm action để thực hiện nhiệm vụ chuyên biệt này.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); #include "bmp1.h" void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); } void action( int x, int y, int width, int high, const char* bitmap_name, unsigned int time_d){ lcd.Bitmap( x,y,width,high,bitmap_name,BLACK); lcd.Display(); delay(time_d); lcd.fillrect(x,y,width,high,DELETE); lcd.Display(); } void walk(int x, int y, unsigned int time_d){ action(x,y,25,28,walk1, time_d); action(x+3,y,25,28,walk2, time_d); action(x+6,y,25,28,walk3, time_d); action(x+9,y,25,28,walk4, time_d); } void loop() { for(int x=0; x<100; x+=9) walk(x,35,250); }
Kết quả vẫn như trước
Giờ thì ta cần quay ảnh ngược lại ra phía sau nếu muốn Penguin đi từ phải sang trái
Ta cần đến Plus_Bitmap để xoay ảnh. Chúng ta cũng cần quy ước về hướng, hướng tổng quát sẽ được hiểu là góc hợp bởi vector chỉ hướng và trục hoành.
Kể từ đây, khi nói hướng 0’:Trái ,180’:Phải,90’: Lên,270’: Xuống Hoặc 45: chếch phải. và hiểu theo nghĩa ngược lại .
Để quay hướng nhân vật ra phía sau, mình sẽ lật ảnh bằng gương
Cùng với đó là định nghĩa thêm 4 hướng.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); #include "bmp1.h"// #define Phai 0 #define Len 90 #define Trai 180 #define Xuong 270 void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); } void action( int x, int y, int width, int high, const char* bitmap_name, unsigned int time_delay,unsigned int huong){ bool mirror=false; if(huong==Trai){ mirror=true;// nếu đi sang trái thì xoay nhân vật về bên trái } lcd.Plus_Bitmap( x,y,width,high,bitmap_name,0,mirror,BLACK); lcd.Display(); delay(time_delay); lcd.fillrect(x,y,width,high,DELETE); lcd.Display(); } void walk(int x, int y, unsigned int time_delay,unsigned int huong){ int denta=1;// sang phải thì x tăng if(huong==Trai){ denta=-1;// sang trái thì x giảm, denta phải âm } action(x,y,25,28,walk1, time_delay,huong); action(x+3*denta,y,25,28,walk2, time_delay,huong); action(x+6*denta,y,25,28,walk3, time_delay,huong); action(x+9*denta,y,25,28,walk4, time_delay,huong); } void loop(){ byte y0=35; for(int x=30; x<60; x+=9) walk(x,y0,220,Phai); for(int x=60; x>30; x-=9) walk(x,y0,220,Trai); }
Kết quả, nhân vật đi qua đi lại như thế này.
Tương tác
Tiếp tục dùng nút bấm để điều khiển nhân vật Penguin nào
Bạn hãy tham khảo cách nối dây tại bài viết trước nhé:
http://arduino.vn/tutorial/1319-st7565-huong-dan-su-dung-glcd-st7565-homephone-va-chia-se-thu-vien
Để thuận tiện trong nạp code, mình sẽ không thay đổi thứ tự nút và chân kết nối, nên bạn hãy nối giống mình nhé.
Bây giờ để điều khiển, chỉ đơn giản mình khai báo thêm 4 dòng ở Setup và dùng hàm kiểm tra sự kiện nhấn nút Pullup_4.
Đầu tiên là điều khiển đối tượng đi qua đi lại
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); #define de 100 #define Phai 0 #define Tren 90 #define Trai 180 #define Duoi 270 #include "bmp1.h"// chú ý thêm void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); pinMode(A3,INPUT_PULLUP); pinMode(A2,INPUT_PULLUP); pinMode(A1,INPUT_PULLUP); pinMode(A0,INPUT_PULLUP); } void action( int x, int y, int width, int high, const char* bitmap_name, unsigned int time_delay,unsigned int huong){ bool mirror=false; if(huong==Trai){ mirror=true;// nếu đi sang trái thì xoay nhân vật về bên trái } lcd.Display(); lcd.Plus_Bitmap( x,y,width,high,bitmap_name,0,mirror,BLACK); lcd.Display(); delay(time_delay); lcd.fillrect(x,y,width,high,DELETE); } void walk(int x, int y, unsigned int time_delay,unsigned int huong){ int denta=1;// sang phải thì x tăng if(huong==Trai){ denta=-1;// sang trái thì x giảm, denta phải âm } action(x,y,25,28,walk1, time_delay,huong); action(x+3*denta,y,25,28,walk2, time_delay,huong); action(x+6*denta,y,25,28,walk3, time_delay,huong); action(x+9*denta,y,25,28,walk4, time_delay,huong); } void stand(int x, int y,unsigned int huong){ bool mirror=false; if(huong==Trai){ mirror=true;// nếu đi sang trái thì xoay nhân vật về bên trái } lcd.Plus_Bitmap( x,y,25,28,walk2,0,mirror,BLACK); lcd.display(); delay(10); lcd.fillrect(x,y,25,28,DELETE); } int x=60;//hoành độ khảo sát int y=30;// tung độ khảo sát byte button; int huong;// góc xoay ảnh void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: huong=Phai;walk(x,y,200,huong); x+=9; break;// right case 3: huong=Trai;walk(x,y,200,huong); x-=9; break;//left default : stand(x,y,huong); break; } }
Và cuối cùng thêm 6 động tác là Nằm-nút down, nhảy cao-nút up+(left/right),Trượt_nút down+(left/right), và Nhảy tại chỗ nút up
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); #define de 100 #define Phai 0 #define Tren 90 #define Trai 180 #define Duoi 270 #include "bmp1.h"// chú ý thêm 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); } void action( int x, int y, int width, int high, const char* bitmap_name, unsigned int time_delay,unsigned int huong){ bool mirror=false; if(huong==Trai){ mirror=true;// nếu đi sang trái thì xoay nhân vật về bên trái } lcd.Display(); lcd.Plus_Bitmap( x,y,width,high,bitmap_name,0,mirror,BLACK); lcd.Display(); delay(time_delay); lcd.fillrect(x,y,width,high,DELETE); } void walk(int x, int y, unsigned int time_delay,unsigned int huong){ int denta=1;// sang phải thì x tăng if(huong==Trai){ denta=-1;// sang trái thì x giảm, denta phải âm } action(x,y,25,28,walk1, time_delay,huong); action(x+3*denta,y,25,28,walk2, time_delay,huong); action(x+6*denta,y,25,28,walk3, time_delay,huong); action(x+9*denta,y,25,28,walk4, time_delay,huong); } void stand(int x, int y,unsigned int huong){ bool mirror=false; if(huong==Trai){ mirror=true;// nếu đi sang trái thì xoay nhân vật về bên trái } lcd.Plus_Bitmap( x,y,25,28,walk2,0,mirror,BLACK); lcd.display(); delay(10); lcd.fillrect(x,y,25,28,DELETE); } void lie(int x, int y, unsigned int time_delay,unsigned int huong){ bool mirror=false; if(huong==Trai){ mirror=true;// nếu đi sang trái thì xoay nhân vật về bên trái } //action(x,y+10,25,22,slide1,time_delay,huong); action(x,y+8,30,19,slide2,time_delay,huong); } void die( int x, int y, unsigned int time_delay,unsigned int huong){ action(x,y,25,26,die1, time_delay,huong); action(x,y+3,25,27,die2, time_delay,huong); action(x,y+6,30,23,die3,time_delay,huong); delay(500); } void jump(int x, int y, unsigned int time_delay,unsigned int huong){ int denta=1;// sang phải thì x tăng if(huong==Trai){ denta=-1;// sang trái thì x giảm, denta phải âm } action(x,y-8,25,27,jump1, time_delay,huong); action(x,y-5,24,24,jump2, time_delay,huong); action(x,y-3,24,24,jump3, time_delay,huong); } void high_jump(int x0, int y, unsigned int time_delay,unsigned int huong){ int denta=1;// sang phải thì x tăng if(huong==Trai){ denta=-1;// sang trái thì x giảm, denta phải âm } for(int denta_x=0; denta_x<10; denta_x+=2){ y-=2; action(x0+denta_x*denta,y,24,24,jump2, time_delay,huong); } x0+=10*denta; for(int denta_x=0; denta_x<10; denta_x+=2){ y+=2; action(x0+denta_x*denta,y,24,24,jump3, time_delay,huong); } } int x=60;//hoành độ khảo sát int y=30;// tung độ khảo sát byte button; byte trangthai; int huong;// góc xoay ảnh void loop(){ button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: huong=Phai;walk(x,y,250,huong); x+=9; break;// right case 2: jump(x,y,150,huong); break;// up case 3: huong=Trai;walk(x,y,250,huong); x-=9; break;//left case 4: lie(x,y,300,huong); break; //down case 40: huong=Phai; x++; lie(x,y,5,huong); break;// slide right case 120: huong=Trai; x--; lie(x,y,5,huong); break;// slide left case 60: huong=Trai; high_jump(x,y,100,huong); x-=20;break;// jump higher case 20: huong=Phai; high_jump(x,y,100,huong);x+=20;break;// jump higher default : stand(x,y,huong); break; } if(x<0){x=0;} if(y<0){y=0;} if(x>100){x=100;} if(y>53){y=53;} }
Vẫn còn hai động tác là Die và Hurt, cái này mình dành ra để cho các bạn sáng tạo
MORE ...
Code đã dài, và vẫn còn nhiều cái hay với chủ đề này, mình xin tạm dừng tại đây, hi vọng bạn cảm thấy thích bài viết của mình.
Mình xin chia sẻ một trang web có tài nguyên đồ họa 2d khá hay untamed.wild-refuge.net
Còn nhiều cái hay hơn nữa.. Hẹn gặp lại các bạn ở bài tiếp theo.
Tác giả: Thái Sơn.