HTML5 – Canvas 2D: Hiệu ứng bóng chuyển động và phản xạ – part 2

HTML5 - Canvas - Bouncing ball 2Tiếp theo phần trước, tôi sẽ thêm các chướng ngại vật hình chữ nhật vào canvas để tạo đường di chuyển của trái bóng. Các chướng ngại vật được tôi lấy từ bài: HTML5 – Chọn và di chuyển đối tượng trên Canvas.

Xem Demo.

Phần quan trọng của ví dụ này nằm ở phương thức kiểm tra va chạm của trái bóng. Phương thức này sẽ duyệt qua tất cả các chướng ngại vật và kiểm tra khoảng cách của nó so với chướng ngại vật theo hai phương dọc và ngang.

Với ví dụ này, tôi kiểm tra va chạm ở mức rất đơn giản nên việc phản xạ của trái bóng chưa hoàn toàn chính xác. Tôi sẽ giới thiệu các kĩ thuật kiểm tra va chạm và di chuyển trong các bài viết sau.

Phương thức kiểm tra va chạm của lớp Ball:

Ball.prototype.checkCollision = function(shapes) {

	if(this.left <= 0 || this.right >= this.mapWidth) this.speedX = -this.speedX;
	if(this.top <= 0 || this.bottom >= this.mapHeight) this.speedY = -this.speedY;

	for(var i = 0; i < shapes.items.length ; i++){

		var item = shapes.items[i];
		var hCollision = false;
		var vCollision = false;

		if(	this.right >= item.left && this.left <= item.right &&  	// the ball is
			((this.cy < item.top && this.bottom >= item.top) ||		// on or
			(this.cy > item.bottom && this.top <= item.bottom)))	// under the rectangle
		{
			this.speedY = -this.speedY;
			vCollision = true;
		}

		if(this.bottom >= item.top && this.top <= item.bottom &&  	// the ball is in the
			((this.cx < item.left && this.right >= item.left) ||	// left or
			(this.cx > item.right && this.left <= item.right)))		// right side of the rectangle
		{
			this.speedX = -this.speedX;
			hCollision = true;
		}

		if(hCollision || vCollision)
			break;
	}

}

Minh họa:

HTML5 - Canvas - Bouncing ball 2

Mã nguồn hoàn chỉnh:

ball.js:

function Ball(mapWidth, mapHeight){
	this.mapWidth = mapWidth;
	this.mapHeight = mapHeight;
	this.radius = 20;
	this.speedX = 2;
	this.speedY = 2;

	this.cx = Math.floor(Math.random()*(this.mapWidth-2*this.radius)) + this.radius;
	this.cy = Math.floor(Math.random()*(this.mapHeight-2*this.radius)) + this.radius;
	this.positions = [];
	this.counter = 0;
}
Ball.prototype.draw = function(context){

	for(var i=0; i<this.positions.length; i++)
	{
		var pos = this.positions[i];
		context.beginPath();
		context.fillStyle = "rgba(255, 100, 100," + (i+1)/10 + ")";
		context.arc(pos.x,pos.y,this.radius,0,Math.PI*2,true);
		context.closePath();
		context.fill();
	}

	context.beginPath();
	context.fillStyle = "red";
	context.arc(this.cx,this.cy,this.radius,0,Math.PI*2,true);
	context.closePath();
	context.fill();
}
Ball.prototype.move = function(){
	this.cx += this.speedX;
	this.cy += this.speedY;

	this.left = this.cx - this.radius;
	this.top = this.cy - this.radius;
	this.right = this.cx + this.radius;
	this.bottom = this.cy + this.radius;

	this.counter++;
	if(this.counter == 10)
	{
		this.trace();
		this.counter = 0;
	}
}
Ball.prototype.trace = function(){
	var b = {
		x : this.cx,
		y : this.cy
	};

	this.positions.push(b);
	if(this.positions.length>10)
		this.positions.splice(0,1);
}
Ball.prototype.checkCollision = function(shapes) {

	if(this.left <= 0 || this.right >= this.mapWidth) this.speedX = -this.speedX;
	if(this.top <= 0 || this.bottom >= this.mapHeight) this.speedY = -this.speedY;

	for(var i = 0; i < shapes.items.length ; i++){

		var item = shapes.items[i];
		var hCollision = false;
		var vCollision = false;

		if(	this.right >= item.left && this.left <= item.right &&  	// the ball is
			((this.cy < item.top && this.bottom >= item.top) ||		// on or
			(this.cy > item.bottom && this.top <= item.bottom)))	// under the rectangle
		{
			this.speedY = -this.speedY;
			vCollision = true;
		}

		if(this.bottom >= item.top && this.top <= item.bottom &&  	// the ball is in the
			((this.cx < item.left && this.right >= item.left) ||	// left or
			(this.cx > item.right && this.left <= item.right)))		// right side of the rectangle
		{
			this.speedX = -this.speedX;
			hCollision = true;
		}

		if(hCollision || vCollision)
			break;
	}

}

rect.js:

function Rect(left,top,width,height) {
	this.width = width ? width : 100;
	this.height = height ? height : 100;

	this.left = left ? left : 100;
	this.top = top ? top : 100;
	this.cx = this.left + this.width/2;
	this.cy = this.top + this.height/2;
	this.right = this.left + this.width;
	this.bottom = this.top + this.height;
	this.isSelected = false;

}
Rect.prototype.contains = function(x,y){
	var right = this.left + this.width;
	var bottom = this.top + this.height;
	return x > this.left && x < right &&
		 y > this.top && y < bottom;
}
Rect.prototype.setPosition = function(x,y){
	this.left = x;
	this.top = y;

	this.right = this.left + this.width;
	this.bottom = this.top + this.height;

}
Rect.prototype.draw = function(context){
	context.fillStyle = "blue";
	context.fillRect(this.left,this.top,this.width,this.height);

}

shape_list.js:

function ShapeList(){
	this.items = [];
	this.selectedItem = null;
}

ShapeList.prototype.addItem = function(x,y){
	var rect = new Rect(x,y);

	this.items.push(rect);

}

ShapeList.prototype.selectAt = function(x,y){
	if(this.selectedItem)
		this.selectedItem.isSelected = false;
	this.selectedItem = null;
	for (var i = 0; i < this.items.length; i++) {
		var rect = this.items[i];
		if(rect.contains(x,y))
		{
			this.selectedItem = this.items[i];
			this.items[i].isSelected = true;
			break;
		}
    }
}

sample.html:

<html>
<head>

<script src="Ball.js"></script>
<script src="rect.js"></script>
<script src="shape_list.js"></script>
<script>

var _canvas;
var _context;
var _shapes = new ShapeList;
var _ismoving = false;
var _ball;
var _offsetX;
var _offsetY;
var _timer;

function canvas_mousedown(e){

	var x = e.pageX - _canvas.offsetLeft;
	var y = e.pageY - _canvas.offsetTop;

	_shapes.selectAt(x,y)

	if(!_shapes.selectedItem)
		_shapes.addItem(x,y);
	else
	{
		_offsetX = x- _shapes.selectedItem.left;
		_offsetY = y - _shapes.selectedItem.top;

		_ismoving = true;
	}

	draw();
}
function canvas_mousemove(e){
	if(_ismoving && _shapes.selectedItem){
		var x = e.pageX - _canvas.offsetLeft - _offsetX;
		var y = e.pageY - _canvas.offsetTop - _offsetY;

		_shapes.selectedItem.setPosition(x,y);
		draw();
	}
}
function canvas_mouseup(e){
	_ismoving = false;
}

function clear(deleteData){

	_context.clearRect(0,0,_canvas.width,_canvas.height);
	if(deleteData)
		_shapes.items = [];
}

function draw(){
	clear();
    for (var i = _shapes.items.length-1;i>=0; i--) {
		_shapes.items[i].draw(_context);
    }

	_ball.draw(_context);
}
function update(){
	_ball.move();
	_ball.checkCollision(_shapes);
	draw();
}
function start_stop(){

	var button = document.getElementById("button2");

	if(_timer)
	{
		clearInterval(_timer);
		_timer = null;
		button.innerHTML = "Start";
	}
	else
	{
		_timer = setInterval("update()",10);
		button.innerHTML = "Stop";
	}
}
window.onload = function(){

	_canvas = document.getElementById("canvas");
	_context = _canvas.getContext("2d");

	_ball = new Ball(_canvas.width,_canvas.height);
	_canvas.onmousedown = canvas_mousedown;
	_canvas.onmousemove = canvas_mousemove;
	_canvas.onmouseup = canvas_mouseup;

	start_stop();

	document.getElementById("button1").onclick = function(){
		clear(true);
	};
	document.getElementById("button2").onclick = function(){
		start_stop();
	};
};
</script>
</head>
<body>
	<div>
	<button id="button1">Clear</button>
	<button id="button2">Stop</button>
	</div>
   <canvas id="canvas" width="400px" height="400px" style="border: 1px solid gray;"></canvas>
</body>
</html>

YinYang’s Programming Blog

2 thoughts on “HTML5 – Canvas 2D: Hiệu ứng bóng chuyển động và phản xạ – part 2

  1. Bài này em làm test thử, nhưng khi đặt 1 hộp ở bên phải vi trí di chuyển của Ball thì dù không va chạm hộp nhưng tại vị trí y = tọa độ lề phải của hộp, Ball vẫn tính là va chạm và di chuyển ngược lại. Em không hiểu tại sao? Mong anh xem lại và hướng dẫn giùm

    Trả lời

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s