Godot扫雷游戏制作记录4——计算周围地雷数并显示

本文内容已上传开源仓库:扫雷:一个简单的扫雷游戏,基于Godot4.6 - AtomGit | GitCode

现在,我们来编写计算周围地雷数量的代码,并将其显示在翻开的地块上:

一、添加文本显示子节点

在tile场景下,选中Tile节点,点击左上角添加子节点:

弹出的窗口中,找到Label节点,点击创建:

右键创建好的Label节点,点击重命名,改为"NumLabel":

选中NumLabel,在右侧检查器中,更改Size属性,使其可以完全覆盖整个地块:

接着在右侧检查器中,将水平对齐和垂直对齐都设置为居中:

还是检查器中,找到Font Sizes属性,设为 28 px:

确保Mouse-Fliter属性为"Ignore",防止出现吃输入情况:

二、完善tile.gd

现在,我们来编写显示文字和地雷的代码:

python 复制代码
extends Control

signal clicked(tile: Node)

@onready var bg: ColorRect = $ColorRect
@onready var num_label: Label = $NumLabel  # 【阶段4新增】绑定数字标签

var is_revealed: bool = false
var is_mine: bool = false
var adjacent_mines: int = 0  # 【阶段4新增】记录周围地雷数

func _on_gui_input(event: InputEvent):
	if event is InputEventMouseButton and event.pressed:
		if event.button_index == MOUSE_BUTTON_LEFT:
			clicked.emit(self)

# 【阶段4修改】更新reveal函数,显示数字和颜色
func reveal():
	if is_revealed:
		return
	is_revealed = true
	
	if is_mine:
		bg.color = Color(0.926, 0.0, 0.67, 1.0)
		num_label.text = "💣"  # 【阶段4新增】显示地雷图标
	else:
		bg.color = Color(0.9, 0.9, 0.9)
		# 【阶段4新增】如果周围有地雷,显示数字和对应颜色
		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) # 灰色

2.1 将NumLabel节点绑定到代码里面:

python 复制代码
@onready var num_label: Label = $NumLabel  # 【阶段4新增】绑定数字标签
  • **@onready:**意思是 "等所有节点加载好再绑定"------ 避免 Label 还没加载好就报错;
  • **var num_label:**定义了一个变量代表我们的NumLabel节点;
  • **:Label:**类型注解,明确这是一个Label节点;
  • **$NumLabel:**节点路径简写,找到场景里我们刚加的、名叫NumLabel的 Label 节点。

2.2 记录周围地雷数

python 复制代码
var adjacent_mines: int = 0  # 【阶段4新增】记录周围地雷数

这个没什么好说的,定义了一个整型变量adjacent_mines,用于存储该地块周围的地雷数量。

2.3 更新reveal()函数

首先,我们加了一个地雷的图标:

python 复制代码
	if is_mine:
		bg.color = Color(0.926, 0.0, 0.67, 1.0)
		num_label.text = "💣"  # 【阶段4新增】显示地雷图标

这里的地雷图标,我们直接使用的Windows系统自带的emoji,这样避免了使用额外的美术素材,但会产生一个问题,在不同系统、不同设备上,可能存在显示问题。

接下来,添加数字显示逻辑:

python 复制代码
	else:
		bg.color = Color(0.9, 0.9, 0.9)
		# 【阶段4新增】如果周围有地雷,显示数字和对应颜色
		if adjacent_mines > 0:
			num_label.text = str(adjacent_mines)
  • **if adjacent_mines > 0:**如果周围没有地雷(即adjacent_mines = 0),就不显示数字,保持空白;
  • **str(adjacent_mines):**类型转换,adjacent_mines是整型(int),需要将其转换为字符串,才可以赋值给num_label.text,所以使用str()把数字转换成文字。

最后,使用match语句给不同的数字填上不同的颜色:

python 复制代码
# 经典扫雷数字配色
			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) # 灰色
  • **match语句:**GDScript 的 "开关语句",类似于C语言中的switch-case语句;
  • **逻辑:**如果adjacent_mines = 1,执行1:后面的代码;adjacent_mines = 2,执行2:后面的代码,以此类推;
  • **num_label.modulate:**Label 的 "调制颜色" 属性,用来修改文字的颜色;

三、完善main.gd

python 复制代码
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()  # 【阶段4新增】计算周围地雷数
	_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

# 【阶段4新增】核心函数:计算每个格子周围的地雷数
func _calculate_adjacent_mines():
	# 遍历所有格子
	for i in range(tiles.size()):
		var tile = tiles[i]
		# 如果是地雷,跳过计算
		if tile.is_mine:
			continue
		
		# 索引转坐标(i = y*10 + x)
		var x = i % GRID_SIZE
		var y = i / GRID_SIZE
		
		# 遍历周围8个格子(dx和dy取-1、0、1)
		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
				
				# 检查邻居是否在边界内(0<=x<10,0<=y<10)
				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]
					# 如果邻居是地雷,计数+1
					if neighbor_tile.is_mine:
						mine_count += 1
		
		# 保存计算结果到Tile的变量里
		tile.adjacent_mines = mine_count

func _connect_signals():
	for tile in tiles:
		tile.clicked.connect(_on_tile_clicked)

func _on_tile_clicked(tile: Control):
	tile.reveal()

新增计算周围地雷数的函数:

在该函数中,我们首先遍历所有的格子,跳过地雷:

python 复制代码
# 【阶段4新增】核心函数:计算每个格子周围的地雷数
func _calculate_adjacent_mines():
	# 遍历所有格子
	for i in range(tiles.size()):
		var tile = tiles[i]
		# 如果是地雷,跳过计算
		if tile.is_mine:
			continue
  • **for i in range(tiles.size()):**遍历数组里的所有 100 个格子,i是当前格子的索引(座位号);
  • **if tile.is_mine:continue:**保护逻辑 ------ 如果当前格子是地雷,就continue(跳过这次循环,不计算),因为地雷不需要显示周围数字。

接下来,我们索引转坐标,把数字索引i转变为(x,y)的网格坐标:

python 复制代码
# 索引转坐标(i = y*10 + x)
		var x = i % GRID_SIZE
		var y = i / GRID_SIZE

现在,想象我们的10×10网格是一个教室座位表,i是座位号,从0~99,从第一排第一列开始数。坐标(x,y)是第几列、第几排(x是列号0~9,y是排号0~9)。

那么,我们的索引转坐标公式就是:

  • x = i % GRID_SIZE:列号 = 座位号 ÷ 10的余数;
  • y = i / GRID_SIZE:排号 = 座位号 ÷ 10的商(整数除法);

这样,我们就把索引拆成了列和行,座位号 = 排号 × 10 + 列号。

比如i = 0(第一个格子):x = 0,y = 0 → 左上角(0,0);

比如i = 99(最后一个格子):x = 9,y = 9 → 右下角(9,9)。

接下来,我们初始化计数器,遍历周围的8个邻居:

python 复制代码
# 遍历周围8个格子(dx和dy取-1、0、1)
		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
				
				# 检查邻居是否在边界内(0<=x<10,0<=y<10)
				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]
					# 如果邻居是地雷,计数+1
					if neighbor_tile.is_mine:
						mine_count += 1
		
		# 保存计算结果到Tile的变量里
		tile.adjacent_mines = mine_count
  • **var mine_count = 0:**定义了一个变量,用于存储当前地块周围有多少个地雷,初始记0个地雷;
  • **双层循环遍历dx和dy:**dx是"列偏移量":-1(左边一列)、0(同一列)、1(右边一列);dy是"行偏移量":-1(上面一排)、0(同一排)、1(下面一排);
  • 双层循环会遍历的所有组合:(-1,-1)、(-1,0)、(-1,1)、(0,-1)、(0,0)、(0,1)、(1,-1)、(1,0)、(1,1) → 共 9 个位置,包括自己。
  • **if dx == 0 and dy == 0:continue:**跳过自己。
  • **nx = x + dx:**邻居的列号 = 当前列号 + 列偏移量;
  • **ny = y + dy:**邻居的行号 = 当前行号 + 行偏移量;
  • **if nx >= 0 and nx < GRID_SIZE and ny >= 0 and ny < GRID_SIZE:**边界检查,如果当前格子在网格边缘(如左上角(0,0)),那么它的左边邻居是nx = -1,上面邻居是ny = -1,这两个位置不存在。如果不做边界检查,直接访问tiles-1,将会报错"数组越界"。
  • **neighbor_index = ny * GRID_SIZE + nx:**用刚才讲过的坐标转索引公式,把邻居的坐标变回数组索引;
  • **neighbor_tile = tilesneighbor_index:**从数组里取出邻居格子;
  • **if neighbor_tile.is_mine:mine_count += 1:**如果邻居是地雷,就把计数器上的计数 + 1。
  • **tile.adjacent_mines = mine_count:**将mine_count赋值给地块的adjacent_mines属性,这样,tile.gd脚本中的reveal()函数就可以读出数字并显示了。

最后,我们在_ready()函数里调用地雷函数:

python 复制代码
func _ready():
	_init_grid()
	_place_mines()
	_calculate_adjacent_mines()  # 【阶段4新增】计算周围地雷数
	_connect_signals()

注意!此处的顺序很重要:

  • 必须先调用_init_grid()生成格子
  • 再调用_place_mines()埋地雷
  • 然后才能调用_calculate_adjacent_mines()数地雷------如果没有埋雷,那么数出来的就全是0!
  • 最后调用_connect_signals()连接信号。

四、运行测试

点击运行按钮,可以看到地块已经正常显示数字和地雷了:

相关推荐
晓13131 小时前
【Cocos Creator 3.x】篇——第二章 入门
前端·javascript·游戏引擎
玖玥拾3 小时前
Cocos学习笔记:粒子系统与对象层批量处理
游戏引擎·cocos2d
远程软件小助理4 小时前
ToDesk节点繁忙、向日葵免费试用受限,还有什么免费好用的远程软件推荐?
经验分享·游戏·远程工作
德迅--文琪4 小时前
游戏盾筑牢网络游戏防攻击安全防线
安全·游戏
Rauser Mack6 小时前
不懂编程,但是vibe coding一个扫雷游戏
人工智能·python·游戏·html·prompt
不知名的老吴7 小时前
Unity3D 2022安装教程及全流程下载步骤指南
unity·游戏引擎
程序员也有头发7 小时前
如何使用AI工具开发Unity
unity·游戏引擎·ai编程
caimouse8 小时前
Godot 引擎官方常见问题(FAQ)整理
游戏引擎·godot
一锅炖出任易仙8 小时前
创梦汤锅学习日记day29
学习·ai·ue5·游戏引擎
晓131310 小时前
【Cocos Creator 3.x】篇——第三章 脚本编程
前端·javascript·游戏引擎