Nick Chung gửi vào
- 13932 lượt xem
Ở hai bài trước, chúng ta đã làm quen và biết cách điều khiển độc lập 1 đối tượng. ở phần 3 này, các bạn sẽ biết thêm về cách quản lý nhiều hơn 1 đối tượng trong game. Hãy cùng bắt đầu nào.
MULTI OBJECT
Đây là bài 3 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 trước thì bạn hãy đọc nó trước khi tiếp tục nhé.
Phần 2|Chuyển động trong lập trình Game và đồ họa
- Trong bài sử dụng bảng lớn, phù hợp hơn với màn hình máy tính khi xem, bạn hãy xoay ngang màn hình nếu đang dùng điện thoại.
- Bài viết sử dụng arduino uno r3 và lcd ST7565 cùng bộ thư viện st7565_homephone.
Cùng bắt đầu lại với ví dụ thần thánh basic nhất trong chuyển động: Cho một đối tượng di chuyển trên màn hình.
#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++){ lcd.fillcircle( x,10,3,BLACK); lcd.display();// cho phép hiển thị vẽ delay(50); lcd.fillcircle( x,10,3,DELETE); lcd.display();// cho phép hiển thị xóa } }
Ví dụ trên là cho đối tượng A (hình tròn, r=3px,) chạy từ trái sang phải.
Yêu cầu là bây giờ vẽ thêm 1 đối tượng B chạy song song với đối tượng A.
Vậy thì hàm loop sẽ như thế này:
void loop(){ for( byte x=0; x<127; x++){ lcd.fillcircle( x,10,3,BLACK); lcd.fillcircle( x,20,3,BLACK); lcd.display();// cho phép hiển thị vẽ delay(50); lcd.fillcircle( x,10,3,DELETE); lcd.fillcircle( x,20,3,DELETE); lcd.display();// cho phép hiển thị xóa } }
Thêm 4 đối tượng C,D,E,F nữa cùng chạy song song với A,B thì code sẽ như thế nào ??
Dưới đây là code cho ví dụ trên.
void loop(){ for( byte x=0; x<127; x++){ lcd.fillcircle( x,10,3,BLACK); lcd.fillcircle( x,20,3,BLACK); lcd.fillcircle( x,30,3,BLACK); lcd.fillcircle( x,40,3,BLACK); lcd.fillcircle( x,50,3,BLACK); lcd.fillcircle( x,60,3,BLACK); lcd.display();// cho phép hiển thị vẽ delay(50); lcd.fillcircle( x,10,3,DELETE); lcd.fillcircle( x,20,3,DELETE); lcd.fillcircle( x,30,3,DELETE); lcd.fillcircle( x,40,3,DELETE); lcd.fillcircle( x,50,3,DELETE); lcd.fillcircle( x,60,3,DELETE); lcd.display();// cho phép hiển thị xóa } }
Chúng ta rút ra vài điều như sau:
- Có bao nhiêu đối tượng trong game thì có bấy nhiêu đối tượng bạn cần phải quản lý.
- Thời gian để hoàn thiện một khung ảnh tỉ lệ thuận với số lượng đối tượng
- Tài nguyên dung lượng bị thiếu hụt ít hay nhiều phụ thuộc vào số lượng đối tượng và mức độ phức tạp trong thuộc tính của đối tượng .
Ta cần làm gì..??
Quay lại với đoạn code trên, nếu cứ giữ nguyên giải thuạt mà lại yêu cầu vẽ 10 đối tượng hoặc hơn thế chạy song song từ trái qua phải thì chúng ta cần có cách quản lý phù hợp hơn.
Có nhiều cách, bảng thống kê là một lựa chọn đơn giản mà chúng ta thường gặp.
Ví dụ, cô giáo của bé khi sử dụng phần mềm LaChildren, sẽ theo dõi và quản lý trẻ thông qua một bảng thống kê mọi thông tin về trẻ.
(nguồn ảnh internet)
Cùng so sánh cách quản lý của cô giáo và máy tính:
Trong lớp học |
Trong lập trình game |
Các bé. |
Các đối tượng trong game. |
Cô giáo là người quản lý các bé. |
Máy tính quản lý các đối tượng game. |
Mỗi bé có một tên để phân biệt. (bỏ qua trường hợp giống tên) |
Mỗi đối tượng cũng cần có gì đó để phân biệt: Chỉ số index, name-id,.. |
Quản lý bé dựa trên các yếu tố giống nhau nhất: Năm sinh, Lớp , Chiều cao, cân nặng.. |
Quản lý đối tượng dựa trên các thuộc tính giống nhau nhất: Tọa độ, kích thước, màu sắc,.. |
Cô giáo quản lý bằng bảng. |
Máy tính cũng quản lý bằng “bảng”. |
Vậy “bảng “ của máy tính là gì, nếu bạn chưa biết, thì mình xin giới thiệu , đó là mảng 2 chiều.
Ví dụ đầu tiên về sử dụng mảng 2 chiều :
Bắt đầu hay rổi đây, cùng mở IDE của bạn lên và trải nghiệm về mảng 2 chiều thôi.!!
void setup(){ Serial.begin(9600); } void loop(){ }
Nếu bạn chưa quen với khái niệm mảng 2 chiều, khi mình viết là “bảng” thì bạn chỉ cần tưởng tượng tới cái bảng này là được.
Đầu tiên,khai báo “bảng” có kích thước 3x4 có kiểu dữ byte , tức là khai báo 3x4=12 biến có kiểu dữ liệu byte.
Bước |
Code |
Trạng thái các biến |
Khai báo |
void setup(){ Serial.begin(9600); } byte Array[3][4]; void loop(){ }
|
|
Ghi dữ liệu |
void setup(){ Serial.begin(9600); } byte Array[3][4]; void loop(){ Array[0][0]=88; Array[2][3]=99; }
|
|
Lấy(kiểm tra) dữ liệu |
void setup(){ Serial.begin(9600); } byte Array[3][4]; void loop(){ Array[0][0]=88; Array[2][3]=99; Serial.print(Array[0][0]); Serial.print("|"); Serial.println(Array[2][3]); }
|
|
Ghi đè(thay đổi) dữ liệu. |
void setup(){ Serial.begin(9600); } byte Array[3][4]; void loop(){ //ghi Array[0][0]=88; Array[2][3]=99; //ghi đè Array[0][0]=77; Array[2][3]=55; //kiểm tra lại Serial.print(Array[0][0]); Serial.print("|"); Serial.println(Array[2][3]); }
|
Rất dễ phải không bạn!^^.
Trước khi tiếp tục, bạn hãy dành ra ít phút để check lại các đoạn code vừa rồi, mạnh dạn sửa đổi code nhé.
…………………………………….
……………………………………
Vậy còn gì mà mình chưa nói về mảng hai chiều nhỉ?
Hihi, hết rồi. Nó đơn giản là mảng các biến thui. Từ giờ mình dùng thuật ngữ mảng 2 chiều được rồi.
Cùng quay lại với yêu cầu quản lý 6 đối tượng ABCDEF bằng mảng 2 chiều
Chúng ta xác định 2 thuộc tính chung của các đối tượng là hoành độ x, tung độ y.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); } int object[6][2]; void loop(){ }
|
|
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); } int object[6][2]; void loop(){ //ghi dữ liệu tung độ y object[0][1]=10; object[1][1]=20; object[2][1]=30; object[3][1]=40; object[4][1]=50; object[5][1]=60; }
|
|
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); } int object[6][2]; void loop(){ //ghi dữ liệu tung độ y for( int i=0; i<6; i++){ object[i][1]=10*(i+1); } //ghi dữ liệu hoành độ x for( int i=0; i<6; i++){ object[i][0]=30; } }
|
|
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { lcd.ON(); lcd.SET(23,0,0,0,4); } int object[6][2]; void loop(){ //ghi dữ liệu tung độ y for( int i=0; i<6; i++){ object[i][1]=10*(i+1); } // vẽ chuyển động for( int x=0; x<127; x++){ //ghi dữ liệu hoành độ và vẽ for( int i=0; i<6; i++){ object[i][0]=x; lcd.fillcircle( object[i][0] , object[i][1],3,BLACK); } lcd.display(); // đợi delay(50); // xóa ảnh for( int i=0; i<6; i++){ lcd.fillcircle( object[i][0] , object[i][1],3,DELETE); } } }
|
Kết quả
Control_object
Bây giờ mình sẽ điều khiển biệt lập đối tượng F , còn các đối tượng ABCDE để cho máy tính đảm nhiệm.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); 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); } int object[6][2]; byte button; void loop(){ //ghi dữ liệu tung độ y for( int i=0; i<6; i++){ object[i][1]=10*(i+1); } // vẽ chuyển động for( int x=0; x<127; x++){ //ghi dữ liệu hoành độ cho ABCDE for( int i=0; i<5; i++){ object[i][0]=x; } // ghi dữ liệu cho đối tượng F button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: object[5][0]++;break;// right case 2: object[5][1]--; break;// up case 3: object[5][0]--; break;//left case 4: object[5][1]++; break; //down default : break; } for( int i=0; i<6; i++){//vẽ ABCDEF lcd.fillcircle( object[i][0] , object[i][1],3,BLACK); } lcd.display(); delay(50); for( int i=0; i<6; i++){ lcd.fillcircle( object[i][0] , object[i][1],3,DELETE); } } }
Mình đoán là đến đây, bạn cũng đã mường tượng được một vài vấn đề trong game rùi đấy.
OK, đó là cách quản lý thứ nhất :sử dụng mảng 2 chiều.
Cách quản lý hay hơn ??
Cách hai là dùng kiểu dữ liệu tự định nghĩa. Class
Cũng giống với ví dụ trên, mình chưa đưa ra định nghĩa Class, vì nó .. nếu chỉ mang mình nó để mà định nghĩa thì sẽ khiến bạn bối rối .
Trên tinh thần, “yêu” trước , “yêu’ rồi mới định nghĩa được “tình yêu”.
Thì chúng ta cần đi vào ví dụ,:
Chủ đề của ví dụ là quản lý đối tượng bằng mảng 2 chiều và class.
Mảng 2 chiều |
Class |
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { Serial.begin(9600); } int circle[2][2]; void loop(){ } Khai báo mảng 2 chiều có 2 thuộc tính x,y (như ví dụ đã biết bên trên ha) quản lý 2 đối tượng . Đối tượng A :circle[0][…]; Đối tượng B: circle[1][…];
|
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { Serial.begin(9600); } class object{ public : int x,y; }; object circle[2]; void loop(){ } Tạo một class để quản lý 2 đối tượng , với 2 thuộc tính x,y. Đối tượng A: circle[0]; Đối tượng B: circle[1];
|
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { Serial.begin(9600); } int circle[2][2]; void loop(){ //ghi dữ liệu cho A circle[0][0]=30;//xA circle[0][1]=40;//yA // đọc dữ liệu của A Serial.print(circle[0][0]); Serial.print("|"); Serial.println(circle[0][1]); }
|
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); void setup() { Serial.begin(9600); } class object{ public : int x,y; }; object circle[2]; void loop(){ //ghi dữ liệu cho A circle[0].x=30;//xA circle[0].y=40;//yA // đọc dữ liệu của A Serial.print(circle[0].x); Serial.print("|"); Serial.println(circle[0].y); }
|
Tạm dừng một chút và so sánh 2 cách trên, cá nhân mình thấy cách dùng class có cảm tình hơn nhiều, các bạn nghĩ sao?
Đúng hơn là nó dễ biểu đạt về mặt thuộc tính.
Và đây là đoạn code sử dụng class thay cho mảng 2 chiều:
Bây giờ mình sẽ điều khiển biệt lập đối tượng F , còn các đối tượng ABCDE để cho máy tính đảm nhiệm.
#include "ST7565_homephone.h" ST7565 lcd(3,4,5,6); 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); } class object{ public : int x,y; }; object circle[6]; byte button; void loop(){ //ghi dữ liệu tung độ y for( int i=0; i<6; i++){ circle[i].y=10*(i+1); } // vẽ chuyển động for( int x=0; x<127; x++){ //ghi dữ liệu hoành độ cho ABCDE for( int i=0; i<5; i++){ circle[i].x=x; } // ghi dữ liệu cho đối tượng F button= lcd.Pullup_4(A3, A2, A1, A0); switch(button){ case 1: circle[5].x++;break;// right case 2: circle[5].y--; break;// up case 3: circle[5].x--; break;//left case 4: circle[5].y++; break; //down default : break; } for( int i=0; i<6; i++){//vẽ ABCDEF lcd.fillcircle( circle[i].x , circle[i].y,3,BLACK); } lcd.display(); // đợi delay(50); // xóa ảnh for( int i=0; i<6; i++){ lcd.fillcircle( circle[i].x , circle[i].y,3,DELETE); } } }
Kết quả vẫn như vậy.
Tạm kết.
Vậy đó, nhờ đam mê làm game mà chúng ta lại biết được vài thứ mới của arduino.
Nếu cả mảng 2 chiều, class là kiến thức mới đối với bạn.
Sau bài viết này có 2 điều mà bạn nên và không nên làm:
- Bạn nên tìm hiểu về mảng 2 chiều.
- Không nên vội vã ngấu nghiến các tài liệu về class. Về phần này, cứ để mình dẫn dắt bạn cùng với những ví dụ trực quan nhất, mọi thứ về class sẽ được mở ra dần thôi.
Hi vọng bạn thấy thích bài viết này
Ở bài tiếp theo, chúng ta sẽ đổi gió một chút, đảm bảo bạn thấy thích thú.
Tác giả: Thái Sơn.