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

HTML5 - Canvas 2D - Simple Bouncing ballMột ví dụ đơn giản để khi làm quen với đồ họa và chuyển động trong lập trình là viết một ví dụ bóng nảy bên trong một vùng cửa sổ (canvas). Một quả bóng sẽ được vẽ bên trong canvas và chuyển động theo một hướng xác định. Khi chạm bất kì thành tường nào, bóng sẽ đổi hướng chuyển động tùy theo hướng di chuyển.

Xem Demo.

Cơ bản

Ta xác định hai giá trị speedX và speedY tương ứng với tốc độ di chuyển theo phương ngang và dọc của trái bóng. Hướng di chuyển của bóng phụ thuộc vào giá trị âm hoặc dương của speedX và speedY.

Trong ví dụ này, thành tường là các biên của canvas và chỉ có hai phương là ngang và dọc. Nguyên lý hoạt động rất đơn giản: khi bóng tiếp xúc với biên dọc của canvas, ta sẽ đổi chiều của speedY và với biên ngang ta sẽ đổi chiều của speedX.

Trong lớp Ball sau, tôi sẽ bổ sung thêm các thuộc tính right, bottom để tiện khi kiểm tra va chạm. Hai thuộc tính cx và cy là vị trí tâm của bóng và sẽ được khởi tạo ngẫy nhiên sao cho nó luôn nằm hoàn toàn bên trong canvas.

Ball.js:

function Ball(mapWidth, mapHeight){
	this.mapWidth = mapWidth;
	this.mapHeight = mapHeight;

	this.radius = 20;
	this.speedX = 3;
	this.speedY = 3;

	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;

}
Ball.prototype.draw = function(context){
	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;
}
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;

}

Sample.html:

<html>
<head>

<script src="ball.js"></script>
<script>

var _canvas;
var _context;
var _ball;

function draw(){
	_context.clearRect(0,0,_canvas.width,_canvas.height);
	_ball.draw(_context);
}
function update(){
	_ball.move();
	_ball.checkCollision();
	draw();
}

window.onload = function(){

	var interval = 10;
	_canvas = document.getElementById("canvas");
	_context = _canvas.getContext("2d");
	_ball = new Ball(_canvas.width,_canvas.height);

	setInterval("update()",interval);
}
</script>
</head>
<body>
   <canvas id="canvas" width="400px" height="300px" style="border: 1px solid gray;"></canvas>
</body>
</html>

Thêm hiệu ứng bóng di chuyển

Để ví dụ không quá đơn điệu, tôi sẽ thêm các ảnh bóng mờ di chuyển phía sau trái bóng chính. Tôi sẽ lưu các vị trí bóng di chuyển qua vào một mảng có tối đa 10 phần tử và vẽ ra canvas trong hàm draw(). Phương thức Ball.draw() cần chỉnh sửa một chút để cho phép nhận tham số alpha xác định độ mờ đục. Tham số alpha này có giá trị nằm giữa đoạn 0 và 1:

Ball.prototype.draw = function(context,alpha){
	if(!alpha)
		alpha = 255;
	context.beginPath();
	context.fillStyle = "rgba(255, 100, 100," + alpha + ")";
	context.arc(this.cx,this.cy,this.radius,0,Math.PI*2,true);
	context.closePath();
	context.fill();

}

Để các trái bóng không nằm quá sát nhau, tôi chỉ thực hiện lưu vị trí của bóng sau mỗi 5 lần bóng di chuyển hay sau mỗi 5 lần phương thức update() được gọi. Phương thức traceBall() sau sẽ tạo ra một đối tượng Ball mới với hai giá trị cx và cy lấy từ trái bóng chính và lưu vào mảng _balls. Khi chiều dài của mảng _balls vượt quá 10, tôi dùng phương thức Array.splice() để cắt đi phần tử nằm ở đầu mảng:

function traceBall(ball){
	var b = new Ball;
	b.cx = ball.cx;
	b.cy = ball.cy;

	_balls.push(b);
	if(_balls.length>10)
		_balls.splice(0,1);
}

Kết quả:

HTML5 - Canvas 2D - Simple Bouncing ball

Mã nguồn hoàn chỉnh

Ball.js:

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

	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;
}
Ball.prototype.draw = function(context,alpha){
	if(!alpha)
		alpha = 255;
	context.beginPath();
	context.fillStyle = "rgba(255, 100, 100," + alpha + ")";
	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;
}
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;
}

Sample.html:

<html>
<head>

<script src="ball.js"></script>
<script>

var _canvas;
var _context;
var _ball;
var _balls = [];
var _counter = 0;
function draw(){

	_context.clearRect(0,0,_canvas.width,_canvas.height);

	for(var i = 0; i < _balls.length; i++){
		_balls[i].draw(_context,(i+1)/10);
	}
	_ball.draw(_context);
}
function update(){
	_counter++;
	if(_counter == 5){
		traceBall(_ball);
		_counter = 0;
	}
	draw();
	_ball.move();
	_ball.checkCollision();

}
function traceBall(ball){
	var b = new Ball;
	b.cx = ball.cx;
	b.cy = ball.cy;

	_balls.push(b);
	if(_balls.length>10)
		_balls.splice(0,1);
}
window.onload = function(){

	var interval = 10;
	_canvas = document.getElementById("canvas");
	_context = _canvas.getContext("2d");
	_ball = new Ball(_canvas.width,_canvas.height);

	window.setInterval(function(){
		update();
	},interval);

}
</script>
</head>
<body>
   <canvas id="canvas" width="400px" height="300px" style="border: 1px solid gray;"></canvas>
</body>
</html>

YinYang’s Programming Blog

5 bình luận về “HTML5 – Canvas 2D: Hiệu ứng bóng chuyển động và phản xạ – part 1

  1. Mình có 1 số thắc mắc sau: _balls.length trong hàm draw() là chiều dài bóng, hiểu thế nào cho đúng nhỉ? chính là đường kính bóng = bán kính x 2 hả bạn ? bạn có thể giải thích các câu lệnh trong hàm traceBall() được không ? mình chưa hiểu lắm, ở file ball.js có 1 đống câu lệnh + – * / bạn có thể giải thích rõ hơn thuật toán không ? hix hỏi hơi nhiều, mong bạn giúp đỡ.

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

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