发布于 2026-01-05 10 阅读
0

如何编写贪吃蛇游戏代码🐍

如何编写贪吃蛇游戏代码🐍

我通常不会把这类博客发到开发者社区,因为这通常是我自己做的事情。游戏开发很有趣,我从中获得了许多乐趣。如果你喜欢这篇博客,请告诉我,也许我会在这里发布更多内容!

诺基亚的贪吃蛇游戏于 1997 年随诺基亚 6110 手机一同推出,这款手机堪称移动游戏的催化剂!然而,贪吃蛇游戏的概念早在 1997 年的版本问世之前就已存在。在另一篇博客中,我探讨了如何编写 Blockade(1976 年)这款游戏,它本质上就是玩家对战的贪吃蛇游戏,比 1997 年的版本早了整整 21 年!

在这篇博客中,我将一步一步地讲解如何编写贪吃蛇游戏的代码,因为贪吃蛇游戏通常被推荐给编程新手。然而,我个人并不推荐初学者直接编写贪吃蛇游戏,因为它确实有一些需要摸索才能解决的棘手问题。如果你是一名编程新手,我建议你看看“适合初学者编写代码的五大最佳游戏”!或者,如果你从未接触过编程,那么可以看看“如何以初学者的身份编写游戏”

让我们开始编写贪吃蛇游戏的代码吧!

所以,在这篇博客中,我将使用https://editor.p5js.org/,它使用了p5.js库,这使得 Canvas 的使用体验更加流畅。我为这篇博客编写的所有代码都可以在这里找到,如果您遇到任何问题,可以随时参考。

为蛇建造一个居住的网格

如果你研究一下贪吃蛇游戏,你会发现所有东西都放置在一个隐藏的网格中,让我们创建一个我们自己的网格——最后会将其移除——这样我们就可以把所有东西都放在合适的位置,让我们的游戏更轻松。



const GRID_SIZE = 20;

function setup() {
   createCanvas(700, 700);
}

function draw() {
  background(155, 204, 153);

  for (let x = 0; x < width; x += width / GRID_SIZE) {
    for (let y = 0; y < height; y += height / GRID_SIZE) {
        stroke(255);
        strokeWeight(1);
        line(x, 0, x, height);
        line(0, y, width, y);
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

这样应该会得到类似这样的结果:

绿底白格<br>

现在我们可以随时调整GRID_SIZE游戏设置,使其更符合我们的喜好。这是一个重要的变量,蛇的位置和食物的位置都将基于此。

创造蛇

让我们创建一个名为 `.snake.js` 的新文件snake.js,其中将包含我们蛇的类。别忘了添加对 `.snake.js` 的引用index.html,以便我们能够使用它:



  <body>
    <script src="sketch.js"></script>
    <script src="snake.js"></script> // add this
  </body>


Enter fullscreen mode Exit fullscreen mode

SNAKE.JS



class Snake {
  constructor() {
    this.body = [];
    this.body.push({x: width/2, y: height/2}); // the head of the snake
    this.dir = 1; // 1 = right, 2 = down, 3 = left, 4 = right
  }

  draw() {
    fill(0);
    for (let b of this.body) {
      rect(b.x, b.y, width / GRID_SIZE, height / GRID_SIZE)
    }
  }

  update() {
    if (this.dir == 1) {
      this.body[0].x += width / GRID_SIZE;  
    } else if (this.dir == 2) {
      this.body[0].y += height / GRID_SIZE;
    } else if (this.dir == 3) {
      this.body[0].x -= width / GRID_SIZE;
    } else if (this.dir == 4) {
      this.body[0].y -= height / GRID_SIZE;
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

所以我们为蛇创建了一个body数组,其中包含蛇身各部分的坐标xy位置。我们还在构造函数中将蛇头添加到蛇身,所以当我们创建一个新的 Snake 对象时,蛇头就被添加到蛇身中。

我正在预判蛇的移动,我知道它可以上下左右移动,所以如果值dir设置为 1,我们就向右移动,如果设置为 2,我们就向下移动,设置为 3,我们就向左移动,设置为 4,我们就向上移动。

我们还有一种draw方法,可以简单地绘制代表蛇身的矩形。

最后,还有一种update方法,它只移动蛇头,使其朝向我们移动的方向。注意,我只移动蛇头,因为如果我移动整条蛇,它看起来就完全不像蛇了。我们之后还需要用到这种方法,因为我们需要更新蛇身的其他部分——前提是我们已经有了蛇身的其他部分,而不仅仅是蛇头。

SKETCH.JS

回到游戏,sketch.js我们需要创建蛇形对象并调用更新/绘制方法。另外,我们将帧速率限制为 4,以营造复古感!



const GRID_SIZE = 20;

let snake;
function setup() {
  createCanvas(700, 700);
  snake = new Snake();
  frameRate(4);
}

function draw() {
  background(155, 204, 153);
  for (let x = 0; x < width; x += width / GRID_SIZE) {
    for (let y = 0; y < height; y += height / GRID_SIZE) {
        stroke(255);
        strokeWeight(1);
        line(x, 0, x, height);
        line(0, y, width, y);
    }
  }

  snake.update();
  snake.draw();
}


Enter fullscreen mode Exit fullscreen mode

点击播放后,你应该会看到类似这样的画面:

蛇向右移动

添加按键处理程序

当我们按下方向键时,我们希望改变方向,请将此功能添加到您的代码中。sketch.js



function keyPressed() {
  if (keyCode === 39 && snake.dir !== 3) {
    snake.dir = 1;
  } else if (keyCode === 40 && snake.dir !== 4) {
    snake.dir = 2;
  } else if (keyCode === 37 && snake.dir !== 1) {
    snake.dir = 3;
  } else if (keyCode === 38 && snake.dir !== 2) {
    snake.dir = 4;
  } 
}


Enter fullscreen mode Exit fullscreen mode

所以,它的意思是,当我们按下右键但没有向左移动时,就改变方向向右;当我们按下下键但没有向上移动时,就向下移动,等等。

玩一下:

蛇移动

给蛇添加食物

蛇吃东西就会长大,我们喂它吧。

食品.JS

创建一个新文件food.js,并记得在你的文件中添加对此文件的引用index.html



class Food {
  constructor() {
    this.spawn();
  }

  spawn() {
    let randX = random(width);
    let randY = random(height);
    this.x = randX - randX % (width / GRID_SIZE);
    this.y = randY - randY % (height / GRID_SIZE)
  }

  draw() {
    fill(255, 100, 100);
    rect(this.x, this.y, width / GRID_SIZE, height / GRID_SIZE);
  }
}


Enter fullscreen mode Exit fullscreen mode

所以我们给食物在网格中随机放置一个位置,代码randX - randX % (width / GRID_SIZE);只是允许我们将食物对齐到网格的某个方格内。

蛇不吃

我们已经放好了食物,但是没有给蛇提供吃东西的途径😢 让我们给它一张嘴吧。

在我们的主绘图函数中sketch.js



...
function draw() {
  background(155, 204, 153);
  for (let x = 0; x < width; x += width / GRID_SIZE) {
    for (let y = 0; y < height; y += height / GRID_SIZE) {
        stroke(255);
        strokeWeight(1);
        line(x, 0, x, height);
        line(0, y, width, y);
    }
  }

  snake.update();

  if (snake.hasEatenFood()) {   // add this code
    food.spawn();
  }

  snake.draw();
  food.draw();

}


Enter fullscreen mode Exit fullscreen mode

我们还没有编写该hasEatenFood方法,所以让我们把它添加进去。snake.js



...
hasEatenFood() {
    if (this.body[0].x == food.x && this.body[0].y == food.y) {
      return true;     
    }
  }


Enter fullscreen mode Exit fullscreen mode

蛇吃东西但不生长

太棒了!现在我们的蛇可以吃东西而不会变大,羡慕吗?让我们添加一些代码,让我们的蛇变大吧。

SNAKE.JS

这里还有一些东西需要添加,这是最终的完整文件,下面我将解释添加的内容:



class Snake {
  constructor() {
    this.body = [];
    this.body.push({x: width/2, y: height/2}); // the head of the snake
    this.dir = 1; // 1 = right, 2 = down, 3 = left, 4 = right

    this.lastX = width/2;
    this.lastY = height/2;
  }

  draw() {
    fill(0);
    for (let b of this.body) {
      rect(b.x, b.y, width / GRID_SIZE, height / GRID_SIZE)
    }
  }

  update() {
    this.lastX = this.body[this.body.length-1].x;     // track the last X and Y  
    this.lastY = this.body[this.body.length-1].y;     // so we can put the new body there
    for (let i = this.body.length-1; i >= 1; i--) {
      this.body[i].x = this.body[i-1].x;
      this.body[i].y = this.body[i-1].y;
    }

    if (this.dir == 1) {
      this.body[0].x += width / GRID_SIZE;  
    } else if (this.dir == 2) {
      this.body[0].y += height / GRID_SIZE;
    } else if (this.dir == 3) {
      this.body[0].x -= width / GRID_SIZE;
    } else if (this.dir == 4) {
      this.body[0].y -= height / GRID_SIZE;
    }
  }

  grow() {
    this.body.push({x: this.lastX, y: this.lastY});
  }

  hasEatenFood() {
    if (this.body[0].x == food.x && this.body[0].y == food.y) {
      return true;     
    }
  }

}


Enter fullscreen mode Exit fullscreen mode

我们现在追踪蛇的头部lastX和尾部lastY,这样当蛇进食时,食物基本上就会落到那个位置,也就是蛇的尾部。我还添加了代码来更新蛇的整个身体,所以在更新方法中你会看到以下代码:



    for (let i = this.body.length-1; i >= 1; i--) {
      this.body[i].x = this.body[i-1].x;
      this.body[i].y = this.body[i-1].y;
    }


Enter fullscreen mode Exit fullscreen mode

这是将每个主体更新为其前x一个y元素的主体,记住头部位于索引 0。

我们还添加了一个grow()方法,该方法只需将新内容添加到数组中即可。

蛇现在正在进食和生长。

添加命中检测

我们要确保蛇不会撞到自己,如果撞到了,我们就需要重新开始游戏。

我添加了一个新方法,该方法会在类的`<method> hitDetection()` 方法中调用update()snake



  hitDetection() {
    for (let i = 1; i < this.body.length; i++) {
      if (this.body[0].x == this.body[i].x && this.body[0].y == this.body[i].y) {
        this.spawn();
      }
    }
  }


Enter fullscreen mode Exit fullscreen mode

这段代码简单地检查蛇头是否碰到身体的其他部位。我还把构造函数代码移到了一个spawn()与类相同的方法中food,以下是全部代码:



class Snake {
  constructor() {
    this.spawn();
  }

  // added this
  spawn() {
    this.body = [];
    this.body.push({x: width/2, y: height/2}); 
    this.dir = 1; 
    this.lastX = width/2;
    this.lastY = height/2;
  }

  draw() {
    fill(0);
    for (let b of this.body) {
      rect(b.x, b.y, width / GRID_SIZE, height / GRID_SIZE)
    }
  }

  update() {
    this.hitDetection();

    this.lastX = this.body[this.body.length-1].x;   
    this.lastY = this.body[this.body.length-1].y;     
    for (let i = this.body.length-1; i >= 1; i--) {
      this.body[i].x = this.body[i-1].x;
      this.body[i].y = this.body[i-1].y;
    }

    if (this.dir == 1) {
      this.body[0].x += width / GRID_SIZE;  
    } else if (this.dir == 2) {
      this.body[0].y += height / GRID_SIZE;
    } else if (this.dir == 3) {
      this.body[0].x -= width / GRID_SIZE;
    } else if (this.dir == 4) {
      this.body[0].y -= height / GRID_SIZE;
    }
  }

  // added this
  hitDetection() {
    for (let i = 1; i < this.body.length; i++) {
      if (this.body[0].x == this.body[i].x && this.body[0].y == this.body[i].y) {
        this.spawn();
      }
    }
  }

  grow() {
    this.body.push({x: this.lastX, y: this.lastY});
  }

  hasEatenFood() {
    if (this.body[0].x == food.x && this.body[0].y == food.y) {
      return true;     
    }
  }

}


Enter fullscreen mode Exit fullscreen mode

蛇现在正在进食和生长。

失去电网

接下来,让我们删除为网格添加的代码,它只是为了确保所有内容都排列整齐!

所以你的绘图函数sketch.js应该看起来像这样:



function draw() {
  background(155, 204, 153);
  /**
  let x = 0; x < width; x += width / GRID_SIZE) {
  for (let y = 0; y < height; y += height / GRID_SIZE) {
      stroke(255);
      strokeWeight(1);
      line(x, 0, x, height);
      line(0, y, width, y);
    }
  }
  */
  snake.update();

  if (snake.hasEatenFood()) {
    food.spawn();
    snake.grow();
  }
  stroke(155, 204, 153); // add this
  snake.draw();
  food.draw();

}


Enter fullscreen mode Exit fullscreen mode

贪吃蛇游戏的最终结果

接下来是什么?

贪吃蛇游戏最复杂的部分已经完成,但还有一些地方需要调整。学习的最佳方法就是实践,所以我鼓励你继续编写代码,看看能否完成以下任务。如果你完成了其中任何一项或全部任务,请务必告诉我,我将不胜感激!

  1. 当蛇到达地图边缘时,游戏应该重新开始,或者蛇应该从墙的另一侧出来。
  2. 添加评分系统并显示分数(可以是蛇的body长度)
  3. 确保食物不会产在蛇的身上。
  4. 这条蛇的初始体长就设定为5。

希望您喜欢这篇博客。如果您奇迹般地喜欢上了我的絮叨,那就去我的博客网站codeheir.com看看吧,我每周都会在那里写博客,内容涵盖编程世界中所有吸引我注意力的话题!

文章来源:https://dev.to/lukegarrigan/how-to-code-snake-1jeb