HTML5 – Canvas: Game xe tăng đơn giản – part 2 (end)

Html5 - Canvas - Final Tank game_thumbnailTiếp theo phần trước, trong phần này tôi sẽ thực hiện các nội dung chính:
– Thay đổi chức năng bắt phím để xe tăng di chuyển mượt hơn.
– Thêm các chướng ngại vật vào game để xe tăng bắn và phá hủy.
– Một số hiệu ứng hình ảnh, âm thanh.

 

Source code và Class Diagram

Download project.

Class diagram:

Tank game - Class Diagram

Cơ chế xử lý input

Trong phần trước, tôi sử dụng cơ chế kích hoạt hành động của các đối tượng trong game mỗi khi nhận được sự kiện tương ứng từ người dùng. Điều này khiến cho việc điều khiển gặp khó khăn và không liên tục. Giải pháp cho vấn đề này là tôi sẽ viết một phương thức xử lý riêng trong mỗi đối tượng cần thiết và truyền trạng thái của bàn phím, chuột,… vào phương thức xử lý này:

// sample.html:

function update(){
	_tank.handleInput(_keyStates);
	// …
}
function canvas_keyDown(e){
	if(AVAILABLE_KEYS.indexOf(e.keyCode)!=-1)
		_keyStates[e.keyCode] = true;
}
function canvas_keyUp(e){
	if(_keyStates[e.keyCode])
		_keyStates[e.keyCode] = false;
}
// tank.js:

Tank.prototype.handleInput = function(keyStates){
	this.speedX = 0;
	this.speedY = 0;

	if(keyStates[Keys.KEY_W])
		this.moveUp();
	if(keyStates[Keys.KEY_S])
		this.moveDown();
	if(keyStates[Keys.KEY_A])
		this.moveLeft();
	if(keyStates[Keys.KEY_D])
		this.moveRight();
	this.move();
}

Thêm các chướng ngại vật: thiên thạch

Cốt truyện của game là người chơi sẽ vào vai một chiếc xe tăng có nhiệm vụ bảo vệ mặt đất khỏi các thiên thạch rớt xuống bằng cách bắn để phá hủy chúng. Như vậy các chướng ngại vật ở đây là các thiên thạch xuất phát ở phía trên canvas và rớt xuống với hướng ngẫu nhiên.

// sample.html:
var _obstacles = [];

function update(){
	_tank.handleInput(_keyStates);

	if(_obstacles.length < 10)
		_obstacles.push(new Obstacle(_canvas.width, _canvas.height,_context));

	for(var i=0;i<_obstacles.length;i++)
    	{
		var ob = _obstacles[i];
		ob.update();
	}
	_tank.update(_obstacles);

	draw();
}

// obstacke.js:

function Obstacle(mapWidth, mapHeight,context){
	this.mapWidth = mapWidth;
	this.mapHeight = mapHeight;
	this.context = context;

	this.size =  Math.floor(Math.random()*60)+60;

	this.left = Math.floor(Math.random()*(mapWidth-this.size))+1;
	this.top = -Math.floor(Math.random()*mapHeight);

	this.speedY = Math.floor(Math.random()*4)+1;
	this.speedX = Math.floor(Math.random()*3)-3;
}

Để vẽ các thiên thạch, tôi dùng ảnh lấy từ một tập tin rock.png. Trong phương thức update(), tôi sẽ cho phép thiên thạch có thể phản xạ lại khi va chạm vào hai thành canvas để đảm bảo chúng không vượt ra ngoài phạm vi màn hình, và cũng để tăng thêm độ khó khăn cho người chơi.

var rockImg = new Image();
rockImg.src = "rock.png";
// ...
Obstacle.prototype.draw = function(){
	this.context.drawImage(rockImg,this.left,this.top,this.size,this.size);
}
Obstacle.prototype.update = function(){
	if(this.left < 0 || this.right > this.mapWidth)
		this.speedX = -this.speedX;

	this.top += this.speedY;
	this.left += this.speedX;
	this.right = this.left + this.size;
	this.bottom = this.top + this.size;
}

Kết quả:

HTML5 - Tank Game 1

Làm các thiên thạch khác biệt nhau

Bạn có thể sử dụng một vài hình ảnh khác nhau và gán ngẫu nhiên cho mỗi thiên thạch để chúng có được đặc điểm riêng về hình dạng lẫn kích thước. Ở đây tôi chọn một phương pháp đơn giản hơn là xoay mỗi thiên thạch một góc ngẫu nhiên.

Cách thực hiện là sử dụng phương thức context.translate() để di chuyển gốc tọa độ vẽ đến chính giữa thiên thạch, xoay với context.rotate() và vẽ. Nhớ lưu và phục hồi trạng thái của context khi bạn thực hiện các thao tác này:

function Obstacle(mapWidth, mapHeight,context){
	// ...
	this.halfSize = Math.floor(Math.random()*30)+30;
	this.size =  this.halfSize*2;

	this.angle = (Math.PI/180)*Math.floor(Math.random()*360);
	// ...

}
Obstacle.prototype.draw = function(){
	this.context.save();
	this.context.translate(this.left+this.halfSize,this.top+this.halfSize);
	this.context.rotate(this.angle);
	this.context.drawImage(rockImg,-this.halfSize,-this.halfSize,this.size,this.size);
	this.context.restore();
}

HTML5 - Tank Game 2

Kiểm tra va chạm

Trong ví dụ này, tôi chỉ làm chức năng kiểm tra va chạm của thiên thạch với các viên đạn. Vì kích thước của viên đạn tương đối nhỏ nên có thể coi nó là 1 điểm. Vậy trong phương thức collide() sau tôi chỉ cần truyền vào hai tham số x, y đại diện cho tọa độ tâm của viên đạn. Việc kiểm tra đơn giản là xác định một điểm có nằm trong một hình chữ nhật hay không.

Obstacle.prototype.collide = function(x,y){
	return this.left <= x && this.right >= x &&
		this.top <= y && this.bottom >= y;
}

Tiếp theo, tôi sẽ sử dụng nó trong lớp Tank:

Tank.prototype.update = function(obstacles){

    for(var i=0;i<this.balls.length;i++)
    {
        var ball = this.balls[i];
        if(ball.update())
        {
            this.balls.splice(i--,1);
        }else{
			for(var j=0;j<obstacles.length;j++)
			{
				var ob = obstacles[j];

				if(ob.collide(ball.cx, ball.cy))
				{
					// do something useful
					this.balls.splice(i--,1);
					break;
				}
			}
		}
    }
}

Hiệu ứng nổ

Để làm hiệu ứng này, tôi chỉ cần vẽ thiên thạch bằng một hình ảnh khác sau khi nó va chạm.

Obstacle.prototype.draw = function(){
	var img = this.isCollided? explodeImg: rockImg;

	this.context.save();
	this.context.translate(this.left+this.halfSize,this.top+this.halfSize);
	this.context.rotate(this.angle);
	this.context.drawImage(img,-this.halfSize,-this.halfSize,this.size,this.size);
	this.context.restore();
}

Việc hiển thị hình ảnh nổ của thiên thạch có thể không kịp nhìn thấy do diễn ra quá nhanh. Vì thế, tôi dùng một biến đếm để duy trì “thời gian sống” của thiên thạch sau khi va chạm:
Khi bị va chạm, cờ isCollided được bật (true), biến đếm explosionCounter được gán là 3.
Sau mỗi lần update, biến này sẽ được giảm đi 1 và khi bằng 0, cờ isExploded sẽ được bật để báo cho ứng dụng xóa nó khỏi danh sách các thiên thạch.

Obstacle.prototype.explode = function(){
	this.isCollided = true;
}

Obstacle.prototype.update = function(){
	if(this.isCollided)
		this.explosionCounter--;
	if(this.explosionCounter==0)
		this.isExploded = true;

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

	this.top += this.speedY;
	this.left += this.speedX;
	this.right = this.left + this.size;
	this.bottom = this.top + this.size;
}

Ngoài ra, tôi thêm một hiệu ứng âm thanh nổ khi phương thức explode() được gọi:

var snd = new Audio("explosion.wav");
Obstacle.prototype.explode = function(){
	this.isCollided = true;

	snd.currentTime=0;
	snd.play();
}

Hoàn chỉnh ví dụ

Thêm các chức năng phụ sau:

– Chỉ cho phép di chuyển theo chiều ngang: chỉ xử lý hai phím A và D.
– Vẽ nền: sử dụng một bức ảnh để vẽ đầy màn hình canvas.
– Điểm số: tạo một biến scores trong lớp Tank và tăn lên 1 mỗi lần phát hiện va chạm đạn với thiên thạch.

// sample.html:
function draw(){
	clear();
	_context.drawImage(_backgroundImg,0,0,_canvas.width,_canvas.height);
	for(var i=0;i<_obstacles.length;i++)
	{
		_obstacles[i].draw();
	}
	_tank.draw();
	_context.fillText("Scores: "+_tank.scores,10,20);
}

Html5 - Canvas - Final Tank game

Tiếp theo là gì?

Để thành một game đúng nghĩa thì ví dụ này còn thiếu nhiều tính năng cần hiện thực. Chẳng hạn như kiểm tra va chạm thiên thạch với xe tăng, HP, level,… và có thể thêm các kẻ địch phức tạp hơn. Các chức năng này không khó thực hiện tuy nhiên tôi sẽ dừng bài viết tại đây phần 2 này để dành lại trình bày trong các bài viết khác, thứ tự và logic hơn.

YinYang’s Programming Blog

Advertisements

11 thoughts on “HTML5 – Canvas: Game xe tăng đơn giản – part 2 (end)

Trả lờ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 Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s