Godot扫雷游戏制作记录5——空白区域自动递归展开
阶段四,我们已经能够显示数字了,但是有一个很麻烦的问题,如果点击一个周围地雷数为0的空白格子,它只会翻开自己这一个,周围的空白格子和数字格子还得我们一个个去点,非常累!现在,我们来修改main.gd,使其可以在点击到白色格子时,自动翻开相邻的所有白格子和数字格子。
·
本文内容已上传开源仓库:扫雷:一个简单的扫雷游戏,基于Godot4.6 - AtomGit | GitCode
阶段四,我们已经能够显示数字了,但是有一个很麻烦的问题,如果点击一个周围地雷数为0的空白格子,它只会翻开自己这一个,周围的空白格子和数字格子还得我们一个个去点,非常累!
现在,我们来修改main.gd,使其可以在点击到白色格子时,自动翻开相邻的所有白格子和数字格子。
一、完善main.gd
extends Control
const GRID_SIZE = 10
const TILE_SCENE = preload("res://场景/tile.tscn")
const MINE_COUNT = 15
var tiles: Array = []
@onready var grid: GridContainer = $grid
func _ready():
_init_grid()
_place_mines()
_calculate_adjacent_mines()
_connect_signals()
func _init_grid():
for i in range(GRID_SIZE * GRID_SIZE):
var tile = TILE_SCENE.instantiate()
tile.custom_minimum_size = Vector2(50, 50)
grid.add_child(tile)
tiles.append(tile)
func _place_mines():
var rng = RandomNumberGenerator.new()
rng.randomize()
var placed_mines = 0
while placed_mines < MINE_COUNT:
var random_index = rng.randi_range(0, tiles.size() - 1)
var tile = tiles[random_index]
if not tile.is_mine:
tile.is_mine = true
placed_mines += 1
func _calculate_adjacent_mines():
for i in range(tiles.size()):
var tile = tiles[i]
if tile.is_mine:
continue
var x = i % GRID_SIZE
var y = i / GRID_SIZE
var mine_count = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
var nx = x + dx
var ny = y + dy
if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:
var neighbor_index = ny * GRID_SIZE + nx
var neighbor_tile = tiles[neighbor_index]
if neighbor_tile.is_mine:
mine_count += 1
tile.adjacent_mines = mine_count
func _connect_signals():
for tile in tiles:
tile.clicked.connect(_on_tile_clicked)
func _on_tile_clicked(tile: Control):
_reveal_tile(tile)
# 【修复】核心递归函数:自动展开空白区域
func _reveal_tile(tile: Control):
if tile.is_revealed:
return
tile.reveal()
# 【修复】只有当“不是地雷”且“周围地雷数为0”时,才递归翻开周围
if not tile.is_mine and tile.adjacent_mines == 0:
var i = tiles.find(tile)
var x = i % GRID_SIZE
var y = i / GRID_SIZE
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
var nx = x + dx
var ny = y + dy
if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:
var neighbor_index = ny * GRID_SIZE + nx
var neighbor_tile = tiles[neighbor_index]
_reveal_tile(neighbor_tile)
1.1 处理点击函数
回顾阶段四的点击处理函数与新的处理函数:
# 阶段四的写法(直接翻开)
# func _on_tile_clicked(tile: Control):
# tile.reveal()
# 【阶段五修改】改成调用递归函数
func _on_tile_clicked(tile: Control):
_reveal_tile(tile)
- 阶段四我们只是 “点击哪个翻开哪个”;
- 阶段五我们需要 “点击一个,翻开一片”,所以要把逻辑拆到一个单独的递归函数里。
1.2 _reveal_tile递归函数
递归就像 “多米诺骨牌”—— 你推倒第 1 个骨牌,第 1 个骨牌会推倒第 2 个,第 2 个推倒第 3 个…… 直到所有能推倒的骨牌都倒下。
- 你点击第 1 个空白格子(推倒第 1 个骨牌);
- 第 1 个空白格子会 “调用”_reveal_tile去翻开它周围的 8 个邻居;
- 邻居里如果有空白格子,又会继续 “调用”_reveal_tile去翻开它的邻居;
- 直到所有相连的空白格子和数字格子都翻开。
# 【修复】核心递归函数:自动展开空白区域
func _reveal_tile(tile: Control):
if tile.is_revealed:
return
tile.reveal()
# 【修复】只有当“不是地雷”且“周围地雷数为0”时,才递归翻开周围
if not tile.is_mine and tile.adjacent_mines == 0:
var i = tiles.find(tile)
var x = i % GRID_SIZE
var y = i / GRID_SIZE
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
var nx = x + dx
var ny = y + dy
if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:
var neighbor_index = ny * GRID_SIZE + nx
var neighbor_tile = tiles[neighbor_index]
_reveal_tile(neighbor_tile)
1.2.1 保护逻辑
if tile.is_revealed:
return
如果当前地块已经被翻开,那么什么都不做。
1.2.2 翻开当前地块
tile.reveal()
1.2.3 【核心】判断是否需要递归
# 【修复】只有当“不是地雷”且“周围地雷数为0”时,才递归翻开周围
if not tile.is_mine and tile.adjacent_mines == 0:
- not tile.is_mine:当前格子不是地雷 —— 如果是地雷,绝对不能递归,否则会把所有地雷都翻开!
- tile.adjacent_mines == 0:当前格子周围地雷数为 0—— 也就是 “空白格子”,只有空白格子才需要自动展开周围。
1.2.4 找到周围的邻居(和阶段四几乎相同)
var i = tiles.find(tile)
var x = i % GRID_SIZE
var y = i / GRID_SIZE
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
var nx = x + dx
var ny = y + dy
if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:
var neighbor_index = ny * GRID_SIZE + nx
var neighbor_tile = tiles[neighbor_index]
这部分和阶段四的_calculate_adjacent_mines()函数几乎一摸一样,我们简单回顾一下:
- 用tiles.find(tile)找到当前格子在数组里的索引;
- 索引转坐标(x,y);
- 双层循环遍历dx和dy,找周围8个邻居;
- 边界检查,避免数组越界;
- 坐标转回索引,取出邻居格子neighbor_tile。
1.2.5 【关键】递归调用自己
_reveal_tile(neighbor_tile)
每取出一个邻居格子,让邻居格子也执行一遍_reveal_tile函数。
执行流程:
- 你点击格子 A(空白格子);
- 格子 A 执行_reveal_tile,翻开自己,然后调用_reveal_tile(格子B)(它的邻居);
- 格子B执行_reveal_tile:如果格子B是数字格子,翻开自己,不递归;如果格子B是空白格子:翻开自己,继续调用_reveal_tile(格子C);
- 以此类推,直到所有相连的空白格子和数字格子都翻开。
二、测试游戏
点击右上角的测试,可以看到当我们的鼠标点击到空白格子时,将会翻开相邻一大片的格子:

更多推荐


所有评论(0)