前言
在静态博客中嵌入 JavaScript 小游戏是一种提升用户体验的好方法。之前用 Lua 为 Unity 实现过多种小游戏,现在尝试将经典的贪吃蛇游戏移植到 Hexo 博客中。
本文将详细介绍如何在 Hexo 博客中实现一个可玩的贪吃蛇小游戏,包括游戏页面搭建、核心逻辑实现以及移动端适配。
项目结构
游戏放置在 source/ai-games/snake/ 目录下,文件结构如下:
1 2 3
| source/ai-games/snake/ ├── index.md # 游戏页面(HTML + 样式) └── snake-game.js # 游戏核心逻辑
|
这种独立目录的方式便于管理,每个游戏都有自己完整的资源。
游戏页面实现
游戏页面使用 Hexo 的 标签来嵌入原始 HTML 代码,这样可以避免 Hexo 对 HTML 标签进行转义。
Canvas 画布
1
| <canvas id="game-canvas" width="400" height="400"></canvas>
|
游戏采用 20×20 网格,每个格子 20px,整体画布大小为 400×400 像素。
样式设计
1 2 3 4 5 6 7
| canvas { border: 2px solid #4CAF50; border-radius: 8px; background: #111; box-shadow: 0 0 20px rgba(76, 175, 80, 0.3); touch-action: none; }
|
暗色系背景配合绿色边框,营造游戏氛围。touch-action: none 用于禁止移动端默认触摸行为,避免游戏时页面滚动。
核心逻辑实现
游戏逻辑全部封装在 snake-game.js 文件中,共约 309 行代码。
全局初始化函数
1 2 3 4 5 6
| window.initSnakeGame = function() { const canvas = document.getElementById('game-canvas'); const ctx = canvas.getContext('2d'); };
|
将游戏初始化函数挂载到 window 对象上,便于 PJAX 页面切换后重新调用。
游戏配置
1 2 3 4 5 6 7 8 9
| const gridSize = 20; const cellSize = 20; let snake = [ {x: 10, y: 10}, {x: 9, y: 10}, {x: 8, y: 10} ]; let direction = 'RIGHT'; let nextDirection = 'RIGHT';
|
蛇身使用数组存储,每个元素表示蛇的一节坐标。direction 和 nextDirection 分离的设计可以防止连续快速按键导致蛇身掉头自杀。
蛇的移动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function move() { const opposite = { 'UP': 'DOWN', 'DOWN': 'UP', 'LEFT': 'RIGHT', 'RIGHT': 'LEFT' }; if (nextDirection && opposite[nextDirection] !== direction) { direction = nextDirection; }
const head = snake[0]; let newHead = { ...head }; switch (direction) { case 'RIGHT': newHead.x++; break; case 'LEFT': newHead.x--; break; case 'UP': newHead.y--; break; case 'DOWN': newHead.y++; break; }
const isEating = food && newHead.x === food.x && newHead.y === food.y; let newSnake = [newHead, ...snake]; if (!isEating) newSnake.pop();
snake = newSnake; }
|
核心思路:
- 每次移动在头部添加新方块
- 吃到食物时不移除尾部,蛇身变长
- 未吃到食物时移除尾部,保持蛇身长度不变
碰撞检测
1 2 3 4 5 6 7 8 9 10 11 12
| if (newHead.x < 0 || newHead.x >= gridSize || newHead.y < 0 || newHead.y >= gridSize) { gameOver = true; return; }
if (newSnake.slice(1).some(seg => seg.x === newHead.x && seg.y === newHead.y)) { gameOver = true; return; }
|
食物生成
1 2 3 4 5 6 7 8 9 10 11
| function randomFood() { for (let i = 0; i < 1000; i++) { const x = Math.floor(Math.random() * gridSize); const y = Math.floor(Math.random() * gridSize); if (!snake.some(seg => seg.x === x && seg.y === y)) { return {x, y}; } } return null; }
|
使用循环尝试生成随机位置,直到找到不与蛇身重叠的位置。
绘制系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i <= gridSize; i++) { }
snake.forEach((seg, idx) => { ctx.fillStyle = idx === 0 ? '#8bc34a' : '#4CAF50'; ctx.fillRect(seg.x * cellSize + 1, seg.y * cellSize + 1, cellSize - 2, cellSize - 2); if (idx === 0) { } });
if (food) { ctx.fillStyle = '#f44336'; ctx.beginPath(); ctx.arc(food.x * cellSize + cellSize / 2, food.y * cellSize + cellSize / 2, 8, 0, 2 * Math.PI); ctx.fill(); } }
|
绘制顺序:清空画布 → 网格线 → 蛇身 → 食物 → 游戏结束遮罩。
控制方式
键盘控制
1 2 3 4 5 6 7 8 9
| function handleKeydown(e) { if (e.key.startsWith('Arrow')) e.preventDefault(); switch (e.key) { case 'ArrowUp': nextDirection = 'UP'; break; case 'ArrowDown': nextDirection = 'DOWN'; break; case 'ArrowLeft': nextDirection = 'LEFT'; break; case 'ArrowRight': nextDirection = 'RIGHT'; break; } }
|
触摸滑动控制
1 2 3 4 5 6 7 8 9 10
| canvas.addEventListener('touchend', function(e) { const deltaX = touchEndX - touchStartX; const deltaY = touchEndY - touchStartY; if (Math.abs(deltaX) > Math.abs(deltaY)) { nextDirection = deltaX > 0 ? 'RIGHT' : 'LEFT'; } else { nextDirection = deltaY > 0 ? 'DOWN' : 'UP'; } });
|
通过比较水平/垂直滑动距离来判断滑动方向,实现移动端手势控制。
PJAX 兼容处理
Hexo 的主题通常使用 PJAX 实现无刷新页面切换,这会导致 JavaScript 状态丢失。需要监听页面切换事件重新初始化游戏:
1 2 3 4 5
| document.addEventListener('pjax:success', function() { if (document.getElementById('game-canvas')) { setTimeout(initGameWhenReady, 100); } });
|
同时需要在游戏初始化时清除旧定时器,防止多个游戏循环同时运行:
1 2 3 4
| if (window._snakeTimer) { clearInterval(window._snakeTimer); window._snakeTimer = null; }
|
总结
通过以上方案,我们在 Hexo 博客中成功实现了一个完整的贪吃蛇小游戏。核心要点总结:
| 功能 |
实现方式 |
| 游戏初始化 |
全局函数 window.initSnakeGame |
| 蛇的移动 |
数组队列操作 |
| 碰撞检测 |
边界 + 自身遍历检测 |
| 控制方式 |
键盘方向键 + 触摸滑动 |
| 页面兼容 |
PJAX 事件监听 + 定时器清理 |
游戏已集成到博客的小游戏栏目中,可以直接访问体验。后续计划添加更多经典小游戏,如俄罗斯方块、扫雷等。
相关链接: