贪吃蛇

版本:4.5.1 2025-10-15

源码

演示

开发流程
蛇头部

新建一个场景(snake),创建头部(Sprite2D)。

声明方向(direction),声明速度(speed),在物理帧 `_physics_process()` 中移动,每帧累加,直至累加至一格才会执行移动,并保留余数。

func _physics_process(delta: float):
  elapse += delta * speed
  var step = floor(elapse / UNIT)
  if step == 0:
    return
  elapse = (elapse as int) % UNIT

  if direciton == Vector2.UP:
    position.y -= UNIT
  elif direction == Vector2.DOWN:
    position.y += UNIT
  elif direction == Vector2.LEFT:
    position.x -= UNIT
  elif direction == Vector2.RIGHT:
    position.x += UNIT
蛇身体

声明一个数组(Array)来保存可以动态增长的身体,每次吃食物时,向数组中增加一个新部分(Sprite2D),位置和头部相同。

func grow() -> void:
  var tail := Sprite2D.new()
  tail.texture = snake_head.texture
  tail.position = snake_head.position
  tail.scale = Vector2(0.9, 0.9)
  
  body_parts.append(tail)
  snake_body.add_child(tail)

每次移动,先保存头部当前的位置,然后头部向对应方向移动一格,再取数组最后一节,位置改成头部原来的位置,并把这个节点放到数组的最前面,视觉上看起来就是整条蛇跟随头部一起移动了。

  if not body_parts.is_empty():
    var last = body_parts.pop_back()
    last.position = current_head_position
    body_parts.insert(0, last)
判断碰撞

遍历对应身体的数组,如果有一个身体部分与头部位置相同,就认为碰撞。

func is_collided(new_position: Vector2):
  for body_part in body_parts:
    if body_part.position.x == new_position.x and body_part.position.y == new_position.y:
      return true
  return false
食物

游戏启动后,随机更新食物的坐标。

蛇每次移动后,判断是否和食物碰撞,如果碰撞,身体长度增加,随机更新食物的位置,如果食物移动到蛇身体上,要再次更新,保证食物更新到空地。

func _process(_delta: float) -> void:
  if is_zero_approx(snake.snake_head.position.x - snake_food.position.x) and is_zero_approx(snake.snake_head.position.y - snake_food.position.y):
    snake.grow()

    move_random()
    while snake.is_collided(snake_food.position):
      move_random()
计分板

回来看蛇本体和食物之外的配套组件,创建一个Label作为计分板,暂停界面是包含两行文字的一个组件(Control)。

每次蛇头部与食物碰撞,会触发一个信号(snake_grow),携带蛇身体数组作为参数,接收到这个信号后,根据蛇身体数组的长度更新计分板分数。

暂停界面

暂停界面是包含两行文字的一个组件(Control)。

游戏启动时,暂停界面默认显示,蛇本体状态为暂停(active=false),按任何健启动游戏。启动时,清空蛇身体数组,随机生成蛇头部位置,蛇身体长度增加一节,速度设为默认,启动游戏(active=true)。

func restart_game():
  snake.restart()
  $StartScreen.set_visible(false)

func restart():
  for body_part in body_parts:
    body_part.queue_free()
  body_parts = []

  var x = (floor(randf_range(0, WIDTH)) * UNIT + OFFSET) as int
  var y = (floor(randf_range(0, HEIGHT)) * UNIT + OFFSET) as int
  snake_head.position = Vector2(x, y)

  grow()

  speed = DEFAULT_SPEED
  active = true
游戏结束

蛇头部与身体碰撞后,触发一个事件(snake_terminate)。设置蛇本体停止,显示暂停界面。

func terminate_game():
  snake.active = false
  $StartScreen.set_visible(true)

实体

  • #1. 蛇本身
  • #2. 食物
  • #3. 计分板
  • #4. 暂停界面
#1. 蛇本身
属性
  • #1.1. 蛇分为头、身两部分,身体最小长度为一
  • #1.2. 头部当前位置
  • #1.3. 有一个方向,控制移动的方向
  • #1.4. 有一个移动速度
  • #1.5. 启用状态
行为
  • #1.6. 头部按照方向和速度移动
  • #1.7. 身体跟随头部移动
  • #1.8. 头碰撞身体,游戏结束
  • #1.9. 头碰撞食物,身体变长
#2. 食物
属性
  • #2.1. 位置
行为
  • #2.2. 随机移动
#3. 计分板
属性
  • #3.1. 得分
行为
  • #3.2. 蛇碰撞到食物,得分加一
  • #3.3. 重启游戏,得分归零
#4. 暂停界面
属性
  • #4.1. 是否显示
行为
  • #4.2. 任意按键,隐藏暂停界面,游戏启动
  • #4.3. 蛇碰撞自身,游戏暂停,显示暂停界面