Nick Chung gửi vào
- 14261 lượt xem
Một hiệu ứng đầu tiên cần nói đến trong chuyển động đó là về xử lý va chạm của các đối tượng. Bị dính đạn, bị cản đường, ảnh hưởng của trướng ngại vật trong di chuyển,… tất cả đều liên quan đến va chạm. Bài viết này, chúng ta sẽ tìm hiểu về vấn đề trên.
Hình chữ nhật với hình chữ nhật
Đây là bài 4 trong 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 bạn hãy đọc nó trước khi tiếp tục :
[phần 3] Chuyển động trong lập trình Game và đồ họa.
Va chạm vật lý của 2 đối tượng trong Game là va chạm vùng không gian của hai đối tượng đó.
Khi đó ta cần khảo sát hai thuộc tính của đối tượng:
- Vị trí tương đối của 2 đối tượng: tọa độ.
- Vùng không gian : hình dạng và kích thước.
- Kiểu vùng : 2d hoặc 3d (bài này chỉ xét đến 2d).
Ví dụ chúng ta khảo sát va chạm của hai đối tượng có tên BLACK và RED.
Chúng có 2 thuộc tính :
- Tọa độ: X, Y.
- Vùng không gian: Hình chữ nhật kích thước WxH.
BLACK là đối tượng chính, RED là vật thể cản, Cho RED đứng im, BLACK đặt ở các vị trí khác nhau, hai đối tượng này được cho là có va chạm khi vùng không gian giao nhau.
Biểu diễn toán học thì đó là khảo sát sự giao nhau của 2 tập trên trục số.
Xét trên trục x
Ta thấy ngay RED giao BLACK khi:
- Nếu x < x0: w >= (x0 - x). Kích thước lớn hơn hoặc bằng khoảng cách.
- Nếu x0 <= x: w0 > (x - x0).
Ta cũng có điều tương tự trên trục y.
//code kiểm tra xem hai đối tượng có chạm nhau hay không
#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); } bool collide(int x, int y, int w, int h, int x0, int y0, int w0, int h0) { if (((x < x0) && (w + x > x0)) || ((x0 <= x) && (w0 + x0 > x))) { // kiểm tra trục x if (((y < y0) && (h + y > y0)) || ((y0 <= y) && (h0 + y0 > y))) { // kiểm tra trục y return true; } } return false; } 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++; break; case 2: y--; break; case 3: x--; break; case 4: y++; break; } bool check = collide(60, 30, 10, 10, x, y, 20, 20); if (check) { lcd.asc_string(10, 5, asc("TRUE"), BLACK); } else { lcd.fillrect(10, 5, 30, 10, DELETE); } lcd.rect(60, 30, 10, 10, BLACK); // RED lcd.rect(x, y, 20, 20, BLACK); lcd.display(); delay(100); lcd.rect(x, y, 20, 20, DELETE); }
Chạy code, ta được;
Quản lí 2 đối tượng bằng class.
Thiết lập thông thường |
#include "ST7565_homephone.h" ST7565 lcd(3, 4, 5, 6); void setup() { lcd.ON(); lcd.SET(23, 0, 0, 0, 4); Serial.begin(9600); pinMode(A3, INPUT_PULLUP); pinMode(A2, INPUT_PULLUP); pinMode(A1, INPUT_PULLUP); pinMode(A0, INPUT_PULLUP); } |
Tạo một class: class<tên class> { public: // cấp quyền truy cập công khai <kiểu biến> _<thuộc tính>; <tên class> _(<tham số>){ // constructor- khởi tạo giá trị ban đầu } <kiểu hàm> _<tên hàm>(<tham trị hoặc tham số>) {} // chúng ta có 1 hàm là collide }; // bạn đừng quên thêm dấu ; đằng sau } khi đóng class nhé // Hàm chúng ta hay gọi trong class được gọi là Phương Thức // Biến Số trong class được gọi là Thuộc Tính.
|
class Object { public: int x, y, w, h; Object(int X, int Y, int W, int H) { // constructor x = X; y = Y; w = W; h = H; } bool collide(Object &T) { if (((x < T.x) && (w + x > T.x)) || ((T.x <= x) && (T.w + T.x > x))) { // kiểm tra trục x if (((y < T.y) && (h + y > T.y)) || ((T.y <= y) && (T.h + T.y > y))) { // kiểm tra trục y return true; } } return false; } }; // class
|
//Tạo thực thể với các thông số định trước <tên class> _<tên thực thể>( tham số); |
Object red(60,30,20,20); Object bla(30,20,10,10); |
Thao tác đọc và ghi dữ liệu thuộc tính Với khai báo Public (cho phép truy cập ) ta dễ dàng làm điều này. Cũng giống như với một biến thông thường, Cấu trúc truy cập thuộc tính của đối tượng là: <Tên thực thể> . <thuộc tính> Ví dụ :
Đó, giống với biến thôi. |
byte button; void loop() { button = lcd.Pullup_4(A3, A2, A1, A0); switch (button) { // dùng switch thay cho if case 1: bla.x++; break; case 2: bla.y--; break; case 3: bla.x--; break; case 4: bla.y++; break; } bool check = bla.collide(red); if (check) { lcd.asc_string(10, 5, asc("TRUE"), BLACK); } else { lcd.fillrect(10, 5, 30, 10, DELETE); } lcd.rect(red.x, red.y, red.w, red.h, BLACK); // RED lcd.rect(bla.x, bla.y, bla.w, bla.h, BLACK); lcd.display(); delay(100); lcd.rect(bla.x, bla.y, bla.w, bla.h, DELETE); }
|
Chạy code với class ta thu được kết quả tương tự:
Bắt đầu có nhiều trò hay rồi, bạn sẵn sàng chưa.?!!
GAME - Hộp đẩy hộp
Kết quả:
Đặt nốt 2 phương thức vẽ và xóa vào trong class cho nó tối ưu nào.
#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, w, h; int huong; // // huong=0 phải; // huong =90' trên // huong=180' trái // huong=270' dưới Object(int X, int Y, int W, int H) { // constructor - khởi tạo giá trị ban đầu x = X; y = Y; w = W; h = H; } bool collide(Object &T) { if (((x < T.x) && (w + x > T.x)) || ((T.x <= x) && (T.w + T.x > x))) { // kiểm tra trục x if (((y < T.y) && (h + y > T.y)) || ((T.y <= y) && (T.h + T.y > y))) { // kiểm tra trục y return true; } } return false; } void control() { byte button; button = lcd.Pullup_4(A3, A2, A1, A0); switch (button) { // dùng switch thay cho if case 1: x++; huong = 0; break; case 2: y--; huong = 90; break; case 3: x--; huong = 180; break; case 4: y++; huong = 270; break; } } void check_collide(Object &T) { if (collide(T)) { switch (T.huong) { case 0: x++; break; case 90: y--; break; case 180: x--; break; case 270: y++; break; } } } }; // class Object red(60, 30, 15, 15); Object bla(30, 20, 10, 10); void loop() { bla.control(); red.check_collide(bla); lcd.rect(red.x, red.y, red.w, red.h, BLACK); // RED lcd.rect(bla.x, bla.y, bla.w, bla.h, BLACK); lcd.display(); delay(20); lcd.rect(red.x, red.y, red.w, red.h, DELETE); // RED lcd.rect(bla.x, bla.y, bla.w, bla.h, DELETE); }
Nhiều hộp hơn thì sao? Chúng ta đã biết cách quản lý nhiều hơn 1 đối tượng ở bài 3, cùng áp dụng nào
#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); } #define Phai 0 #define Tren 90 #define Trai 180 #define Duoi 270 class Object { public: int x, y, w, h; int huong; // // huong=0 phải; // huong =90' trên // huong=180' trái // huong=270' dưới Object(int X, int Y, int W, int H) { // constructor - khởi tạo giá trị ban đầu x = X; y = Y; w = W; h = H; } bool collide(Object &T) { if (((x < T.x) && (w + x > T.x)) || ((T.x <= x) && (T.w + T.x > x))) { // kiểm tra trục x if (((y < T.y) && (h + y > T.y)) || ((T.y <= y) && (T.h + T.y > y))) { // kiểm tra trục y return true; } } return false; } void control() { byte button; button = lcd.Pullup_4(A3, A2, A1, A0); switch (button) { // dùng switch thay cho if case 1: x++; huong = Phai; break; case 2: y--; huong = Tren; break; case 3: x--; huong = Trai; break; case 4: y++; huong = Duoi; break; } } void check_collide(Object &T) { if (collide(T)) { switch (T.huong) { case 0: x++; break; case 90: y--; break; case 180: x--; break; case 270: y++; break; } } } void draw() { lcd.rect(x, y, w, h, BLACK); lcd.display(); } void del() { lcd.rect(x, y, w, h, DELETE); lcd.display(); } }; // class Object red0(60, 30, 15, 15); Object red1(10, 30, 20, 15); Object red2(80, 30, 5, 5); Object bla(30, 20, 10, 10); Object red[3] = {red0, red1, red2}; // mảng 3 đối tượng void loop() { bla.control(); for (byte i = 0; i < 3; i++) { // i=0,1,2 red[i].check_collide(bla); red[i].draw(); } bla.draw(); delay(20); for (byte i = 0; i < 3; i++) { // i=0,1,2 red[i].del(); } bla.del(); }
Hình tròn với hình tròn
Cho hai hình tròn: RED tâm (x0,y0) bán kính R0. BLACK tâm (x,y) bán kính R.
Vì hình tròn có tính đối xứng, mọi điểm trên đường tròn đều cách đề tâm một đoạn bằng bán kính.Để xét vị trí tương đối của hai hình tròn, ta sẽ so sánh khoảng cách D của hai hình tròn (tâm với tâm) với tổng bán kính của 2 hai hình.
- D > (R0+R) không chạm
- D <= (R0+R) chạm
Theo định lý Pitago ,Ta có khoảng cách của tâm hai đường tròn:
D2= (x0-x)2+(y0-y)2
Pitago
Như vậy,hai hình tròn :
- Không chạm: D >(R0+R) hay (x0-x)2+(y0-y)2 >(R0+R)2.
- Chạm: D <= (R0+R) hay hay (x0-x)2+(y0-y)2 <=(R0+R)2.
#include "ST7565_homephone.h" ST7565 lcd(3, 4, 5, 6); void setup() { lcd.ON(); lcd.SET(23, 0, 0, 0, 4); Serial.begin(9600); pinMode(A3, INPUT_PULLUP); pinMode(A2, INPUT_PULLUP); pinMode(A1, INPUT_PULLUP); pinMode(A0, INPUT_PULLUP); } class Object { public: int x, y, r; Object(int X, int Y, int R) { // constructor x = X; y = Y; r = R; } bool collide(Object &T) { if (sq(this->r + T.r) > sq(this->x - T.x) + sq(this->y - T.y)) { return true; } else { return false; } } }; // class Object red(60, 30, 10); Object bla(30, 20, 10); byte button; void loop() { button = lcd.Pullup_4(A3, A2, A1, A0); switch (button) { // dùng switch thay cho if case 1: bla.x++; break; case 2: bla.y--; break; case 3: bla.x--; break; case 4: bla.y++; break; } bool check = bla.collide(red); if (check) { lcd.asc_string(10, 5, asc("TRUE"), BLACK); } else { lcd.fillrect(10, 5, 30, 10, DELETE); } lcd.circle(red.x, red.y, red.r, BLACK); // RED lcd.circle(bla.x, bla.y, bla.r, BLACK); lcd.display(); delay(10); lcd.circle(bla.x, bla.y, bla.r, DELETE); }
Test code
Hình tròn với hình chữ nhật:
Xét hình chữ nhật tọa độ (x,y) size= (w.h).
Hình tròn tâm (x0,y0) bán kính r.
Gọi A(xa,ya) là điểm gần nhất thuộc hình chữ nhật đến tâm của đường tròn. C(x0,y0) là tâm đường tròn.
Khi đó ta so sánh khoảng cách CA với bán kính r để xét va chạm.
Để tìm điểm A ta cần 3 bước:
- B1: Gán xa=x0, ya=y0.
- B2:
- Nếu x0 < x thì xa=x.
- Nếu x0 > x + w thì xa = x + w.
- Nếu x <= x0 <= x + w thì xa=x0.
- B3:
- Nếu y0 < y thì ya=y.
- Nếu y0 > y + h thì ya = y + h.
- Nếu y <= y0 <= y + h thì ya = y0.
Sau khi có điểm A(xa,ya) ta so sánh đoạn CA với r.
#include "ST7565_homephone.h" ST7565 lcd(3, 4, 5, 6); void setup() { lcd.ON(); lcd.SET(23, 0, 0, 0, 4); Serial.begin(9600); pinMode(A3, INPUT_PULLUP); pinMode(A2, INPUT_PULLUP); pinMode(A1, INPUT_PULLUP); pinMode(A0, INPUT_PULLUP); } class Object { public: int x, y, r; int w, h; Object(int X, int Y, int R) { // constructor x = X; y = Y; r = R; } Object(int X, int Y, int W, int H) { // constructor x = X; y = Y; w = W; h = H; } bool collide(Object &C) { // tìm điểm A int xa = C.x, ya = C.y; if (C.x < x) { xa = x; } if (C.x > x + w) { xa = x + w; } if (C.y < y) { ya = y; } if (C.y > y + h) { ya = y + h; } // so sánh CA với r if (sq(C.r) >= sq(xa - C.x) + sq(ya - C.y)) { return true; } else { return false; } } }; // class Object RECT(60, 30, 30, 20); Object CIRCLE(30, 20, 8); byte button; void loop() { button = lcd.Pullup_4(A3, A2, A1, A0); switch (button) { // dùng switch thay cho if case 1: CIRCLE.x++; break; case 2: CIRCLE.y--; break; case 3: CIRCLE.x--; break; case 4: CIRCLE.y++; break; } bool check = RECT.collide(CIRCLE); if (check) { lcd.asc_string(10, 5, asc("TRUE"), BLACK); } else { lcd.fillrect(10, 5, 30, 10, DELETE); } lcd.rect(RECT.x, RECT.y, RECT.w, RECT.h, BLACK); lcd.circle(CIRCLE.x, CIRCLE.y, CIRCLE.r, BLACK); lcd.display(); delay(10); lcd.circle(CIRCLE.x, CIRCLE.y, CIRCLE.r, DELETE); }
Trong class trên, tính nạp trồng phương thức được thể hiện ở chỗ, ta có thể gọi nhiều lần một hàm (tên hàm giống nhau) tuy nhiên cách truyền vào một tham số phải khác nhau. Ở đây Constructor đã được dùng như hai loại khác nhau, một cái nạp tham số cho hình tròn, một cái nạp cho hình chữ nhật.
Các hình khác.
Các ví dụ trên đều là các trường hợp cơ bản nhất, tuy nhiên cũng có trường hợp ta cần xét va chạm của đa giác bất kì nào đó. Người ta giải quyết vấn đề này bằng cách chia nhỏ đối tượng đó về các hình cơ bản rồi xét hoặc là lại tiếp tục xây dựng các hàm xử lí chuyên biệt với những hình chuyên biệt.
Để tìm hiểu xâu hơn về vấn đề này,bạn có thể truy cập các trang sau:
http://www.ibm.com/developerworks/library/wa-html5-game8/
https://www.toptal.com/game/video-game-physics-part-ii-collision-detection-for-solid-objects
http://gamedev.stackexchange.com/questions/25397/obb-vs-obb-collision-detection
Tạm kết
Oai, mệt quá..hihi.
Vì đam mê í mà .
Chủ đề này khá hay, vẫn còn nhiều cái để khám phá. Tất nhiên vẫn còn nhiều cái thú vị hơn nhiều.
Hi vọng bạn thấy thích bài viết này, Hẹn gặp lại các bạn ở bài sau nhé.
Tác giả: Thái Sơn.