Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

Hướng dẫn JavaScript: Bắt đầu với hoạt ảnh Canvas

Trong bài viết này, bạn sẽ học cách vẽ và tạo hoạt ảnh canvas cho các đối tượng bằng HTML5 Canvas và JavaScript trước khi chúng tôi tối ưu hóa hiệu suất.

Tôi thích chơi trò chơi. Và tôi cũng thích viết code. Vì vậy, một ngày, tôi nghĩ, tại sao không sử dụng những kỹ năng viết code đó để tạo ra một trò chơi? Nhưng nghe có vẻ khó. Làm thế nào một người thậm chí sẽ bắt đầu?

Với những bước đi đầu tiên.

“Hoạt hình không phải là nghệ thuật của những bức vẽ chuyển động mà là nghệ thuật của những chuyển động được vẽ ra.” – Norman McLaren

Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

Lịch sử của Canvas

Apple đã giới thiệu Canvas vào năm 2004 để cung cấp năng lượng cho các ứng dụng và trình duyệt Safari. Một vài năm sau, nó đã được tiêu chuẩn hóa bởi WHATWG. Nó đi kèm với khả năng kiểm soát chi tiết hơn đối với việc hiển thị nhưng với chi phí phải quản lý từng chi tiết theo cách thủ công. Nói cách khác, nó có thể xử lý nhiều đối tượng, nhưng chúng ta cần phải viết mã mọi thứ một cách chi tiết.

Canvas có bối cảnh vẽ 2D được sử dụng để vẽ hình dạng, văn bản, hình ảnh và các đối tượng khác. Đầu tiên, tôi chọn màu và cọ vẽ, sau đó bắt đầu vẽ. Bạn có thể thay đổi bút vẽ và màu sắc trước mỗi bản vẽ mới, hoặc bạn có thể tiếp tục với những gì bạn có.

Canvas sử dụng kết xuất ngay lập tức : Khi tôi vẽ, nó ngay lập tức hiển thị trên màn hình. Nhưng, nó là một hệ thống chữa cháy và quên. Sau khi chúng ta vẽ thứ gì đó, canvas sẽ quên đi đối tượng và chỉ biết nó dưới dạng pixel. Vì vậy, không có vật thể nào mà có thể di chuyển được. Thay vào đó, bạn phải vẽ lại.

Tạo hoạt ảnh trên Canvas giống như làm một bộ phim stop-motion. Trong mỗi khung hình cần phải di chuyển các đối tượng một chút để làm sinh động chúng.

Phần tử canvas là gì?

Phần tử HTML <canvas> cung cấp một vùng chứa trống để chúng ta có thể vẽ đồ họa. Chúng ta có thể vẽ các hình dạng và đường thẳng trên đó thông qua API Canvas, cho phép vẽ đồ họa thông qua JavaScript.

Canvas là một vùng hình chữ nhật trên trang HTML theo mặc định không có đường viền hoặc nội dung. Kích thước mặc định của canvas là 300 pixel × 150 pixel (rộng × cao). Tuy nhiên, kích thước tùy chỉnh có thể được xác định bằng cách sử dụng HTML height và thuộc tính width:

<canvas id="canvas" width="600" height="300"></canvas>
Code language: HTML, XML (xml)

Chỉ định thuộc tính id để có thể tham chiếu đến nó từ một tập lệnh. Để thêm đường viền, hãy sử dụng thuộc tính style hoặc sử dụng CSS với thuộc tính class:

<canvas id="canvas" width="600" height="300" style="border: 2px solid"></canvas>
<button onclick="animate()">Play</button>Code language: HTML, XML (xml)

Bây giờ bạn đã thêm đường viền, bạn thấy kích thước của canvas trống trên màn hình. Tôi cũng có một nút với một sự kiện onclick để chạy  chức năng animate() của tôi khi tôi nhấp vào nó.

Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

Tôi có thể đặt mã JavaScript của mình trong các phần tử <script> mà tôi đặt vào tài liệu <body> sau phần tử <canvas>:

<script type="text/javascript" src="canvas.js"></script>
Code language: HTML, XML (xml)

Chúng tôi nhận được tham chiếu đến phần tử <canvas> HTML trong DOM (Mô hình đối tượng tài liệu) với phương thức getElementById():

const canvas = document.getElementById('canvas');
Code language: JavaScript (javascript)

Bây giờ bạn có sẵn phần tử canvas nhưng không thể vẽ trực tiếp lên nó. Thay vào đó, canvas có các ngữ cảnh kết xuất mà bạn có thể sử dụng.

Bối cảnh canvas là gì?

Canvas có bối cảnh vẽ 2D được sử dụng để vẽ hình dạng, văn bản, hình ảnh và các đối tượng khác. Đầu tiên, tôi chọn màu và cọ vẽ, sau đó tôi vẽ. Mình có thể thay đổi bút vẽ và màu sắc trước mỗi bản vẽ mới, hoặc có thể tiếp tục với những gì mình đã có.

Phương thức HTMLCanvasElement.getContext() này trả về một bối cảnh bản vẽ, nơi tôi kết xuất đồ họa. Bằng cách cung cấp '2d'làm đối số, bạn có được bối cảnh kết xuất canvas 2D:

const ctx = canvas.getContext('2d');
Code language: JavaScript (javascript)

Có những ngữ cảnh có sẵn khác, như webgl đối với bối cảnh kết xuất ba chiều, nằm ngoài phạm vi của bài viết này.

Có nhiều phương pháp CanvasRenderingContext2D để vẽ các đường thẳng và hình dạng trên canvas. Để đặt màu của đường tôi sử dụng strokeStyle và để đặt độ dày, tôi sử dụng lineWidth:

ctx.strokeStyle = 'black';
ctx.lineWidth = 5;Code language: JavaScript (javascript)

Bây giờ, tôi đã sẵn sàng để vẽ dòng đầu tiên của mình trên canvas. Tuy nhiên, trước khi làm điều đó, bạn cần hiểu cách bạn chỉ cho canvas ở đâu để vẽ. Khung HTML là một lưới hai chiều. Góc trên bên trái của canvas có tọa độ (0, 0).

   XY [(0,0), (1,0), (2,0), (3,0), (4,0), (5,0)][(0,1), (1,1), (2,1), (3,1), (4,1), (5,1)]
  [(0,2), (1,2), (2,2), (3,2), (4,2), (5,2)]Code language: CSS (css)

Vì vậy, khi bạn nói rằng bạn muốn moveTo(4, 1) trên canvas, có nghĩa là mình bắt đầu ở góc trên bên trái (0,0) và di chuyển bốn cột sang bên phải và một hàng xuống dưới.

Vẽ đối tượng của bạn

Khi đã có ngữ cảnh canvas, chúng ta có thể vẽ trên đó bằng cách sử dụng API ngữ cảnh canvas. Phương pháp lineTo()thêm một đường thẳng vào đường dẫn con hiện tại bằng cách nối điểm cuối cùng của nó với tọa độ ( x, ) được chỉ định y.

Giống như các phương thức khác sửa đổi đường dẫn hiện tại, phương thức này không trực tiếp hiển thị bất kỳ thứ gì. Để vẽ đường dẫn lên canvas, bạn có thể sử dụng fill() hoặc các phương phápstroke().

ctx.beginPath();      // Start a new path
ctx.moveTo(100, 50);  // Move the pen to x=100, y=50.
ctx.lineTo(300, 150); // Draw a line to x=300, y=150.
ctx.stroke();         // Render the pathCode language: JavaScript (javascript)
Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

Tôi có thể sử dụng fillRect() để vẽ một hình chữ nhật đầy. Cài đặt fillStyle xác định màu sắc được sử dụng khi tô các hình dạng đã vẽ:

ctx.fillStyle = 'blue';
ctx.fillRect(100, 100, 30, 30); // (x, y, width, height);Code language: JavaScript (javascript)

Điều này vẽ một sprite hình chữ nhật màu xanh lam được tô đầy:

Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

Tạo hoạt ảnh cho đối tượng của bạn

Bây giờ, hãy xem liệu bạn có thể làm cho khối của mình di chuyển trên canvas hay không. Bạn bắt đầu bằng cách đặt giá trị size của hình vuông thành 30. Sau đó, bạn có thể di chuyển giá trị x sang bên phải với các bước size và vẽ đối tượng lặp đi lặp lại. Đoạn code sau sẽ di chuyển khối sang bên phải cho đến khi nó chạm đến phần cuối của canvas:

const size = 30;
ctx.fillStyle = 'blue';

for (let x = 0; x < canvas.width; x += size) {
  ctx.fillRect(x, 50, size, size);
}Code language: JavaScript (javascript)
Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

OK, bạn đã có thể vẽ hình vuông như mình muốn. Nhưng có hai vấn đề:

  1. Mình không tự dọn dẹp sau đó.
  2. Quá nhanh để xem hoạt ảnh.

Bạn cần phải xóa bỏ khối cũ. Những gì bạn có thể làm là xóa các pixel trong một khu vực hình chữ nhật với clearRect(). Bằng cách sử dụng chiều rộng và chiều cao của canvas, mình có thể làm sạch nó giữa các lớp sơn.

for (let x = 0; x < canvas.width; x += size) {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // Clean up
  ctx.fillRect(x, 50, size, size);
}Code language: JavaScript (javascript)
Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

Tuyệt quá! Tôi đã khắc phục sự cố đầu tiên. Bây giờ bạn hãy thử làm chậm bức tranh để bạn có thể xem hoạt ảnh.

Bạn có thể quen thuộc với setInterval(function, delay). Nó bắt đầu thực thi lặp đi lặp lại functionmỗi delaymili giây được chỉ định. Tôi đặt khoảng thời gian là 200 ms, có nghĩa là code chạy năm lần một giây.

let x = 0;
const id = setInterval(() => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);      
  ctx.fillRect(x, 50, size, size);
  x += size;

  if (x >= canvas.width) {
    clearInterval(id);
  }
}, 200);    Code language: JavaScript (javascript)

Để dừng một bộ đếm thời gian được tạo bởi setInterval(), bạn cần gọi clearInterval()và cung cấp cho nó số nhận dạng để khoảng thời gian hủy bỏ. ID để sử dụng là id được trả về setInterval() và đây là lý do tại sao mình cần lưu trữ nó.

Bây giờ bạn có thể thấy rằng nếu bạn nhấn nút, sẽ có một hình vuông di chuyển từ trái sang phải. Tuy nhiên, nếu nhấn nút phát nhiều lần, bạn có thể thấy rằng có vấn đề khi tạo hoạt ảnh nhiều ô vuông cùng một lúc.

Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

Mỗi hình vuông có khoảng của nó xóa bảng và sơn hình vuông. Nó ở khắp nơi! Hãy xem cách tôi có thể sửa lỗi này.

Tạo hoạt ảnh cho nhiều đối tượng

Để có thể chạy các hoạt ảnh cho một số khối, chúng ta cần phải suy nghĩ lại về logic. Hiện tại, mỗi khối có phương thức hoạt ảnh với setInterval(). Thay vào đó, bạn nên quản lý các đối tượng chuyển động trước khi gửi chúng để được vẽ, tất cả cùng một lúc.

Chúng ta có thể thêm một biến started để chỉ bắt đầu setInterval() khi nhấp vào nút đầu tiên. Mỗi khi nhấn nút phát, bạn cần thêm một giá trị mới 0 vào một mảng squares. Điều này là đủ cho hoạt ảnh đơn giản này nhưng đối với một cái gì đó phức tạp hơn, bạn có thể tạo một Square đối tượng với các tọa độ và các thuộc tính cuối cùng khác như màu sắc.

let squares = [];
let started = false;

function play() {
  // Add 0 as x value for object to start from the left.
  squares.push(0);

  if (!started) {
      started = true;
      setInterval(() => {
        tick();
      }, 200)
  }
}

function tick() {
  // Clear canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Paint objects
  squares.forEach(x => ctx.fillRect(x, 50, size, size));

  squares = squares.map(x => x += size) // move x to right
      .filter(x => x < canvas.width);  // remove when at end
}Code language: JavaScript (javascript)

Hàm tick() xóa màn hình và tô tất cả các đối tượng trong mảng sau mỗi 200ms. Và bằng cách chỉ có một khoảng thời gian, tôi tránh được hiện tượng nhấp nháy mà tôi đã có trước đây. Và bây giờ tôi có được những hình ảnh động đẹp hơn:

Hướng dẫn JavaScript Bắt đầu với hoạt ảnh Canvas

Những gì tôi đã làm ở đây là bước đầu tiên của việc tạo một vòng lặp trò chơi. Vòng lặp này là trung tâm của mọi trò chơi. Đó là một vòng lặp vô hạn được kiểm soát giúp trò chơi của bạn tiếp tục chạy; đó là nơi mà tất cả các mảnh nhỏ của bạn được cập nhật và vẽ trên màn hình.

Tối ưu hóa hoạt ảnh của bạn

Một tùy chọn khác để tạo hoạt ảnh là sử dụng requestAnimationFrame(). Nó cho trình duyệt biết rằng bạn muốn thực hiện hoạt ảnh và yêu cầu trình duyệt gọi một hàm để cập nhật hoạt ảnh trước lần sơn lại tiếp theo. Nói cách khác, tôi nói với trình duyệt: “Lần tới khi bạn vẽ trên màn hình, hãy chạy chức năng này vì tôi cũng muốn vẽ một thứ gì đó”.

Cách để tạo hiệu ứng requestAnimationFrame() là tạo một hàm vẽ một khung và sau đó tự lên lịch để gọi lại. Với điều này, tôi nhận được một vòng lặp không đồng bộ thực thi khi tôi vẽ trên canvas. Tôi gọi phương thức animate lặp đi lặp lại cho đến khi chúng tôi quyết định dừng lại. Vì vậy, bây giờ thay vào đó chúng ta gọi hàm animate():

function play() {
  // Add 0 as x value for object to start from the left.
  squares.push(0);

  if (!started) {
      animate();
  }
}

function animate() {
  tick();
  requestAnimationFrame(animate);  
}Code language: JavaScript (javascript)

Nếu tôi thử điều này, nhận thấy rằng mình có thể thấy hình ảnh động, điều này không xảy ra setInterval(), mặc dù nó rất nhanh. Số lần gọi lại thường là 60 lần mỗi giây.

Phương thức requestAnimationFrame() trả về một idmà chúng tôi sử dụng để hủy khung hoạt hình đã lên lịch. Để hủy một khung hoạt hình đã lên lịch, bạn có thể sử dụng phương pháp cancelAnimationFrame(id) này.

Để làm chậm hoạt ảnh, bạn cần một bộ đếm thời gian để kiểm tra elapsedthời gian kể từ lần cuối chúng ta gọi hàm tick(). Để giúp, hàm gọi lại được truyền vào một đối số, DOMHighResTimeStamp, cho biết thời điểm requestAnimationFrame() bắt đầu thực hiện các hàm gọi lại.

let start = 0;

function animate(timestamp) {    
  const elapsed  = timestamp - start;
  if (elapsed > 200) {
    start = timestamp;
    tick();
  }
  requestAnimationFrame(animate);  
}Code language: JavaScript (javascript)

Với điều này, tôi có chức năng tương tự như mình đã có trước đó setInterval().

Vì vậy, kết luận, tại sao bạn nên sử dụng requestAnimationFrame() thay vì setInterval()?

  • Nó cho phép tối ưu hóa trình duyệt.
  • Nó xử lý tốc độ khung hình.
  • Hoạt ảnh chỉ chạy khi hiển thị.

Kết thúc

Trong bài viết này, tôi đã tạo một HTML5 Canvas và sử dụng JavaScript và ngữ cảnh kết xuất 2D của nó để vẽ trên canvas. Tôi đã được giới thiệu với một số phương pháp có sẵn trong ngữ cảnh canvas và sử dụng chúng để hiển thị các hình dạng khác nhau.

Cuối cùng, tôi đã có thể tạo hoạt ảnh cho nhiều đối tượng trên canvas. Tôi đã học cách sử dụng setInterval() để tạo một vòng lặp hoạt ảnh quản lý và vẽ các đối tượng trên màn hình. Chúng tôi cũng đã học cách tối ưu hóa hoạt ảnh với requestAnimationFrame().

Bây giờ bạn đã học được một số hoạt ảnh cơ bản, bạn đã hoàn thành các bước đầu tiên để phát triển trò chơi. Bước tiếp theo của bạn là tham gia một dự án dành cho lập trình viên trò chơi mới bắt đầu để tìm hiểu cách:

  • Thiết kế vòng lặp chơi trò chơi
  • Triển khai các điều khiển tương tác vớiaddEventListener
  • Phát hiện va chạm đối tượng
  • Theo dõi điểm số

Chúc các bạn học tập vui vẻ!

Cảm ơn bạn đã theo dõi bài viết!

Các bạn có thể tham khảo các bài viết hay về JavaScript tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

TỔNG HỢP TÀI LIỆU HỌC LẬP TRÌNH CƠ BẢN CHO NGƯỜI MỚI BẮT ĐẦU

KHOÁ HỌC BOOTCAMP JAVA/JAVASCRIPT/PHP TRỞ THÀNH LẬP TRÌNH VIÊN TRONG 5-6 THÁNG

Leave a Reply

Your email address will not be published. Required fields are marked *