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

news2024/11/16 11:49:47

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)

很简单的代码,几个对属性进行增删改的操作,有个地方很关键: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)

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

health 属性发生改变: previous = <null>, current = 2
health 属性发生改变: previous = 2, current = 3

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

#============================================================
#    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)

添加了两个新的信号: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)

运行结果:

新增属性:health, value = 2
health 属性发生改变: previous = 2, current = 3
移除属性:health
移除 health 属性后的值:<null>

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

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

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

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

|- test (Node2D)
	|- data_management (DataManagement,刚刚创建的那个脚本类)
	|- health_bar (ProgressBar)
	|- add (Button)
	|- sub (Button)

给 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)

按 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)

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

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

property_consts.gd

## 属性名常量
class_name PropertyConsts


const HEALTH = "health"
const HEALTH_MAX = "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(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)

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

Buff 功能示例

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

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

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

我们创建一个 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")

给场景根节点 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)

点击 在这里插入图片描述
运行按钮或按 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

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

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

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

相关文章

Git使用教程

Git项目的三个工作区域的概念&#xff1a; 1、Git仓库Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分&#xff0c;从其它计算机克隆仓库时&#xff0c;拷贝的就是这里的数据。 2、工作目录工作目录是对项目的某个版本独立提取出来的内容…

Ansible之 AWX 创建管理项目的一些笔记

写在前面 分享一些 AWX 创建管理项目的笔记博文内容涉及&#xff1a; 容器化 AWX 手工创建项目Demo通过 SCM 创建项目 Demo项目角色&#xff0c;更新策略介绍&#xff0c;SCM 凭据的创建 食用方式&#xff1a; 需要了解 Ansible理解不足小伙伴帮忙指正 傍晚时分&#xff0c;你坐…

ssm项目改造spring boot项目

快速创建 Spring Boot 项目 添加依赖 如果是普通 Maven 项目&#xff0c;需要手动添加。 <!-- 打包方式 jar 包 --> <packaging>jar</packaging><!-- 指定父工程 --> <parent><groupId>org.springframework.boot</groupId><ar…

操作系统学习笔记(Ⅰ):概述

目录 1 操作系统概念 1.1 定义 1.2 功能 1.系统资源的管理者 2.用户和计算机硬件间接口 3.最接近硬件的层次 2 操作系统的特征 2.1 并发 2.2 共享 2.3 虚拟 2.4 异步 3 发展和分类 3.1 手工操作阶段 3.2 批处理阶段 1.单道批处理阶段 2.多道批处理系统 3.3 分…

启明欣欣STM32开发板闪烁LED实验

最近在咸鱼上买了一块启明欣欣的STM32板子&#xff0c;准备在上面测试open62541和CANopen&#xff0c;到货后如下图&#xff0c; 找商家要了资料&#xff0c;然后运行一个LED灯的实验来简单测试下板子&#xff0c;本文记录一下这个过程。 一 准备 安装Keil 5.35&#xff0c;安…

【selection】 学习光标API并实现编辑区插入表情图片的功能

目录场景介绍selection介绍selection APIrange 介绍range API实现编辑区插入表情图片参考资料场景介绍 在写web版聊天器时&#xff0c;遇到一个需求&#xff1a; 聊天时用户可以在编辑区加入表情图片&#xff0c;并且表情图片要插入在光标位置。// *web版聊天器地址&#xff…

useMemo 使用误区

文章の目录问题背景useMemo 使用前后组件性能对比结论问题背景 在某一个h5项目中&#xff0c;使用了 useMemo 对项目中的组件进行优化&#xff0c;减少组件不必要的re-render, 优化后的结果&#xff1a; 在组件的props和状态未改变时&#xff0c;组件不再进行 re-render 表面上…

生意不好如何逆风翻盘 | 多门店经营必读技巧(1):导购管理 连锁店管理的技巧 连锁店生意经 如何做导购管理

很多连锁店老板反馈&#xff0c;为了优化门店的销售业绩&#xff0c;什么方法都试过了&#xff0c;改店铺陈列、搞优惠活动、做会员管理.......为什么分店的业绩还是看不到明显的提升&#xff1f; 方法试过了&#xff0c;结果没变化&#xff0c;那只能是执行这块出了问题&#…

量子计算(八):观测量和计算基下的测量

文章目录 观测量和计算基下的测量 一、观测量 二、计算基下的测量 三、投影测量 观测量和计算基下的测量 一、观测量 量子比特&#xff08;qubit&#xff09;不同于经典的比特&#xff08;bit&#xff09;&#xff0c;一个量子比特|>可以同时处于|0>和|1>两个状态…

Linux从入门到精通(八)——Linux磁盘管理

文章篇幅较长&#xff0c;建议先收藏&#xff0c;防止迷路 文章跳转Linux从入门到精通&#xff08;八&#xff09;——Linux磁盘管理goLinux从入门到精通&#xff08;九&#xff09;——Linux编程goLinux从入门到精通&#xff08;十&#xff09;——进程管理goLinux从入门到精…

C++ 结合mysql写一个服务端

1 libhv和mysql libhv是一个跨平台的C网络库。 mysql是一个关系型数据库。 2 下载MySQL&#xff0c; 最好不要下载高版本的&#xff0c;容易出错&#xff01;&#xff01;&#xff01; 下载地址MySQL 下载好后目录是这样的&#xff1a; 然后在环境变量里配置&#xff1a;…

Hive 3.1.3

1.下载安装包 Index of /hive/hive-3.1.3https://dlcdn.apache.org/hive/hive-3.1.3/ 2.安装&修改配置文件 2.1 安装MySQL a. 搜索centos7默认的mariadb & 卸载 [root@node1 ~]# rpm -qa | grep mariadb mariadb-libs-5.5.44-2.el7.centos.x86_64 卸载 [r…

【计算机组成原理Note】5.4.2 控制器-微程序

5.4.2 控制器-微程序 硬布线工作原理&#xff1a;微操作控制信号由组合逻辑电路根据当前的指令码、状态和时序&#xff0c;即时产生微程序工作原理&#xff1a;事先把微操作控制信号存储在一个专门的存储器(控制存储器)中&#xff0c;将每一条机器指令编写成一个微程序&#xf…

差钱吗?周杰伦线上演唱会没关打赏惹争议,看看同时直播的腾格尔

随着世界杯的到来&#xff0c;全球都进入了世界杯时间&#xff0c;音乐领域的明星们&#xff0c;都以各种形式欢迎世界杯的到来。在世界杯开幕的前一晚上&#xff0c;著名音乐人周杰伦&#xff0c;在某手平台开启了线上演唱会&#xff0c;吸引了众多人前来围观。 据不完全统计&…

【正点原子FPGA连载】 第七章 Verilog HDL语法 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1&#xff09;实验平台&#xff1a;正点原子MPSoC开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id692450874670 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第七章 Verilog …

【计组】入门篇 --《深入浅出计算机组成原理》

课程链接&#xff1a;深入浅出计算机组成原理_组成原理_计算机基础-极客时间 目录 一、为什么需要学习计算机组成原理 二、冯诺依曼体系结构&#xff1a;计算机组成的金字塔 1、计算机的基本硬件组成 2、冯诺依曼体系结构 三、通过CPU主频谈性能 1、什么是性能 2、计算…

【微信早安定时推送消息】微信公众号定时推送早安消息 带天气、纪念日、生日、定时推送等(完整代码)

我挥舞着键盘和本子&#xff0c;发誓要把世界写个明明白白。 简介 利用所学知识给他/她一个惊喜&#xff0c;是作为计算机专业的你最大的乐趣。 无计算机基础&#xff0c;5分钟即设置好 &#xff08;定时推送 及 最新版 将在下期带来&#xff09; 获取完整代码&#xff0c;关注…

性能测试怎么做?性能测试重点和各项性能测试流程(超级详细)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 性能测试基础 1&am…

【设计模式】过滤器模式(Filter Pattern)

过滤器模式属于结构型模式&#xff0c;但它并不属于我们常说的二十三种设计模式。主要是以相对解耦的方式来过滤一组对象。 文章目录过滤器模式的介绍优点应用场景过滤器模式的使用类图实现方法第一步&#xff0c;创建员工类第二步&#xff0c;创建过滤器接口第三步&#xff0c…

如何部署商城项目

如何部署商城项目 1.导入数据库 ​ 注意&#xff1a;先保证数据库开启了远程访问&#xff08;见数据库安装和配置笔记&#xff09;。 1.1设置数据库服务器编码 ​ vi /etc/my.cnf ​ 在[mysqld]下面添加 character_set_serverutf8 init_connectSET NAMES utf81.2打开本地…