Hướng dẫn lập trình game Flappy Bird bằng JavaScript từ A-Z
Game Flappy Bird hiển thị trên trình duyệt với chú chim màu vàng bay qua các ống nước xanh
Hướng dẫn lập trình game Flappy Bird bằng JavaScript từ A-Z
Lập trình game là cách tuyệt vời để nắm vững các khái niệm lập trình cơ bản, và Flappy Bird là dự án hoàn hảo cho người mới bắt đầu làm quen với JavaScript và HTML5 Canvas. Với chỉ vài trăm dòng code, bạn có thể xây dựng một trò chơi hoàn chỉnh có cơ chế vật lý, phát hiện va chạm và tính điểm số, tất cả chạy trực tiếp trên trình duyệt mà không cần cài đặt bất kỳ phần mềm nào phức tạp.
Chuẩn bị môi trường phát triển và cấu trúc dự án
Để bắt đầu xây dựng game Flappy Bird, bạn cần tạo hai file chính: file HTML để hiển thị giao diện và file JavaScript để chứa logic game. File HTML sẽ chứa thẻ Canvas với kích thước 288x512 pixel - đây là vùng màn hình nơi game sẽ được render. Bạn nên đặt hai file này cùng thư mục để dễ dàng quản lý và tránh lỗi đường dẫn khi tải tài nguyên.
Tài nguyên game bao gồm hình ảnh và âm thanh cần được tổ chức trong các thư mục riêng biệt: thư mục images chứa bird.png, bg.png (background), fg.png (foreground), pipeNorth.png và pipeSouth.png, trong khi thư mục sounds lưu fly.mp3 và score.mp3. Bạn có thể tải bộ tài nguyên mẫu từ các repository GitHub hoặc tự tạo bằng các công cụ đồ họa như Photoshop, GIMP, hoặc các trang web miễn phí như Pixabay, Freepik. Việc tự tạo tài nguyên giúp game của bạn trở nên độc đáo và tránh vi phạm bản quyền.
Cấu trúc thư mục chuẩn sẽ giúp bạn dễ dàng mở rộng sau này. Khi dự án lớn lên, bạn có thể tách code thành nhiều module, thêm level mới, hoặc tích hợp tính năng lưu điểm người chơi. Tránh đặt file code lộn xộn trong cùng thư mục với tài nguyên, vì điều này sẽ gây khó khăn khi bảo trì hoặc chia sẻ dự án với người khác. Một dự án được tổ chức tốt là dấu hiệu của một lập trình viên chuyên nghiệp.
Khởi tạo đối tượng game và cấu hình thông số ban đầu
Bước đầu tiên trong flappyBird.js là lấy reference đến thẻ Canvas và tạo ngữ cảnh 2D bằng phương thức getContext('2d'). Context này cung cấp các API mạnh mẽ để vẽ hình ảnh, văn bản, hình học và nhiều yếu tố đồ họa khác. Sau khi có context, bạn cần khởi tạo các đối tượng Image cho từng yếu tố game: bird, bg, fg, pipeNorth, pipeSouth. Mỗi đối tượng Image cần được gán nguồn thông qua thuộc tính src, và trình duyệt sẽ tự động tải hình ảnh từ thư mục images.
Các biến thông số game quyết định trải nghiệm chơi. Biến gap được đặt là 85 pixel - đây là khoảng cách giữa ống nước trên và ống nước dưới. Khoảng cách này cần được cân nhắc kỹ: quá rộng sẽ khiến game quá dễ, quá hẹp sẽ khiến game bất khả thi. Biến gravity bằng 1.5 pixel mỗi frame mô phỏng trọng lực, trong khi bX và bY định vị chim ở tọa độ (10, 150) khi game bắt đầu. Tọa độ này đảm bảo chim xuất hiện ở bên trái màn hình và đủ cao để người chơi có thời gian phản ứng.
Âm thanh được tạo bằng đối tượng Audio với hai file fly.mp3 và score.mp3. Bạn nên kiểm tra định dạng âm thanh - MP3 hoạt động tốt trên hầu hết trình duyệt, nhưng WebM hoặc OGG có thể là lựa chọn thay thế tốt hơn cho một số thiết bị. Lưu ý rằng trình duyệt hiện đại thường chặn autoplay âm thanh, nên người chơi cần tương tác với trang web (nhấn phím hoặc click) trước khi âm thanh phát. Điều này là chính sách bảo mật của trình duyệt và không thể tắt bằng code JavaScript thông thường.
Xử lý điều khiển và cơ chế vật lý của nhân vật
Cơ chế bay của chim được kích hoạt khi người chơi nhấn phím bất kỳ trên bàn phím. Bạn sẽ thêm một EventListener lắng nghe sự kiện keydown, và khi sự kiện xảy ra, hàm moveUp() được gọi. Hàm này giảm tọa độ Y của chim đi 25 pixel (bY -= 25), tạo hiệu ứng chim bay lên, đồng thời phát âm thanh fly.mp3. Giá trị 25 pixel được chọn sau nhiều lần thử nghiệm để tạo cảm giác nhảy tự nhiên mà không quá mạnh hoặc quá yếu.
Trọng lực được áp dụng trong game loop bằng cách tăng tọa độ Y của chim (bY += gravity) mỗi frame. Với gravity = 1.5, chim sẽ rơi nhanh dần theo thời gian - đây là mô phỏng vật lý đơn giản của gia tốc trọng trường. Cơ chế này tạo ra sự cân bằng giữa việc nhấn phím để bay lên và lực trọng lực kéo chim xuống, tạo nên gameplay mượt mà và có chiều sâu. Bạn có thể điều chỉnh gravity để thay đổi độ khó của game: giá trị lớn hơn làm chim rơi nhanh hơn, game khó hơn, trong khi giá trị nhỏ hơn làm game dễ hơn nhưng có thể cảm giác "lửng lơ".
Một vấn đề thường gặp là việc nhấn phím liên tục khiến chim bay quá cao và vượt khỏi màn hình. Để giải quyết, bạn nên thêm giới hạn tọa độ Y, đảm bảo chim không bay quá cao trên nền trời hoặc rơi xuống dưới đất. Ngoài ra, có thể thêm debounce (trì hoãn) để ngăn người chơi spam phím, tạo ra độ trễ nhỏ giữa các lần nhấn. Điều này giống như thời gian hồi chiếu trong các game chuyên nghiệp, giúp game có nhịp điệu hơn và tránh việc người chơi "gõ phím" để thắng.

Minh họa cơ chế bay của chú chim với lực nhảy lên và trọng lực kéo xuống
Xây dựng hệ thống ống nước và logic di chuyển
Hệ thống ống nước được quản lý bằng một mảng pipe, với mỗi phần tử là object chứa tọa độ X và Y. Ống đầu tiên được khởi tạo ở vị trí pipe[0] = {x: cvs.width, y: 0} - tức là xuất phát từ mép phải màn hình. Trong game loop, mỗi ống di chuyển sang trái bằng cách giảm tọa độ X (pipe[i].x--). Khi ống di chuyển đến vị trí x == 125, một ống mới được thêm vào mảng với tọa độ X bằng chiều rộng màn hình và tọa độ Y ngẫu nhiên. Giá trị 125 được chọn để đảm bảo khoảng cách giữa các ống đủ rộng để người chơi có thời gian phản ứng.
Tọa độ Y của ống trên được tính bằng Math.floor(Math.random() * pipeNorth.height) - pipeNorth.height, tạo ra vị trí ngẫu nhiên nhưng vẫn nằm trong phạm vi hợp lý. Ống dưới sẽ được vẽ ở vị trí Y cộng thêm constant (khoảng cách gap). Cơ chế random này đảm bảo mỗi lần chơi là một trải nghiệm khác nhau, tăng tính tái chơi của game. Tuy nhiên, bạn cần đảm bảo random không tạo ra vị trí bất khả thi - ví dụ ống quá cao hoặc quá thấp khiến chim không thể vượt qua.
Hiệu suất game có thể giảm nếu mảng pipe tiếp tục lớn lên vô hạn. Để tối ưu, bạn nên xóa các ống đã di chuyển ra khỏi màn hình (khi pipe[i].x + pipeNorth.width < 0) khỏi mảng. Điều này giữ bộ nhớ ở mức ổn định và đảm bảo game chạy mượt ngay cả sau nhiều phút chơi. Ngoài ra, bạn có thể giới hạn số lượng ống tối đa trên màn hình cùng lúc, chẳng hạn 3-4 ống, để tránh việc quá nhiều object phải được vẽ mỗi frame.
Phát hiện va chạm và hệ thống tính điểm
Phát hiện va chạm là phần quan trọng nhất để game hoạt động chính xác. Va chạm xảy ra khi hình chữ nhật bao quanh chim (bX, bY, bird.width, bird.height) giao thoa với hình chữ nhật của ống nước. Điều kiện va chạm được kiểm tra bằng các phép so sánh tọa độ: nếu tọa độ X của chim cộng với chiều rộng lớn hơn tọa độ X của ống VÀ tọa độ X của chim nhỏ hơn tọa độ X của ống cộng với chiều rộng ống, TẠI THỜI ĐIỂM ĐÓ, nếu tọa độ Y của chim nhỏ hơn tọa độ Y của ống cộng với chiều cao ống trên HOẶC tọa độ Y của chim cộng với chiều cao lớn hơn tọa độ Y của ống dưới, thì va chạm đã xảy ra.
Khi va chạm được phát hiện, game sẽ gọi location.reload() để tải lại trang và bắt đầu lại từ đầu. Đây là cách đơn giản nhất để reset game, nhưng trong phiên bản nâng cao, bạn nên tạo hàm resetGame() riêng để đặt lại tất cả biến về trạng thái ban đầu mà không cần tải lại trang. Việc này giúp chuyển mượt mà hơn và không gây nháy màn hình. Ngoài ra, bạn có thể thêm màn hình Game Over với nút "Chơi lại" để trải nghiệm người dùng tốt hơn.
Điểm số được tính khi ống nước đi qua tọa độ x == 5 - tức là khi ống vừa vượt qua vị trí của chim. Khi điều kiện này thỏa mãn, score tăng lên 1 và âm thanh score.mp3 phát. Điểm số được hiển thị ở góc trái dưới màn hình bằng ctx.fillText với màu đen và font Verdana 20px. Để tăng tính cạnh tranh, bạn có thể lưu điểm cao nhất (high score) vào localStorage và hiển thị cùng với điểm hiện tại. Điều này khuyến khích người chơi thử phá kỷ lục của chính mình.
Câu hỏi thường gặp
Làm thế nào để thay đổi độ khó của game Flappy Bird?
Bạn có thể điều chỉnh biến gravity (trọng lực) để thay đổi tốc độ rơi của chim, hoặc thay đổi giá trị gap (khoảng cách giữa các ống) để tăng/giảm độ khó. Gravity càng lớn và gap càng hẹp thì game càng khó.
Tôi có thể thêm tính năng lưu điểm người chơi không?
Có, sử dụng localStorage để lưu điểm cao nhất và hiển thị mỗi khi game bắt đầu. Code đơn giản: localStorage.setItem('highScore', score) khi game kết thúc và localStorage.getItem('highScore') để hiển thị.
Khám Phá
Lỗi Thường Gặp Khi Tự Chạy Quảng Cáo Công Nghệ: Hướng Dẫn Khắc Phục Chi Tiết
vivo X300 Ultra: Camera 200MP đưa nhiếp ảnh smartphone lên tầm cao mới
HUAWEI Pura X Max: Bước ngoặt trong xu hướng điện thoại gập siêu rộng 2026 ```






