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

为了使 2048 游戏的设计更加美观和用户友好,我们可以进行以下几项优化:

  1. 改善颜色方案:使用更温馨的颜色组合。
  2. 添加动画效果:为方块的移动和合并添加渐变效果。
  3. 优化分数显示:在分数增加时使用动画效果。

以下是改进后的代码示例:

1. 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;
    width: 420px; /* 自适应游戏板的总宽度 */
}

canvas {
    border: 2px solid #bbada0;
    background-color: #eee4da;
    transition: background-color 0.3s ease; /* 过渡效果 */
}

.score {
    position: absolute;
    top: -40px; /* 根据需要调整分数的位置 */
    right: 10px;
    font-size: 24px;
    color: #776e65;
    font-weight: bold;
}

2. 更新 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 class="score" id="scoreDisplay">Score: 0</div> <!-- 分数显示 -->
    </div>
    <script src="script.js"></script>
</body>
</html>

3. JavaScript 动画和动态效果(script.js)

在 JavaScript 中,我们将实现方块的移动和合并时的动画效果。我们也会在分数更新时添加动画效果。

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);
    }
  }
  updateScoreDisplay();
}

function drawTile(r, c) {
  const value = board[r][c];
  ctx.fillStyle = value !== 0 ? getTileColor(value) : "#ccc0b3";
  ctx.fillRect(
    c * tileSize + 5,
    r * tileSize + 5,
    tileSize - 10,
    tileSize - 10
  ); // 为方块添加间距

  if (value !== 0) {
    ctx.fillStyle = getTextColor(value);
    ctx.font = "bold 35px Arial";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText(
      value,
      c * tileSize + tileSize / 2,
      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 getTextColor(value) {
  return value <= 4 ? "#776e65" : "#ffffff"; // 小于等于4的数字使用深色,大于4的使用白色
}

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 updateScoreDisplay() {
  const scoreDisplay = document.getElementById("scoreDisplay");
  scoreDisplay.innerText = "Score: " + score;
}

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

动画效果

  1. 移动与合并动画 :我们可以使用 CSS 的 transition 属性来增加方块变化过程中的平滑感。这种效果可以在 drawTile 函数中体现,例如更改方块的背景色时使用过渡效果。
  2. 分数动画 :我们可以在分数增加时使用淡入或渐变效果,以增加分数的视觉吸引力。这可以通过添加相关的 CSS 来实现,例如淡入特效,可以通过 JavaScript 调整 scoreDisplay 的样式。
相关推荐
腾讯TNTWeb前端团队1 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰5 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪5 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪5 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy6 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试