JavaScript Canvas 贪吃蛇游戏实现详解

作者:竹影时间:2026-10-23 06:55:25浏览:309

本文详细解析基于 JavaScript 和 Canvas 实现的贪吃蛇游戏。内容涵盖初始化画布与蛇身数据、绘制背景与果实、绑定键盘事件控制方向、以及通过定时器实现蛇的移动、碰撞检测与加速机制。

JavaScript Canvas 贪吃蛇游戏实现详解

本文详细介绍使用 JavaScript 实现的贪吃蛇游戏案例,供读者参考学习。

GitHub 项目地址:https://github.com/LEERTRT/Snake

在 <script></script> 标签中,文档加载完成后执行以下初始化代码:


$(function () {
  var game = new Game("canvas");
  game.init();
});

游戏初始化与数据准备

其中构造函数 Game() 接收 canvas 的 id 作为参数,实例化对象后调用 init() 函数。init() 函数内部包含三个子函数,下文将逐一说明。


/**
 * 初始化函数,生成构造函数实例后调用此函数
 */
this.init = function () {
  /**
   * 初始化画布和果实
   */
  this.initData();
  /**
   * 1.清空画布;2.画背景格子;3.画蛇;4.画果实
   */
  this.draw();
  /**
   * 绑定事件:方向和速度控制
   */
  this.bindEvents();
};

第一个子函数 initData():在该函数中,声明画布、果实、画布大小、格子数量、方向、速度及蛇身等变量:


var canvas = document.getElementById(id);
if (canvas && canvas.getContext) {
  this.ctx = canvas.getContext('2d');
}
this.foodNode = null;//果实
this.size = canvas.width || $(canvas).width();//画布大小
this.columns = 50;//画布行列数
this.direction = 0;//方向
this.speed = 10;//速度
this.bodyNodes = [];//蛇体

随后随机生成蛇的初始位置:


var rdx = Math.round(Math.random() * (this.columns - 1));
var rdy = Math.round(Math.random() * (this.columns - 1));

将随机生成的 rdx 和 rdy 坐标存入记录蛇体的数组中:


this.bodyNodes.push({
  x: rdx, y: rdy
});

至此,initData() 初始化完成。

画布绘制逻辑

第二个子函数 draw():该函数包含四个步骤:1. 清空画布;2. 绘制背景;3. 绘制蛇身;4. 绘制果实:


this.draw = function () {
  /**
   * 清空画布
   */
  this.clear();
  /**
   * 画画布
   */
  this.drawBG();
  /**
   * 画蛇体
   */
  this.drawBody();
  /**
   *画果实
   */
  this.drawFood();
};

1. 清空画布

使用 clearRect(0, 0, size, size) 即可实现:


this.ctx.clearRect(0, 0, this.size, this.size);

2. 绘制背景

计算每个格子的大小(画布尺寸除以格子数量),然后逐行绘制,类似于绘制棋盘:


var _this = this;
var x = 0, y = 0, size = _this.size / _this.columns;
_this.ctx.strokeStyle = "rgba(124,124,124,0.1)";
//一行一行画画布
for (var i = 0; i < _this.columns; i++) {
  y = i * size;
  for (var j = 0; j < _this.columns; j++) {
    x = j * size;
    _this.ctx.strokeRect(x, y, size, size);
  }
}

3. 绘制蛇身

在 initData() 中,随机生成的蛇位置已存入 bodyNodes 数组。

此处使用 each() 遍历 bodyNodes 中的元素进行绘制。由于蛇吃果实后 bodyNodes 数组元素会增加,因此每次重绘都遍历所有元素。目前处于初始化阶段,bodyNodes 中仅包含 initData() 生成的一个随机元素。

由于后续蛇吃果实后 bodyNodes 数组元素会增加,因此每次重绘都遍历所有元素。目前处于初始化阶段,bodyNodes 中仅包含 initData() 生成的一个随机元素。

由于后续蛇吃果实后 bodyNodes 数组元素会增加,因此每次重绘都遍历所有元素。目前处于初始化阶段,bodyNodes 中仅包含 initData() 生成的一个随机元素。


var nodes = _this.bodyNodes;
$.each(nodes, function (i, node) {
  _this.ctx.fillRect(node.x * size, node.y * size, size, size);
});

4. 绘制果实

绘制果实时,先判断 foodNode 是否存在。若存在则直接绘制;若不存在则随机生成位置。

注意,需判断随机生成的果实位置是否与蛇身重合,若重合则重新生成:


//如果果实重新出现的位置和蛇体重合,重画果实
if (_this.Utils.contains(_this.bodyNodes, {x: rdx, y: rdy})) {
   _this.drawFood();
} else {
   _this.ctx.strokeRect(rdx * size, rdy * size, size, size);
   _this.foodNode = {x: rdx, y: rdy};
}

至此,初始化画布和果实数据及静态绘制已完成,接下来是动态事件绑定。

此处主要描述贪吃蛇的动态逻辑,包括前进、吃果实后变大、越界判断及速度控制等。

动态事件与游戏控制

在 init() 函数已完成 initData() 和 draw() 后,接下来是最后一个函数 bindEvents()。

在 bindEvents() 函数中,包含两个子函数:① 方向控制;② 时间控制。

① 方向控制 directionController

通过 document.body.onkeydown 捕获按键,对 keyCode 进行判断(左 37,上 38,右 39,下 40),并根据 keyCode 设置 direction 标志。需注意:若当前方向为 x,而按下方向为 -x,则按键无效。例如,当蛇向左移动时,按下右键无法改变方向。


case 37: // 左, 1表示右,即当蛇向右行时,按左键不能改变方向,下面同理
  if (_this.direction == 1) return;
  _this.direction = -1;
  break;

设置好方向后,调用 move() 函数控制蛇的移动。即在蛇身数组中新增头部元素并移除尾部元素,同时需进行两项判断:1) 蛇是否咬到自己;2) 是否出界。

调用 food() 方法判断蛇是否吃到果实。在蛇数组中设置好元素后,调用 draw() 重绘,从而完成蛇的移动和吃果实逻辑。


this.food = function () {
  var _this = this;
  var headNode = _this.bodyNodes[0];
  //吃到果实
  if (_this.Utils.equals(headNode, _this.foodNode)) {
    _this.bodyNodes.push(_this.foodNode);//push()方法可向数组的末尾添加一个或多个元素,并返回新的长度。
    _this.foodNode = null;
    var score = _this.bodyNodes.length - 1;
    $('#score').text(score);
    if (score % 10 == 0) {//加速提高难度
      _this.speed += 10;
      _this.timerController();
    }
  }
};

② 时间控制 timerController

时间控制逻辑如下:

利用 setInterval() 函数定时调用 move() 方法,时间间隔为 6000/speed。


this.timerController = function () {
  var _this = this;
  if (_this.timer) {
    clearInterval(_this.timer);
  }
  _this.timer = setInterval(function () {
    _this.move();
  }, 6000 / _this.speed);
  $('#speed').text(_this.speed);
};

最后是工具类方法 contains() 和 equals()。contains 用于判断新生成的随机果实位置是否与蛇身重合,若重合则重新生成随机果实;同时也用于判断蛇是否碰到自己,若碰到则游戏结束。

equals 用于判断蛇头是否与果实重合。两者的区别在于,contains 需要遍历比较,因为蛇身数组包含多个节点,需循环逐一比较;而 equals 仅比较蛇头与果实是否重合,无需循环。


this.Utils = {
  contains: function (arr, o) {
    var _this = this;
    if (!arr || !o) return false;
    var flag = false;
    $.each(arr, function () {
      if (!this) return true;
      if (_this.equals(this, o)) {
        flag = true;
        return false;
      }
    });
    return flag;
  },
  equals: function (o1, o2) {
    if (o1 == o2) return true;
    if (!o1 || !o2) return false;
    return o1.x == o2.x && o1.y == o2.y;
  }
};

更多关于 JavaScript 相关内容感兴趣的读者可查看本站专题:《JavaScript 数学运算用法总结》、《JavaScript 数据结构与算法技巧总结》、《JavaScript 数组操作技巧总结》、《JavaScript 排序算法总结》、《JavaScript 遍历算法与技巧总结》、《JavaScript 查找算法技巧总结》及《JavaScript 错误与调试技巧总结》

希望本文所述对大家 JavaScript 程序设计有所帮助。

作者最新文章