+4

Làm game 2D với Javascript thuần (phần 1)

Bài viết này được dịch dựa trên nguồn sau đây.

Screenshot from 2016-03-23 10:06:25.png

DEMO : https://jsfiddle.net/erajpypL/

PHẦN HTML

Trước hết, chúng ta sẽ xây dựng khung HTML để có thể render game này.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>DUONG-GAME STUDIO</title>
    <style>
    	* { padding: 0; margin: 0; }
    	canvas { background: #eee; display: block; margin: 0 auto; }
    </style>
</head>
<body>

<canvas id="myCanvas" width="480" height="320"></canvas>

<script>
	// Code JavaScript
</script>

</body>
</html>

Khá đơn giản phải ko? Nguyên nhân là game sẽ được render toàn bộ vào thẻ <canvas>.

CANVAS CƠ BẢN

Trước hết, bàn cần phải lấy được thẻ <canvas> trong body của html bằng Javascript. Hãy làm theo bước sau :

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");

Biến ctx được tạo để lưu lại nội dung render 2D.

Dưới đây là 1 ví dụ cơ bản về việc vẽ 1 hình vuông bằng canvas.

ctx.beginPath();
ctx.rect(20, 40, 50, 50);
ctx.fillStyle = "#FF0000";
ctx.fill();
ctx.closePath();

Tất cả mọi hướng dẫn sẽ nằm trong 2 methods beginPath()closePath(). Chúng ta đang định nghĩa một hình vuông sử dụng rect() : 2 giá trị đầu tiên cho thấy tọa độ của góc trên cùng bên trái của hình vuông đó, trong khi 2 giá trị tiếp theo thì định nghĩa chiều rộng và cao của hình vuông. Thuộc tính fillStyle định nghĩa màu sẽ đc sử dụng trong phương thức fill() tiếp theo để vẽ hình vuông này.

Tất nhiên ko chỉ giới hạn ở hình vuông hay chữ nhật. Chúng ta còn có thể vẽ cả hình tròn màu xanh với đoạn code dưới đây.

ctx.beginPath();
ctx.arc(240, 160, 20, 0, Math.PI*2, false);
ctx.fillStyle = "green";
ctx.fill();
ctx.closePath();

Bạn cũng có thê sử dụng stroke() thay vì fill(). Hàm này sẽ cho phép bạn tô màu mỗi phần viền của hình thay vì tô màu hết cả hình đó.

ctx.beginPath();
ctx.rect(160, 10, 100, 40);
ctx.strokeStyle = "rgba(0, 0, 255, 0.5)";
ctx.stroke();
ctx.closePath();

ĐỊNH NGHĨA MỘT DRAWING LOOP

Vậy là bạn đã biết cách vẽ 1 quả bóng và những hình vuông rồi. Nào tiếp tục là hãy làm cho chúng có thể di chuyển được. Về mặt kĩ thuật, chúng ta sẽ vẽ quả bóng trên màn hình, xóa nó và vẽ nó lại lần nữa trong một vị trí gần gần với vị trí vừa rồi cho mỗi frame khác nhau. Việc này sẽ khiến cho bạn cảm giác sự di chuyển của nó. Đại ý là nó giống kiểu làm film ấy.

Để có thể update canvas vẽ trên mỗi frame, chúng ta cần định nghĩa một function chạy đi chạy lại. Rất may mắn là Javascript cung cấp cho bạn loại hàm này setInterval()requestAnimationFrame().

Cho đoạn code sau vào phần thẻ <script>. Hàm draw() sẽ được thực thi mỗi 10 mili giây.

function draw() {
    // drawing code
}
setInterval(draw, 10);

Nào trước hết bạn hãy vẽ thử một quả bóng. Chú ý xóa hết mấy cái hình ở mục bên trên trước đi nhé.

ctx.beginPath();
ctx.arc(50, 50, 10, 0, Math.PI*2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();

MAKE IT MOVEEEE

Bạn sẽ thấy rằng ở trên bạn đang hard code vị trí của quả bóng là (50, 50) và cho dù frame đó đc vẽ đi vẽ lại liên tục mỗi 10 mili giây thì bạn cung sẽ ko thấy nó di chuyển. Hãy suy nghĩ cách định nghĩa vị trí khác. (x, y) này là tọa độ của tâm quẩ bóng nhé.

var x = canvas.width/2;
var y = canvas.height-30;

Tiếp, mình định nghĩa 2 giá trị dxdy thể hiện cho sự thay đổi giá trị xy khi quả bóng di chuyển. Việc này giống ngày xưa các bạn học về đạo hàm này nọ hổi cấp 3 ấy.

var dx = 2;
var dy = -2;

Cuối cũng, viết lại hàm draw() như sau. Chú ý giá trị xy sẽ được update mỗi lần frame thay đổi.

function draw() {
    ctx.beginPath();
    ctx.arc(x, y, 10, 0, Math.PI*2);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
    x += dx;
    y += dy;
}

Xóa canvas trước khi vẽ frame mới

Bạn sẽ thấy ở trên bạn có 1 vệt ảnh dài tạo thành đường thẳng, nguyên nhân mình vẫn chưa xóa các nội dung canvas được vẽ trước đó. Hãy dùng hàm clearRect(). Add hàm này vào hàm draw() như sau.

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.arc(x, y, 10, 0, Math.PI*2);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
    x += dx;
    y += dy;
}

CLEAN CODE

Chú ý đoạn code ở trên cần viết lại theo hướng đẹp hơn nhé. Nếu ko bạn sẽ bị các reviewer comment đấy 😄

function drawBall() {
    ctx.beginPath();
    ctx.arc(x, y, 10, 0, Math.PI*2);
    ctx.fillStyle = "#0095DD";
    ctx.fill();
    ctx.closePath();
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawBall();
    x += dx;
    y += dy;
}

DETECT SỰ VA CHẠM

Collision là 1 khái niệm cơ bản của việc làm game. Mình sẽ tính toán thử xem liệu quả bóng có chạm vào tường ko ? Và nếu chạm vào thì nó sẽ bật ra như thế nào nhé ?

Trước hết định nghĩa biến ballRadius làm bán kính quả bóng.

var ballRadius = 10;

Tiếp mình update lại hàm vẽ bóng

ctx.arc(x, y, ballRadius, 0, Math.PI*2);

Haiz. Bước tiếp theo phải dùng một chút kiến thức toán rồi.

Bóng di chuyển lên và xuống

Ở đây mình có 4 bức tường, đầu tiên focus vào cái trên cùng trước nhé. Trên bất kì frame nào mình cũng phải check xem bóng đã chạm tường ở trên chưa. Nếu YES thì cho bóng di chuyển ngược lại.

if(y + dy < 0) {
    dy = -dy;
}

Tiếp sẽ là cạnh dưới (tường dưới nhé). Cùng 1 tư tưởng thôi.

if(y + dy > canvas.height) {
    dy = -dy;
}

OK, vậy mình sẽ có đoạn code như sau

if(y + dy > canvas.height || y + dy < 0) {
    dy = -dy;
}

Bóng di chuyển trái và phải

Cũng tương tự với tư tưởng bóng trên xuống, nhưng lần này là với trục x. Ta sẽ có đoạn code sau .

if(x + dx > canvas.width || x + dx < 0) {
    dx = -dx;
}

Nếu bạn sử dụng đoạn code trên để chạy thì sẽ gặp hiện tượng là bóng bị ẩn 1 nửa sau tường. Cái này dễ hiểu là vif mình đang tính toán collision giữa tường và tâm bóng, trong khi đáng lẽ phải là viền quả bóng nhỉ ? Thử update code như sau đây và sẽ thấy nó chạy rất ngon

if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) {
    dx = -dx;
}
if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) {
    dy = -dy;
}

Các bạn tiếp tục theo dõi phần 2 nhé.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí