Cách tạo game Bắn bóng cổ điển với Gemini AI
Trong bài viết bên dưới, GameVui sẽ cùng bạn khám phá cách dùng Gemini AI để tạo nên một phiên bản game bắn bóng cực thú vị: từ giao diện, cơ chế bắn bóng, tính điểm cho tới những hiệu ứng đẹp mắt khiến người chơi khó mà thoát game.
Dù bạn là người mới bắt đầu hay chỉ đơn giản muốn thử sức với AI trong lập trình game, đây sẽ là trải nghiệm thú vị hơn bạn nghĩ rất nhiều.
Hướng dẫn tạo game Bắn bóng cổ điển với Gemini AI
Bước 1: Trước tiên, bạn hãy mở ứng dụng Gemini AI lên rồi nhập Prompt sau để nó tạo một game Bắn bóng
Tạo một game Bubble Shooter cổ điển bằng HTML/CSS/JavaScript thuần, chạy trực tiếp trên trình duyệt.
# Mục tiêu
Tạo game giống:
* Puzzle Bobble
* Bubble Shooter cổ điển
* Bubble Witch Saga
Gameplay đơn giản, gây nghiện, mượt mà, chuyên nghiệp.
Game phải chơi tốt trên:
* máy tính,
* điện thoại,
* tablet.
Màn hình ưu tiên landscape (ngang).
# Yêu cầu kỹ thuật
* Dùng:
* HTML5 Canvas
* CSS
* JavaScript thuần
* Không dùng framework nặng.
* Không cần backend/server.
* Chạy bằng mở file index.html.
* Tách riêng:
* index.html
* style.css
* game.js
# Thiết kế tổng thể
Phong cách:
* arcade cổ điển hiện đại,
* màu sắc tươi sáng,
* casual mobile game,
* bóng có glow nhẹ,
* animation mềm mại.
Background:
* gradient động nhẹ,
* particle nền nhỏ,
* không quá rối mắt.
# Gameplay chi tiết
## Màn chơi
* Các quả bóng xếp dạng tổ ong (hex grid) ở phía trên.
* Người chơi điều khiển súng bắn ở cạnh dưới màn hình.
* Khi bắn:
* bóng bay theo hướng ngắm,
* phản xạ khi chạm tường,
* dính vào cụm bóng.
## Luật phá bóng
* Nếu có từ 3 bóng cùng màu trở lên chạm nhau:
* phát nổ,
* biến mất,
* cộng điểm.
## Bóng rơi
* Các cụm bóng không còn kết nối với trần sẽ rơi xuống.
* Có animation rơi mềm mại.
* Có particle effect khi rơi.
# Điều khiển
## PC
* Chuột để ngắm.
* Click chuột trái để bắn.
## Mobile
* Chạm và kéo để ngắm.
* Thả tay để bắn.
* Điều khiển touch mượt.
# Hiển thị aim
* Có đường aim preview.
* Đường aim:
* phản xạ khi chạm tường,
* hiển thị bằng đường chấm chấm,
* animation nhẹ.
# Hệ thống bóng
## Màu sắc
Tạo tối thiểu:
* đỏ,
* xanh dương,
* xanh lá,
* vàng,
* tím,
* cam.
## Thiết kế bóng
Không dùng ảnh PNG ngoài.
Vẽ bóng bằng Canvas:
* radial gradient,
* highlight phản chiếu giả,
* glow nhẹ,
* outline mềm.
Mỗi bóng:
* nhìn bóng bẩy,
* giống game mobile hiện đại.
# Hệ thống level
* Level tăng dần.
* Độ khó tăng:
* nhiều màu hơn,
* bố cục khó hơn,
* hàng bóng hạ xuống nhanh hơn.
# Cơ chế thua
Người chơi thua khi:
* bóng chạm vạch giới hạn phía dưới.
Có:
* animation game over,
* màn hình restart.
# Hệ thống điểm
Hiển thị:
* score,
* combo,
* level.
Combo:
* phá nhiều lần liên tiếp,
* nhân điểm thưởng.
# Hiệu ứng
Yêu cầu game feel tốt:
## Animation
* bounce nhẹ khi bóng va chạm,
* easing animation,
* squash & stretch nhẹ.
## Particle
* particle nổ,
* particle ánh sáng,
* trail nhỏ khi bóng bay.
## Camera
* screen shake nhẹ khi combo lớn.
# Âm thanh
Tạo âm thanh bằng Web Audio API:
* bắn bóng,
* bóng nổ,
* combo,
* game over.
Âm thanh arcade vui nhộn.
# UI
## Menu chính
Có:
* nút Play,
* nút Sound On/Off,
* nút Restart.
## In-game UI
Hiển thị:
* score,
* level,
* bóng tiếp theo.
## Responsive
UI phải:
* đẹp trên mobile,
* không bị che gameplay,
* scale tự động.
# Mobile optimization
* Responsive hoàn toàn.
* Canvas tự scale.
* Không méo hình.
* Ưu tiên landscape.
* Nếu portrait:
* hiện thông báo xoay ngang điện thoại.
# Hiệu năng
* Mượt 60 FPS.
* Tối ưu render.
* Object pooling cho particle.
* Collision chính xác.
# Kiến trúc code
Code rõ ràng, có comment.
Tạo class:
* Game
* Bubble
* Shooter
* Grid
* Particle
* UIManager
* SoundManager
# Yêu cầu gameplay nâng cao
Thêm:
* bóng đặc biệt ngẫu nhiên:
* bomb bubble,
* rainbow bubble,
* lightning bubble.
* combo multiplier.
* chain reaction.
* slow motion ngắn khi combo lớn.
# Hiệu ứng chuyên nghiệp
Tập trung tạo:
* juicy game feel,
* animation mượt,
* visual sạch sẽ,
* cảm giác arcade hiện đại.
# Yêu cầu cuối cùng
Xuất toàn bộ code hoàn chỉnh:
* index.html
* style.css
* game.js
Code phải:
* chạy ngay,
* không thiếu phần nào,
* không cần chỉnh sửa.
Sau khi hoàn thành:
* giải thích cách chạy game,
* giải thích nơi chỉnh:
* tốc độ bóng,
* số màu,
* độ khó,
* spawn pattern,
* score system.
Bước 2: Sau đó, Gemini AI sẽ phân tích Prompt của bạn rồi tạo cho bạn 3 file như dưới đây
Index.html
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bắn Bóng Cổ Điển</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="game-container">
<!-- Thanh công cụ phía trên -->
<div id="hud" class="hidden">
<div class="hud-item">Điểm: <span id="scoreVal">0</span></div>
<div class="hud-item">Kỷ lục: <span id="highScoreVal">0</span></div>
<button id="btn-sound-toggle">Loa: BẬT</button>
</div>
<canvas id="gameCanvas" width="900" height="600"></canvas>
<!-- Màn hình chờ (Start Screen) -->
<div id="menu-screen" class="screen">
<div class="menu-content">
<h1 class="game-title">BẮN BÓNG<br><span class="subtitle">CLASSIC</span></h1>
<div class="decoration-bubbles">
<div class="dec-b" style="background:#FF3B30; left:-50px; top:20px"></div>
<div class="dec-b" style="background:#007AFF; right:-40px; top:80px"></div>
</div>
<button id="btn-play" class="btn pulse">CHƠI NGAY</button>
</div>
</div>
<!-- Màn hình kết thúc (Game Over) - Đã sửa bố cục -->
<div id="game-over-screen" class="screen hidden">
<div class="over-content">
<h1 class="game-title">KẾT THÚC</h1>
<div class="result-box">
<p class="label-score">ĐIỂM CỦA BẠN</p>
<p id="final-score" class="score-number">0</p>
<div id="new-record-msg"></div>
</div>
<button id="btn-restart" class="btn">CHƠI LẠI</button>
</div>
</div>
</div>
<script src="game.js"></script>
</body>
</html>Style.css
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Arial, sans-serif; }
body { background-color: #121d29; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; }
#game-container {
position: relative; width: 900px; height: 600px;
background: linear-gradient(180deg, #e0f7ff 0%, #b3e5fc 100%);
border: 8px solid #ffffff; border-radius: 24px;
box-shadow: 0 30px 60px rgba(0,0,0,0.5); overflow: hidden;
}
#gameCanvas { display: block; width: 100%; height: 100%; }
/* HUD Header */
#hud {
position: absolute; top: 0; left: 0; width: 100%; height: 65px;
display: flex; justify-content: center; align-items: center; gap: 40px;
background: rgba(255, 255, 255, 0.95); border-bottom: 4px solid #3498db; z-index: 10;
}
.hud-item {
background: #fff; border: 2px solid #3498db; padding: 8px 25px;
border-radius: 30px; color: #2980b9; font-weight: 900; font-size: 18px;
box-shadow: 0 4px 0 #bdc3c7;
}
#btn-sound-toggle {
background: #fff; border: 2px solid #3498db; padding: 8px 20px;
border-radius: 30px; color: #2980b9; font-weight: bold; cursor: pointer;
box-shadow: 0 4px 0 #bdc3c7; transition: 0.1s;
}
#btn-sound-toggle:active { transform: translateY(2px); box-shadow: 0 2px 0 #bdc3c7; }
/* Các màn hình lớp phủ */
.screen {
position: absolute; inset: 0; display: flex; justify-content: center;
align-items: center; z-index: 20; background: rgba(15, 25, 35, 0.9);
text-align: center; color: white;
}
.menu-content, .over-content { position: relative; display: flex; flex-direction: column; align-items: center; }
.game-title {
font-size: 85px; color: #f1c40f; font-weight: 900;
text-shadow: 0 10px 0 #d35400, 0 20px 30px rgba(0,0,0,0.5);
margin-bottom: 40px; line-height: 0.9;
}
.subtitle { font-size: 40px; color: #fff; letter-spacing: 8px; }
/* Bảng điểm kết thúc */
.result-box {
background: rgba(255, 255, 255, 0.1); padding: 30px 60px;
border-radius: 30px; border: 2px solid rgba(255,255,255,0.2);
margin-bottom: 30px; backdrop-filter: blur(10px);
}
.label-score { font-size: 18px; color: #bdc3c7; margin-bottom: 10px; }
.score-number { font-size: 75px; font-weight: 900; color: #fff; text-shadow: 0 0 20px #f1c40f; }
#new-record-msg { color: #2ecc71; font-weight: bold; font-size: 20px; margin-top: 10px; }
.btn {
padding: 20px 60px; font-size: 30px; font-weight: 900; color: #fff;
background: linear-gradient(to bottom, #e74c3c, #c0392b);
border: none; border-radius: 60px; cursor: pointer;
box-shadow: 0 8px 0 #922b21, 0 15px 25px rgba(0,0,0,0.4); transition: 0.1s;
}
.btn:active { transform: translateY(4px); box-shadow: 0 4px 0 #922b21; }
.pulse { animation: pulseAnim 2s infinite; }
@keyframes pulseAnim { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
.hidden { display: none !important; }
.decoration-bubbles .dec-b {
position: absolute; width: 70px; height: 70px; border-radius: 50%;
opacity: 0.6; filter: blur(1px); animation: float 4s infinite ease-in-out;
}
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-30px); } }Game.js
const CANVAS_WIDTH = 900;
const CANVAS_HEIGHT = 600;
const BUBBLE_RADIUS = 20;
const BUBBLE_DIAMETER = BUBBLE_RADIUS * 2;
const ROW_HEIGHT = 34; // Khoảng cách chuẩn giữa các hàng bóng
const COLORS = ['#FF3B30', '#007AFF', '#34C759', '#FFCC00', '#AF52DE', '#FF9500'];
const GRID_OFFSET_Y = 75;
const SHOOT_SPEED = 20;
const randomColor = () => COLORS[Math.floor(Math.random() * COLORS.length)];
class Bubble {
constructor(x, y, color) {
this.x = x; this.y = y; this.color = color;
}
draw(ctx) {
ctx.save(); ctx.translate(this.x, this.y);
ctx.beginPath(); ctx.arc(0, 0, BUBBLE_RADIUS - 1, 0, Math.PI * 2);
const grad = ctx.createRadialGradient(-6, -6, 2, 0, 0, BUBBLE_RADIUS);
grad.addColorStop(0, '#fff'); grad.addColorStop(0.4, this.color); grad.addColorStop(1, '#000');
ctx.fillStyle = grad; ctx.fill(); ctx.restore();
}
}
class Game {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.ui = {
menu: document.getElementById('menu-screen'),
gameOver: document.getElementById('game-over-screen'),
hud: document.getElementById('hud'),
score: document.getElementById('scoreVal'),
highScore: document.getElementById('highScoreVal'),
finalScore: document.getElementById('final-score'),
recordMsg: document.getElementById('new-record-msg')
};
this.grid = [];
this.state = 'MENU';
this.score = 0;
this.highScore = localStorage.getItem('bubbleRec_v2') || 0;
this.shooterX = CANVAS_WIDTH / 2;
this.shooterY = CANVAS_HEIGHT - 60;
this.aimAngle = -Math.PI / 2;
this.bullet = null;
this.nextColor = randomColor();
this.isFiring = false;
this.lastTime = 0;
this.initEvents();
requestAnimationFrame((t) => this.loop(t));
}
initEvents() {
document.getElementById('btn-play').onclick = () => this.startGame();
document.getElementById('btn-restart').onclick = () => this.startGame();
window.onmousemove = (e) => {
if (this.state !== 'PLAYING') return;
const rect = this.canvas.getBoundingClientRect();
const mouseX = (e.clientX - rect.left) * (CANVAS_WIDTH / rect.width);
const mouseY = (e.clientY - rect.top) * (CANVAS_HEIGHT / rect.height);
this.aimAngle = Math.atan2(mouseY - this.shooterY, mouseX - this.shooterX);
// Giới hạn góc bắn để không bắn ngược ra sau
this.aimAngle = Math.max(-Math.PI + 0.2, Math.min(-0.2, this.aimAngle));
};
window.onmouseup = () => { if (this.state === 'PLAYING' && !this.isFiring) this.shoot(); };
}
startGame() {
this.state = 'PLAYING'; this.score = 0;
this.ui.menu.classList.add('hidden'); this.ui.gameOver.classList.add('hidden');
this.ui.hud.classList.remove('hidden');
this.ui.recordMsg.innerText = "";
this.grid = []; this.generateInitialGrid(); this.reloadShooter(); this.updateHUD();
}
generateInitialGrid() {
for (let r = 0; r < 7; r++) {
this.grid[r] = [];
let cols = (r % 2 === 0) ? 22 : 21;
for (let c = 0; c < cols; c++) {
let pos = this.getGridXY(r, c);
this.grid[r][c] = new Bubble(pos.x, pos.y, randomColor());
}
}
}
getGridXY(row, col) {
let offset = (row % 2 === 0) ? BUBBLE_RADIUS : BUBBLE_RADIUS * 2;
return { x: col * BUBBLE_DIAMETER + offset, y: row * ROW_HEIGHT + BUBBLE_RADIUS + GRID_OFFSET_Y };
}
reloadShooter() {
this.bullet = { x: this.shooterX, y: this.shooterY, vx: 0, vy: 0, color: this.nextColor };
this.nextColor = randomColor(); this.isFiring = false;
}
shoot() {
this.bullet.vx = Math.cos(this.aimAngle) * SHOOT_SPEED;
this.bullet.vy = Math.sin(this.aimAngle) * SHOOT_SPEED;
this.isFiring = true;
}
update(deltaTime) {
if (this.state !== 'PLAYING' || !this.isFiring) return;
// deltaTime giúp bóng bay mượt bất kể cấu hình máy
const speedMultiplier = deltaTime / 16.67;
this.bullet.x += this.bullet.vx * speedMultiplier;
this.bullet.y += this.bullet.vy * speedMultiplier;
// Bật tường trái phải
if (this.bullet.x < BUBBLE_RADIUS || this.bullet.x > CANVAS_WIDTH - BUBBLE_RADIUS) {
this.bullet.vx *= -1;
this.bullet.x = Math.max(BUBBLE_RADIUS, Math.min(this.bullet.x, CANVAS_WIDTH - BUBBLE_RADIUS));
}
let hit = false;
if (this.bullet.y <= GRID_OFFSET_Y + BUBBLE_RADIUS) {
hit = true;
} else {
// Kiểm tra va chạm với bóng khác
for (let r = 0; r < this.grid.length; r++) {
if (!this.grid[r]) continue;
for (let c = 0; c < this.grid[r].length; c++) {
let b = this.grid[r][c];
if (b && Math.hypot(this.bullet.x - b.x, this.bullet.y - b.y) < BUBBLE_DIAMETER - 5) {
hit = true; break;
}
}
if (hit) break;
}
}
if (hit) this.snapToGrid();
}
snapToGrid() {
let r = Math.round((this.bullet.y - BUBBLE_RADIUS - GRID_OFFSET_Y) / ROW_HEIGHT);
r = Math.max(0, r);
let offset = (r % 2 === 0) ? BUBBLE_RADIUS : BUBBLE_RADIUS * 2;
let c = Math.round((this.bullet.x - offset) / BUBBLE_DIAMETER);
let maxCols = (r % 2 === 0) ? 22 : 21;
c = Math.max(0, Math.min(c, maxCols - 1));
// Nếu ô đã bị chiếm, dời xuống hàng dưới
if (this.grid[r] && this.grid[r][c]) r++;
if (!this.grid[r]) this.grid[r] = [];
let pos = this.getGridXY(r, c);
let newBubble = new Bubble(pos.x, pos.y, this.bullet.color);
this.grid[r][c] = newBubble;
this.handleMatches(r, c, newBubble.color);
this.reloadShooter();
this.checkGameOver();
}
handleMatches(row, col, color) {
let matches = [], queue = [[row, col]], visited = new Set([`${row},${col}`]);
while(queue.length > 0) {
let [r, c] = queue.shift();
matches.push({r, c});
const neighbors = (r % 2 === 0) ? [[0,-1],[0,1],[-1,-1],[-1,0],[1,-1],[1,0]] : [[0,-1],[0,1],[-1,0],[-1,1],[1,0],[1,1]];
neighbors.forEach(([dr, dc]) => {
let nr = r + dr, nc = c + dc;
if (this.grid[nr] && this.grid[nr][nc] && this.grid[nr][nc].color === color && !visited.has(`${nr},${nc}`)) {
visited.add(`${nr},${nc}`); queue.push([nr, nc]);
}
});
}
if (matches.length >= 3) {
matches.forEach(m => delete this.grid[m.r][m.c]);
this.score += matches.length * 10;
this.dropFloating();
this.updateHUD();
}
}
dropFloating() {
let connected = new Set(), queue = [];
if (this.grid[0]) this.grid[0].forEach((b, c) => { if(b){ queue.push([0, c]); connected.add(`0,${c}`); }});
while(queue.length > 0) {
let [r, c] = queue.shift();
const neighbors = (r % 2 === 0) ? [[0,-1],[0,1],[-1,-1],[-1,0],[1,-1],[1,0]] : [[0,-1],[0,1],[-1,0],[-1,1],[1,0],[1,1]];
neighbors.forEach(([dr, dc]) => {
let nr = r + dr, nc = c + dc;
if (this.grid[nr] && this.grid[nr][nc] && !connected.has(`${nr},${nc}`)) {
connected.add(`${nr},${nc}`); queue.push([nr, nc]);
}
});
}
this.grid.forEach((row, r) => row && row.forEach((b, c) => {
if(b && !connected.has(`${r},${c}`)) delete this.grid[r][c];
}));
}
checkGameOver() {
this.grid.forEach(row => row && row.forEach(b => {
if (b && b.y > CANVAS_HEIGHT - 120) this.endGame();
}));
}
endGame() {
this.state = 'GAMEOVER';
this.ui.finalScore.innerText = this.score;
if (this.score > this.highScore) {
this.highScore = this.score;
localStorage.setItem('bubbleRec_v2', this.highScore);
this.ui.recordMsg.innerText = "KỶ LỤC MỚI!";
}
this.ui.gameOver.classList.remove('hidden');
}
updateHUD() { this.ui.score.innerText = this.score; this.ui.highScore.innerText = this.highScore; }
draw() {
this.ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
this.grid.forEach(row => row && row.forEach(b => b && b.draw(this.ctx)));
if (this.state === 'PLAYING') {
// Vẽ súng bắn
this.ctx.save(); this.ctx.translate(this.shooterX, this.shooterY);
this.ctx.rotate(this.aimAngle + Math.PI/2);
this.ctx.fillStyle = '#34495e'; this.ctx.fillRect(-10, -40, 20, 40);
this.ctx.restore();
// Vẽ bóng đang nạp
if (this.bullet) new Bubble(this.bullet.x, this.bullet.y, this.bullet.color).draw(this.ctx);
// Vẽ bóng tiếp theo
this.ctx.fillStyle = "#2c3e50"; this.ctx.fillText("Tiếp:", 30, CANVAS_HEIGHT - 35);
new Bubble(80, CANVAS_HEIGHT - 40, this.nextColor).draw(this.ctx);
}
// Vạch giới hạn
this.ctx.setLineDash([10, 10]); this.ctx.strokeStyle = "rgba(231, 76, 60, 0.4)";
this.ctx.beginPath(); this.ctx.moveTo(0, CANVAS_HEIGHT - 100); this.ctx.lineTo(CANVAS_WIDTH, CANVAS_HEIGHT - 100); this.ctx.stroke();
}
loop(timestamp) {
let deltaTime = timestamp - this.lastTime;
if (deltaTime > 100) deltaTime = 16; // Chống lag giật khi chuyển tab
this.lastTime = timestamp;
this.update(deltaTime);
this.draw();
requestAnimationFrame((t) => this.loop(t));
}
}
window.onload = () => new Game();Bước 3: Bạn lưu 3 file trên vào thư mục Bắn bóng rồi mở file index.html lên để chơi

★ 👨 60
Bài viết liên quan
0 Bình luận
Sắp xếp theo
Xóa Đăng nhập để Gửi
Bài viết mới nhất
Game PC
-
Tất cả thông tin về Suncoil trong Grow a Garden
-
Game Đấu Trường Chân Lý là gì? Những điều cần biết trước khi chơi
-
Hướng dẫn nhận Sprays trong Grow a Garden
-
Mẹo cứu những chú hươu trong 99 Nights in the Forest
-
Sailor Piece: Hướng dẫn nhận Worthiness Fragments
-
Among Us: Mẹo chơi để luôn thắng khi làm Impostor
TOP Game
Game Mobile
Game PC
Code game
Cách chơi Game
Mẹo vặt
Anime Manga
Đấu Trường Chân Lý
Liên Minh Huyền Thoại
Call Of Duty
Coin Master 
