Thứ bảy, ngày 3 tháng 12 năm 2016

HTML5 – Canvas: Game xe tăng đơn giản – part 1

Ngày đăng: 12/4/2012, 8:22:51AM | Lượt xem: 2,365
Hot!

Từ ví dụ trước về Di chuyển đối tượng bằng bàn phím, tôi tiếp tục thêm phát triển thêm một số tính năng để làm một đối tượng xe tăng có thể di chuyển bằng bàn phím và xoay góc, bắn đạn bằng chuột.

calculate angle based on mouse locationTừ ví dụ trước về Di chuyển đối tượng bằng bàn phím, tôi tiếp tục thêm phát triển thêm một số tính năng để làm một đối tượng xe tăng có thể di chuyển bằng bàn phím và xoay góc, bắn đạn bằng chuột.

Xem Demo.

Xoay góc súng theo chuột

Trong bài viết trước, bạn đã biết cách để tính và xoay góc đối tượng trong canvas dựa vào hai giá trị speedY và speedX. Hai giá trị này chính là cạnh đối và cạnh kề của một tam giác vuông. Phát triển thêm một chút, tôi muốn góc của súng (cannon) sẽ tự động xoay theo góc di chuyển của chuột.

calculate angle based on mouse location

Cách thực hiện điều này được thể hiện trong phương thức sau, với targetX và targetY là vị trí của chuột trên canvas.

// tank.js:

Tank.prototype.rotateCannon = function(targetX, targetY){
	var dx = targetX - this.cx;
	var dy = targetY - this.cy;
	this.angle = Math.atan2(dy,dx);
}c

 // sample.html:

function canvas_mousemove(e){

	var x = e.pageX - _canvas.offsetLeft;
	var y = e.pageY - _canvas.offsetTop;
	_tank.rotateCannon(x,y);
}

Ball – Đạn

Mỗi viên đạn được bắn ra là một instance của lớp Ball. Phạm vi di chuyển của viên đạn sẽ được giới hạn trong “life-space”, ra ngoài phạm vi này, ta sẽ xóa nó ra khỏi danh sách lưu trữ dựa vào kết quả trả về của phương thức update().

Các tham số của constructor có nhiệm vụ:

  • mapWidth, mapHeight: xác định “life-space”. Phạm vi này sẽ có giá trị bằng độ lớn của canvas trừ đi bán kính của đạn.
  • startX, startY: vị trí xuất hiện của đạn.
  • directionX, directionY: xác định vector đơn vị chỉ hướng di chuyển của đạn. Tùy vào giá trị power của đạn mà tốc độ di chuyển của đạn sẽ nhanh hay chậm.

function Ball(mapWidth, mapHeight,startX,startY,directionX,directionY){
	this.power = 5;
	this.radius = 4;

	// the "life-space"
	this.minX = this.radius;
	this.minY = this.radius;
	this.maxX = mapWidth - this.radius;
	this.maxY = mapHeight - this.radius;

	this.speedX = directionX * this.power;
	this.speedY = directionY * this.power;

	this.cx = startX;
	this.cy = startY;
}

Ball.prototype.draw = function(context){

	context.fillStyle = "black";
	context.beginPath();
	context.arc(this.cx,this.cy,this.radius,0,Math.PI*2,true);
	context.closePath();
	context.fill();
}
Ball.prototype.update = function(){
	this.cx += this.speedX;
	this.cy += this.speedY;

	if(this.cx < this.minX || this.cx > this.maxX ||
		this.cy < this.minY || this.cy > this.maxY)
		return true;
	return false;
}

Tank: bắn đạn

Lớp này sẽ chứa một mảng lưu trữ các viên đạn được bắn ra với tên balls. Khi được lệnh “Bắn!”, phương thức fire() sẽ được gọi để tạo một instance mới của Ball và thêm vào mảng balls.

Tank.prototype.fire = function(){
	var directionX = Math.cos(this.angle);
	var directionY = Math.sin(this.angle);

	var startX = this.cx + this.cannonWidth * directionX;
	var startY = this.cy + this.cannonWidth * directionY;

	var ball = new Ball(this.mapWidth,this.mapHeight,startX,startY,directionX,directionY);
	this.balls.push(ball);
}

Với hướng bắn được lưu trong biến this.angle, và độ lớn của lực bắn được xác định là 1, ta tạo được một tam giác vuông có:

- Cạnh huyền = 1 đơn vị.

- Góc alpha = this.angle.

Như vậy ta sẽ tính được hai giá trị directionX và directionY bằng phép toán cos và sin đơn giản, hai giá trị này tạo thành một vector đơn vị do có độ lớn bằng 1. Từ vector đơn vị này, ta có thể dễ dàng xác định được vị trí xuất hiện của viên đạn dựa vào vị trí hiện tại của tank.

Bạn cũng có thể giới hạn số lượng đạn bắn ra một lúc của tank bằng cách kiểm tra giá trị của this.balls.length.

Vòng lặp game

Mỗi lớp tôi tạo ra đều chứa hai phương thức là update() và draw(). Hai phương thức này của mỗi instance có tác dụng cập nhật và vẽ lại chính nó trên canvas. Tôi chỉ cần gọi một hàm update() duy nhất trên trang html và nó sẽ tự động gọi update() xuống các đối tượng bên trong (tank – > balls):

// sample.hml:
window.setInterval(update,50);
// ...
function update() {
	_tank.update();
	draw();
}

// tank.js:

Tank.prototype.update = function(){

	for(var i=0;i<this.balls.length;i++)
	{
		var item = this.balls[i];
		item.update();
		if(item.update())
		{
			this.balls.splice(i,1);
		}
	}
}

// ball.js:
Ball.prototype.update = function(){
	this.cx += this.speedX;
	this.cy += this.speedY;

	if(this.cx < this.minX || this.cx > this.maxX ||
		this.cy < this.minY || this.cy > this.maxY)
		return true;
	return false;
}

Mã nguồn hoàn chỉnh

HMML5 - Canvas - Demo Tank Game
keys.js xem trong bài trước.

ball.js:

function Ball(mapWidth, mapHeight,startX,startY,directionX,directionY){
	this.power = 5;
	this.radius = 4;

	// the "life-space"
	this.minX = this.radius;
	this.minY = this.radius;
	this.maxX = mapWidth - this.radius;
	this.maxY = mapHeight - this.radius;

	this.speedX = directionX * this.power;
	this.speedY = directionY * this.power;

	this.cx = startX;
	this.cy = startY;
}

Ball.prototype.draw = function(context){

	context.fillStyle = "black";
	context.beginPath();
	context.arc(this.cx,this.cy,this.radius,0,Math.PI*2,true);
	context.closePath();
	context.fill();
}
Ball.prototype.update = function(){
	this.cx += this.speedX;
	this.cy += this.speedY;

	if(this.cx < this.minX || this.cx > this.maxX ||
		this.cy < this.minY || this.cy > this.maxY)
		return true;
	return false;
}

tank.js:

function Tank(mapWidth, mapHeight){
	this.mapWidth = mapWidth;
	this.mapHeight = mapHeight;
	this.radius = 20;
	this.speedX = 0;
	this.speedY = 0;
	this.power = 3;
	this.cx = mapWidth/2;
	this.cy = mapHeight/2;
	this.angle = 0;
	this.balls = [];
	this.cannonHeight = this.radius/2;
	this.cannonWidth = this.cannonHeight*3;
}
Tank.prototype.draw = function(context){
	context.beginPath();
	context.fillStyle = "green";
	context.arc(this.cx,this.cy,this.radius,0,Math.PI*2,true);
	context.closePath();
	context.fill();

	// cannon
	context.save();
	context.translate(this.cx,this.cy);
	context.rotate(this.angle);
	context.beginPath();
	context.fillStyle = "red";
	context.rect(0,-this.cannonHeight/2,this.cannonWidth,this.cannonHeight);
	context.closePath();
	context.fill();
	context.restore();

	// yellow circle
	context.beginPath();
	context.fillStyle = "yellow";
	context.arc(this.cx,this.cy,this.radius/2,0,Math.PI*2,true);
	context.closePath();
	context.fill();

	for(var i=0;i<this.balls.length;i++)
	{
		this.balls[i].draw(context);
	}

}

Tank.prototype.update = function(){

    for(var i=0;i<this.balls.length;i++)
    {
        var item = this.balls[i];
        item.update();
        if(item.update())
        {
            this.balls.splice(i,1);
        }
    }
}

Tank.prototype.fire = function(){
    if(this.balls.length > 4)
        return;
    var directionX = Math.cos(this.angle);
    var directionY = Math.sin(this.angle);

    var startX = this.cx + this.cannonWidth * directionX;
    var startY = this.cy + this.cannonWidth * directionY;

    var ball = new Ball(this.mapWidth,this.mapHeight,startX,startY,directionX,directionY);
    this.balls.push(ball);
}
Tank.prototype.rotateCannon = function(targetX, targetY){
	var dx = targetX - this.cx;
	var dy = targetY - this.cy;
	this.angle = Math.atan2(dy,dx);
}
Tank.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;
}
Tank.prototype.resetSpeed = function(){
	this.speedX = 0;
	this.speedY = 0;
}
Tank.prototype.moveUp = function(){
	this.speedY = -this.power;
}
Tank.prototype.moveDown = function(){
	this.speedY = this.power;
}
Tank.prototype.moveLeft = function(){
	this.speedX = -this.power;
}
Tank.prototype.moveRight = function(){
	this.speedX = this.power;
}

sample.html:

<html>
<head>
<script src="tank.js"></script>
<script src="ball.js"></script>
<script src="keys.js"></script>
<script type="text/javascript">
const AVAILABLE_KEYS =
	[ 	Keys.KEY_W,
		Keys.KEY_S,
		Keys.KEY_A,
		Keys.KEY_D
	];

var _canvas;
var _context;
var _tank;
var _keypressed = {};

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

function init() {
	_canvas = document.getElementById("canvas");
	_context = _canvas.getContext("2d");
	_tank = new Tank(_canvas.width,_canvas.height);
	draw();
}
function draw() {
	clear();
	_tank.draw(_context);
}
function update() {
	_tank.update();
	draw();
}
function canvas_mousemove(e){

	var x = e.pageX - _canvas.offsetLeft;
	var y = e.pageY - _canvas.offsetTop;
	_tank.rotateCannon(x,y);
}
function canvas_mousedown(e){
	_tank.fire();
}
function canvas_keyDown(e){
	if(AVAILABLE_KEYS.indexOf(e.keyCode)!=-1)
	{
		_keypressed[e.keyCode] = true;
		doKeypress();
	}
}
function canvas_keyUp(e){
	if(_keypressed[e.keyCode])
	{
		_keypressed[e.keyCode] = false;
		_tank.resetSpeed();
	}
}

function doKeypress(){
	if(_keypressed[Keys.KEY_W])
		_tank.moveUp();
	if(_keypressed[Keys.KEY_S])
		_tank.moveDown();
	if(_keypressed[Keys.KEY_A])
		_tank.moveLeft();
	if(_keypressed[Keys.KEY_D])
		_tank.moveRight();
	_tank.move();
	draw();
}

window.onload = function(){
	init();
_canvas.onkeydown = canvas_keyDown;
_canvas.onkeyup = canvas_keyUp;
_canvas.onmousemove = canvas_mousemove;
_canvas.onmousedown = canvas_mousedown;
window.setInterval(update,50);
}
</script>
</head>
<body>
<p>* Using W, A, S, D to move</p>
<canvas id="canvas" width="400" height="400"
	tabindex="1" style="border: 1px solid;cursor: crosshair" ></canvas>
</body>
</html>

Phần tiếp theo

Thêm các chướng ngại vật bay ngẫu nhiên để xe tăng có thể né tránh và phá hủy.

 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.