【Godot4.2】自定义简单的参数化2D网格节点

概述

在某些情况下我们可能需要在Godot中自定义2D网格,因为此时可能用TileMap会显得太“重”,因为我们可能只需要其作为网格的功能却不需要它的其他功能,比如绘制瓦片地图。而且我们可能需要在网格功能的基础上,添加更多自定义的内容。

本篇就介绍如何通过几个简单的参数定义2D网格,以及用CanvasItem的绘图函数来在ControlNode2D上显示网格。

最后,介绍如何将一个普通脚本变为为自定义节点,以便于在多个场景或多个项目中重复使用。

定义网格

定义一个2D网格,只需要2个参数,grid_sizecell_size:

  • grid_size:定义网格的尺寸 ,也就是水平有多少列,垂直有多少行,当然你也可以拆分成类似rowscols这样的两个单独参数
  • cell_size:定义单个单元格的尺寸

我们从一个简单的2D场景开始:
在这里插入图片描述
直接在空的Node2D节点上添加如下代码:

extends Node2D

var grid_size = Vector2i(10,10) # 网格尺寸 - 有多少行、多少列
var cell_size = Vector2i(32,32) # 单元格大小

没错,一个逻辑上的二维网格就定义完了,虽然你无法看到它,但你绝对可以使用它。

显示网格

我们有两种思路绘制网格:

  • 思路1:将单元格视为矩形,网格就是这些单元格水平和垂直阵列的结果
  • 思路2:将整个网格视为多条水平线段和垂直线段依次绘制叠加的结果

基于单元格矩形绘制网格

为了方便绘制,这里首先编写一个简单的函数来获取对应位置单元格的矩形Rect2类型)。

# 返回对应单元格的矩形
func get_cell_rect(cell_pos:Vector2i) -> Rect2:
	return Rect2(cell_pos * cell_size,cell_size)

然后我们只需要在_draw()虚函数中,遍历网格中所有的(x,y)组合的位置,用draw_rect绘图函数绘制单元格矩形就可以了。

func _draw():
	# 绘制网格
	for x in grid_size.x:
		for y in grid_size.y:
			var rect:Rect2 = get_cell_rect(Vector2(x,y)) # 获取对应位置单元格的Rect2
			draw_rect(rect,Color.YELLOW,false,1)

完整代码如下:

extends Node2D

var grid_size = Vector2i(10,10) # 网格尺寸 - 有多少行、多少列
var cell_size = Vector2i(32,32) # 单元格大小

# 返回对应单元格的矩形
func get_cell_rect(cell_pos:Vector2i) -> Rect2:
	return Rect2(cell_pos * cell_size,cell_size)

func _draw():
	# 绘制网格
	for x in grid_size.x:
		for y in grid_size.y:
			var rect:Rect2 = get_cell_rect(Vector2(x,y)) # 获取对应位置单元格的Rect2
			draw_rect(rect,Color.YELLOW,false,1)

运行场景后可以看到绘制出的网格:
在这里插入图片描述

优化代码

上面的代码优化如下:

@tool
extends Node2D

## 是否显示网格
@export var show_grid:bool = false:
	set(val):
		show_grid = val
		queue_redraw()

## 网格尺寸
@export var grid_size = Vector2i(10,10):
	set(val):
		grid_size = val
		queue_redraw()

## 单元格大小
@export var cell_size = Vector2i(32,32):
	set(val):
		cell_size = val
		queue_redraw()

## 边框颜色
@export var border_color = Color.YELLOW:
	set(val):
		border_color = val
		queue_redraw()

## 边框线宽
@export var border_width:float = 1.0:
	set(val):
		border_width = val
		queue_redraw()



# 返回对应单元格的矩形
func get_cell_rect(cell_pos:Vector2i) -> Rect2:
	return Rect2(cell_pos * cell_size,cell_size)

func _draw():
	if show_grid:
		# 绘制网格
		for x in grid_size.x:
			for y in grid_size.y:
				var rect:Rect2 = get_cell_rect(Vector2(x,y)) # 获取对应位置单元格的Rect2
				draw_rect(rect,border_color,false,border_width) 

此时,你便拥有了一个参数化的2D网格,可以直接在检视器面板修改参数,且实时在Godot编辑器中查看效果:

请添加图片描述

初学看似神奇,实际很简单,这里我只给不知晓工具脚本的新手讲解一下,大佬可以略过

我们拎出前面的几行代码来看:

@tool
extends Node2D

## 是否显示网格
@export var show_grid:bool = false:
	set(val):
		show_grid = val
		queue_redraw()
  • 首先,我在代码最顶部添加了@tool关键字,这意味着,整个脚本的逻辑可以直接在Godot编辑器中运行,而不需要运行场景
  • 其次,我用@export var申明了一个布尔类型的show_grid导出变量,默认值为false,导出变量是一种可以在检视器面板显示和编辑的脚本变量。
  • @export var申明导出变量的最后,我添加了一个额外的冒号,回车缩进一格后使用set(val):,这是Godot4新版本的GDScript中,为变量设定setter函数的新写法
  • show_grid变量的setter函数体中,我们用show_grid = val形式将参数val传入的新值赋值给变量自身
  • 接着我们调用queue_redraw(),它会主动调用执行一次_draw()

然后我们再看_draw()的部分:

func _draw():
	if show_grid:
		...

我们通过if语句判断show_grid的值是否为真,再执行其后续的代码。这样一切就联动起来了:

  • 我们在检视器面板修改show_grid的值,也就是勾选或不勾选时,其setter函数会在存储修改的新值后,调用queue_redraw()
  • queue_redraw()执行_draw()_draw()内我们使用show_grid的新值进行判断…

通过类似上面的联动方式,自己工具脚本在Godot编辑器中直接执行的特性,让我们可以通过修改导出变量,来获得实时生成的效果。

将普通脚本声明为自定义节点

上面的代码离自定义节点只差一步之遥。而这关键的一步就是声明自定义类名

通过class_name关键字,我们可以将普通脚本声明为自定义类,而如果这个脚本刚好是某个节点的脚本,那么这个自定义类就会因为extends关键字的存在而继承这个节点的类型,从而成为其子类型,也就变成了一个自定义节点(Custom Node)。

@tool
class_name Grid2D # 申明自定义类名
extends Node2D    # 继承自Node2D,所以Grid2D便是Node2D的子类型,也就成了自定义节点

class_nameextends可以分开两行写,也可以组合成一行写,也就是:

@tool
class_name Grid2D extends Node2D

而作为自定义节点,我们就可以通过添加节点对话框,查找和添加该类型的实例。
在这里插入图片描述
恭喜你已经学会了Godot自定义节点的基础技能!!


个人认为:

  • 自定义节点自定义资源自定义类自定义静态函数库以及面向对象思路Godot没有明说的核心
  • 包括编写Godot编辑器插件,因为这些自定义功能的存在,才让Godot拥有强大的拓展能力和无限可能。

拓展功能

上面已经成功编写出了自定义的2D网格节点,并且可以实现基础的定义、修改以及显示。

但是离实用性可能还差的很远。这时你就需要编写一些额外的方法来拓展这个自定义节点的功能,比如说:

  • 获取鼠标所在位置单元格的坐标
  • 获取某个单元格的中心点,放置棋子或玩家
  • 基于网格实现上下左右移动等等

这里我只简单拓展几个函数,你可以根据自己的需要,自行拓展。

将屏幕坐标转为单元格坐标

这里的屏幕坐标指的是运行场景后,游戏窗体内的全局坐标。

# 将屏幕坐标转为单元格坐标
func to_cell_pos(screen_pos:Vector2):
	return floor(screen_pos / Vector2(cell_size))

获取单元格中心位置

编写函数获取指定位置单元格的中心点。

# 获取单元格的中心点
func get_cell_center(cell_pos:Vector2i) -> Vector2:
	return get_cell_rect(cell_pos).get_center()

我们申明一个变量target_pos来全局的存储鼠标位置,然后在_input中,设定当鼠标在屏幕上移动时,不断地记录新的鼠标位置,并调用queue_redraw()请求执行_draw()

var target_pos:Vector2  # 记录鼠标位置

func _input(event):
	if event is InputEventMouseMotion:  # 鼠标移动
		var mouse_pos = get_global_mouse_position() # 鼠标位置
		target_pos = to_cell_pos(mouse_pos)         # 获取鼠标所在位置单元格坐标
		queue_redraw()                              # 请求重绘

同时我们修改_draw()的代码,添加绘制鼠标所在位置单元格的中心点的代码:

func _draw():
	if show_grid:
		# 绘制网格
		for x in grid_size.x:
			for y in grid_size.y:
				var rect:Rect2 = get_cell_rect(Vector2(x,y)) # 获取对应位置单元格的Rect2
				draw_rect(rect,border_color,false,border_width) 
				
		# 绘制鼠标所在位置单元格的中心点
		var center = get_cell_center(target_pos)
		draw_circle(center,5,Color.CHARTREUSE)

运行场景,可以看到随着鼠标移动,会自动获取鼠标所在单元格的位置,并绘制一个绿色的圆点:

请添加图片描述

高亮显示鼠标所在单元格

在之前代码的基础上,我们只需要将_draw()中绘制单元格中心点的代码改为绘制单元格矩形的就可以了:

func _draw():
	if show_grid:
		# 绘制网格
		for x in grid_size.x:
			for y in grid_size.y:
				var rect:Rect2 = get_cell_rect(Vector2(x,y)) # 获取对应位置单元格的Rect2
				draw_rect(rect,border_color,false,border_width) 
				
		# 绘制鼠标所在位置单元格矩形
		var rect = get_cell_rect(target_pos)
		draw_rect(rect,Color.GREEN_YELLOW,true) 

运行后效果:

请添加图片描述

总结

本文介绍了如何用简单的参数定义一个自定义的二维网格,并通过工具脚本、导出变量以及申明自定义类,将其创建为一个可以复用的自定义参数化节点。

最后对其进行了一些方法的扩充。

补充:用多条水平线段和垂直线段形式绘制网格

通过绘制单元格矩形的方式来绘制网格,其中有很多边是重复绘制的,虽然看不出来,但是对有强迫症的人来说,还是可能无法接受。所以这里补充用多条线段绘制网格的方法。

用多条水平线段和垂直线段来绘制网格也有两种方式,一种是通过draw_line绘图函数,一种是使用多draw_multiline。前者每次只绘制一条线段,后者可以批量绘制线段。效果是一致的。

使用draw_line

func _draw():
	if show_grid:
		# 绘制网格
		# 绘制所有水平线
		for row in range(grid_size.y + 1):
			var w = grid_size.x * cell_size.x
			var offset = Vector2(0,cell_size.y) * row
			var h_line = [Vector2(0,0) + offset,Vector2(w,0) + offset]
			draw_line(h_line[0],h_line[1],border_color,border_width)
		# 绘制所有垂直线
		for col in range(grid_size.x + 1):
			var h = grid_size.y * cell_size.y # 垂直线高度
			var offset = Vector2(cell_size.x,0) * col
			var v_line = [Vector2(0,0) + offset,Vector2(0,h) + offset]
			draw_line(v_line[0],v_line[1],border_color,border_width)

使用draw_multiline

func _draw():
	if show_grid:
		# 绘制网格
		var h_lines:PackedVector2Array = []  # 所有水平线
		var v_lines:PackedVector2Array = []  # 所有垂直线
		# 获取所有水平线
		for row in range(grid_size.y + 1):
			var w = grid_size.x * cell_size.x
			var offset = Vector2(0,cell_size.y) * row
			var h_line = [Vector2(0,0) + offset,Vector2(w,0) + offset]
			h_lines.append_array(h_line)
		# 获取所有垂直线
		for col in range(grid_size.x + 1):
			var h = grid_size.y * cell_size.y # 垂直线高度
			var offset = Vector2(cell_size.x,0) * col
			var v_line = [Vector2(0,0) + offset,Vector2(0,h) + offset]
			v_lines.append_array(v_line)
		
		draw_multiline(h_lines,border_color,border_width)
		draw_multiline(v_lines,border_color,border_width)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巽星石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值