Html5 – Canvas: Viết game đua xe – part1

Html5 - canvas - Racing game -part1(Game đua xe đơn giản với góc nhìn từ trên xuống)
Phần này sẽ giúp bạn biết cách tạo các đối tượng di chuyển với sự thay đổi hướng di chuyển, vận tốc, quán tính và ma sát trên địa hình.

Các thông số của xe

Trong game này, ta sẽ sử dụng bốn phím mũi tên để điều khiển xe. Hai phím UP, DOWN để di chuyển tiến lùi và LEFT, RIGHT sẽ dùng để quay hướng xe.
Một xe đua cơ bản cần các thuộc tính sau:
– max/min speed: tốc độ tối đa/tối thiểu của xe. Nên để tốc độ tối đa có giá trị tuyệt đối cao hơn tốc độ tối thiểu.
– acceleration: Khả năng tăng tốc của xe. Giá trị càng lớn thì xe càng nhanh đạt vận tốc tối đa.
– rotationAngle: Khả năng điều chỉnh góc quay của xe.
– friction: độ ma sát của xe trên từng loại địa hình. Giá trị này sẽ giúp xe giảm tốc độ khi người chơi không giữ phím di chuyển.

Car.prototype.handleInput = function(keyStates){
	if(keyStates[Keys.UP_ARROW])
	{
		this.speed += this.acceleration;
		if(this.speed > this.maxspeed)
			this.speed = this.maxspeed;
	}else if(keyStates[Keys.DOWN_ARROW])
	{
		this.speed -= this.acceleration;
		if(this.speed < this.minspeed)
			this.speed = this.minspeed;
	}

	if(keyStates[Keys.LEFT_ARROW])
		this.angle -= this.rotationAngle;
	if(keyStates[Keys.RIGHT_ARROW])
		this.angle += this.rotationAngle;
	// keep the angle as small	as possible
	this.angle = this.angle % _360_RADIAN;
	// decrease the speed base on the friction
	this.speed *= (1 - this.friction);
	if(Math.abs(this.speed)<0.1)
		this.speed = 0;
}

Di chuyển và quay xe

Với mục đích kiểm tra va chạm, ta sẽ sử dụng một mảng các điểm bao quanh xe hay xảy ra tiếp xúc nhất. Ở đây, các điểm mà tôi chọn là 4 đỉnh từ vùng bao hình chữ nhật của xe (có thể coi như 4 bánh xe). Khi xe xoay góc alpha và di chuyển, ta phải tính lại tọa độ các điểm này tương ứng. Công thức để tính tọa độ mới của một điểm sau khi xoay góc alpha là:

x’ = cos(alpha) * x − sin(alpha) * y
y’ = sin(alpha) * x + cos(alpha) * y

rotate car

Ví dụ tại góc 0 độ, với gốc tọa độ nằm ở tâm hình chữ nhật, điểm A có tọa độ là {x: -width/2,y: -height/2}. Vậy với góc xoay alpha, tọa độ mới của A là:

x: cos(alpha)*(-width/2) – sin(alpha)*(-height/2)
y: sin(alpha)*(-width/2) + cos(alpha)*(-height/2)

Car.prototype.update = function(){
	var cos = Math.cos(this.angle);
	var sin = Math.sin(this.angle);

	if(this.speed!=0)
	{
		// move
		this.cx += cos*this.speed;
		this.cy += sin*this.speed;
		if(this.cx<0)
			this.cx = 0;
		else if(this.cx>this.mapWidth)
			this.cx = this.mapWidth;

		if(this.cy<0)
			this.cy = 0;
		else if(this.cy>this.mapHeight)
			this.cy = this.mapHeight;
	}
	// update 4 vertices based on the rotation angle and their original position
	// top-left
	this.vertices[0] = {
		x: Math.floor(this.cx + cos*-this.h_width-sin*-this.h_height),
		y: Math.floor(this.cy + sin*-this.h_width+cos*-this.h_height)
	};
	// top-right
	this.vertices[1] = {
		x: Math.floor(this.cx + cos*this.h_width-sin*-this.h_height),
		y: Math.floor(this.cy + sin*this.h_width+cos*-this.h_height)
	};
	// bottom-right
	this.vertices[2] = {
		x: Math.floor(this.cx + cos*this.h_width-sin*this.h_height),
		y: Math.floor(this.cy + sin*this.h_width+cos*this.h_height)
	};
	// left-bottom
	this.vertices[3] = {
		x: Math.floor(this.cx + cos*-this.h_width-sin*this.h_height),
		y: Math.floor(this.cy + sin*-this.h_width+cos*this.h_height)
	};
}

Kiểm tra va chạm (tiếp xúc) với địa hình

Với bản đồ là một hình ảnh, ta cần kiểm tra bằng cách dựa vào pixel. Tuy nhiên không phải bằng cách lặp mà dựa vào kiểm tra một vài vị trí xác định. Các vị trí này ta chính là 4 điểm thuộc các góc của xe mà ta đã xác định trong phần trước.
– Đầu tiên ta cần lấy đối tượng ImageData của ảnh làm bản đồ.
– Với mỗi điểm dùng để kiểm tra va chạm của xe, ta tính vị trí alpha của chúng trong ImageData ((x+y*width)*4+3) và so sánh giá trị alpha với 0 (tương ứng với mặt đường). Thay vì kiểm tra giá trị alpha, ta có thể kiểm tra màu sắc tại pixel đó cho từng loại địa hình khác nhau.
– Tăng, giảm ma sát ứng với từng loại địa hình.

Trong ví dụ này, tôi chỉ tạo bản đồ với hai loại địa hình là đường và bãi cỏ. Độ ma sát sẽ tăng dần theo số lượng điểm (bánh xe) tiếp xúc với cỏ:

// ...
var friction = ROAD_FRICTION;

	for(var i=0;i<_car.vertices.length;i++)
	{
		var p = _car.vertices[i];

		var index = Math.floor((p.x+p.y*_imageData.width)*4+3);

		if(_imageData.data[index]!=0)
		{
			_context.beginPath();
			_context.arc(p.x, p.y, 4, 0 , 2 * Math.PI, false);
			_context.fill();
			// increase the friction by 0.05 for each vertex (or wheel) collide with grass
			friction += GRASS_FRICTION;
			//break;
		}
	}

	_car.friction = friction;
// ...

Mã nguồn

Download demo+src.

Html5 - canvas - Racing game -part1 - fullsize

car.js:

var ONE_RADIAN = Math.PI/180;
var _360_RADIAN = 360*ONE_RADIAN;

function Car(x,y,mapWidth, mapHeight){
	this.img = new Image();
	this.img.src = "car.png";
	this.mapWidth = mapWidth;
	this.mapHeight = mapHeight;

	this.maxspeed = 17;
	this.minspeed = -5;
	this.speed = 0;

	this.acceleration = 0.5;
	this.friction = 0;
	this.rotationAngle = 5*ONE_RADIAN;
	this.angle = -90 * ONE_RADIAN;
	this.height = 20;
	this.width = 30;
	this.h_height = this.height/2;
	this.h_width = this.width/2;
	// car's location
	this.cx = x;
	this.cy = y;

	// 4 vertices of rectangle (using to detect collision)
	this.vertices = [];

	this.vertices.push({x: 0, y: 0});
	this.vertices.push({x: 0, y: 0});
	this.vertices.push({x: 0, y: 0});
	this.vertices.push({x: 0, y: 0});

}
Car.prototype.draw = function(context){
	context.save();
	context.translate(this.cx,this.cy);
	context.rotate(this.angle);
	context.drawImage(this.img,-this.h_width,-this.h_height,this.width,this.height);
	context.restore();
}

Car.prototype.update = function(){
	var cos = Math.cos(this.angle);
	var sin = Math.sin(this.angle);

	if(this.speed!=0)
	{
		// move
		this.cx += cos*this.speed;
		this.cy += sin*this.speed;
		if(this.cx<0)
			this.cx = 0;
		else if(this.cx>this.mapWidth)
			this.cx = this.mapWidth;

		if(this.cy<0)
			this.cy = 0;
		else if(this.cy>this.mapHeight)
			this.cy = this.mapHeight;
	}
	// update 4 vertices based on the rotation angle and their original position
	// top-left
	this.vertices[0] = {
		x: Math.floor(this.cx + cos*-this.h_width-sin*-this.h_height),
		y: Math.floor(this.cy + sin*-this.h_width+cos*-this.h_height)
	};
	// top-right
	this.vertices[1] = {
		x: Math.floor(this.cx + cos*this.h_width-sin*-this.h_height),
		y: Math.floor(this.cy + sin*this.h_width+cos*-this.h_height)
	};
	// bottom-right
	this.vertices[2] = {
		x: Math.floor(this.cx + cos*this.h_width-sin*this.h_height),
		y: Math.floor(this.cy + sin*this.h_width+cos*this.h_height)
	};
	// left-bottom
	this.vertices[3] = {
		x: Math.floor(this.cx + cos*-this.h_width-sin*this.h_height),
		y: Math.floor(this.cy + sin*-this.h_width+cos*this.h_height)
	};
}
Car.prototype.handleInput = function(keyStates){
	if(keyStates[Keys.UP_ARROW])
	{
		this.speed += this.acceleration;
		if(this.speed > this.maxspeed)
			this.speed = this.maxspeed;
	}else if(keyStates[Keys.DOWN_ARROW])
	{
		this.speed -= this.acceleration;
		if(this.speed < this.minspeed)
			this.speed = this.minspeed;
	}

	if(keyStates[Keys.LEFT_ARROW])
		this.angle -= this.rotationAngle;
	if(keyStates[Keys.RIGHT_ARROW])
		this.angle += this.rotationAngle;

	// keep the angle as small	as possible
	this.angle = this.angle % _360_RADIAN;

	this.speed *= (1 - this.friction);
	if(Math.abs(this.speed)<0.1)
		this.speed = 0;
}

test.js:

<html>
<head>
<script src="car.js"></script>
<script type="text/javascript">
var Keys = {
      LEFT_ARROW: 37,
      UP_ARROW: 38,
      RIGHT_ARROW: 39,
      DOWN_ARROW: 40
    };

var ROAD_FRICTION = 0.05;
var GRASS_FRICTION = 0.05;
var FPS = 20;
var AVAILABLE_KEYS =
	[ 	Keys.UP_ARROW,
		Keys.DOWN_ARROW,
		Keys.LEFT_ARROW,
		Keys.RIGHT_ARROW
	];

var _canvas;
var _context;
var _car;

var _backgroundImg = new Image();

var _imageData;
var _keyStates = [];
function clear() {
	_context.clearRect(0, 0, _canvas.width, _canvas.height);
}

function init() {

	_canvas = document.getElementById("canvas");

	_context = _canvas.getContext("2d");
	_context.font = "16px arial";
	_canvas.onkeydown = canvas_keyDown;
	_canvas.onkeyup = canvas_keyUp;

	_backgroundImg.src = "map1.png";
	_backgroundImg.onload = function(){
		// get the imagedata of background image
		_context.drawImage(_backgroundImg,0,0,_canvas.width,_canvas.height);
		_imageData = _context.getImageData(0,0,_canvas.width,_canvas.height);

		window.setInterval(update,1000/FPS);
	};

	_car = new Car(_canvas.width-20,250,_canvas.width,_canvas.height);
	_car.friction = ROAD_FRICTION;

	//update();

}

function draw(){
	clear();
	_context.drawImage(_backgroundImg,0,0,_canvas.width,_canvas.height);

	_car.draw(_context);
	_context.fillStyle="red";

	// you should place this below code in the update() function
	var friction = ROAD_FRICTION;

	for(var i=0;i<_car.vertices.length;i++)
	{
		var p = _car.vertices[i];

		var index = Math.floor((p.x+p.y*_imageData.width)*4+3);

		if(_imageData.data[index]!=0)
		{
			_context.beginPath();
			_context.arc(p.x, p.y, 3, 0 , 2 * Math.PI, false);
			_context.fill();
			// increase the friction by 0.05 for each vertex (or wheel) collide with grass
			friction += GRASS_FRICTION;
			//break;
		}
	}

	_car.friction = friction;
}

function update(){
	_car.handleInput(_keyStates);
	_car.update();
	draw();
}

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;
}

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

YinYangIt’s Blog

Advertisements

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