ST7565 | Chuyển động trong lập trình Game và đồ họa | Phần 4

07/08/2019
st7565-chuyen-dong-trong-lap-trinh-game-va-do-hoa-phan-4

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.

1

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 >= (x- x). Kích thước lớn hơn hoặc bằng khoảng cách.
  • Nếu x<= x: w> (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

  1. #include "ST7565_homephone.h"
  2. ST7565 lcd(3, 4, 5, 6);
  3. void setup() {
  4. lcd.ON();
  5. lcd.SET(23, 0, 0, 0, 4);
  6. pinMode(A3, INPUT_PULLUP);
  7. pinMode(A2, INPUT_PULLUP);
  8. pinMode(A1, INPUT_PULLUP);
  9. pinMode(A0, INPUT_PULLUP);
  10. }
  11. bool collide(int x, int y, int w, int h, int x0, int y0, int w0, int h0) {
  12. if (((x < x0) && (w + x > x0)) ||
  13. ((x0 <= x) && (w0 + x0 > x))) { // kiểm tra trục x
  14. if (((y < y0) && (h + y > y0)) ||
  15. ((y0 <= y) && (h0 + y0 > y))) { // kiểm tra trục y
  16. return true;
  17. }
  18. }
  19. return false;
  20. }
  21.  
  22. int x = 60; // hoành độ khảo sát
  23. int y = 30; // tung độ khảo sát
  24. byte button;
  25. void loop() {
  26. button = lcd.Pullup_4(A3, A2, A1, A0);
  27. switch (button) { // dùng switch thay cho if
  28. case 1:
  29. x++;
  30. break;
  31. case 2:
  32. y--;
  33. break;
  34. case 3:
  35. x--;
  36. break;
  37. case 4:
  38. y++;
  39. break;
  40. }
  41. bool check = collide(60, 30, 10, 10, x, y, 20, 20);
  42.  
  43. if (check) {
  44. lcd.asc_string(10, 5, asc("TRUE"), BLACK);
  45. } else {
  46. lcd.fillrect(10, 5, 30, 10, DELETE);
  47. }
  48. lcd.rect(60, 30, 10, 10, BLACK); // RED
  49. lcd.rect(x, y, 20, 20, BLACK);
  50. lcd.display();
  51. delay(100);
  52. lcd.rect(x, y, 20, 20, DELETE);
  53. }

Chạy code, ta được;

Quản lí 2 đối tượng bằng class.

Thiết lập thông thường

 
  1. #include "ST7565_homephone.h"
  2. ST7565 lcd(3, 4, 5, 6);
  3. void setup() {
  4. lcd.ON();
  5. lcd.SET(23, 0, 0, 0, 4);
  6. Serial.begin(9600);
  7. pinMode(A3, INPUT_PULLUP);
  8. pinMode(A2, INPUT_PULLUP);
  9. pinMode(A1, INPUT_PULLUP);
  10. pinMode(A0, INPUT_PULLUP);
  11. }

Tạo một class:

 
  1. class<tên class>
  2.  
  3. {
  4. public: // cấp quyền truy cập công khai
  5. <kiểu biến> _<thuộc tính>;
  6.  
  7. <tên class> _(<tham số>){
  8.  
  9. // constructor- khởi tạo giá trị ban đầu
  10.  
  11. }
  12.  
  13. <kiểu hàm> _<tên hàm>(<tham trị hoặc tham số>) {}
  14.  
  15. // chúng ta có 1 hàm là collide
  16. };
  17.  
  18. // bạn đừng quên thêm dấu ; đằng sau } khi đóng class nhé
  19.  
  20. // Hàm chúng ta hay gọi trong class được gọi là Phương Thức
  21.  
  22. // Biến Số trong class được gọi là Thuộc Tính.

 

 
  1. class Object {
  2. public:
  3. int x, y, w, h;
  4. Object(int X, int Y, int W, int H) {
  5. // constructor
  6. x = X;
  7. y = Y;
  8. w = W;
  9. h = H;
  10. }
  11. bool collide(Object &T) {
  12. if (((x < T.x) && (w + x > T.x)) ||
  13. ((T.x <= x) && (T.w + T.x > x))) { // kiểm tra trục x
  14. if (((y < T.y) && (h + y > T.y)) ||
  15. ((T.y <= y) && (T.h + T.y > y))) { // kiểm tra trục y
  16. return true;
  17. }
  18. }
  19. return false;
  20. }
  21. }; // class

 

 
  1. //Tạo thực thể với các thông số định trước
  2. <tên class> _<tên thực thể>( tham số);
 
  1. Object red(60,30,20,20);
  2. 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ụ :

  • Ghi: red.x=10;
  • Đọc: Serial.println(red.x);

Đó, giống với biến thôi. 

 
  1. byte button;
  2. void loop() {
  3. button = lcd.Pullup_4(A3, A2, A1, A0);
  4. switch (button) { // dùng switch thay cho if
  5. case 1:
  6. bla.x++;
  7. break;
  8. case 2:
  9. bla.y--;
  10. break;
  11. case 3:
  12. bla.x--;
  13. break;
  14. case 4:
  15. bla.y++;
  16. break;
  17. }
  18. bool check = bla.collide(red);
  19. if (check) {
  20. lcd.asc_string(10, 5, asc("TRUE"), BLACK);
  21. } else {
  22. lcd.fillrect(10, 5, 30, 10, DELETE);
  23. }
  24. lcd.rect(red.x, red.y, red.w, red.h, BLACK); // RED
  25. lcd.rect(bla.x, bla.y, bla.w, bla.h, BLACK);
  26. lcd.display();
  27. delay(100);
  28. lcd.rect(bla.x, bla.y, bla.w, bla.h, DELETE);
  29. }

 

 

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.?!!devilcheeky

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.laugh

  1. #include "ST7565_homephone.h"
  2. ST7565 lcd(3, 4, 5, 6);
  3. void setup() {
  4. lcd.ON();
  5. lcd.SET(23, 0, 0, 0, 4);
  6.  
  7. pinMode(A3, INPUT_PULLUP);
  8. pinMode(A2, INPUT_PULLUP);
  9. pinMode(A1, INPUT_PULLUP);
  10. pinMode(A0, INPUT_PULLUP);
  11. }
  12.  
  13. class Object {
  14. public:
  15. int x, y, w, h;
  16. int huong; //
  17. // huong=0 phải;
  18. // huong =90' trên
  19. // huong=180' trái
  20. // huong=270' dưới
  21. Object(int X, int Y, int W, int H) {
  22. // constructor - khởi tạo giá trị ban đầu
  23. x = X;
  24. y = Y;
  25. w = W;
  26. h = H;
  27. }
  28. bool collide(Object &T) {
  29. if (((x < T.x) && (w + x > T.x)) ||
  30. ((T.x <= x) && (T.w + T.x > x))) { // kiểm tra trục x
  31. if (((y < T.y) && (h + y > T.y)) ||
  32. ((T.y <= y) && (T.h + T.y > y))) { // kiểm tra trục y
  33. return true;
  34. }
  35. }
  36. return false;
  37. }
  38. void control() {
  39. byte button;
  40. button = lcd.Pullup_4(A3, A2, A1, A0);
  41. switch (button) { // dùng switch thay cho if
  42. case 1:
  43. x++;
  44. huong = 0;
  45. break;
  46. case 2:
  47. y--;
  48. huong = 90;
  49. break;
  50. case 3:
  51. x--;
  52. huong = 180;
  53. break;
  54. case 4:
  55. y++;
  56. huong = 270;
  57. break;
  58. }
  59. }
  60. void check_collide(Object &T) {
  61. if (collide(T)) {
  62. switch (T.huong) {
  63. case 0:
  64. x++;
  65. break;
  66. case 90:
  67. y--;
  68. break;
  69. case 180:
  70. x--;
  71. break;
  72. case 270:
  73. y++;
  74. break;
  75. }
  76. }
  77. }
  78. }; // class
  79. Object red(60, 30, 15, 15);
  80. Object bla(30, 20, 10, 10);
  81. void loop() {
  82. bla.control();
  83. red.check_collide(bla);
  84. lcd.rect(red.x, red.y, red.w, red.h, BLACK); // RED
  85. lcd.rect(bla.x, bla.y, bla.w, bla.h, BLACK);
  86. lcd.display();
  87. delay(20);
  88. lcd.rect(red.x, red.y, red.w, red.h, DELETE); // RED
  89. lcd.rect(bla.x, bla.y, bla.w, bla.h, DELETE);
  90. }

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

  1. #include "ST7565_homephone.h"
  2. ST7565 lcd(3, 4, 5, 6);
  3. void setup() {
  4. lcd.ON();
  5. lcd.SET(23, 0, 0, 0, 4);
  6.  
  7. pinMode(A3, INPUT_PULLUP);
  8. pinMode(A2, INPUT_PULLUP);
  9. pinMode(A1, INPUT_PULLUP);
  10. pinMode(A0, INPUT_PULLUP);
  11. }
  12.  
  13. #define Phai 0
  14. #define Tren 90
  15. #define Trai 180
  16. #define Duoi 270
  17. class Object {
  18. public:
  19. int x, y, w, h;
  20. int huong; //
  21. // huong=0 phải;
  22. // huong =90' trên
  23. // huong=180' trái
  24. // huong=270' dưới
  25. Object(int X, int Y, int W, int H) {
  26. // constructor - khởi tạo giá trị ban đầu
  27. x = X;
  28. y = Y;
  29. w = W;
  30. h = H;
  31. }
  32. bool collide(Object &T) {
  33. if (((x < T.x) && (w + x > T.x)) ||
  34. ((T.x <= x) && (T.w + T.x > x))) { // kiểm tra trục x
  35. if (((y < T.y) && (h + y > T.y)) ||
  36. ((T.y <= y) && (T.h + T.y > y))) { // kiểm tra trục y
  37. return true;
  38. }
  39. }
  40. return false;
  41. }
  42. void control() {
  43. byte button;
  44. button = lcd.Pullup_4(A3, A2, A1, A0);
  45. switch (button) { // dùng switch thay cho if
  46. case 1:
  47. x++;
  48. huong = Phai;
  49. break;
  50. case 2:
  51. y--;
  52. huong = Tren;
  53. break;
  54. case 3:
  55. x--;
  56. huong = Trai;
  57. break;
  58. case 4:
  59. y++;
  60. huong = Duoi;
  61. break;
  62. }
  63. }
  64. void check_collide(Object &T) {
  65. if (collide(T)) {
  66. switch (T.huong) {
  67. case 0:
  68. x++;
  69. break;
  70. case 90:
  71. y--;
  72. break;
  73. case 180:
  74. x--;
  75. break;
  76. case 270:
  77. y++;
  78. break;
  79. }
  80. }
  81. }
  82. void draw() {
  83. lcd.rect(x, y, w, h, BLACK);
  84. lcd.display();
  85. }
  86. void del() {
  87. lcd.rect(x, y, w, h, DELETE);
  88. lcd.display();
  89. }
  90. }; // class
  91. Object red0(60, 30, 15, 15);
  92. Object red1(10, 30, 20, 15);
  93. Object red2(80, 30, 5, 5);
  94. Object bla(30, 20, 10, 10);
  95. Object red[3] = {red0, red1, red2}; // mảng 3 đối tượng
  96. void loop() {
  97. bla.control();
  98. for (byte i = 0; i < 3; i++) { // i=0,1,2
  99. red[i].check_collide(bla);
  100. red[i].draw();
  101. }
  102.  
  103. bla.draw();
  104. delay(20);
  105. for (byte i = 0; i < 3; i++) { // i=0,1,2
  106. red[i].del();
  107. }
  108. bla.del();
  109. }

2

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.
  1. #include "ST7565_homephone.h"
  2. ST7565 lcd(3, 4, 5, 6);
  3. void setup() {
  4. lcd.ON();
  5. lcd.SET(23, 0, 0, 0, 4);
  6. Serial.begin(9600);
  7. pinMode(A3, INPUT_PULLUP);
  8. pinMode(A2, INPUT_PULLUP);
  9. pinMode(A1, INPUT_PULLUP);
  10. pinMode(A0, INPUT_PULLUP);
  11. }
  12.  
  13. class Object {
  14. public:
  15. int x, y, r;
  16.  
  17. Object(int X, int Y, int R) {
  18. // constructor
  19. x = X;
  20. y = Y;
  21. r = R;
  22. }
  23. bool collide(Object &T) {
  24. if (sq(this->r + T.r) > sq(this->x - T.x) + sq(this->y - T.y)) {
  25. return true;
  26. } else {
  27. return false;
  28. }
  29. }
  30. }; // class
  31.  
  32. Object red(60, 30, 10);
  33. Object bla(30, 20, 10);
  34.  
  35. byte button;
  36. void loop() {
  37. button = lcd.Pullup_4(A3, A2, A1, A0);
  38. switch (button) { // dùng switch thay cho if
  39. case 1:
  40. bla.x++;
  41. break;
  42. case 2:
  43. bla.y--;
  44. break;
  45. case 3:
  46. bla.x--;
  47. break;
  48. case 4:
  49. bla.y++;
  50. break;
  51. }
  52. bool check = bla.collide(red);
  53.  
  54. if (check) {
  55. lcd.asc_string(10, 5, asc("TRUE"), BLACK);
  56. } else {
  57. lcd.fillrect(10, 5, 30, 10, DELETE);
  58. }
  59. lcd.circle(red.x, red.y, red.r, BLACK); // RED
  60. lcd.circle(bla.x, bla.y, bla.r, BLACK);
  61. lcd.display();
  62. delay(10);
  63. lcd.circle(bla.x, bla.y, bla.r, DELETE);
  64. }

Test code

3

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 x< x thì xa=x.
    • Nếu x> x + w thì x= x + w.
    • Nếu x <= x<= x + w thì xa=x0.
  • B3:
    • Nếu y< y thì ya=y.
    • Nếu y> y + h thì y= y + h.
    • Nếu y <= y<= y + h thì y= y0.

Sau khi có điểm A(xa,ya) ta so sánh đoạn CA với r.

  1. #include "ST7565_homephone.h"
  2. ST7565 lcd(3, 4, 5, 6);
  3. void setup() {
  4. lcd.ON();
  5. lcd.SET(23, 0, 0, 0, 4);
  6. Serial.begin(9600);
  7. pinMode(A3, INPUT_PULLUP);
  8. pinMode(A2, INPUT_PULLUP);
  9. pinMode(A1, INPUT_PULLUP);
  10. pinMode(A0, INPUT_PULLUP);
  11. }
  12.  
  13. class Object {
  14. public:
  15. int x, y, r;
  16. int w, h;
  17. Object(int X, int Y, int R) {
  18. // constructor
  19. x = X;
  20. y = Y;
  21. r = R;
  22. }
  23.  
  24. Object(int X, int Y, int W, int H) {
  25. // constructor
  26. x = X;
  27. y = Y;
  28. w = W;
  29. h = H;
  30. }
  31.  
  32. bool collide(Object &C) {
  33. // tìm điểm A
  34. int xa = C.x, ya = C.y;
  35. if (C.x < x) {
  36. xa = x;
  37. }
  38. if (C.x > x + w) {
  39. xa = x + w;
  40. }
  41. if (C.y < y) {
  42. ya = y;
  43. }
  44. if (C.y > y + h) {
  45. ya = y + h;
  46. }
  47. // so sánh CA với r
  48.  
  49. if (sq(C.r) >= sq(xa - C.x) + sq(ya - C.y)) {
  50. return true;
  51. } else {
  52. return false;
  53. }
  54. }
  55. }; // class
  56.  
  57. Object RECT(60, 30, 30, 20);
  58. Object CIRCLE(30, 20, 8);
  59.  
  60. byte button;
  61. void loop() {
  62. button = lcd.Pullup_4(A3, A2, A1, A0);
  63. switch (button) { // dùng switch thay cho if
  64. case 1:
  65. CIRCLE.x++;
  66. break;
  67. case 2:
  68. CIRCLE.y--;
  69. break;
  70. case 3:
  71. CIRCLE.x--;
  72. break;
  73. case 4:
  74. CIRCLE.y++;
  75. break;
  76. }
  77. bool check = RECT.collide(CIRCLE);
  78. if (check) {
  79. lcd.asc_string(10, 5, asc("TRUE"), BLACK);
  80. } else {
  81. lcd.fillrect(10, 5, 30, 10, DELETE);
  82. }
  83. lcd.rect(RECT.x, RECT.y, RECT.w, RECT.h, BLACK);
  84. lcd.circle(CIRCLE.x, CIRCLE.y, CIRCLE.r, BLACK);
  85. lcd.display();
  86. delay(10);
  87.  
  88. lcd.circle(CIRCLE.x, CIRCLE.y, CIRCLE.r, DELETE);
  89. }

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.

4

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.

5

Tạm kết

Oai, mệt quá..hihi.

Vì đam mê í mà laughcheeky.

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.wink

Hi vọng bạn thấy thích bài viết này, yes Hẹn gặp lại các bạn ở bài sau nhé

Bình luận
Nội dung này chưa có bình luận, hãy gửi bình luận đầu tiên của bạn.
VIẾT BÌNH LUẬN CỦA BẠN