Lập trình game java cơ bản - phát hiện va chạm (P2) - hình tròn và hình chữ nhật

Xin chào các bạn đã quay trở lại với blog.
Mấy hôm nay lười quá không viết bài mới :))

Hôm nay chúng ta tiếp tục tìm hiểu về việc xử lý va chạm trong game, bài hôm trước chúng ta đã thực hiện việc xử lý va chạm giữa hai hình chữ nhật với nhau mà không tốn tí công sức nào :v (vì nó có sẵn chỉ lấy ra xài thôi).

Bài hôm nay thì sẽ cần code một chút vì chúng ta cần xét va chạm giữa một hình tròn và một hình chữ nhật, cơ mà trong java nó lại không có lớp hình tròn sẵn, cho nên chúng ta sẽ đi xây dựng lớp hình tròn ngay bây giờ, việc này thì rất đơn giản:

Một hình tròn thì sẽ có 2 thuộc tính quan trọng là: tọa độ của tâm và bán kính, tọa độ tâm ta có thể dùng lớp Point có sẵn của java hoặc tự xây dựng 1 lớp Point cho riêng mình, nhưng ở đây ta cũng không cần xây dựng làm gì, cứ để tọa độ tâm là 2 biến kiểu nguyên cũng được, bán kính ta khai báo kiểu int luôn cho nó đơn giản nhé :v.
public class Circle {
private int x;
private int y;
private int radius;

public Circle(int x, int y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
public int getRadius() {
return radius;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Rồi, bây giờ chúng ta sẽ vẽ ra hình tròn này thử xem nhé.
các bạn thêm đoạn code sau vào class CollisionDetection, tại phương thức paintComponent(...) nhé:

g.drawOval( circle.getX() - circle.getRadius(), circle.getY()-circle.getRadius(), circle.getRadius() * 2,circle.getRadius() * 2);


Sau đó các bạn chạy thử xem, mình giải thích thêm:
Tại sao tọa độ (x, y) lại là circle.getX() - circle.getRadius(), circle.getY() và circle.getRadius()???
Các bạn lưu ý là phương thức drawOval(...) nó sẽ vẽ hình Oval nằm trong hình chữ nhật.
Để hiểu rõ phương thức drawOval(...) vẽ như nào các bạn tạm thời comment hết đoạn code trong phương thức paintComponent(...) lại rồi thêm đoạn này vào:
g.drawOval(100, 100, 200, 100);
g.drawRect(100, 100, 200, 100);

Bây giờ các bạn thử chạy lên xem, kết quả như sau:

Vậy nên để vẽ hình tròn chính xác theo tâm, ta sẽ trừ 1 đoạn bằng chính bán kính của nó.
Hai tham số sau có giá trị circle.getRadius() * 2 bởi vì ta vẽ hình tròn nên phương thức drawOval sẽ truyền vào chiều dài và rộng bằng nhau - hình oval nội tiếp hình vuông sẽ cho ra hình tròn :D.

Nói sơ qua về cách vẽ hình tròn với java thế thôi. Bây giờ là phần chính của bài hôm nay.
Bây giờ làm sao để ta có thể xét được va chạm giữa một hình tròn và một hình chữ nhật đây ta?

Chúng ta bắt đầu suy nghĩ và tìm thuật toán nhé.
Ý tưởng: để kiểm tra va chạm giữa hình chữ nhật và hình tròn ta sẽ tìm một điểm, gọi là điểm A(xA, yA) là điểm gần tâm hình tròn nhất thuộc hình chữ nhật, sau khi đã tìm ra được điểm A này rồi thì công việc còn lại là chỉ cần tính khoảng cách từ tâm hình tròn tới điểm A và so sánh với bán kính hình tròn là ok.
Các bạn xem qua hình vẽ này (ảnh mình lấy từ google và có sửa lại để dễ hình dung hơn):
Nhìn sơ thì ta cũng chỉ có mấy trường hợp như vậy thôi.
Ta quy ước tọa độ các đỉnh hình chữ nhật giống như hình vẽ mình đã chú thích luôn nhé, tọa độ tâm sẽ là C(xC, yC).
Đầu tiên ta cho tọa độ điểm A bằng với tọa độ tâm hình tròn luôn (vì điểm gần tầm nhất thì là nó luôn rồi :D), hay tọa độ A lúc này là (xA, yA) = (xC, yC).
Tiếp theo ta sẽ xét các trường hợp sau:
Nếu xC < rectLeft thì gán xA = rectLeft (như hình vẽ), ngược lại nếu xC > rectRight thì gán xA = rectRight.
Nếu yC < rectTop thì gán yC = rectTop, ngược lại nếu yC > rectBottom thì gán yC = rectBottom.
Trường hợp nữa là rectLeft < xC < rectRight thì xA = xC, rectTop < yC < rectBottom thì yA = yC (nhưng do đầu tiên ta đã gán tọa độ của A bằng tọa độ của C rồi cho nên ta không cần code đoạn này.

Bây giờ chúng ta sẽ thực hiện thuật toán bằng code, tròn class CollisionDetection các bạn tạo thêm 1 phương thức xét va chạm giữa hình tròn và hình chữ nhật như sau:
public boolean checkColiision(Rectangle rect, Circle cir) {
int rectLeft = (int) rect.getX();
int rectRight = (int) (rect.getX() + rect.getWidth());
int rectTop = (int) rect.getY();
int rectBottom = (int) (rect.getY() + rect.getHeight());

int xC = cir.getX();
int yC = cir.getY();
int xA = xC;
int yA = yC;

if (xC < rectLeft) {
xA = rectLeft;
} else if (xC > rectRight) {
xA = rectRight;
}

if (yC < rectTop) {
yA = rectTop;
} else if (yC > rectBottom) {
yA = rectBottom;
}
int dx = xA - xC;
int dy = yA - yC;
return (dx * dx + dy * dy) <= cir.getRadius() * cir.getRadius();
}

Code trên thực hiện y chang thuật toán đã nói ở trên rồi :)
Nhưng cái câu lệnh return (dx * dx + dy * dy) <= cir.getRadius() * cir.getRadius(); là sao nhỉ?

Công thức tính độ dài đoạn thẳng khi biết tọa độ 2 điểm là:
sqrt(deltaX^2 + deltaY^2)
Ở đây để đỡ thêm 1 bước tính căn bậc hai thì ta chỉ cần bình phương 2 vế lên là xong.

Trong phương thức actionPerformed(ActionEvent e) các bạn thêm dòng này vào nữa để test:
if (checkColiision(myChar.getBound(), circle)) {
System.out.println("Hinh chu nhat va cham hinh tron");
}

Kết quả chạy được như hình dưới:


Bài hôm nay dừng tại đây, cám ơn các bạn đã theo dõi :).
SHARE

Xuho

  • Image
  • Image
  • Image
  • Image
  • Image

0 comments:

Post a Comment