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

Mô tả dự án: 

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ượngBị 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 BLACKRED.

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

#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ụ :

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

Đó, 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.?!!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

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

#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à 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é.

Tác giả:  Thái Sơn.

lên
10 thành viên đã đánh giá bài viết này hữu ích.
Các dự án được truyền cảm hứng

Select any filter and click on Apply to see results

Các bài viết cùng tác giả

BÀI 2 - Sử dụng nhiều ic eeprom 24Cxx cùng lúc

Qua bài đầu, mình đã giúp các bạn giải quyết được một vấn đề khá quan trọng trong việc sử dụng EEPROM. Bài này sẽ giúp bạn biết cách ghép nhiều module eeprom để tạo ra Super eeprom như đã hứa.

lên
12 thành viên đã đánh giá bài viết này hữu ích.
Từ khóa: 

Sử dụng Ic SRAM 23LC1024 bằng Driver với thư viện SRAM_MANAGER cho arduino

Ở 2 bài trước, chúng ta đã biết cách giao tiếp và lưu trữ cơ bản trên 2 loại SRAM. Sau gần 6 tháng làm việc với SRAM trong các dự án, hôm nay mình sẽ chia sẻ 1 driver do mình thiết kế, nó dùng để hỗ trợ quản lý SRAM 23lc1024, đem đến cách dùng RAM ngoài thân thiện hơn cho lập trình viên.

lên
4 thành viên đã đánh giá bài viết này hữu ích.