HTML5 – Canvas: Viết game tâng bóng phá gạch (Breaking Blocks)

Breaking Block GameDựa vào phương pháp kiểm tra va chạm giữa hình tròn và chữ nhật, tôi sẽ phát triển một game tâng bóng phá gạch (Brick Bong, Breaking Blocks,…). Mã nguồn của game này chủ yếu dựa trên các ví dụ trước mà tôi đã viết, nên bạn có thể đọc lại các bài viết trước để hiểu rõ hơn.
Xem Demo.

Thay đổi hướng di chuyển của bóng khi va chạm

Hãy xem lại phương thức kiểm tra va chạm của hình tròn với hình chữ nhật. Điểm còn thiếu trong bài trước là tôi chưa xác định hướng va chạm của hình tròn. Trong trường hợp hình tròn đang di chuyển, bạn cần xác định được hướng di chuyển của nó để thay đổi hướng bay.

Việc xác định này được dựa vào vị trí của điểm gần nhất nằm trên hình chữ nhật đến hình tròn. Nếu điểm này nằm trên cạnh trái hoặc cạnh phải, ta thay đổi hướng di chuyển ngang. Tương tự nếu điểm này nằm trên cạnh trên hoặc dưới, ta thay đổi hướng di chuyển dọc.

Ball.prototype.collide = function(rect) {
	if(!this.running)
		return;
	var p = { x: this.cx, y: this.cy};

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

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

	var dx = this.cx - p.x;
	var dy = this.cy - p.y;

	var isCollided = (dx*dx + dy*dy) <= this.radius*this.radius;

	if(isCollided)
	{
		if(p.x == rect.left || p.x == rect.right)
			this.speedX = -this.speedX;

		if(p.y == rect.top || p.y == rect.bottom)
			this.speedY = -this.speedY;
	}
	return isCollided;
}

Thanh tâng bóng (paddle)

Đối tượng này chỉ đơn giản là một hình chữ nhật được gắn thêm sự kiện mousemove (thông qua canvas) để di chuyển.

function Rect(left, top, width, height,color) {
	this.MAX_SPEED = 4;
	this.width = width;
	this.height = height;

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

	this.color = color;
}
Rect.prototype.moveTo = function(x) {

	this.left = x - this.width/2;

	this.right = this.left + this.width;
}

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

// ...
function canvas_mousemove(e) {
	var x = e.pageX - _canvas.offsetLeft;

	_paddle.moveTo(x);
}

Các khối gạch

Các khối gạch tương tự như paddle nhưng đơn giản hơn vì bạn không cần phải di chuyển chúng. Tôi sử dụng mảng hai chiều để lưu các khối gạch (bản chất là mảng trong mảng). Mỗi viên gạch sẽ chuyển thành giá trị null khi bị trái bóng chạm trúng.

var BRICK_WIDTH = 40;
var BRICK_HEIGHT = 20;
var BRICK_ROWS = 3;
var BRICK_COLS = 8;

// init bricks
_bricks = new Array(BRICK_ROWS);
 for (var i=0; i < BRICK_ROWS; i++) {
	_bricks[i] = new Array(BRICK_COLS);
	for (var j=0; j < BRICK_COLS; j++) {
	  _bricks[i][j] = new Rect(j*(BRICK_WIDTH+5)+10, i*(BRICK_HEIGHT+5)+10,BRICK_WIDTH,BRICK_HEIGHT,"blue");
	}
 }

Vẽ

for (var i=0; i < BRICK_ROWS; i++) {
	for (var j=0; j < BRICK_COLS; j++) {
		if(_bricks[i][j])
			_bricks[i][j].draw(_context);
	}
}

và kiểm tra va chạm:

for (var i=0; i < BRICK_ROWS; i++) {
	for (var j=0; j < BRICK_COLS; j++) {
	  if(_bricks[i][j] && _ball.collide(_bricks[i][j]))
			_bricks[i][j] = null;
	}
}

Kết quả

Minh họa:

Breaking Block  Game

Sourcecode:

<html>
<head>
  <script>
	/***************** ball ***************/
	function Ball(mapWidth, mapHeight) {
		this.mapWidth = mapWidth;
		this.mapHeight = mapHeight;

		this.radius = 10;
		this.speedX = 3;
		this.speedY = -3;

		this.cx = Math.floor(mapWidth/2);
		this.cy = mapHeight-100;

		this.color = "green";
		this.running = true;
	}
	Ball.prototype.update = function() {
		if(this.running)
		{
			this.cx += this.speedX;
			this.cy += this.speedY;

			if(this.cx-this.radius < 0 || this.cx+this.radius>this.mapWidth)
				this.speedX = -this.speedX;

			if(this.cy-this.radius < 0)
				this.speedY = -this.speedY;
			else if(this.cy+this.radius>this.mapHeight)
				this.running = false;
				//this.speedY = -this.speedY;
		}
	}
	Ball.prototype.draw = function(context) {
		context.beginPath();
		context.fillStyle = this.color;
		context.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2, true);
		context.closePath();
		context.fill();
	}
	Ball.prototype.collide = function(rect) {
		if(!this.running)
			return;
		var p = { x: this.cx, y: this.cy};

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

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

		var dx = this.cx - p.x;
		var dy = this.cy - p.y;

		var isCollided = (dx*dx + dy*dy) <= this.radius*this.radius;

		if(isCollided)
		{

			if(p.x == rect.left || p.x == rect.right)
				this.speedX = -this.speedX;

			if(p.y == rect.top || p.y == rect.bottom)
				this.speedY = -this.speedY;
		}
		return isCollided;
	}

	/*************** Rect *******************/

	function Rect(left, top, width, height,color) {
		this.MAX_SPEED = 4;
		this.width = width;
		this.height = height;

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

		this.color = color;
	}
	Rect.prototype.moveTo = function(x) {

		this.left = x - this.width/2;

		this.right = this.left + this.width;
	}

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

	/************** Main *************/
	var FPS = 60;
	var BRICK_WIDTH = 40;
	var BRICK_HEIGHT = 20;
	var BRICK_ROWS = 3;
	var BRICK_COLS = 8;

	var _canvas;
	var _context;
	var _ball;
	var _paddle;
	var _bricks = [];
	var _timer;

	function canvas_mousemove(e) {
		var x = e.pageX - _canvas.offsetLeft;

		_paddle.moveTo(x);
	}

	function clear() {
		_context.clearRect(0, 0, _canvas.width, _canvas.height);
	}

	function draw() {
		clear();

		for (var i=0; i < BRICK_ROWS; i++) {
			for (var j=0; j < BRICK_COLS; j++) {
				if(_bricks[i][j])
					_bricks[i][j].draw(_context);
			}
		}

		_ball.draw(_context);
		_paddle.draw(_context);
	}

	function update() {
		_ball.update();

		for (var i=0; i < BRICK_ROWS; i++) {
			for (var j=0; j < BRICK_COLS; j++) {
			  if(_bricks[i][j] && _ball.collide(_bricks[i][j]))
					_bricks[i][j] = null;
			}
		}
		_ball.collide(_paddle);
		draw();
	}
	function init(){
		var paddleWidth = 100;
		var paddleHeight = 20;
		_canvas = document.getElementById("canvas");
		_canvas.onmousemove = canvas_mousemove;
		_context = _canvas.getContext("2d");

		_ball = new Ball(_canvas.width, _canvas.height);
		_paddle = new Rect((_canvas.width-paddleWidth)/2,_canvas.height-paddleHeight-20,paddleWidth,paddleHeight,"brown");

		// init bricks
		_bricks = new Array(BRICK_ROWS);
		 for (var i=0; i < BRICK_ROWS; i++) {
			_bricks[i] = new Array(BRICK_COLS);
			for (var j=0; j < BRICK_COLS; j++) {
			  _bricks[i][j] = new Rect(j*(BRICK_WIDTH+5)+10, i*(BRICK_HEIGHT+5)+10,BRICK_WIDTH,BRICK_HEIGHT,"blue");
			}
		 }
		 // start
		_timer = window.setInterval(update,1000/FPS);
	}

	var _output;

	window.onload = function(){
		init();
	}
  </script>
</head>
<body>
<canvas id="canvas" width="400px" height="350px" style="border: 1px solid"></canvas>
</body>
</html>

3 thoughts on “HTML5 – Canvas: Viết game tâng bóng phá gạch (Breaking Blocks)

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