【Godot4.3】CanvasShape资源化改造

news2024/12/26 21:32:01

概述

通过把之前自定义的CanvasShape类变成资源类型,将可以同时用于CanvasItem绘图和创建扩展的Node2DPolygon2D节点等。

本篇就完成CanvasShape类的资源化改造,并记录改造过程和思路。

CanvasShape资源类型体系

在这里插入图片描述

  • CanvasShape仍然为图形基类,提供共有的属性和方法,只不过改为继承自Resource,成为自定义资源类型
  • CanvasXXX代表具体的图形类,扩展自CanvasShape,因为继承的好处,可以只关注自己的参数设计

CanvasShape(图形基类)

# =============================================
# 名称:CanvasShape
# 类型:Resource
# 描述:资源化后的CanvasItem绘图函数图形基类
# 作者:巽星石
# 创建时间:202472515:25:24
# 最后修改时间:20249522:26:00
# =============================================
@tool
@icon("res://lib/绘图相关/CanvasShape/icons/CanvasShape.png")
class_name CanvasShape extends Resource
# ============================== 属性 ==============================
@export_category("CanvasShape")
# -------------------- 路径点
# 点集合
@export var points:PackedVector2Array = []:
	set(val):
		points = val
		emit_changed() # 触发资源的changed信号
# 是否闭合轮廓线
@export var close_path:bool = false:
	set(val):
		close_path = val
		emit_changed() # 触发资源的changed信号
# 是否绘制局部坐标系原点         
@export var draw_position:bool = false:
	set(val):
		draw_position = val
		emit_changed() # 触发资源的changed信号
# -------------------- 边线样式
@export_group("border")
## 是否绘制轮廓
@export var draw_border:bool = true:
	set(val):
		draw_border = val
		emit_changed() # 触发资源的changed信号 
## 虚线间隔
@export_range(0.0,100.0,1.0,"suffix:px") var dash = 0.0:
	set(val):
		dash = val
		emit_changed() # 触发资源的changed信号
## 轮廓线宽度
@export_range(0,100,1,"suffix:px") var border_width:int = 1:
	set(val):
		border_width = val
		emit_changed() # 触发资源的changed信号
## 轮廓线颜色
@export var border_color:Color = Color.BLACK:
	set(val):
		border_color = val
		emit_changed() # 触发资源的changed信号

# -------------------- 边线样式
@export_group("fill")
## 是否绘制填充         
@export var draw_fill:bool = true:
	set(val):
		draw_fill = val
		emit_changed() # 触发资源的changed信号
## 填充颜色
@export var fill_color:Color = Color.WHITE:
	set(val):
		fill_color = val
		emit_changed() # 触发资源的changed信号

# -------------------- 偏移
## 相对于坐标原点的偏移位置
@export var offset:Vector2 = Vector2.ZERO:
	set(val):
		offset = val
		emit_changed() # 触发资源的changed信号    
# -------------------- 阴影设置
@export_group("shadow")
## 是否绘制阴影
@export var draw_shadow:bool = false:
	set(val):
		draw_shadow = val
		emit_changed() # 触发资源的changed信号
## 阴影颜色               
@export var shadow_color:Color = Color(Color.BLACK.lightened(0.1),0.6):
	set(val):
		shadow_color = val
		emit_changed() # 触发资源的changed信号
## 阴影偏移      
@export var shadow_offset:Vector2 = Vector2(10,10):
	set(val):
		shadow_offset = val
		emit_changed() # 触发资源的changed信号
# 顶点绘制参数
@export_group("point")
## 是否绘制顶点              
@export var draw_points:bool = false:
	set(val):
		draw_points = val
		emit_changed() # 触发资源的changed信号
## 顶点圆半径
@export var point_r = 3:
	set(val):
		point_r = val
		emit_changed() # 触发资源的changed信号
## 顶点轮廓线宽度
@export var point_border_width:int = 1:
	set(val):
		point_border_width = val
		emit_changed() # 触发资源的changed信号
## 顶点轮廓线颜色
@export var point_border_color:Color = Color.BLACK:
	set(val):
		point_border_color = val
		emit_changed() # 触发资源的changed信号
## 顶点填充颜色
@export var point_fill_color:Color = Color.WHITE:
	set(val):
		point_fill_color = val
		emit_changed() # 触发资源的changed信号     
# 矩形范围绘制参数
@export_group("rect2")
## 是否绘制矩形范围      
@export var draw_rect2:bool = false:
	set(val):
		draw_rect2 = val
		emit_changed() # 触发资源的changed信号
## 矩形轮廓线宽度
@export var rect2_border_width:int = 1:
	set(val):
		rect2_border_width = val
		emit_changed() # 触发资源的changed信号
## 矩形轮廓线颜色        
@export var rect2_border_color:Color = Color.AQUAMARINE:
	set(val):
		rect2_border_color = val
		emit_changed() # 触发资源的changed信号

# ============================== 方法 ==============================
func draw(canvas:CanvasItem) -> void:
	# 对所有点进行位移变换
	var offset_points = Transform2D().translated(offset) * points
	
	
	
	# 1.绘制阴影
	if draw_shadow and offset_points.size()>2: # 至少有3个点
		var shadow_points = Transform2D().translated(shadow_offset) * offset_points
		canvas.draw_colored_polygon(shadow_points,shadow_color) 
	# 2.绘制矩形范围
	if draw_rect2:
		canvas.draw_rect(get_rect(),Color(rect2_border_color,0.5),false,rect2_border_width)
	# 3.绘制填充
	if draw_fill and fill_color and offset_points.size()>2: # 至少有3个点
		canvas.draw_colored_polygon(offset_points,fill_color)
	# 4.绘制轮廓
	if draw_border and border_width!=0 and border_color!=null:
		var close_points = offset_points.duplicate()
		# 闭合
		if close_path:
			close_points.append(close_points[0])
		if dash>0.0:  # 虚线间隔大于0
			for seg in segments(close_points):
				canvas.draw_dashed_line(seg[0],seg[1],border_color,border_width,dash)
			pass
		else:
			canvas.draw_polyline(close_points,border_color,border_width)   # 绘制实现闭合轮廓
	# 5.绘制顶点
	if draw_points:
		for p in points:
			# 绘制圆点
			canvas.draw_circle(p,point_r,point_fill_color)
			# 绘制边线
			canvas.draw_arc(p,point_r,0,TAU,TAU * point_r,point_border_color,point_border_width/2.0,true)
	# 6.绘制位置点
	if draw_position:
		var line_count = 4
		var ang = 360.0/float(line_count)  # 每次旋转角度
		for i in range(line_count):
			var p1 = offset
			var p2 = p1 + pVector2(ang * i,5)
			canvas.draw_line(p1,p2,border_color,border_width)

# 获取图形的矩形
func get_rect() -> Rect2:
	# 对所有点进行位移变换
	var points = Transform2D().translated(offset) * points
	# 拆分出X坐标和Y坐标数组
	var x_arr = []
	var y_arr = []
	for p in points:
		x_arr.append(p.x)
		y_arr.append(p.y)
	# 最小值构成Rect2的offset
	var pos = Vector2(x_arr.min(),y_arr.min())
	
	# 最大值 - pos = Rect2 的 size
	var siz = Vector2(x_arr.max(),y_arr.max()) - pos
	
	print(pos,"   ",siz)
	return Rect2(pos,siz)

# ============================== 子类通用函数 ==============================
# 极坐标点函数 - 通过角度和长度定义一个点
func pVector2(angle:float = 0.0,length:float =0.0) -> Vector2:
	var dir = Vector2.RIGHT.rotated(deg_to_rad(angle))
	return dir * length

# points的点按顺序两两相连的所有线段
func segments(points:PackedVector2Array) -> Array[PackedVector2Array]:
	var arr:Array[PackedVector2Array]
	if points.size() >1:  # 至少有两个点
		for i in range(points.size() -1):
			var seg:PackedVector2Array = [points[i],points[i+1]]
			arr.append(seg)
	return arr

代码改造要点:

  • 属性:单纯的类设计时,属性不能也不需要被设计为导出变量形式,而自定义资源类型,其参数大多数都需要设置为导出变量,用于方便在编辑器检视器面板修改
# 点集合
@export var points:PackedVector2Array = []:
	set(val):
		points = val
		emit_changed() # 触发资源的changed信号
  • emit_changed()是触发资源实例的changed信号,在自定义节点或场景中我们可以连接此信号,用于在资源的属性修改后,进行一定的处理

CanvasRect(矩形)

# =============================================
# 名称:CanvasRect
# 类型:类
# 描述:CanvasItem绘图函数图形类 - 矩形
# 作者:巽星石
# 创建时间:202472716:07:23
# 最后修改时间:20249523:08:37
# =============================================
@tool
class_name CanvasRect extends CanvasShape
# ============================== 属性 ==============================

# 宽度
@export var width:float = 50:
	set(val):
		width = val
		update_points()
		emit_changed()
# 高度
@export var height:float = 50:
	set(val):
		height = val
		update_points()
		emit_changed()

func _init() -> void:
	close_path = true
	update_points()

# ============================== 方法 ==============================
# 更新点集
func update_points() -> void:
	points.clear()
	var center = Vector2(width,height)/2.0
	var half_width = Vector2(width,0)/2.0
	var half_height = Vector2(0,height)/2.0
	
	# 求点
	points.append(offset - center)
	points.append(offset - half_height + half_width)
	points.append(offset + center)
	points.append(offset - half_width + half_height)
	

func draw(canvas:CanvasItem) -> void:
	# 绘制
	super.draw(canvas)

CanvasRegularPolygon(正多边形)

# =============================================
# 名称:CanvasRegularPolygon
# 类型:类
# 描述:CanvasItem绘图函数图形类 - 正多边形
# 作者:巽星石
# 创建时间:202472516:03:41
# 最后修改时间:20249523:09:18
# =============================================
@tool
class_name CanvasRegularPolygon extends CanvasShape
# ============================== 属性 ==============================
## 正多边形的外接圆半径
@export var r:float = 30:
	set(val):
		r = val
		update_points()
		emit_changed()
## 正多边形的边数
@export_range(3,1000,1,"suffix:边") var edges:int = 3:
	set(val):
		edges = val
		update_points()
		emit_changed()
## 起始角度(与X轴正方向夹角)
@export_range(-360,360,1,"degrees") var start_angle:= 0.0:
	set(val):
		start_angle = val
		update_points()
		emit_changed()

func _init() -> void:
	close_path = true
	update_points()

# ============================== 方法 ==============================
# 更新点集
func update_points() -> void:
	points.clear()
	# 求点
	var ang = 360.0/float(edges) # 每次旋转角度
	for i in range(edges):
		points.append(pVector2(i * ang + start_angle,r))

func draw(canvas:CanvasItem) -> void:
	# 绘制
	super.draw(canvas)
		

CanvasLine(线段)

# =============================================
# 名称:CanvasLine
# 类型:类
# 描述:CanvasItem绘图函数图形类 - 线段
# 作者:巽星石
# 创建时间:202472716:33:28
# 最后修改时间:20249523:10:27
# =============================================
@tool
class_name CanvasLine extends CanvasShape
# ============================== 属性 ==============================

# 起点
@export var p1:=Vector2():
	set(val):
		p1 = val
		update_points()
		emit_changed()

# 起点
@export var p2:=Vector2(100,0):
	set(val):
		p2 = val
		update_points()
		emit_changed()

func _init() -> void:
	border_color = Color.WHITE
	update_points()

# ============================== 方法 ==============================
# 更新点集
func update_points() -> void:
	points.clear()
	# 求点
	points.append(p1)
	points.append(p2)
	

func draw(canvas:CanvasItem) -> void:
	# 绘制
	super.draw(canvas)

测试场景

场景根节点代码如下:

@tool
extends Node2D

# 修改shape属性时,连接CanvasShape资源的changed信号处理函数
# 仅在已经存在赋值时
@export var shape:CanvasShape:
	set(val):
		shape = val
		queue_redraw()	
		_enter_tree()

# 加载场景时,连接CanvasShape资源的changed信号处理函数
# 仅在已经存在赋值时
func _enter_tree() -> void:
	if shape:
		shape.changed.connect(func():
			queue_redraw()	
		)

# 调用CanvasShapedraw()方法,绘制
func _draw() -> void:
	if shape:
		shape.draw(self)

此时便可以在检视器面板选择相应的CanvasShape资源及其子类型了。赋值shape属性后,会在场景根节点按默认参数绘制图形,修改资源的属性时,根节点会动态进行修改。

初期未自定义图标


自定义图标

在InkScape中简单绘制一个大致正方形区域的图标,导出图标到Godot项目中。可以是SVG或PNG格式。

在自定义资源或节点顶部用@icon("图标路径")语法可以设定图标。例如:

@icon("res://lib/绘图相关/CanvasShape/icons/CanvasShape.png")

此时,就可以在检视器面板看到自定义类的图标:

自定义图标后的资源


ShapeNode2D

通过为上面测试场景的根节点设定class_name,就可以创建自定义的Node2D节点。这里我创建了一个专门显示CanvasShape资源的ShapeNode2D节点。

# =============================================
# 名称:ShapeNode2D
# 类型:Node2D
# 描述:专用于显示CanvasShape资源图形的自定义2D节点
# 作者:巽星石
# 创建时间:20249520:04:05
# 最后修改时间:20249523:03:01
# =============================================
@tool
@icon("res://lib/绘图相关/CanvasShape/icons/CanvasShape.png")
class_name ShapeNode2D extends Node2D

# 修改shape属性时,连接CanvasShape资源的changed信号处理函数
# 仅在已经存在赋值时
@export var shape:CanvasShape:
	set(val):
		shape = val
		queue_redraw()
		_enter_tree()

# 加载场景时,连接CanvasShape资源的changed信号处理函数
# 仅在已经存在赋值时
func _enter_tree() -> void:
	if shape:
		shape.changed.connect(func():
			queue_redraw()	
		)

# 调用CanvasShapedraw()方法,绘制
func _draw() -> void:
	if shape:
		shape.draw(self)

ShapeNode2D实例化测试

  • ShapeNode2D只有一个shape属性,通过设定CanvasShape资源及其子类型,然后设定其属性就可以动态的绘制出一些参数化的2D几何图形。

为shape属性设定CanvasShape资源实例

  • 基于继承的优势:因为ShapeNode2D继承自Node2D,所以它天然的可以使用Node2D类型及其继承链上的所有类型的属性、方法和信号。当然也就包括了Node2D的位移、旋转、缩放等。

ShapePolygon2D

以同样的思路,我们可以设计一个Polygon2D的扩展类型节点。

# =============================================
# 名称:ShapePolygon2D
# 类型:Polygon2D拓展类型
# 描述:专用于显示CanvasShape资源图形的自定义Polygon2D节点
# 作者:巽星石
# 创建时间:20249523:59:17
# 最后修改时间:20249600:02:23
# =============================================
@tool
class_name ShapePolygon2D extends Polygon2D

# 修改shape属性时,连接CanvasShape资源的changed信号处理函数
# 仅在已经存在赋值时
@export var shape:CanvasShape:
	set(val):
		shape = val
		update_polygon()
		_enter_tree()

# =================================== 虚函数 ===================================	
func _init():
	update_polygon()

# 加载场景时,连接CanvasShape资源的changed信号处理函数
# 仅在已经存在赋值时
func _enter_tree() -> void:
	if shape:
		shape.changed.connect(func():
			update_polygon()
		)
# =================================== 方法 ===================================
# 更新形状
func update_polygon():
	polygon = shape.points

ShapePolygon2D测试

总结

  • 通过将CanvasItem绘制的图形编写为自定义类CanvasShape,完成了第一次简单的进化
  • 而将CanvasShape变成自定义资源类型,则完成了二次进化,它将可以用于Node2D、Control、Polygon2D以及其他自定义节点上,也可以单独用于代码形式的CanvasItem绘图
  • 通过在类、场景节点或自定义节点中设定Array[CanvasShape]的导出变量,我们将可以在检视器面板设定和维护多个CanvasShape的列表。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2109344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android Auto未来可能支持无线电广播

通过Android Auto,可以在车载收音机上使用 Google 地图、音乐、收听播客,还能获取天气等基本信息。最近,国外科技媒体9to5Google通过分析 Android Auto v12.3 和 v12.4的应用程序的代码发现了一些提示信息,特别提到了 AM、FM、HD …

【Protobuf】xml、json与protobuf有什么区别,protobuf详解(保姆篇)

文章目录 简介Protobuf 的原理安装 Protobuf 编译器在 Python 中使用 Protobuf安装语言特定的 Protobuf 库定义消息结构生成代码使用 Protobuf 进行序列化和反序列化 在 Java 中使用 Protobuf安装和配置编译 .proto 文件使用生成的 Java 类创建和序列化对象 代码注释 高级特性嵌…

LabVIEW灵活集成与调试的方法

在LabVIEW开发中,为了构建一个既便于调试又能灵活集成到主VI中的控制VI,开发者需要采用适当的编程方式和架构。常见的选择包括模块化设计、状态机架构以及事件驱动编程。这些方法有助于简化调试过程、提高系统的稳定性,并确保代码的重用性和可…

day43-测试平台搭建之前端vue学习-基础2

目录 一、数据代理 二、事件处理 三、计算属性 四、监控属性 五、绑定样式 六、今日学习思维导图 一、数据代理 1.1.数据代理:通过一个对象代理对另外一个对象中属性的操作 (读/写) 1.2.Vue中数据代理的好处:更加方便的操作data中的数据 1.3.基本原…

vue2结合element-ui使用tsx格式实现formily自定义组件

简洁 在公司实习,需要参与开发一个基于formily的低代码平台,实现自定义formily组件,在此记录一下。 示例源码 demo源码 实现思路 开始实现自定义组件之前最好先看一下formily官网的core、vue、element部分,如果有能力也可以阅…

2024数学建模国赛A题word版成品论文30页【附带完整解题代码+可视化图表】

0906 0:30 v1.0 问题一、问题二的完整可运行代码,模型建立与求解这一部分的论文。 0906 5:20 v1.1 增加了第三问的完整可运行代码和第二、三问的“模型建立与求解”的论文。(即1-3问的代码、模型建立与求解、算法设计、结果分析) 1-4问完整可…

TensorFlow创建回归神经网络及Optimizer优化器

一.TensorFlow创建神经层 如图所示,通过该神经网络识别动物猫或狗,共包括输入层(Input Layer)、隐藏层3层(Hidden Layer)和输出层(Output Layer)。其中每个隐藏层神经元都有一个激励…

Unity(2022.3.41LTS) - UI详细介绍- 原始图像

目录 零.简介 一、基本功能 二、属性和设置 三、与其他 UI 元素的配合 四、代码控制 六. 和 image的区别 零.简介 在 Unity 中,RawImage 是一种用于显示原始图像的 UI 组件。 一、基本功能 显示图像:RawImage 主要用于在 UI 中直接显示一张图像。…

Python数组遍历-从基础到高级的全面指南

你有没有想过,为什么有些程序员能够轻松地操纵大量数据,而其他人却在简单的数组操作上挣扎?答案往往藏在一个看似简单却至关重要的技能中:数组遍历。无论你是刚入门的新手,还是寻求提升的老手,掌握Python中的数组遍历技巧都将极大地提升你的编程效率和代码质量。 在这篇文章中…

使用 systemd-analyze 分析 Linux 系统启动慢的原因

使用 systemd-analyze 命令可以查看 Linux 系统在启动过程中每个服务的耗时情况, 方便我们排查是哪个环节导致系统启动缓慢, 以下是整理的常用命令参数和效果. 例子中一下子就可以定位到是 gssproxy.service 服务启动耗时过长. systemd-analyze blame Print list of running u…

LabVIEW如何自学成为专业开发者

自学成为LabVIEW专业开发者需要一个系统化的学习和实践过程,以下是一些关键步骤: 1. 扎实的基础学习 了解LabVIEW的基础概念:首先要熟悉LabVIEW的基本操作、数据流编程理念和图形化编程环境。可以通过LabVIEW的官方教程、Bilibili上的视频课程…

【舞动生命,不缺营养!】亨廷顿舞蹈症患者的维生素补给站

Hey小伙伴们~ 👋 今天我们要聊的是一个温暖而重要的话题——关于亨廷顿舞蹈症(HD)患者如何通过合理补充维生素,来更好地支持他们的健康与生活品质!🌟 🌸 ‌首先,了解亨廷顿舞蹈症‌…

TCP的传输速度

如何确定TCP最大传输速度? TCP 的传输速度,受限于发送窗⼝,接收窗⼝以及⽹络设备传输能⼒。 其中,窗⼝⼤⼩由内核缓冲区⼤⼩决定。如果缓冲区与⽹络传输能⼒匹配,那么缓冲区的利⽤率就达到了最⼤化。 如何计算网络传…

JAVA:Spring Boot 整合 Swagger 的技术指南

请关注微信公众号:拾荒的小海螺 博客地址:http://lsk-ww.cn/ 1、简述 在现代Web开发中,API文档的生成和维护是非常重要的。Swagger是一款流行的API文档生成工具,它可以帮助开发者自动生成API文档,并提供可视化的接口…

Redis从简单使用到底层原理与分布式缓存

文章目录 [Redis参考手册](https://redis.io/docs/latest/commands/)1 基础认识1.1 安装配置1.2 通用命令1.3 数据类型1.3.1 数据结构与内部编码stringkey的结构hashlistsetsorted_set 1.4 单线程模型 2 redis客户端2.1 RESP协议(Redis serialization protocol&…

SpringBoot2:请求处理原理分析-接口参数的常用注解

1、PathVariable 作用说明&#xff1a;获取路径参数 案例&#xff1a; 接口收参形式&#xff1a; GetMapping("/car/{id}/owner/{username}")public Map<String,Object> getCar(PathVariable("id") Integer id,PathVariable("username")…

echarts圆饼图定时器动画

(function () {const WdxjEcharts echarts.init(document.getElementById(wdxjEchart))let num 0;var imgURL "../imagesNew/wd-center.png";var trafficWay [{name: 火车,value: 20}, {name: 飞机,value: 10}, {name: 客车,value: 30}, {name: 轮渡,value: 40}]…

深入解读Docker核心网络管理:架构、模式与通信机制

在容器化技术中&#xff0c;网络管理是影响容器通信和应用部署的重要组成部分。Docker不仅简化了应用的部署过程&#xff0c;还提供了强大的网络管理功能&#xff0c;确保容器之间以及容器与外部系统的网络通信能够高效、稳定地进行。 本文将深入解读Docker的核心网络管理原理…

查看hprof文件

hprof可以用来分析某个进程的内存情况&#xff0c;对我们分析内存泄漏问题有很大帮助&#xff0c;本文主要记录如何生成及查看hprof文件。 1生成.hprof文件 可以使用adb命令生成 .hprof文件&#xff0c;生成的是在执行命令的那一刻&#xff0c;该进程的内存情况&#xff1a; …

后端Web之SpringBoot原理

目录 1.配置优先级 2.Bean 3.SpringBoot原理 1.配置优先级 SpringBoot中支持三种格式的配置文件: .application.properties、application.yml和application. yaml。它们的配置优先级分别降低。虽然springboot支持多种格式配置文件&#xff0c;但是在项目开发时,推荐统一使用…