Godot扫雷游戏制作记录6——右键插旗功能与胜负判定
生活类比:切换就像 “按开关灯”—— 按一下开灯(true),再按一下关灯(flase),再按一下又开灯…… 循环往复。# 【阶段6新增】核心功能:切换插旗状态# 已翻开不能插旗return# 切换状态:true变false,false变trueis_flagged# 更新显示else:# 发送信号,通知Main更新状态保护逻辑,已翻开和被插旗的格子都不能被翻开切换形态,用!逻辑非来取反更新显示,
·
本文内容已上传开源仓库:扫雷:一个简单的扫雷游戏,基于Godot4.6 - AtomGit | GitCode
现在,我们来完成右键插旗与胜负判定的代码。
一、完善tile.gd
extends Control
signal clicked(tile: Node)
signal flag_changed(flagged: bool) # 【阶段6新增】插旗状态变化信号
@onready var bg: ColorRect = $ColorRect
@onready var num_label: Label = $NumLabel
var is_revealed: bool = false
var is_mine: bool = false
var is_flagged: bool = false # 【阶段6新增】标记是否插旗
var adjacent_mines: int = 0
# 【阶段6修改】处理输入:新增右键逻辑
func _on_gui_input(event: InputEvent):
if event is InputEventMouseButton and event.pressed:
# 左键点击
if event.button_index == MOUSE_BUTTON_LEFT:
clicked.emit(self)
# 【阶段6新增】右键点击:插旗
elif event.button_index == MOUSE_BUTTON_RIGHT:
toggle_flag()
func reveal():
if is_revealed or is_flagged: # 【阶段6修改】已插旗也不能翻开
return
is_revealed = true
if is_mine:
bg.color = Color(0.926, 0.0, 0.67, 1.0)
num_label.text = "💣"
else:
bg.color = Color(0.9, 0.9, 0.9)
if adjacent_mines > 0:
num_label.text = str(adjacent_mines)
match adjacent_mines:
1: num_label.modulate = Color(0, 0, 1)
2: num_label.modulate = Color(0, 0.5, 0)
3: num_label.modulate = Color(1, 0, 0)
4: num_label.modulate = Color(0, 0, 0.5)
5: num_label.modulate = Color(0.5, 0, 0)
6: num_label.modulate = Color(0, 0.5, 0.5)
7: num_label.modulate = Color(0, 0, 0)
8: num_label.modulate = Color(0.5, 0.5, 0.5)
# 【阶段6新增】核心功能:切换插旗状态
func toggle_flag():
# 已翻开不能插旗
if is_revealed:
return
# 切换状态:true变false,false变true
is_flagged = !is_flagged
# 更新显示
if is_flagged:
num_label.text = "🚩"
else:
num_label.text = ""
# 发送信号,通知Main更新状态
flag_changed.emit(is_flagged)
1.1 新增插旗状态变化信号
signal flag_changed(flagged: bool) # 【阶段6新增】插旗状态变化信号
- 新增的这个信号,用于告诉 Main“我插旗了,剩余地雷数减 1”;或把旗拔了,需要告诉 Main“我拔旗了,剩余地雷数加 1”;这个信号就是 Tile 和 Main 之间的 “短信”,用来传递 “插旗 / 拔旗” 的状态。
- 参数flagged: bool是信号的 “捎带信息”,告诉 Main “现在是插旗了还是拔旗了”。
1.2 新增插旗状态变量
var is_flagged: bool = false # 【阶段6新增】标记是否插旗
- 变量名含义:is_flagged,“是否被插旗了”;
- 类型:bool(布尔值),只有两个值:true(是,插旗了)或flase(否,没插旗);
- 初始值:flase——游戏刚开始时,所有格子都没插旗。
1.3 处理输入函数修改
# 【阶段6修改】处理输入:新增右键逻辑
func _on_gui_input(event: InputEvent):
if event is InputEventMouseButton and event.pressed:
# 左键点击
if event.button_index == MOUSE_BUTTON_LEFT:
clicked.emit(self)
# 【阶段6新增】右键点击:插旗
elif event.button_index == MOUSE_BUTTON_RIGHT:
toggle_flag()
这里,我们新增了一个处理右键点击的判断,如果是左键,发送clicked信号,让Main翻开格子。如果是右键,调用toggle_flag()函数,切换插旗状态。
1.4 修改翻开函数
func reveal():
if is_revealed or is_flagged: # 【阶段6修改】已插旗也不能翻开
return
is_revealed = true
if is_mine:
bg.color = Color(0.926, 0.0, 0.67, 1.0)
num_label.text = "💣"
else:
bg.color = Color(0.9, 0.9, 0.9)
if adjacent_mines > 0:
num_label.text = str(adjacent_mines)
match adjacent_mines:
1: num_label.modulate = Color(0, 0, 1)
2: num_label.modulate = Color(0, 0.5, 0)
3: num_label.modulate = Color(1, 0, 0)
4: num_label.modulate = Color(0, 0, 0.5)
5: num_label.modulate = Color(0.5, 0, 0)
6: num_label.modulate = Color(0, 0.5, 0.5)
7: num_label.modulate = Color(0, 0, 0)
8: num_label.modulate = Color(0.5, 0.5, 0.5)
在保护逻辑处加了一个判断,已被翻开的格子和被插旗的格子都不能被翻开。
1.5 新增切换插旗函数
先讲一个核心概念:什么是 “切换”(Toggle)?
生活类比:切换就像 “按开关灯”—— 按一下开灯(true),再按一下关灯(flase),再按一下又开灯…… 循环往复。
# 【阶段6新增】核心功能:切换插旗状态
func toggle_flag():
# 已翻开不能插旗
if is_revealed:
return
# 切换状态:true变false,false变true
is_flagged = !is_flagged
# 更新显示
if is_flagged:
num_label.text = "🚩"
else:
num_label.text = ""
# 发送信号,通知Main更新状态
flag_changed.emit(is_flagged)
这个函数总共做了件事:
- 保护逻辑,已翻开和被插旗的格子都不能被翻开
- 切换形态,用!逻辑非来取反
- 更新显示,插旗显示🚩,拔旗显示空
- 发送信号,通知Main更新状态
在这里,我们还是使用了系统自带的emoji,而非使用额外的美术素材,所以在不同系统不同电脑上可能存在显示问题。
二、完善main.gd
extends Control
const GRID_SIZE = 10
const TILE_SCENE = preload("res://场景/tile.tscn")
const MINE_COUNT = 15
var tiles: Array = []
var remaining_mines: int = MINE_COUNT # 【阶段6新增】剩余地雷数
var game_over: bool = false # 【阶段6新增】游戏结束标志
@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
# 【阶段6修改】连接信号:新增插旗信号
func _connect_signals():
for tile in tiles:
tile.clicked.connect(_on_tile_clicked)
tile.flag_changed.connect(_on_tile_flag_changed) # 【阶段6新增】
# 【阶段6修改】处理左键点击:新增游戏结束判断
func _on_tile_clicked(tile: Control):
if game_over:
return
_reveal_tile(tile)
# 【阶段6新增】如果踩到地雷,游戏结束
if tile.is_mine:
_game_over(false)
# 【阶段6新增】处理插旗信号
func _on_tile_flag_changed(flagged: bool):
if game_over:
return
# 更新剩余地雷数
if flagged:
remaining_mines -= 1
else:
remaining_mines += 1
print("剩余地雷:", remaining_mines) # 后续可以换成UI显示
# 【阶段6修改】递归翻开:新增游戏结束保护
func _reveal_tile(tile: Control):
if game_over or tile.is_revealed or tile.is_flagged:
return
tile.reveal()
if 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)
# 【阶段6新增】每次翻开后检查是否胜利
_check_victory()
# 【阶段6新增】检查是否胜利
func _check_victory():
var unrevealed_safe_count = 0
# 统计未翻开的非地雷格子数
for tile in tiles:
if not tile.is_revealed and not tile.is_mine:
unrevealed_safe_count += 1
# 如果所有非地雷格子都翻开了,胜利
if unrevealed_safe_count == 0:
_game_over(true)
# 【阶段6新增】游戏结束逻辑
func _game_over(victory: bool):
game_over = true
# 翻开所有地雷
for tile in tiles:
if tile.is_mine:
tile.reveal()
# 简单提示(后续可以换成UI弹窗)
if victory:
print("🎉 恭喜你赢了!")
else:
print("💥 游戏结束!你踩到地雷了!")
2.1 新增两个关键状态变量
var remaining_mines: int = MINE_COUNT # 【阶段6新增】剩余地雷数
var game_over: bool = false # 【阶段6新增】游戏结束标志
- remaining_mines:插一个旗减 1,拔一个旗加 1,让玩家知道还剩多少地雷没找出来。
- game_over:一旦变成true,所有操作(左键翻开、右键插旗)都要停止。
2.2 修改信号连接函数
# 【阶段6修改】连接信号:新增插旗信号
func _connect_signals():
for tile in tiles:
tile.clicked.connect(_on_tile_clicked)
tile.flag_changed.connect(_on_tile_flag_changed) # 【阶段6新增】
- 遍历所有 100 个格子;
- 每个格子的
clicked信号都连接到_on_tile_clicked函数; - 每个格子的
flag_changed信号都连接到_on_tile_flag_changed函数(新增的)。
2.3 处理左键点击函数
# 【阶段6修改】处理左键点击:新增游戏结束判断
func _on_tile_clicked(tile: Control):
if game_over:
return
_reveal_tile(tile)
# 【阶段6新增】如果踩到地雷,游戏结束
if tile.is_mine:
_game_over(false)
- 保护:如果
game_over是true(游戏结束了),直接返回,不执行后面的翻开操作; - 翻开地块;
- 踩到地雷,游戏结束。
2.4 新增插旗信号函数
# 【阶段6新增】处理插旗信号
func _on_tile_flag_changed(flagged: bool):
if game_over:
return
# 更新剩余地雷数
if flagged:
remaining_mines -= 1
else:
remaining_mines += 1
print("剩余地雷:", remaining_mines) # 后续可以换成UI显示
- 保护:和左键点击一样,游戏结束了也不能插旗 / 拔旗。
- 如果
flagged是true(插旗了)→ 剩余地雷数减 1(remaining_mines -= 1);如果flagged是false(拔旗了)→ 剩余地雷数加 1(remaining_mines += 1)。 - 打印剩余地雷数
2.5 修改递归翻开函数
# 【阶段6修改】递归翻开:新增游戏结束保护
func _reveal_tile(tile: Control):
if game_over or tile.is_revealed or tile.is_flagged:
return
tile.reveal()
if tile.adjacent_mines == 0:
# ...(遍历邻居的代码不变)
_reveal_tile(neighbor_tile)
# 【阶段6新增】每次翻开后检查是否胜利
_check_victory()
- 游戏结束保护:在最前面加了
game_over—— 游戏结束了,递归也要立刻停止。 - 每次翻开后检查是否胜利;
2.6新增胜利检查函数
# 【阶段6新增】检查是否胜利
func _check_victory():
var unrevealed_safe_count = 0
# 统计未翻开的非地雷格子数
for tile in tiles:
if not tile.is_revealed and not tile.is_mine:
unrevealed_safe_count += 1
# 如果所有非地雷格子都翻开了,胜利
if unrevealed_safe_count == 0:
_game_over(true)
扫雷胜利条件:所有非地雷格子都被翻开了,就算胜利!和插旗数量无关!
- 定义unrevealed_safe_count,统计未翻开的安全格子数量
- 遍历所有格子,统计未翻开的安全格子。如果格子没有被翻开,并且不是地雷,则计数器加一
- 判断是否胜利:如果
unrevealed_safe_count等于 0,说明 “没有未翻开的安全格子了”—— 也就是所有安全格子都翻开了!
2.7 新增游戏结束函数
# 【阶段6新增】游戏结束逻辑
func _game_over(victory: bool):
game_over = true
# 翻开所有地雷
for tile in tiles:
if tile.is_mine:
tile.reveal()
# 简单提示(后续可以换成UI弹窗)
if victory:
print("🎉 恭喜你赢了!")
else:
print("💥 游戏结束!你踩到地雷了!")
- 锁定游戏,把
game_over设为true—— 这样所有的左键点击、右键插旗、递归翻开都会被保护逻辑拦截,游戏彻底锁定。 - 翻开所有地雷
- 简单提示
三、测试游戏
点击右上角的运行按钮,进行简单游戏,可以看到胜利:

失败后,也会有对应的提示:

更多推荐



所有评论(0)