GameDev – Kiểm tra va chạm: hình tròn và chữ nhật

Basic Collision DemoThông thường các đối tượng trong game sẽ được xác định va chạm bằng cách đưa chúng về một dạng hình học cơ bản như hình chữ nhật, hình tròn. Bài viết này sẽ giúp bạn cách tính toán để kiểm tra va chạm giữa hai loại hình học này.

Xem Demo.

Giữa hai hình chữ nhật

Phương pháp: kiểm tra từng đỉnh của hình này có nằm bên trong hình kia không.

Rect.prototype.collideWidthRect = function(rect) {
	return this.contains(rect.left,rect.top) ||
		this.contains(rect.right,rect.top) ||
		this.contains(rect.right,rect.bottom) ||
		this.contains(rect.left,rect.bottom);
}

Cách trên không phải cách nhanh nhất, vì vậy bạn có thể dùng cách sau, đơn giản và hiệu quả hơn:

Rect.prototype.collideWidthRect = function(rect) {
	return !(this.left > rect.right ||
			this.right < rect.left ||
			this.top > rect.bottom ||
			this.bottom < rect.top);
}

Giữa hai hình tròn

Phương pháp: Bởi vì mọi điểm nằm trên đường tròn cách đều tâm, nên việc kiểm tra va chạm giữa hai hình tròn sẽ được xác định dựa vào khoảng cách tâm giữa chúng.
Để xác định khoảng cách giữa hai điểm, ta dựa vào định lý Pythagoras (Pythagorean theorem) . Ta coi khoảng cách giữa hai điểm là đường chéo của một tam giác vuông. Vậy độ lớn của đường chéo này là:

c² = a² + b²
=> c = sqrt(a² + b²)
Circle.prototype.collideWithCircle = function(circle){
	var dx = this.cx - circle.cx;
	var dy = this.cy - circle.cy;

	return Math.sqrt(dx*dx + dy*dy) <= this.radius+circle.radius;
}

Trong minh họa dưới đây, hai hình tròn có màu mặc định là xanh lá, khi va chạm nhau, chúng sẽ chuyển sang màu đỏ.

Two Circles - Collision delection

Giữa hình tròn và hình chữ nhật

Phương pháp: Gọi C là tâm và R là bán kính hình tròn. Ta sẽ tìm cách xác định điểm A là điểm gần nhất thuộc hình chữ nhật đến tâm C. Sau đó so sánh độ lớn của CA với R.
Khoảng cách giữa tâm C hình tròn và điểm A của hình chữ nhật được minh họa như hình dưới đây. Khi tâm hình tròn nằm bên trong hình chữ nhật, thì điểm C và A sẽ trùng nhau.

Closed point between rectangle and circleGọi rect là hình chữ nhật cần xác định va chạm. Ta có thuật toán để xác định điểm A như sau:

– B1: Gán A bằng với C.
– B2: Nếu C.X < rect.Left, đặt A.X = rect.Left. Ngược lại nếu C.X > rect.Right, đặt A.X = rect.Right.
– B3: Nếu C.Y < rect.Top, đặt A.Y = rect.Top. Ngược lại nếu C.Y > rect.Bottom, đặt A.Y = rect.Bottom.

Khi đã có điểm A, ta lại dùng công thức Pythagoras để so sánh với bán kính của hình tròn.

Circle.prototype.collideWithRect = function(rect){
	var px = this.cx;
	var py = this.cy;

	if(px < rect.left)
		px = rect.left;
	else if(px > rect.right)
		px = rect.right;

	if(py < rect.top)
		py = rect.top;
	else if(py > rect.bottom)
		py = rect.bottom;

	var dx = this.cx - px;
	var dy = this.cy - py;

	return (dx*dx + dy*dy) <= this.radius*this.radius;
}

YinYang’s Blog

14 bình luận về “GameDev – Kiểm tra va chạm: hình tròn và chữ nhật

  1. Đây là cách để tạo một phương thức instance cho một “class” trong javascript. Tức là khi bạn tạo một đối tượng Rect, bạn có gọi phương thức collideWidthRect của đối tượng này.

  2. Cho em hỏi va chạm giữa 2 đường tròn có thể tính khoảng cách giữa 2 tâm rồi sau đó so sánh với tổng bán kính 2 đường tròn nếu tổng bán kính nhỏ hơn hoặc bằng khoảng cách thì va chạm, cách này được ko anh?

  3. Cám ơn bạn đã hướng dẫn bài này bằng hình ảnh và code rất dễ hiểu.Nhưng cho mình xin hỏi.Mình có thắc mắc sau: như trong trường hợp va chạm thứ 3(Tròn vs HCN),ở 4 phía Left(rect.X – rect.W/2),Right(rect.X + rect.W/2),Top(rect.Y+rect.H/2),Bottom(rect.Y-rect.H/2)
    – Sau khi gán A = C ;
    – if(C.X <= Left ){ A.X = Left; }
    else(C.X <= Right ) { A.X = Right; }
    Vậy có cần xét thêm trường hợp :
    else if(Left <= C.X <= Right){?}
    và các hướng còn lại tương tự.

    Ý mình là nếu không xét thêm trường hợp tiếp điểm nằm khoảng giữa thì nó có tự hiểu giữ nguyên giá trị của A.X = C.X và hàm này có được thực thi đúng công thức hay không?Mình là người khá kỹ tính mà code chưa vững lắm nên mình mới thắc mắc.Rất mong bạn chỉ dẫn mình thêm.Chân thành cám ơn bạn.

  4. Mình bổ sung câu hỏi thêm :
    Vì nếu như trường hợp mình nêu trên là tiếp điểm nằm giữa,có thể nó sẽ ko chạy vào if()else() –> dx = this.cx – px // =0 ? (ban đầu đã gán px = cx) .

  5. Mình hiểu rồi..xin lỗi và cám ơn bài viết của bạn rất nhiều..mình quên là khúc cuối vẫn có trường hợp xét <= r*r 🙂 hihi..nếu có choáng chỗ cmt cho mọi người thì bạn có thể tháo cmt của mình xuống :)..chúc bạn nhiều sức khỏe 🙂

Đã đóng bình luận.