基于 HTML5 Canvas 制作一个精美的 2048 小游戏--day 1

基于 HTML5 Canvas 制作一个精美的 2048 小游戏

在这个快节奏的生活中,简单而富有挑战性的游戏总能给我们带来乐趣。2048 是一款受欢迎的益智游戏,不仅考验智力,还能让人回味无穷。今天,我带领大家将一起学习如何使用 HTML5 Canvas 来制作一个精美的 2048 小游戏。

一、了解游戏规则

在深入代码之前,我们需要了解游戏的基本规则:

  1. 目标:通过合并相同的数字块,最终达到2048。
  2. 操作:玩家可以通过上下左右的箭头键控制数字块的移动。
  3. 生成新块:每次成功移动后,会随机生成一个"2"或"4"的数字块。
  4. 游戏结束:当所有方块被填满且无法进行任何合并时,游戏结束。

二、建立 HTML 结构

首先,我们需要搭建游戏的基础 HTML 结构。在 HTML 文件中,引入 Canvas 元素以绘制游戏界面。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2048 游戏</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <canvas id="gameCanvas"></canvas>
    </div>
    <script src="script.js"></script>
</body>
</html>

三、样式设计

接下来,我们需要为游戏添加一些样式,使其更具吸引力。我们将在 CSS 文件中设置按钮和画布的样式。

css 复制代码
body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #faf8ef;
    font-family: 'Arial', sans-serif;
}

.container {
    position: relative;
}

canvas {
    border: 2px solid #bbada0;
    background-color: #eee4da;
}

四、游戏逻辑实现

在脚本文件中,我们将编写游戏的核心逻辑,包括初始化游戏、绘制方块、移动操作和合并方块等。

4.1 初始化

首先,我们需要创建一个类似于二维数组的数字格子,并用随机数填充初始状态。

javascript 复制代码
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const gridSize = 4;
const tileSize = 100;
canvas.width = gridSize * tileSize;
canvas.height = gridSize * tileSize;

let board = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));

function initBoard() {
    addRandomTile();
    addRandomTile();
    drawBoard();
}

function drawBoard() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (let r = 0; r < gridSize; r++) {
        for (let c = 0; c < gridSize; c++) {
            drawTile(r, c);
        }
    }
}

function drawTile(r, c) {
    const value = board[r][c];
    ctx.fillStyle = value !== 0 ? getTileColor(value) : '#ccc0b3';
    ctx.fillRect(c * tileSize, r * tileSize, tileSize - 10, tileSize - 10);

    if (value !== 0) {
        ctx.fillStyle = '#776e65';
        ctx.font = 'bold 45px Arial';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(value, c * tileSize + tileSize / 2 - 5, r * tileSize + tileSize / 2);
    }
}

function getTileColor(value) {
    switch (value) {
        case 2: return '#eee4da';
        case 4: return '#ede0c8';
        case 8: return '#f2b179';
        case 16: return '#f59563';
        case 32: return '#f67c5f';
        case 64: return '#f67c5f';
        case 128: return '#edcf72';
        case 256: return '#edcc61';
        case 512: return '#edc850';
        case 1024: return '#edc53f';
        case 2048: return '#edc22e';
        default: return '#ccc0b3';
    }
}

function addRandomTile() {
    let emptyCells = [];
    for (let r = 0; r < gridSize; r++) {
        for (let c = 0; c < gridSize; c++) {
            if (board[r][c] === 0) {
                emptyCells.push({ r, c });
            }
        }
    }

    if (emptyCells.length) {
        const { r, c } = emptyCells[Math.floor(Math.random() * emptyCells.length)];
        board[r][c] = Math.random() < 0.9 ? 2 : 4;
    }
}

4.2 移动与合并方块

通过键盘事件监听来实现方块的移动和合并,我们定义方向常量,结合用户输入的方向实现移动和合并的逻辑。

javascript 复制代码
document.addEventListener('keydown', (event) => {
    switch (event.key) {
        case 'ArrowUp':
            moveUp();
            break;
        case 'ArrowDown':
            moveDown();
            break;
        case 'ArrowLeft':
            moveLeft();
            break;
        case 'ArrowRight':
            moveRight();
            break;
    }
    drawBoard();
});

function canMergeTiles(r1, c1, r2, c2) {
    return board[r1][c1] !== 0 && board[r1][c1] === board[r2][c2];
}

function moveUp() {
    for (let c = 0; c < gridSize; c++) {
        for (let r = 1; r < gridSize; r++) {
            if (board[r][c] !== 0) {
                let targetRow = r;
                while (targetRow > 0 && board[targetRow - 1][c] === 0) {
                    // 向上移动
                    board[targetRow - 1][c] = board[targetRow][c];
                    board[targetRow][c] = 0;
                    targetRow--;
                }
                if (targetRow > 0 && canMergeTiles(targetRow - 1, c, targetRow, c)) {
                    // 合并方块
                    board[targetRow - 1][c] *= 2;
                    board[targetRow][c] = 0;
                }
            }
        }
    }
}

function moveDown() {
    for (let c = 0; c < gridSize; c++) {
        for (let r = gridSize - 2; r >= 0; r--) {
            if (board[r][c] !== 0) {
                let targetRow = r;
                while (targetRow < gridSize - 1 && board[targetRow + 1][c] === 0) {
                    // 向下移动
                    board[targetRow + 1][c] = board[targetRow][c];
                    board[targetRow][c] = 0;
                    targetRow++;
                }
                if (targetRow < gridSize - 1 && canMergeTiles(targetRow + 1, c, targetRow, c)) {
                    // 合并方块
                    board[targetRow + 1][c] *= 2;
                    board[targetRow][c] = 0;
                }
            }
        }
    }
}

function moveLeft() {
    for (let r = 0; r < gridSize; r++) {
        for (let c = 1; c < gridSize; c++) {
            if (board[r][c] !== 0) {
                let targetCol = c;
                while (targetCol > 0 && board[r][targetCol - 1] === 0) {
                    // 向左移动
                    board[r][targetCol - 1] = board[r][targetCol];
                    board[r][targetCol] = 0;
                    targetCol--;
                }
                if (targetCol > 0 && canMergeTiles(r, targetCol - 1, r, targetCol)) {
                    // 合并方块
                    board[r][targetCol - 1] *= 2;
                    board[r][targetCol] = 0;
                }
            }
        }
    }
}

function moveRight() {
    for (let r = 0; r < gridSize; r++) {
        for (let c = gridSize - 2; c >= 0; c--) {
            if (board[r][c] !== 0) {
                let targetCol = c;
                while (targetCol < gridSize - 1 && board[r][targetCol + 1] === 0) {
                    // 向右移动
                    board[r][targetCol + 1] = board[r][targetCol];
                    board[r][targetCol] = 0;
                    targetCol++;
                }
                if (targetCol < gridSize - 1 && canMergeTiles(r, targetCol + 1, r, targetCol)) {
                    // 合并方块
                    board[r][targetCol + 1] *= 2;
                    board[r][targetCol] = 0;
                }
            }
        }
    }
}

五、完善游戏体验

在游戏逻辑实现后,我们需要添加分数计算、胜利和失败的提示,以及重新开始游戏的功能。这些都将进一步提升游戏体验。

5.1 分数系统

我们为游戏添加分数系统,每次合并方块时更新分数。

javascript 复制代码
let score = 0;

function mergeTiles(r1, c1, r2, c2) {
    if (board[r1][c1] === board[r2][c2]) {
        board[r1][c1] *= 2;
        score += board[r1][c1];
        board[r2][c2] = 0;
    }
}

5.2 结束提示

当玩家没有可移动的方块时,可以弹出提示框告知游戏结束。

javascript 复制代码
function checkGameOver() {
    for (let r = 0; r < gridSize; r++) {
        for (let c = 0; c < gridSize; c++) {
            if (board[r][c] === 0) {
                return false; // 还有空格
            }
            if (c < gridSize - 1 && canMergeTiles(r, c, r, c + 1)) {
                return false; // 可以合并
            }
            if (r < gridSize - 1 && canMergeTiles(r, c, r + 1, c)) {
                return false; // 可以合并
            }
        }
    }
    return true; // 游戏结束
}

function showGameOver() {
    alert('游戏结束!您的得分是:' + score);
}

结论

现在,您应该对如何使用 HTML5 Canvas 制作一个 2048 小游戏有了更详细的了解。从简单的 UI 设计到移动和合并方块的逻辑实现,每一步都至关重要。虽然本文未能详细说明所有代码,但希望您能根据提供的思路和示例进行深入探索和实现。创建游戏是一项有趣且富有成就感的工作,快去尝试制作您自己的 2048 小游戏吧!

完整代码

HTML (index.html)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2048 游戏</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <canvas id="gameCanvas"></canvas>
    </div>
    <script src="script.js"></script>
</body>
</html>

CSS (style.css)

css 复制代码
body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #faf8ef;
    font-family: 'Arial', sans-serif;
}

.container {
    position: relative;
}

canvas {
    border: 2px solid #bbada0;
    background-color: #eee4da;
}

JavaScript (script.js)

javascript 复制代码
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const gridSize = 4;
const tileSize = 100;
canvas.width = gridSize * tileSize;
canvas.height = gridSize * tileSize;

let board = Array.from({ length: gridSize }, () => Array(gridSize).fill(0));
let score = 0;

initBoard();

function initBoard() {
    addRandomTile();
    addRandomTile();
    drawBoard();
}

function drawBoard() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (let r = 0; r < gridSize; r++) {
        for (let c = 0; c < gridSize; c++) {
            drawTile(r, c);
        }
    }
    // 显示分数
    ctx.fillStyle = '#776e65';
    ctx.font = 'bold 20px Arial';
    ctx.textAlign = 'center';
    ctx.fillText('Score: ' + score, canvas.width / 2, canvas.height - 20);
}

function drawTile(r, c) {
    const value = board[r][c];
    ctx.fillStyle = value !== 0 ? getTileColor(value) : '#ccc0b3';
    ctx.fillRect(c * tileSize, r * tileSize, tileSize - 10, tileSize - 10);

    if (value !== 0) {
        ctx.fillStyle = '#776e65';
        ctx.font = 'bold 45px Arial';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(value, c * tileSize + tileSize / 2 - 5, r * tileSize + tileSize / 2);
    }
}

function getTileColor(value) {
    switch (value) {
        case 2: return '#eee4da';
        case 4: return '#ede0c8';
        case 8: return '#f2b179';
        case 16: return '#f59563';
        case 32: return '#f67c5f';
        case 64: return '#f67c5f';
        case 128: return '#edcf72';
        case 256: return '#edcc61';
        case 512: return '#edc850';
        case 1024: return '#edc53f';
        case 2048: return '#edc22e';
        default: return '#ccc0b3';
    }
}

function addRandomTile() {
    let emptyCells = [];
    for (let r = 0; r < gridSize; r++) {
        for (let c = 0; c < gridSize; c++) {
            if (board[r][c] === 0) {
                emptyCells.push({ r, c });
            }
        }
    }

    if (emptyCells.length) {
        const { r, c } = emptyCells[Math.floor(Math.random() * emptyCells.length)];
        board[r][c] = Math.random() < 0.9 ? 2 : 4;
    }
}

document.addEventListener('keydown', (event) => {
    let moved = false;
    switch (event.key) {
        case 'ArrowUp':
            moved = moveUp();
            break;
        case 'ArrowDown':
            moved = moveDown();
            break;
        case 'ArrowLeft':
            moved = moveLeft();
            break;
        case 'ArrowRight':
            moved = moveRight();
            break;
    }
    if (moved) {
        addRandomTile();
        drawBoard();
        if (checkGameOver()) {
            showGameOver();
        }
    }
});

function canMergeTiles(r1, c1, r2, c2) {
    return board[r1][c1] !== 0 && board[r1][c1] === board[r2][c2];
}

function moveUp() {
    let moved = false;
    for (let c = 0; c < gridSize; c++) {
        for (let r = 1; r < gridSize; r++) {
            if (board[r][c] !== 0) {
                let targetRow = r;
                while (targetRow > 0 && board[targetRow - 1][c] === 0) {
                    board[targetRow - 1][c] = board[targetRow][c];
                    board[targetRow][c] = 0;
                    targetRow--;
                    moved = true;
                }
                if (targetRow > 0 && canMergeTiles(targetRow - 1, c, targetRow, c)) {
                    board[targetRow - 1][c] *= 2;
                    score += board[targetRow - 1][c];
                    board[targetRow][c] = 0;
                    moved = true;
                }
            }
        }
    }
    return moved;
}

function moveDown() {
    let moved = false;
    for (let c = 0; c < gridSize; c++) {
        for (let r = gridSize - 2; r >= 0; r--) {
            if (board[r][c] !== 0) {
                let targetRow = r;
                while (targetRow < gridSize - 1 && board[targetRow + 1][c] === 0) {
                    board[targetRow + 1][c] = board[targetRow][c];
                    board[targetRow][c] = 0;
                    targetRow++;
                    moved = true
                }
                if (targetRow < gridSize - 1 && canMergeTiles(targetRow + 1, c, targetRow, c)) {
                    board[targetRow + 1][c] *= 2;
                    score += board[targetRow + 1][c];
                    board[targetRow][c] = 0;
                    moved = true;
                }
            }
        }
    }
    return moved;
}

function moveLeft() {
    let moved = false;
    for (let r = 0; r < gridSize; r++) {
        for (let c = 1; c < gridSize; c++) {
            if (board[r][c] !== 0) {
                let targetCol = c;
                while (targetCol > 0 && board[r][targetCol - 1] === 0) {
                    board[r][targetCol - 1] = board[r][targetCol];
                    board[r][targetCol] = 0;
                    targetCol--;
                    moved = true;
                }
                if (targetCol > 0 && canMergeTiles(r, targetCol - 1, r, targetCol)) {
                    board[r][targetCol - 1] *= 2;
                    score += board[r][targetCol - 1];
                    board[r][targetCol] = 0;
                    moved = true;
                }
            }
        }
    }
    return moved;
}

function moveRight() {
    let moved = false;
    for (let r = 0; r < gridSize; r++) {
        for (let c = gridSize - 2; c >= 0; c--) {
            if (board[r][c] !== 0) {
                let targetCol = c;
                while (targetCol < gridSize - 1 && board[r][targetCol + 1] === 0) {
                    board[r][targetCol + 1] = board[r][targetCol];
                    board[r][targetCol] = 0;
                    targetCol++;
                    moved = true;
                }
                if (targetCol < gridSize - 1 && canMergeTiles(r, targetCol + 1, r, targetCol)) {
                    board[r][targetCol + 1] *= 2;
                    score += board[r][targetCol + 1];
                    board[r][targetCol] = 0;
                    moved = true;
                }
            }
        }
    }
    return moved;
}

function checkGameOver() {
    for (let r = 0; r < gridSize; r++) {
        for (let c = 0; c < gridSize; c++) {
            if (board[r][c] === 0) {
                return false; // 还有空格
            }
            if (c < gridSize - 1 && canMergeTiles(r, c, r, c + 1)) {
                return false; // 可以合并
            }
            if (r < gridSize - 1 && canMergeTiles(r, c, r + 1, c)) {
                return false; // 可以合并
            }
        }
    }
    return true; // 游戏结束
}

function showGameOver() {
    alert('游戏结束!您的得分是:' + score);
}
相关推荐
exploration-earth几秒前
Angular 19 新增的一些核心功能
前端·javascript·angular.js
陶甜也11 分钟前
uniapp 自定义日历组件 源码
前端·javascript·css·vue.js·uni-app
计算机-秋大田11 分钟前
基于微信小程序的摄影竞赛系统设计与实现(LW+源码+讲解)
java·前端·后端·微信小程序·小程序·课程设计
nfenghklibra12 分钟前
npm的包管理
前端·npm·node.js
大王在路上15 分钟前
Ant Design Vue --- select组件静态实现模糊搜索
前端·javascript·vue.js·anti-design-vue
远洋录2 小时前
Electron 开发者的 Tauri 2.0 实战指南:文件系统操作
前端·人工智能·react
兔帮大人2 小时前
npm配置electron专属的淘宝镜像进行安装
前端·electron·npm
m0_748248652 小时前
【JavaEE】Spring Web MVC
前端·spring·java-ee
涔溪2 小时前
使用 electron-builder 构建一个 Electron 应用程序 常见问题以及解决办法
前端·javascript·electron
小玉起起2 小时前
前端包管理工具npm、pnpm 和 Yarn 的总结对比
前端·npm·node.js