Thứ hai, ngày 5 tháng 12 năm 2016

Html5-Canvas: Viết game Tower Defense – part 2

Ngày đăng: 12/7/2012, 13:23:56AM | Lượt xem: 3,692
Hot!

 Trong phần này tôi sẽ thêm các tower với các chức năng cơ bản như tìm enemy gần nhất và xoay góc súng theo. Tôi cũng sẽ hướng dẫn phương pháp để giới hạn vị trí đặt các tower trên bản đồ.

Xem phần 1.

 Demo

 

Tạo lớp Tower

Trước tiên ta định nghĩa các thuộc tính cơ bản cho Tower:

var ONE_RAD = Math.PI / 180;
function Tower(left,top){

	this.setPosition(left,top);

	this.shootingRange = 100;

	// has value true if this tower has been placed on the map
	this.isPlaced = false;

	this.angle = 0;

	this.rotationSpeed = ONE_RAD;
} 

Trong đó, setPosition() là phương thức dùng để đặt vị trí của tower trên bản đồ. Từ hai giá trị left và top, nó sẽ xác định tọa độ tâm của tower để dùng cho quá trình vẽ cũng như tính toán khoảng cách giữa tower và enemy.

Tower.prototype = {
	setPosition : function(x, y){
		this.left = x;
		this.top = y;
		this.cx = this.left + HALF_UNIT_SIZE;
		this.cy = this.top + HALF_UNIT_SIZE;
	},
	// ...
} 

 

Xác định Enemy trong tầm bắn

Vì các enemy có tốc độ như nhau và ra theo thứ tự nên ta sẽ xác định enemy gần đích nhất làm mục tiêu của tower. Với mỗi enemy trong vòng lặp, ta sẽ tính toán khoảng cách của nó với tower và so sánh với giá trị shootingRange. Nếu khoảng cách này nhỏ hơn hoặc bằng shootingRange, ta sẽ tính góc xoay nòng súng của tower (targetAngle).

 

// find the nearest enemy within range
var targetAngle;
for(var x in enemies)
{
	var e = enemies[x];
	var dx = e.cx - this.cx;
	var dy = e.cy - this.cy;
	var distance = Math.sqrt(dx * dx + dy * dy);

	if(distance <= this.shootingRange)
	{
		targetAngle = Math.atan2(dy, dx);
		break;
	}
}
// ... 

 

Xoay hướng Tower

Một vài game khi tính được giá trị targetAngle này sẽ thực hiện xoay hướng ngay lập tức nòng súng theo góc đó. Điều này không cho thấy được sự khác biệt về loại tower và chỉ thích hợp với các game có các loại “tower” linh hoạt (như tướng trong Tam Quốc Mini).

Với loại tower defense cổ điển sử dụng các cỗ máy nặng nề ta cần chú ý đến điều này. Ví dụ như với tower có sức công phá cao, ta nên nó một tốc độ xoay hướng chậm cùng với một tốc độ bắn tương ứng.

 

Để thực hiện điều này, ta cần tìm hiểu về kết quả mà hàm Math.atan2() tính được tương ứng với mỗi góc:

 

Sau một hồi mò mẫm công thức không xong, tôi đành sử dụng if else cho nhanh. Phần code sau là phần nối tiếp đoạn code trong phần trước:

// found an enemy
if(targetAngle)
{
	var rotation = 0;
	// targetAngle and current angle have the same sign
	if(targetAngle * this.angle >= 0)
		rotation = targetAngle > this.angle ? this.rotationSpeed: -this.rotationSpeed;
	else{

		var diff = Math.abs(targetAngle - this.angle);

		rotation = diff >= Math.PI ? -this.rotationSpeed : this.rotationSpeed;

		if(targetAngle < 0)
			rotation = -rotation;
	}
	// rotate the gun
	this.angle += rotation;
	// keep the angle small
	if(Math.abs(this.angle) > Math.PI)
		this.angle += this.angle > 0 ? -2*Math.PI : 2*Math.PI;
} 

 

OK, vậy là ta đã có 1 lớp Tower đơn giản có khả năng “dí súng” theo các enemy.

Đặt Tower trên bản đồ

Trong bản đồ, tại 1 thời điểm có thể có một tower được chọn. Tower này có thể đã được đặt trên bản dồ hoặc chọn từ danh sách các tower mà người chơi có thể xây dựng. Tôi đặt 1 biến là selectedTower trong lớp Map để lưu tower được chọn. Sau đó bổ sung một phương thức để kiểm tra điểm ảnh tại một vị trí xác định của ImageData có màu sắc hay không (dựa vào kênh Alpha):

Map.prototype.contain = function(imageData, x, y){

	if(!imageData)
		return false;
	var index = Math.floor((x+y*WIDTH)*4+3);
	return imageData.data[index]!=0;
}

 

Trong phần trước tôi đã tạo một buffer bản đồ với nền xanh và đường màu trắng. Bây giờ, ta sẽ chuyển phần đường sang trong suốt bằng cách dùng chế độ “xóa” của context. Khi người chơi muốn đặt tower, ta sẽ kiểm tra tâm của tower có nằm trong vùng bản đồ trong suốt hay không. Nếu có thì ta sẽ không cho phép đặt tại đó.

context.globalCompositeOperation = “destination-out”;

Bởi vì ta chỉ kiểm tra 1 điểm duy nhất là tâm nên phần thân của tower có thể nhô ra đường, cách giải quyết đơn giản là vẽ con đường lớn hơn kích thước thật của nó. Bạn đừng lo lắng vì nó sẽ ảnh hưởng đến đồ họa. Đây chỉ là dữ liệu dùng để kiểm tra va chạm theo pixel, bạn cần tạo một ảnh bản đồ thực sự để vẽ làm nền cho game. Lúc đó hãy vẽ một con đường có kích thước vừa phải.

 

Ngoài ra tôi tạo thêm một buffer khác dùng để chứa vị trí các tower đã được đặt, như vậy người chơi không thể đặt các tower đè lên nhau. Khi một tower được đặt lên bản đồ, tôi sẽ vẽ 1 vòng tròn với kích thước gấp đôi kích thước của tower thực sự. Bởi vì ta chỉ kiểm tra pixel bằng 1 điểm duy nhất là tâm của tower sắp được đặt nên việc vẽ một vòng tròn lớn như thế sẽ giúp các tower đặt xa nhau với khoảng cách từ hai lần bán kính trở lên (hoặc tổng hai bán kính của tower).

Map.prototype.onmousemove = function(x, y){
	// if selected tower wasn't placed on the map, change it's position
	if(this.selectedTower && !this.selectedTower.isPlaced){
		this.selectedTower.setPosition(x - HALF_UNIT_SIZE, y - HALF_UNIT_SIZE);
	}
}
Map.prototype.onmousedown = function(x, y){
	// check if tower can be placed here
	if(this.selectedTower
	&& 	!this.selectedTower.isPlaced
	&& 	this.contain(this.land_bufferData,this.selectedTower.cx, this.selectedTower.cy)
	&&	!this.contain(this.towers_bufferData,this.selectedTower.cx, this.selectedTower.cy)){

		// draw a circle place-holder for tower
		this.towers_context.fillStyle = "blue";
		this.towers_context.beginPath();
		this.towers_context.arc(x, y, UNIT_SIZE, 0 , 2 * Math.PI, false);
		this.towers_context.fill();
		this.towers_bufferData = this.towers_context.getImageData(0,0,WIDTH,HEIGHT);

		this.towers.push(this.selectedTower);
		this.selectedTower.isPlaced = true;
		this.selectedTower = new Tower(x - HALF_UNIT_SIZE, y - HALF_UNIT_SIZE);

	}
}

Đoạn mã trên bạn thấy tôi viết code trong hai sự kiện mousemove và mousedown của lớp Map. Hai sự kiện này được gọi thủ công từ đối tượng canvas:

canvas.onmousemove = function(e){
	var x = e.pageX - canvas.offsetLeft;
	var y = e.pageY - canvas.offsetTop;

	if(_map.onmousemove)
		_map.onmousemove(x, y);
};
canvas.onmousedown = function(e){
	var x = e.pageX - canvas.offsetLeft;
	var y = e.pageY - canvas.offsetTop;

	if(_map.onmousedown)
		_map.onmousedown(x, y);
}; 

Khi một tower được chọn, ta sẽ vẽ một vòng tròn xác định bởi phạm vi bắn (shootingRange) của nó:

Map.prototype.draw = function(context){

	context.drawImage(this.land_buffer,0,0);

	context.fillStyle = "brown";
	for(var x in this.enemies)
		this.enemies[x].draw(context);

	for(x in this.towers)
		this.towers[x].draw(context);

	// draw the shooting range of selected tower
	if(this.selectedTower){

		var x = this.selectedTower.cx;
		var y = this.selectedTower.cy;

		this.selectedTower.draw(context);
		if(this.contain(this.land_bufferData,x, y) &&
			!this.contain(this.towers_bufferData, x, y))
			context.fillStyle = "rgba(255,255,0,0.3)";
		else
			context.fillStyle = "rgba(255,0,0,0.3)";
		context.beginPath();
		context.arc(x, y, this.selectedTower.shootingRange, 0 , 2 * Math.PI, false);
		context.fill();
	}
}

 

 

 

 

 

YinYangIt’s Blog
 Chia sẻ qua: 
Hot!
Ý kiến bạn đọc

These items will be permanently deleted and cannot be recovered. Are you sure?

Gallery

image

Maecenas viverra rutrum pulvinar

Maecenas viverra rutrum pulvinar! Aenean vehicula nulla sit amet metus aliquam et malesuada risus aliquet. Vestibulum rhoncus, dolor sit amet venenatis porta, metus purus sagittis nisl, sodales volutpat elit lorem…

Read more

Text Links

Thiết kế logo chuyên nghiệp Insky
DAFABET
W88 w88b.com/dang-ky-tai-khoan-w88
W88
Copyright © 2011 - 2012 vietshare.vn by phamkhuong102@gmail.com doanhkisi2315@gmail.com. All rights reserved.