Godot学习中的问题及思考、学习笔记 02

【【中文语音】Godot4终极入门教程01:什么是Godot】

Godot的节点和场景

节点是基本的构建块

一个节点 可以是一个图像,一个声音,一个骨骼,一个区域
每个节点都做一件特定的事情。

Node2D主要用于组织

Area2D 检测其他物体是否进入的区域
检查玩家是否进入房子,房子就是一个Area2D,它可以检查是否有物体进入。
这些区域将检查这个项目里的 激光是否与敌人、墙壁、障碍物等发生碰撞。

->bool 这个是指定的返回值类型



Godot中的函数的生命周期执行顺序,是先执行,_init 还是_执行_start?

在Godot的生命周期中,_init_ready 是两个不同的函数,而没有直接的 _start 函数。让我解释一下它们的作用和执行顺序:

  1. _init函数:

    • _init 是一个可选的函数,用于在对象实例化时执行一些初始化代码。
    • 这个函数只会在对象实例化时调用一次,通常在创建对象后立即执行。
    • 它用于初始化对象的成员变量或执行其他一次性的初始化任务。
  2. _ready函数:

    • _ready 是一个常用的函数,用于在节点及其所有子节点都已被加载和初始化后执行。
    • 这个函数在场景树中连接节点后调用,通常在 _init 之后。
    • _ready 中,你可以执行那些需要访问其他节点或资源的初始化工作。

执行顺序通常是这样的:

  • _init (如果存在,仅执行一次)
  • _ready (每次节点及其子节点准备好时执行)

所以,如果你想在节点实例化时执行初始化操作,使用 _init;如果需要在节点及其子节点都准备好后执行操作,使用 _ready



Error connect(signal: String, target: Object, method: String, binds: Array = [ ], flags: int = 0)

将信号 signal 连接到目标 target 对象上的方法 method。将可选的绑定 binds 作为一个 Array 的参数传递给调用。这些参数将在调用 emit_signal() 中使用的任何参数之后传递给方法。使用 flags 设置延迟或一次性连接。请参阅 ConnectFlags 常量。

一个信号 signal 与同一方法 method 只能连接一次。除非之前在连接信号时使用了 CONNECT_REFERENCE_COUNTED,否则在进行重复连接时会打印错误。为避免这种情况,首先使用 is_connected() 检查是否已有连接。

如果 target 在游戏生命周期中被销毁,连接将丢失。

例子:

connect("pressed", self, "_on_Button_pressed") # BaseButton 信号
connect("text_entered", self, "_on_LineEdit_text_entered") # LineEdit 信号
connect("hit", self, "_on_Player_hit", [ Weapon_type, damage ]) # 用户自定义信号

传递给 connect() 的 binds 与调用 emit_signal() 时使用的参数的关系示例:

connect("hit", self, "_on_Player_hit", [ Weapon_type, damage ]) # 武器类型和伤害最后传递
emit_signal("hit", "Dark lord", 5) # 先传递 "Dark lord" 和 5
func _on_Player_hit(hit_by, level, Weapon_type, damage):
print("被 %s(等级 %d)使用武器 %s 击中,造成 %d 点伤害" % [hit_by, level, weapon_type, damage])

game_data_mutex.lock() 是在 GDScript 中使用互斥锁(Mutex)的一种方式。

互斥锁是一种用于保护共享资源的工具,它可以确保在任何时候只有一个线程可以访问某个特定的资源或者代码段。这在多线程编程中非常重要,因为它可以防止多个线程同时修改同一份数据,从而导致数据的不一致。

在你的代码中,game_data_mutex 是一个互斥锁对象,lock 是它的一个方法。当你调用 game_data_mutex.lock() 时,当前线程会尝试获取这个互斥锁。如果这个互斥锁已经被其他线程获取,那么当前线程会被阻塞,直到其他线程释放这个互斥锁。如果这个互斥锁没有被其他线程获取,那么当前线程会立即获取这个互斥锁,并继续执行。

通常,你会在修改共享数据之前获取互斥锁,然后在修改完成之后释放互斥锁。这样可以确保在你修改共享数据的过程中,其他线程不能同时修改这份数据。例如:

game_data_mutex.lock()
# 修改共享数据
game_data_mutex.unlock()

在这个代码中,game_data_mutex.lock() 获取互斥锁,然后修改共享数据,最后 game_data_mutex.unlock() 释放互斥锁。

》》》》》》》》》》》》》》》》
get_tree().call_group("player_%s" % attacker_id, "thanks", someting_num)
"player_%s" % attacker_id,是什么。

在Godot中,get_tree().call_group()是一种调用特定组中所有节点的指定方法的方式。这个函数非常适合于当你需要对某一组具有相同特征的节点执行相同操作时使用。

"player_%s" % attacker_id这部分是字符串格式化的一个例子,用于动态生成组名。

"player_%s" % attacker_id使用了Python风格的字符串格式化。其中%s是一个占位符,用于表示一个字符串。%操作符用来替换%s为attacker_id变量的值(将其转换为字符串)。

假设attacker_id的值为123,那么"player_%s" % attacker_id将被解析为"player_123"。这意味着你正在引用名为"player_123"的组。

get_tree().call_group()详解
get_tree().call_group("组名", "方法名", 参数...)函数调用是说:“在场景树(Scene Tree)中找到属于指定组("组名")的所有节点,并对这些节点调用指定的方法("方法名"),传入指定的参数(参数...)”。

在你的例子中,get_tree().call_group("player_%s" % attacker_id, "thanks", someting_num)表示“在场景树中找到所有属于"player_123"(假设attacker_id为123)这个组的节点,并对这些节点调用thanks方法,传入someting_num作为参数”。

这种方法特别有用于实现诸如群体伤害、状态更新、群体消息通知等功能,其中你需要对一组特定的节点执行相同的操作。

在Godot中,当你调用queue_free()方法时,当前节点会被标记为删除,但实际的删除操作会延迟到当前帧的

_ready godot里的下划线是什么意思,这些内置的。还有init和godotScripts关于继承方面的内容

在Godot引擎中,下划线前缀通常用于表示一个特殊的函数或变量。特别是,当下划线用于函数名的开头时,它通常指的是Godot的内置函数,这些函数在特定的时间点或事件发生时自动被引擎调用。这些函数通常被称为“虚拟函数”或“回调函数”,它们允许开发者在节点的生命周期的关键时刻插入自定义的代码逻辑。

_ready() 函数

  • _ready() 是Godot最常用的内置函数之一。当节点及其所有子节点进入场景树时,_ready() 会被自动调用。这个函数是执行初始化代码的理想位置,比如获取节点引用、设置初始状态、连接信号等。因为在 _ready() 被调用时,可以保证节点以及它的子节点都已经被完全实例化,并且已经添加到场景树中。

下划线的用法

  • 下划线不仅用于特定的回调函数,如 _ready()_process(delta)_physics_process(delta) 等,也可以用于自定义的私有函数或变量。在Godot社区中,一些开发者将下划线前缀用于私有成员的命名,以区分公共接口和内部实现的细节,尽管这不是强制性的规范。

关于 init 和 继承

  • 在Godot中,_init() 函数类似于其他编程语言中的构造函数,用于初始化类的实例。如果你定义了一个自定义类(可以是一个继承自Godot内置类的脚本),你可以通过 _init() 方法来初始化该类的属性或执行其他构造逻辑。_init() 在对象创建的时候自动调用,比如通过 new() 方法实例化时。
  • 继承是面向对象编程中的一个核心概念,Godot也支持继承。在Godot脚本中使用继承可以让你的类继承另一个类的属性和方法。这对于代码的复用和组织极为有用。在Godot脚本中,使用 extends 关键字来声明继承,例如 extends Sprite 表示你的脚本将继承自 Sprite 类。

Godot 脚本与继承

  • 在Godot的脚本系统中,继承的使用非常灵活。你不仅可以继承自Godot的内置节点类型(如 Node, Spatial, Control 等),也可以继承自其他GDScript脚本。这使得开发者能够建立自己的类层次结构,从通用行为到特定行为逐层细化。
    通过使用这些特性,你可以编写出结构清晰、易于管理和扩展的游戏代码。

父类_init(value:int)
{ print("我是父类init函数")
}

子类继承
func _inti(value:int)
._init()
{ print("我是子类init函数")
}
这样对嘛? 我的子类如何初始化,调用顺序是什么。godotScript如何重写?

在Godot中,当你使用GDScript编写脚本并涉及到继承时,正确地调用父类的初始化函数 _init 是非常重要的。但是,您提供的代码中有一些小错误和常见的误区,让我们一步一步来纠正并解释。

正确的语法:

  1. 父类的 _init 函数:当你定义一个父类,并在其中定义 _init 函数时,你可以在其中添加任何初始化逻辑。例如:
    # 父类
    class Parent:
        func _init(value: int):
            print("我是父类init函数,值为", value)
    
  2. 子类的 _init 函数:当你的子类继承自这个父类时,你也可能想要定义一个 _init 函数来进行自己的初始化。在子类的 _init 函数中,你应该使用 . 语法来调用父类的 _init 函数。注意,_init 函数不支持 super() 调用,这是其他编程语言(如Python)中常见的用法。在Godot GDScript中,你应该直接使用 . 来调用父类的方法。
    # 子类
    class Child extends Parent:
        func _init(value: int).(value):  # 注意这里的调用方式
            print("我是子类init函数,值为", value)
    

在这个例子中,子类的 _init 函数通过在参数列表后面使用 .(value) 来调用父类的 _init 函数,并传递 value 参数。这确保了在子类对象被创建时,父类的初始化逻辑也会被执行。

调用顺序:

  1. 当你创建一个子类的实例时,子类的 _init 函数首先被调用。
  2. 在子类的 _init 函数中,父类的 _init 函数通过 .(value) 被显式调用,然后执行父类中的初始化代码。
  3. 父类的 _init 完成后,控制回到子类的 _init,执行子类的初始化代码。

关于重写(Override)和扩展(Extend):

  • 在GDScript中,你不是“重写”父类的 _init 函数,而是“扩展”它。通过在子类的 _init 函数中调用父类的 _init 函数,你保留了父类的初始化逻辑,并在此基础上添加了子类特有的初始化逻辑。
    通过这种方式,你可以确保在继承层次结构中,所有的初始化代码都得到了正确的执行,从而保持了数据的一致性和对象的完整性。

扩展和重写有什么本质区别?godot里面如何重写,我就要重写。 如何保留父类函数的内容,并且补充子类函数的内容

扩展和重写(有时也称为覆盖)是面向对象编程中两个核心概念,它们在功能和目的上有本质的区别。在Godot(以及许多其他编程语言)中,这两个概念同样适用,并且有明确的实现方式。

扩展(Extending)

  • 扩展是指在现有功能的基础上添加新功能或修改现有功能的过程。在Godot中,当你创建一个新的类并使用extends关键字时,你实际上是在扩展一个父类。这意味着你的新类继承了父类的所有属性和方法,并且你可以添加新的属性和方法或修改继承来的方法以改变或增强其行为。

重写(Overriding)

  • 重写是面向对象编程中的一个概念,指的是子类中提供一个与父类中具有相同名称、相同参数的方法,以实现不同的功能。在重写中,子类的方法会在运行时替换掉父类的同名方法。

在Godot中如何重写

在Godot中重写一个方法,你只需要在子类中定义一个与父类中同名的方法。如果你想在子类方法中保留父类方法的行为,并在此基础上添加新的行为,你需要显式地调用父类的方法。

示例:保留父类函数的内容并补充子类函数的内容

假设你有一个父类方法do_something,你想在子类中重写它,同时保留父类的行为:

# 父类
class Parent:
    func do_something():
        print("这是父类的行为")
# 子类
class Child extends Parent:
    func do_something():
        .do_something() # 调用父类的方法
        print("这是子类添加的行为")

在这个例子中,当你在子类的实例上调用do_something时,首先会执行父类的do_something方法(打印“这是父类的行为”),然后执行子类中添加的代码(打印“这是子类添加的行为”)。
请注意,虽然这个例子展示了如何在子类中保留并扩展父类的方法,但Godot的GDScript并不支持使用super关键字来调用父类的方法,如你在某些其他编程语言中所见。相反,你应该使用.语法来调用父类的方法,如上述示例所示。
总之,扩展允许你增加或修改行为,而重写则是替换现有的行为。在Godot中,通过在子类中定义同名方法来实现重写,并通过直接调用这些方法来保留和扩展父类的行为。

godot的class和class_name的区别??

在Godot中,classclass_name关键字用于不同的目的,它们之间有一些关键区别:

class 关键字

  • 匿名类:在GDScript中,使用class关键字可以在一个脚本文件内定义一个新的类。这种类是匿名的,意味着它没有一个全局识别的名字。这主要用于那些仅在当前脚本或场景中使用的辅助类,而不是全局可用的类。
  • 局部作用域:用class关键字定义的类只能在定义它的脚本或其子类中访问。如果你想在其他脚本中使用这个类,你需要先获取一个到定义了该类的脚本的实例的引用。

class_name 关键字

  • 全局类名class_name关键字用于给脚本定义一个全局识别的名字。这样,该类就可以在项目的任何地方通过名字被引用,而不需要先加载脚本文件。
  • 自动注册:使用class_name定义的类会被Godot自动注册,这意味着它们在编辑器中也可用。例如,可以直接在场景树中创建这些类的实例,或者在其他脚本中通过名字引用它们,无需使用preloadload函数加载脚本文件。
  • 可选图标:与class关键字不同,class_name还允许你为类指定一个图标,这使得在编辑器中识别这个类的实例变得更加容易。

使用场景比较

  • class:当你需要一个仅在当前脚本中使用的辅助类时,或者当你不需要在项目的其他地方通过名字引用这个类时,使用class是一个好选择。
  • class_name:当你创建一个可能在项目多个地方使用的类,或者你希望在编辑器中通过名字直接访问这个类时,使用class_name更合适。

示例

假设你有一个公共的工具类,你希望在多个地方使用,那么使用class_name是理想的选择:

# 使用class_name
class_name MyUtility
func some_function():
    pass

现在,MyUtility类可以在项目的任何地方通过其名字直接引用。
相比之下,如果你在一个脚本中有一个仅用于内部逻辑的辅助类,那么使用class就足够了:

# 使用class
class MyHelper:
    func helper_function():
        pass

这个MyHelper类仅在定义它的脚本中可见。
总的来说,classclass_name的使用取决于你的具体需求——是否需要全局访问和编辑器支持。

posted @ 2024-01-23 12:21  专心Coding的程侠  阅读(140)  评论(0编辑  收藏  举报