首页 > 其他分享 >Godot的几个附加脚本和进行继承时比较特别的特性

Godot的几个附加脚本和进行继承时比较特别的特性

时间:2023-06-03 22:44:51浏览次数:49  
标签:脚本 Godot 自定义 继承 附加 Sprite2D 类型 节点

注: 这是在Godot4.0中总结出的内容,并且语言是C#。
特别的,下面有的特性和C#关系比较大。

基本特性

在Godot中,为某个节点编写特别的代码时,需要为节点新建脚本,或引用已有脚本。

引用脚本时,填入脚本路径即可,相当于是复用代码了。

新建脚本时,一般做法是新建一个自定义类型,并且这个类型继承自原有节点的类型。

其实,你也可以不继承自原有节点的那个类型。下面的各小节文章均是针对这个情况的。

在选择继承的目标时,你可以选择的范围有:(最常规的)继承自原有类型、继承自自定义类型、继承自"中间"类型。

继承自自定义类型

继承自自定义类型,一般来说是为了满足这个需求的:

用户为某节点编写了一个自定义了类型后,在新的情境下,需要扩充这个类型,让它有新的功能,并且旧的不能变。
此时,程序员一般第一个想到的就是继承。Godot顺理成章地允许用户这样进行继承。

想要进行这样的继承时,为节点新建脚本时必须先选择已有类型,若直接填入想继承的类型,Godot不允许这样做。

image

创建完成后,用户可以打开脚本文件,手动修改继承类型。

虽然看上去就像作弊,但是Godot方面认可这种做法,也有人建议在建立脚本时,"继承"内容框可以选择自定义类型进行继承,但是Godot的开发者们暂时并没有这么做。

需要注意的是,这种做法对此脚本附加到的节点有要求。

  • 首先,你在编写一系列一层一层继承起来的类时,你建立起来的最底层的那个自定义类肯定是继承自"原生类型"的
    • "原生类型"就是新建节点时,你能在面板里选到的类型(没错,这里找不到你自定义的类型,它们100%是Godot内置的!)
  • 要将这一系列自定义类中的任何一个附加到一个目标节点时,这个目标节点在编辑器中体现出来的类型必须能够兼容自定义类最底层的"原生类型",和它相同,或是继承自它,都可以。
    (注:如果不兼容,运行时会报错。)

继承自"中间"类型

读了上面的内容时,你也许会意识到,其实任何一个自定义类型底层的"原生类型",可以是它附加的节点的继承树中大于等于Node的类型之中的任何一个类型。

我称这种做法为"继承自中间类型"。




事实上,也许需要调整一下观念。大家一开始似乎会认为,节点附加了一个自定义类型后,节点就是自定义类型的实例了。

实际情况似乎要稍微割裂一点。因为继承自中间类型后,你会发现,这个自定义类型无法完全描述这个节点。请看接下来的例子。

这里,我给一个精灵Sprite2d挂上这样一个自定义类型,它继承自Node。

public partial class TNode : Node
{
}

image

我想知道,这个节点从C#继承体系的角度看,还是不是"精灵(Sprite2D)"了

让我们在根节点中写一点代码,试图了解该节点的类型。

public partial class AskForClass : Node2D
{
    public override void _Ready()
    {
        var t1node = GetNode("TNode");

        //方法1 打印继承树
        Type tobj;
        tobj = t1node.GetType();
        while (tobj != null)
        {
            GD.Print(tobj.Name);
            tobj = tobj.BaseType;
        }
        GD.Print("以上是继承树。");

        //方法2 类型转换。转换失败的话就是null。
        var t2node = t1node as Sprite2D;
        GD.Print(t2node);

        //方法3 我发现Godot有一个IsClass()方法
        GD.Print("is sprite2D class?" + t1node.IsClass("Sprite2D"));

    }
}

(小提示:Godot的_Ready()函数被执行顺序是先子节点,再父节点,这样嵌套的,所以父节点访问子节点总是万无一失的,当然,这个示例就算顺序不是这样也不存在问题)

上面用了3种方式试图确认我们的t1node有没有Sprite2D的成分,以及Sprite2D的成分通过哪种方式能查到,猜猜看?

结论是:

TNode
Node
GodotObject
Object
以上是继承树。
null
is sprite2D class?True

除非使用Godot提供的函数IsClass(), 光靠C#的继承体系,查不到Sprite2D的成分, 而且实例无法转换成Sprite2D类型,这样就不能以C#通常的方式操作Sprite2D特有的函数和变量了。

在C#内,虽然它"放弃了作为Sprite2D的身份",但我们的节点在运行时仍然做着一个"精灵"会做的事情,比如我在编辑器里对它的位置和旋转进行了变更,这些变更都没有丢失。
个人推测,此时需要使用GetIndexed()SetIndexed()等方法来操作那些无法直接访问的东西。

image



这个实例和实际存在于Godot运行时的节点竟然有这样的不同。

以后也许时不时需要想起来这样一件事——C#实例和Godot内的节点只是连在一起,有一个映射的关系罢了,并不100%是那个节点本身。

节点除了有C#能提供给它的函数和字段外,还可以拥有相当突出的Godot赋予它的不同类型的功能,即各种类型的"原生节点"的各种各样的功能。


阅读文档后,大概可以这样理解,Godot运行时维护的节点身上的函数和变量的表现更符合动态语言的特征,而不是静态类型语言。
不论是C#脚本、Godot脚本、还是"原生类型"的节点,行为都是将自己的各种功能附加或覆盖到了节点身上。

Godot is very dynamic. An object's script, and therefore its properties, methods and signals, can be changed at run-time. Because of this, there can be occasions where, for example, a property required by a method may not exist. To prevent run-time errors, see methods such as set, get, call, has_method, has_signal, etc. Note that these methods are much slower than direct references.
Godot很是动态。对象的脚本及其属性、方法和信号可以在运行时更改。因此,在某些情况下,例如,方法所需的属性可能不存在。若要防止运行时错误,请参阅设置、获取、调用、has_method、has_signal等方法。请注意,这些方法比直接引用慢得多。

继承自中间类型后可能遇到的坑

上述特性可能会引发一个问题,当你需要用C#找到场景中的所有某一原生类型的节点时,从"中间"继承的节点被获取后,由于一些身份被放弃了,有可能被漏掉!

也就是说,在上面的案例中,想找Sprite2D时,用下面的方法,挂载了TNode脚本的精灵将被跳过,尽管它这么大一个放在屏幕上。

List<Sprite2D> lst = new List<Sprite2D>();
var children = FindChildren("*");
foreach (var chi in children)
{
    if (chi is Sprite2D sp)
    {
        lst.Add(sp);
        GD.Print("这个是精灵" + chi.Name);
    }
    else
    {
        GD.Print("这个不是精灵" + chi.Name);
    }
}

image

我没有测试GDScript,不知道是否情况会不同。也许它支持多重继承?

综上所述,个人建议尽量避免附加脚本时从中间继承

实在有这样的需求,要么避免一个类型的每一个身份都需要被C#直接操作,要么用IsClass()配合GetIndexed()SetIndexed()等方法处理该对象。

参考:
https://godotengine.org/qa/141137/best-way-to-add-a-node-that-extends-a-custom-class
https://docs.godotengine.org/en/latest/classes/class_object.html

标签:脚本,Godot,自定义,继承,附加,Sprite2D,类型,节点
From: https://www.cnblogs.com/qiiimiu/p/17454585.html

相关文章

  • 4、虚拟机单机、集群的克隆、删除脚本(以初始化好的虚拟机为模板)
    虚拟机克隆、删除脚本[root@ubunt~]#catclone.sh#!/bin/bash##./etc/init.d/functions(如果是ubuntu,注释此行)Red="\e[1;31m"Purple="\e[1;35m"Green="\e[1;32m"Blue="\e[1;36m"Yellow="\e[1;33m"End="\e[0m&......
  • 国产化麒麟系统编译程序打run包脚本和说明
    1.打包脚本package文件夹下放一个程序文件夹OvitFrame文件夹、package.sh脚本、install.sh脚本;执行package.sh脚本将OvitFrame文件夹压缩成一个压缩包,然后将install.sh和压缩包用cat命令生成一个run包;然后就#!/bin/bash#将源文件打包成压缩包tarczvfOvitFrame.tar.gzOvitF......
  • Springboot项目启动脚本
    #!/bin/bashSpringBoot=$2if["$1"=""];thenecho-e"\033[0;31m未输入操作名\033[0m\033[0;34m{start|stop|restart|status}\033[0m"exit1fiif["$SpringBoot"=""];thenecho-e&qu......
  • matlab中通过ode函数求解常微分方程附加简单的钟摆模型
    ✅作者简介:热爱科研的算法开发者,Python、Matlab项目可交流、沟通、学习。......
  • ld链接脚本一些随笔
    链接:将多个目标文件(xx.o)、库文件(xx.a)、动态库(.so)等等链接成一个可执行文件。常见段含义:.text代码段存放程序执行代码区域.data初始化数据段存放程序中已初始化的全局变量.bss未初始化的数据段存放程序中未初始化的全局变量.rodata只读数据段存放C中的字符串和#define定义......
  • linux sh脚本启动springboot
    1、restart.sh#!/bin/bashAPP_NAME=xxxxx.jar#定义JAVA程序名LOG_FILE="$APP_NAME.log"#定义日志文件名称#查询进程并终止PID=`ps-ef|grep$APP_NAME|grep-vgrep|awk'{print$2}'`kill-9$PIDecho"$APP_NAME的进程$PID已经终止"#启动jar包,指......
  • 仙境传说RO怎样创建一个NPC rAthena脚本语言的hello word
    仙境传说RO怎样创建一个NPCrAthena脚本语言的helloword大家好,我是艾西。上一篇文章中我们有教大家怎么编译仙境传说RO服务端和客户端,当我们自己可以搭建架设游戏时,那么这个游戏在某种意义上就是我们说的算了。比如增加一些特色功能等,今天艾西教大家怎么创建一个NPC(可售卖装备、药......
  • 仙境传说RO :ra脚本加载结构和开发语法讲解​
    仙境传说RO:ra脚本加载结构和开发语法讲解大家好,我是艾西。上一篇文章中我们聊完了怎么在游戏中新建NPC,感觉还是有不少小伙伴没有太看懂原理。今天艾西给大家深度讲解一下脚本加载结构和开发语法环境文档。我们最后都是以ra脚本为主要讲解以及实操,那想完全掌握ra脚本查看相关的文档......
  • 写了个命令行初始化VMWARE ESXI主机的脚本
    #addnictovswitchesxclinetworkvswitchstandarduplinkadd--vswitch-name=vSwitch0--uplink-name=vmnic1#addportgrouptovswitchesxclinetworkvswitchstandardportgroupadd-vvSwitch0-pVLAN-210esxclinetworkvswitchstandardportgroupset-v21......
  • 微软自动化框架Playwright学习和使用-脚本录制和回访过程
    接上回,可以使用 playwright inspector 来进行脚本录制。今天就说下具体的录制步骤。playwright inspector都会显示出来。   1.点击PlaywrightInspector中的 Record按钮,开始录制  2.点击 Record按钮后,Record按钮显示为红色,代表已经开始录制了。这时将鼠标移动到要测......