• 【Godot】数据响应的方式执行功能


    Godot Engine 版本:4.0 beta 6

    下载地址:Index of /godotengine/4.0/beta6/ (downloads.tuxfamily.org)

    在这个教程中,学会理解以数据为主的进行处理执行逻辑的代码编写方式,虽然看似简单,但是确是方便又好用。

    以及下方会有一个 buff 的示例,触类旁通,你可以设计更高复杂更强大的功能

    基础代码

    在游戏开发中,对数据的处理那绝对是重中之重。角色的属性、游戏配置等一系列的玩家数据都要进行保存处理。有时还要根据数据的变化进行响应处理其他代码逻辑,那有没有一种比较方便的方式对数据的变化进行处理呢?那肯定是有的,要不我来干嘛(#滑稽)

    算了,我也不太会说话,直接先看代码:

    data_management.gd

    #============================================================
    #    Data Management
    #============================================================
    # - datetime: 2022-11-23 19:35:39
    #============================================================
    ## 数据管理
    class_name DataManagement
    extends Node
    
    
    ##  数据发生改变
    ##[br]
    ##[br][code]property[/code]  属性名
    ##[br][code]previous[/code]  改变前的属性的值
    ##[br][code]current[/code]  当前的属性值
    signal property_changed(property, previous, current)
    
    
    var _data : Dictionary = {}
    var _tmp_value
    
    
    #============================================================
    #  SetGet
    #============================================================
    ##  设置属性值
    func set_property(property, value):
    	_tmp_value = _data.get(property)
    	if _tmp_value != value:
    		_data[property] = value
    		property_changed.emit(property, _tmp_value, value)
    
    
    ##  获取属性值
    ##[br]
    ##[br][code]default[/code]  如果没有这个属性时返回的默认值
    func get_property(property, default = null):
    	return _data.get(property, default)
    
    
    ##  添加属性
    func add_property(property, value):
    	if value is float or value is int:
    		set_property(property, _data.get(property, 0) + value )
    	else:
    		set_property(property, value)
    
    ## 减去属性值
    func sub_property(property, value):
    	if value is float or value is int:
    		set_property(property, _data.get(property, 0) - value )
    	else:
    		set_property(property, value)
    
    ##  移除属性值
    func remove_property(property):
    	_data.erase(property)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    很简单的代码,几个对属性进行增删改的操作,有个地方很关键:signal property_changed(property, previous, current),属性改变信号,对属性发生改变能够有方便判断监听的地方,好在 Godot 的信号非常方便就能实现这一功能。

    使用 Dictionary 对属性进行记录管理很方便,最终通过 set_property 方法对属性的改变进行判断处理。这样,只要连接完信号,通过调用这些方法进行操作,就能判断属性何时发生了变化。

    创建一个编辑器脚本进行测试

    data_mana_test.gd

    #============================================================
    #    Data Mana Test
    #============================================================
    # - datetime: 2022-11-23 19:58:42
    #============================================================
    @tool
    extends EditorScript
    
    
    var data_management = DataManagement.new()
    
    
    func _run():
    	# 连接 property_changed 属性到当前对象的 _property_changed 方法上 (4.0 版本之后的新的连接方式)
    	data_management.property_changed.connect(_property_changed)
    	
    	# 设置属性
    	data_management.set_property("health", 2)
    	# 添加属性值
    	data_management.add_property("health", 1)
    
    
    func _property_changed(property, previous, current):
    	print(property, " 属性发生改变: previous = ", previous, ", current = ", current)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    点击脚本代码左上角的“文件 > 运行”菜单或者按下 ctrl + shift + x 快捷键运行当前脚本,可以看到底部“输出”面板中的如下信息

    health 属性发生改变: previous = , current = 2
    health 属性发生改变: previous = 2, current = 3
    
    • 1
    • 2

    是的,非常简陋,但是核心就是这个,非常重要,必不可少,现在我们需要稍微增强一下这个类,比如我想知道这是否是新添加的属性数据:

    #============================================================
    #    Data Management
    #============================================================
    # - datetime: 2022-11-23 19:35:39
    #============================================================
    ## 数据管理
    class_name DataManagement
    extends Node
    
    
    ##  数据发生改变
    ##[br]
    ##[br][code]property[/code]  属性名
    ##[br][code]previous[/code]  改变前的属性的值
    ##[br][code]current[/code]  当前的属性值
    signal property_changed(property, previous, current)
    ## 新添加属性
    signal newly_added_property(property, value)
    ## 移除了属性
    signal removed_property(property, value)
    
    
    var _data : Dictionary = {}
    var _tmp_value
    
    
    #============================================================
    #  SetGet
    #============================================================
    ##  设置属性值
    ##  set_property
    ##[br]
    ##[br][code]force_change[/code]  强制进行修改
    func set_property(property, value, force_change: bool = false):
    	_tmp_value = _data.get(property)
    	if _data.has(property):
    		if _tmp_value != value or force_change:
    			_data[property] = value
    			property_changed.emit(property, _tmp_value, value)
    	else:
    		_data[property] = value
    		newly_added_property.emit(property, value)
    
    
    ##  获取属性值
    ##[br]
    ##[br][code]default[/code]  如果没有这个属性时返回的默认值
    func get_property(property, default = null):
    	return _data.get(property, default)
    
    
    ##  添加属性
    func add_property(property, value):
    	if value is float or value is int:
    		set_property(property, _data.get(property, 0) + value )
    	else:
    		set_property(property, value, true)
    
    ## 减去属性值
    func sub_property(property, value):
    	if value is float or value is int:
    		set_property(property, _data.get(property, 0) - value )
    	else:
    		set_property(property, value, true)
    
    ##  移除属性值
    func remove_property(property):
    	if _data.has(property):
    		removed_property.emit(property, _data[property])
    		_data.erase(property)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    添加了两个新的信号:newly_added_propertyremoved_property,这样我们就可以做更多判断操作,以及部分更新,尤其是 func set_property(property, value, force_change: bool = false) 方法部分新增了 force_change 参数,这样在增加和减少非数字属性的时候也会发出信号。

    修改上次的编辑器脚本,再次运行测试

    #============================================================
    #    Data Mana Test
    #============================================================
    # - datetime: 2022-11-23 19:58:42
    #============================================================
    @tool
    extends EditorScript
    
    
    var data_management = DataManagement.new()
    
    
    func _run():
    	# 连接信号当前对象的方法 (4.0 版本之后的新的连接方式)
    	data_management.property_changed.connect(_property_changed)
    	data_management.newly_added_property.connect(_newly_added_property)
    	data_management.removed_property.connect(_removed_property)
    	
    	# 设置属性
    	data_management.set_property("health", 2)
    	# 添加属性值
    	data_management.add_property("health", 1)
    	# 移除属性
    	data_management.remove_property("health")
    	
    	print("移除 health 属性后的值:", data_management.get_property("health") )
    
    
    func _newly_added_property(property, value):
    	print("新增属性:", property, ", value = ", value)
    
    
    func _removed_property(property, value):
    	print("移除属性:", property)
    
    
    func _property_changed(property, previous, current):
    	print(property, " 属性发生改变: previous = ", previous, ", current = ", current)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    运行结果:

    新增属性:health, value = 2
    health 属性发生改变: previous = 2, current = 3
    移除属性:health
    移除 health 属性后的值:
    
    • 1
    • 2
    • 3
    • 4

    非常方便就可以对属性发生改变进行响应

    简单的生命发生改变的示例

    我们对这个功能进行一个实例操作演示。

    创建一个场景,创建一个名称为 test_01 的文件夹保存到这个文件夹下。节点名称和类型如下,左边名称,右边括号内的是类名

    |- test (Node2D)
    	|- data_management (DataManagement,刚刚创建的那个脚本类)
    	|- health_bar (ProgressBar)
    	|- add (Button)
    	|- sub (Button)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    给 test 节点添加一个脚本,然后修改 add 和 sub 的 text 属性为 addsub,连接两个按钮的 pressed 信号到脚本里,调整一下几个节点的位置

    在这里插入图片描述

    然后就是写代码逻辑,test 代码如下

    extends Node2D
    
    
    @onready var health_bar = $health_bar as ProgressBar
    @onready var data_management = $data_management as DataManagement
    
    
    #============================================================
    #  内置
    #============================================================
    func _ready():
    	health_bar.max_value = 10
    	data_management.property_changed.connect(_property_changed)
    	data_management.set_property("health", 0)
    
    
    #============================================================
    #  连接信号
    #============================================================
    func _property_changed(property, previous, current):
    	if property == "health":
    		health_bar.value = current
    
    
    func _on_add_pressed():
    	data_management.add_property("health", 1)
    
    
    func _on_sub_pressed():
    	data_management.sub_property("health", 1)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    按 F6 运行场景进行测试,点击 add 和 sub 按钮,可以看到点击之后属性发生了改变

    在这里插入图片描述

    但是还不完善,因为点击完会一直增加或减少,我们稍作一点限制即可,添加一个 health_max 属性,根据这个属性进行判断

    extends Node2D
    
    
    @onready var health_bar = $health_bar as ProgressBar
    @onready var data_management = $data_management as DataManagement
    
    
    #============================================================
    #  内置
    #============================================================
    func _ready():
    	data_management.newly_added_property.connect(_newly_added_property)
    	data_management.property_changed.connect(_property_changed)
    	data_management.set_property("health", 0)
    	data_management.set_property("health_max", 10)
    
    
    #============================================================
    #  连接信号
    #============================================================
    func _newly_added_property(property, value):
    	if property == "health_max":
    		health_bar.max_value = value
    
    
    func _property_changed(property, previous, current):
    	if property == "health":
    		health_bar.value = current
    
    
    func _on_add_pressed():
    	if data_management.get_property("health") < data_management.get_property("health_max"):
    		data_management.add_property("health", 1)
    
    
    func _on_sub_pressed():
    	if data_management.get_property("health") > 0:
    		data_management.sub_property("health", 1)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    再次运行,可以不断点击,再点击减少,就可以看到超出最大值则不再增加了

    不过像上面不断手动输入属性值,这样需要手动输入的值我们把它叫做“魔法值”,这种值有时候可能会因为手误输入错误,导致逻辑错误,所以我们需要给他专门做一个类存储这个属性名称

    property_consts.gd

    ## 属性名常量
    class_name PropertyConsts
    
    
    const HEALTH = "health"
    const HEALTH_MAX = "health_max"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    将手动输入的值替换成常量

    extends Node2D
    
    
    @onready var health_bar = $health_bar as ProgressBar
    @onready var data_management = $data_management as DataManagement
    
    
    #============================================================
    #  内置
    #============================================================
    func _ready():
    	data_management.newly_added_property.connect(_newly_added_property)
    	data_management.property_changed.connect(_property_changed)
    	data_management.set_property(PropertyConsts.HEALTH, 0)
    	data_management.set_property(PropertyConsts.HEALTH_MAX, 10)
    
    
    #============================================================
    #  连接信号
    #============================================================
    func _newly_added_property(property, value):
    	if property == PropertyConsts.HEALTH_MAX:
    		health_bar.max_value = value
    
    
    func _property_changed(property, previous, current):
    	if property == PropertyConsts.HEALTH:
    		health_bar.value = current
    
    
    func _on_add_pressed():
    	if data_management.get_property(PropertyConsts.HEALTH) < data_management.get_property(PropertyConsts.HEALTH_MAX):
    		data_management.add_property(PropertyConsts.HEALTH, 1)
    
    
    func _on_sub_pressed():
    	if data_management.get_property(PropertyConsts.HEALTH) > 0:
    		data_management.sub_property(PropertyConsts.HEALTH, 1)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    可以看到完全没有手动输入的值了,当然这里不仅仅只适用于这个地方,魔法值,其他数据的改变都可以通过次方式进行

    Buff 功能示例

    上面的生命改变功能还是比较简单的,这里我们可以进阶做个比较高级点的功能,通过数据属性新增移除进行增加 buff 的功能,其实游戏就是这样,关键在于对数据的操控

    创建一个测试场景 test,保存到一个文件夹名为 test_02 的文件夹下,场景节点结构如下

    |- test (Node2D)
    	|- data_management (DataManagement)
    		|- fire_buff (Node,给这个节点添加下面的 fire_buff.gd 脚本)
    	|- add_buff (Button)
    
    • 1
    • 2
    • 3
    • 4

    我们创建一个 FireBuff 脚本,意为火焰buff

    fire_buff.gd

    #============================================================
    #    Fire Buff
    #============================================================
    # - datetime: 2022-11-23 21:26:51
    #============================================================
    ##火焰魔法buff
    ##[br]
    ##[br]放到 DataManagement 节点下边自动连接信号,每次添加属性值和下方的 FireBuff.NAME 的值一样的
    ##时候会自动添加这个buff
    class_name FireBuff
    extends Node
    
    
    @onready 
    var data_management := get_parent() as DataManagement
    
    
    # 叠加的伤害数据
    var _damage_data : Array[Dictionary] = []
    
    
    #============================================================
    #  常量
    #============================================================
    ## 当前 Buff 名称常量
    const NAME = "火焰魔法BUFF"
    
    
    ## Buff 参数值常量
    class BuffParamConsts:
    	
    	const DURATION = "duration"
    	const DAMAGE = "damage"
    
    
    #============================================================
    #  内置
    #============================================================
    func _ready():
    	if data_management != null:
    		data_management.newly_added_property.connect(_newly_added_property)
    		data_management.property_changed.connect(_property_changed)
    		data_management.removed_property.connect(_removed_property)
    	else:
    		printerr("父节点不是 DataManager 类型的节点!")
    
    
    #============================================================
    #  自定义
    #============================================================
    # 根据添加的数据创建计时器
    func _create_timer_by_data(data: Dictionary):
    	var timer := Timer.new()
    	add_child(timer)
    	timer.start(data[BuffParamConsts.DURATION])
    	timer.timeout.connect(_timer_timeout.bind(data))
    	# 结束删除这个计时器
    	timer.timeout.connect(timer.queue_free)
    	_damage_data.append(data)
    
    
    #============================================================
    #  连接信号
    #============================================================
    func _newly_added_property(property, value):
    	if property == FireBuff.NAME:
    		print("-- 新添加buff: ", property)
    		set_physics_process(true)
    		_create_timer_by_data(value)
    
    
    func _property_changed(property, previous, current):
    	# 开始叠加火焰魔法 buff
    	if property == FireBuff.NAME:
    		print("-- 开始叠加 ", property, " 属性")
    		# 额外增加上个 buff 持续时间的一半时间
    		current[BuffParamConsts.DURATION] += previous[BuffParamConsts.DURATION] * 0.5
    		# 额外增加上个 buff 伤害的一半伤害
    		current[BuffParamConsts.DAMAGE] += previous[BuffParamConsts.DAMAGE] * 0.5
    		
    		_create_timer_by_data(current)
    
    
    func _removed_property(property, value):
    	if property == FireBuff.NAME:
    		set_physics_process(false)
    
    
    func _timer_timeout(data):
    	print("倒计时结束,造成伤害:", data[BuffParamConsts.DAMAGE])
    	
    	# 结束时造成伤害
    	data_management.sub_property(PropertyConsts.HEALTH, data[BuffParamConsts.DAMAGE])
    	# 到达时间减去叠加的火焰buff数据,如果没有了,则移除buff状态
    	_damage_data.erase(data)
    	if _damage_data.is_empty():
    		self.data_management.remove_property(NAME)
    		print("-- 没有叠加数据了,移除buff")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    给场景根节点 test 添加一个脚本,修改 add_buff 的 text 属性为 Add buff 并连接这个按钮的 pressed 属性到 test 脚本里

    在这里插入图片描述

    test 脚本里的代码如下

    extends Node2D
    
    
    @onready var data_management = $data_management as DataManagement
    
    
    #============================================================
    #  内置
    #============================================================
    func _ready():
    	data_management.set_property(PropertyConsts.HEALTH, 10)
    	# 连接属性发生改变信号
    	data_management.property_changed.connect(_property_changed)
    
    
    #============================================================
    #  连接信号
    #============================================================
    func _on_add_buff_pressed():
    	# 火焰魔法BUFF 数据
    	data_management.add_property(FireBuff.NAME, {
    		FireBuff.BuffParamConsts.DURATION: 1,		# 持续时间
    		FireBuff.BuffParamConsts.DAMAGE: 1,			# 造成伤害
    	})
    
    
    func _property_changed(property, previous, current):
    	if property == PropertyConsts.HEALTH:
    		print("> 生命值发生改变,当前生命值:", current)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    点击 在这里插入图片描述
    运行按钮或按 F6 运行场景

    然后点击 Add buff 按钮开始添加 buff,比如我快速点击了 4 次,输出如下结果:

    -- 新添加buff: 火焰魔法BUFF
    -- 开始叠加 火焰魔法BUFF 属性
    -- 开始叠加 火焰魔法BUFF 属性
    -- 开始叠加 火焰魔法BUFF 属性
    倒计时结束,造成伤害:1
    > 生命值发生改变,当前生命值:9
    倒计时结束,造成伤害:1.5
    > 生命值发生改变,当前生命值:7.5
    倒计时结束,造成伤害:1.75
    > 生命值发生改变,当前生命值:5.75
    倒计时结束,造成伤害:1.875
    > 生命值发生改变,当前生命值:3.875
    -- 没有叠加数据了,移除buff
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    由此你也可以写出其他的 buff 功能,添加到 这个 DataManagement 节点下面,然后以此类推,添加技能功能等等(当然设计通用技能节点又是一个不小的挑战),看看能否触类旁通做个简单的。

  • 相关阅读:
    【没用的小知识又增加了--CCS】
    redis我记不住的那些命令(七)
    原子范数初探:以到达角估计为例
    ssh远程连接不了虚拟机ubuntu
    flink 1.15.1集群安装部署及测试
    RK3588 实现温控风扇之通过extcon子系统上报状态(三)
    tokenizers processors模块
    网络安全之ARP欺骗防护
    探秘Nutch:揭秘开源搜索引擎的工作原理与无限应用可能(二)
    Qt SQLite的创建和使用
  • 原文地址:https://blog.csdn.net/qq_37280924/article/details/128009776