铺垫了这么多,(我觉得)终于有足够的基础知识可以到Godot中用C#编写脚本了。
请先使用.NET版的Godot创建一个新项目作为试验场地,我们先从编写一个简单的玩家脚本开始熟悉Godot的C# API。
支持.NET的Godot和标准版的Godot在界面和核心功能上区别不大。我们新建一个2D场景,然后同样是点击添加脚本按钮新建脚本。
和标准版不一样的是,这里的语言选项除了GDScript之外多了一个C#选项。
这里需要选择C#。当然在.NET版中GDScript也是支持的,后面会将如何用交互使用两种不同的脚本语言。
一般来说我们会在其它编辑器或者IDE中编辑C#代码,这里需要检查一下Godot的外部编辑器设置。
在Editor->Editor Settings(编辑器设置)中,打开高级设置开关,找到或者搜索DotNet部分。选择Editor(编辑器)选项以进行C#编辑器相关的设置。
一般来说只要你正常安装了你的IDE或者编辑器,这里勾选使用外部编辑器,选择你需要用的编辑器就好了。
如果你打开刚才新建的C#源文件,你首先会看到这个类的头部有一个我还没提到过的关键字:
public partial class Player : CharacterBody
partial 关键字表示这里的代码只是这个类的部分定义,还有部分定义出现在其它地方。
为什么会这样呢?因为Godot会为你的C#脚本通过源代码生成器生成一写额外的代码,这些代码用于支持你的代码和Godot交互,也是为了方便开发。只要你需要创建一个“脚本”——一个继承了Godot内置类型的C#脚本, 建议 直接从Godot编辑器中创建!这样可以避免一些麻烦或者Godot抽疯。
通过Godot编辑器创建的C#脚本会自带 using Godot; ,Godot的API都放在这个命名空间里。如果你需要在非脚本文件中使用Godot提供的各种类型或者功能,只需要using一下就可以了。
我们在GDScript中经常会写一些下划线开头的特殊方法来实现游戏功能,比如 _ready 、 _process 啥的。这些方法主要是响应一些比较关键的引擎事件通知,这些方法可以在GDScript中重写,只不过GDScript不用override关键字罢了。
自然,这些方法在C#中也表现为对对应方法的重写。由于代码风格的不同,这些方法在C#中也以下划线开头,但是首字母是大写的。
我们时常需要通过一个变量来引用当前场景下的某个节点,但是获取节点只有在场景准备完成之后才是安全的。GDScript的 @onready 本身也就是个语法糖,相当于在 _ready 中调用 get_node 方法获得对节点的引用。
Godot自己没有通过某种办法在C#中加入这个功能。因此要获得对节点的引用,需要在 _Ready 中调用 GetNode :
private Area2D area;
public override void _Ready()
{
area = GetNode<Area2D>("Area2D");
}
GetNode 方法在C#中会有多个重载版本,一般来说直接用泛型版本,传入你的节点的类型作为类型参数就可以了。这样就不用把返回值又转换一遍类型。参数同样是用字符串表示的节点路径。
这个东西很微妙,有其他开发者利用代码生成器生成这些代码,但是实际上也省不了多少代码。
天经地义的,在开发过程中,我们时常会需要往终端输出一些文本便于调试。
当然,你仍然 可以 使用C#的 Console.WriteLine ,你的游戏也不会报错也不会怎样。只不过 Console.WriteLine 输出的东西 不会 显示在Godot的输出面板中。你可能注意到无论是标准版还是.NET版Godot都有两个启动文件,有一个是带console字样的。带console字样的会启动一个终端,终端里会显示输出信息。如果你用 Console.WriteLine 输出内容的话,输出的东西会显示在这个终端中(其实你的编IDE中的终端也会显示)。
话说回来,你其实也可以用Godot的 print 。正如前面所说,C#是相当面向对象的语言,没有所谓的全局函数。而在GDScript中表现为全局函数的那些常用函数,在C#的API中也得放到一个类中。这个类就是 GD 。要使用GDScript中的print,只需要调用 GD.Print 。
GDScript的全局函数中有很多数学函数,这些函数 没有 被引入 GD 类中。
前面的文章中提到过,C#的标准库中提供了一个 System.MathF (考虑到Godot主要使用 float ,你可能不太会用 System.Math ),里面有各种常用的数学函数。然而,Godot的C# API也提供了GDScript里那些数学函数,它们放在 Godot.Mathf 里面。我觉得这个名字真得改,太容易混淆了。
那么你该用哪个呢?毕竟 System.MathF 和 Godot.Mathf 提供的函数实际上有一些是重合的。考虑到Godot本身是C++写的,有些API可能会涉及跨语言调用,这个过程可能会有一些额外的消耗。因此C#里本身就有的函数我认为应该考虑使用C#的版本。当然, Godot.Mathf 里面还有一些是 System.MathF 没有的(比如 Lerp ),这种函数就别多想了,直接用就是了。
好在 @export 在C#中有一个比较简单的写法。比如你想export玩家的移动速度,你要这样写:
[Export]
private int speed;
[Export] 是C#中特性(attribute)的语法。它可以给C#中的各种元素(比如字段、属性、方法、类型)添加一些“元数据”。所谓元数据就是比代码本身更高层次的数据,这些东西会给编译器或者其它要读取、检查代码本身的代码提供额外的信息。说白了,这些东西说到底是给其他东西看的,我们直接用就好。
它的语法是方括号中间写上某个特性的构造函数。由于C#的语法糖设计, Export 本身叫 ExportAttribute ,后面的Attribute可以省掉。如果这里调用的无参构造函数,括号也可以省掉。所以总的来说特性的语法还是比较简洁好看。如果你喜欢也可以把特性写在前面: [Export] private int speed; 。当然有些时候只能写在前面,比如应用到参数的时候。
如果你有更详细的配置需要,也可以在C#中实现。比如你在GDScript中写 @export_category("Character") 来给属性分类展示,你在C#中也可以写:
[ExportCategory("Character")]
[Export]
private int speed;
当然有一些东西和GDScript是不一样的。比如你想限制 speed 的范围,在GDScript中你会写 @export_range(1, 100) 。在C#中没有单独这样一个特性,而是需要单独在 Export 特性的参数中指明:
[Export(PropertyHint.Range, "1,100")]
private int speed;
Export 的这个版本的构造函数有两个参数,第一个参数是所谓 PropertyHint 也就是告诉Godot你想要在编辑器中如何显式它的编辑项目。第二个参数比较抽象,是一个字符串,不同的hint写法也不一样。对于这里的范围来说,这个字符串应该是一个用逗号隔开的几个数字。最多写3个数字,分别对应最小值、最大值、步长。步长就是你点击编辑器中的上下箭头时,这个属性要加减多少。
更具体的要查看Godot的文档中对GDScript和 C# 中export相关的介绍。 如果你使用过Unity,可能记得Unity的 [SerializeField] ,功能是类似的。Godot的 [Export] 不仅支持字段也支持属性,并且非public的访问级别也支持。
假如我们要实现一个基本的移动功能,我们在GDScript中可能会这样做:
if Input.is_action_just_pressed("right"):
velocity.x = speed
GDScript的 Input 是一个全局对象,当然在C#中就是一个静态类了。语法其实都一样的。
Velocity.Y = speed;
这行代码会直接报错。当然要解决这个问题,就是直接给它一个新值,Vector2同样用 new 关键字加构造函数构造。:
Velocity = new Vector2(speed, Velocity.Y);
前面的代码错误信息说的是,因为Velocity等向量类型属性是作为属性暴露出来的,返回的值是一个临时的值,不允许直接修改临时值。这里具体是怎么回事我会在后面的文章中详细讲解。你可能心里已经在骂C#了,但是先别急,后面会说这到底是怎么回事。
C#和GDScript(至少在直观感受上)不太一样的一点是,C#需要一个明显的编译的过程。这是C#和.NET的设计造成的。你可能听说过编程语言有编译型的和解释型的。编译型的编程语言在运行前必须要编译成某种状态,解释型不需要在运行前编译,而是在运行时处理云云。当然说实话,如果把编译型和解释型作为数轴的两极,很多语言其实是落在靠中间的位置。
C#运行前需要进行编译,但是很多时候不会直接编译成机器码,而是其运行时运行的一种中间代码。修改C#代码之后,你可以直接在你的编辑器中build(这个操作有的翻译成“构建”,有的又翻译成“生成”),也可以点击Godot编辑器右上角那排按钮中的锤子图标(快捷键Alt+B)。如果你直接运行游戏Godot也会自动先构建一遍。看到这个警告不要怕,如果你在代码中修改了一些会影响编辑器中展示的东西的代码,Godot也会提示你要手动构建一遍才会显示出来。
这个编译的过程明显要个几秒钟,但是肯定比你在Unreal中编译C++代码要快很多。
评论区
共 条评论热门最新