1739 字
9 分钟
为 Godot 学 C# 07:GDScript 到 C# API 对照速查表

教程二 · 第 7 章:Godot ↔ C# 对照速查表#

写代码时随时翻。左列是你在 GDScript 教程里常见的写法(网上 90% 教程是 GDScript),右列是 C# 对应物。

7.1 GDScript → C# 翻译规则#

GDScriptC#
extends CharacterBody2Dpublic partial class X : CharacterBody2D
func _ready():public override void _Ready()
func _process(delta):public override void _Process(double delta)
func _physics_process(delta)public override void _PhysicsProcess(double delta)
print(a, b)GD.Print(a, b)
@export var speed = 300[Export] public float Speed = 300f;
@onready var s = $Spriteprivate Sprite2D _s = null!; 然后 _Ready 里 GetNode<Sprite2D>("Sprite")
$Sprite / $"../Player"GetNode<Sprite2D>("Sprite")
@unique %SpriteGetNode<Sprite2D>("%Sprite")(场景里勾”唯一名称”)
signal died[Signal] public delegate void DiedEventHandler();
died.emit()EmitSignal(SignalName.Died)
body.died.connect(_on_died)body.Died += OnDied;
queue_free()QueueFree()
add_child(b)AddChild(b)
global_positionGlobalPosition
move_and_slide()MoveAndSlide()
randf() / randi()GD.Randf() / GD.Randi()(或 RandomNumberGenerator)
randf_range(a,b)GD.RandRange(a, b) / _rng.RandfRange(a,b)
lerp(a,b,t)Mathf.Lerp(a,b,t)
clamp(v,a,b)Mathf.Clamp(v,a,b)
sin/pi/deg_to_radMathf.Sin / Mathf.Pi / Mathf.DegToRad
Vector2.RIGHTVector2.Right
await get_tree().create_timer(1).timeoutawait ToSignal(GetTree().CreateTimer(1f), SceneTree.SignalName.Timeout)
get_tree().reload_current_scene()GetTree().ReloadCurrentScene()
get_tree().change_scene_to_file(p)GetTree().ChangeSceneToFile(p)
is_instance_valid(x)GodotObject.IsInstanceValid(x)
add_to_group("x")AddToGroup("x")
get_nodes_in_group("x")GetTree().GetNodesInGroup("x")
preload("res://b.tscn")ResourceLoader.Load<PackedScene>("res://b.tscn")(或 [Export] PackedScene)
instance().Instantiate<T>()
数组 [](引擎边界)Godot.Collections.Array<T> / 内部用 List<T>
字典 {}(引擎边界)Godot.Collections.Dictionary / 内部用 Dictionary<K,V>
str(x) / 字符串格式化x.ToString() / $"{x}"
nullnull(节点另需 IsInstanceValid)
func _unhandled_input(event)public override void _UnhandledInput(InputEvent @event)(@ 因为 event 是 C# 关键字)

命名规律:snake_case → PascalCase;常量 INPUT_MOUSE_BUTTON 风格 → Input.MouseButton 枚举风格。

7.2 节点生命周期回调签名表#

public override void _Ready();
public override void _EnterTree();
public override void _ExitTree();
public override void _Process(double delta);
public override void _PhysicsProcess(double delta);
public override void _UnhandledInput(InputEvent @event);
public override void _Input(InputEvent @event);
public override void _Draw();
public override void _ExitTree();

调用顺序:_EnterTree(进树瞬间,子节点可能没就绪)→ 子树全部 _EnterTree → _Ready(子先父后)→ 每帧 _Process/_PhysicsProcess → 离开时 _ExitTree。

7.3 移动与物理速查#

// CharacterBody2D/3D 标准循环
Velocity = Velocity with { Y = Velocity.Y + Gravity * dt }; // 重力自己加
Velocity = Velocity with { X = dir * MoveSpeed };
MoveAndSlide();
bool grounded = IsOnFloor();
// 手动碰撞信息
var collisions = MoveAndSlideCollide();
foreach (var c in collisions)
Node collider = c.GetCollider();
// 刚体(RigidBody2D):施力而不是设位置
body.ApplyCentralImpulse(force);
body.LinearVelocity = v;
// Area2D 信号
BodyEntered += (Node2D body) => { }; // 物理体进入
AreaEntered += (Area2D area) => { }; // 另一个区域进入
// 射线/形状查询(PhysicsDirectSpaceState2D)
var space = GetWorld2D().DirectSpaceState;
var query = PhysicsRayQueryParameters2D.Create(GlobalPosition, target);
var result = space.IntersectRay(query); // Godot.Collections.Dictionary
if (result.Count > 0 && result["collider"].As<Node>() is Enemy e) { }

7.4 输入速查#

float x = Input.GetAxis("move_left", "move_right"); // -1/0/1
Vector2 dir = Input.GetVector("left","right","up","down"); // 四方向归一化
bool held = Input.IsActionPressed("jump"); // 按住
bool justPressed = Input.IsActionJustPressed("jump"); // 按下瞬间
bool justReleased = Input.IsActionJustReleased("jump");
bool key = Input.IsKeyPressed(Key.Shift);
Input.GetVector("ui_left","ui_right","ui_up","ui_down"); // 内置方向动作
// 事件细节(_UnhandledInput 里)
if (@event is InputEventMouseButton mb && mb.Pressed && mb.ButtonIndex == MouseButton.Left) { }
if (@event is InputEventKey k && k.Pressed && k.Key == Key.Escape) { }

7.5 信号声明/连接/发射全形态#

// 无参
[Signal] public delegate void DiedEventHandler();
EmitSignal(SignalName.Died);
who.Died += OnDied;
// 带参(参数类型必须是 Godot 可封送的:int/float/string/StringName/Node/Vector2...)
[Signal] public delegate void HealthChangedEventHandler(int current, int max);
EmitSignal(SignalName.HealthChanged, _hp, 100);
who.HealthChanged += (cur, max) => HpBar.Value = cur;
// 编辑器连接生成的回调命名惯例
private void OnButtonPressed() { }
private void OnAreaBodyEntered(Node2D body) { }
// 代码版底层 API(需要动态名/一次性连接时)
area.Connect("body_entered", Callable.From<Node2D>(OnBody));
area.Disconnect("body_entered", Callable.From<Node2D>(OnBody));

7.6 场景/节点操作速查#

// 找节点
GetNode<T>("路径"); // 相对路径
GetNode<T>("%UniqueName"); // 唯一名
GetParent<T>(); // 父
GetTree().CurrentScene; // 当前主场景根
GetTree().GetFirstNodeInGroup("g"); // 组
GetTree().GetNodesInGroup("g"); // 组全部
// 生成与销毁(实例化三步:实例化 → 注入 → 入树)
var item = scene.Instantiate<Bullet>();
item.Setup(dir, speed);
GetTree().CurrentScene.AddChild(item);
item.QueueFree(); // 帧末安全销毁;立刻释放用 Free()(极少用)
item.RemoveChild(item); // 摘树但不销毁(对象池用)
// 显隐与处理开关
Visible = false;
SetPhysicsProcess(false); // 停物理回调
SetProcess(false);
ProcessMode = ProcessModeEnum.Always; // 暂停时仍运行(暂停菜单)
// 节点间等待
await ToSignal(node, Node.SignalName.Ready);
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);

7.7 常用类型/数学/随机#

Vector2 a = new(1, 2), b = new(3, 4);
a + b; a - b; a * 2f;
a.Length(); a.DistanceTo(b); a.DistanceSquaredTo(b); // 比较距离用平方,省开方
a.Normalized(); a.Angle(); a.Rotated(ang); a.Dot(b); a.Cross(b);
a.Lerp(b, 0.5f);
a.Reflect(normal); a.Bounce(normal);
Mathf.Clamp(v, min, max);
Mathf.Lerp(a, b, t); Mathf.MoveToward(a, target, maxDelta);
Mathf.Sin/Cos/Tan/Atan2; Mathf.Pi; Mathf.DegToRad/RadToDeg;
Mathf.Sign(v); Mathf.Abs(v); Mathf.Floor/Ceil/Round;
Mathf.InverseLerp(a, b, v); Mathf.Remap(v, inA, inB, outA, outB);
float t = Mathf.Clamp((float)delta * speed, 0, 1); // 帧率无关 lerp 系数
GD.Randf(); GD.RandRange(-1f, 1f); GD.Randi() % 100;
GD.RandRange(1, 6); // int 重载含两端
// 实例随机(推荐,可设种子做确定性回放)
var rng = new RandomNumberGenerator(); rng.Randomize();
rng.RandfRange(0, 10); rng.RandiRange(1, 6);

7.8 UI / Tween / 音频 / 场景#

Label.Text = $"{hp}";
ProgressBar.Value = hp;
Button.Pressed += OnClick;
Control.Visible / Show() / Hide();
button.GrabFocus();
lineEdit.Text; lineEdit.TextChanged += s => { };
// Tween
var tw = CreateTween();
tw.TweenProperty(node, "position", targetPos, 0.3f)
.SetTrans(Tween.TransitionType.Quad).SetEase(Tween.EaseType.Out);
tw.SetParallel(true);
await tw.ToSignal(Tween.SignalName.Finished);
CreateTween().TweenProperty(node, "modulate:a", 0f, 0.2f);
// 音频
AudioStreamPlayer.Play();
AudioServer.SetBusVolumeDb(AudioServer.GetBusIndex("Master"), v);
AudioServer.GetBusVolumeDb(...);
// 场景树/时间
GetTree().Paused = true;
GetTree().CreateTimer(seconds, processAlways: true, processInPhysics: false, ignoreTimeScale: true);
GetTree().Quit();
Time.GetTicksMsec(); // 启动以来毫秒
Engine.TimeScale = 0.5f; // 全局慢动作
// 文件
FileAccess.FileExists("user://save.tres");
using var f = FileAccess.Open("user://a.txt", FileAccess.ModeFlags.Write);
f.StoreString(text);
ResourceSaver.Save(resource, "user://x.tres");
ResourceLoader.Exists(path); ResourceLoader.Load<T>(path);
ProjectSettings.GlobalizePath("user://"); // 看 user:// 的真实磁盘位置

7.9 C# 语言一页回顾(全部关键字到此已见过)#

类型 int float double bool string char long
Vector2 Vector3 Color StringName(Godot)
var 类型推断;const 常量;enum 枚举;record 数据对象
空值 null, ?. , ?? , ??=, null!, IsInstanceValid, <Nullable> enable
流程 if else switch(表达式/pattern) for foreach while break continue return
方法 参数/out/ref/默认值/命名参数/重载/static/override/virtual/abstract/=> 表达式体
类 class partial new this base sealed
面向对象 :继承, interface, virtual/override/abstract, protected, is/as/(T), where T:
成员 字段/属性 {get;set;}/[Export]/[Signal]/event/Action/Func
集合 List<Dictionionary> HashSet Queue Stack; 数组 T[]; Godot.Collections.*
工具 using/namespace, try-catch-finally, throw, nameof
异步 async/await, Task, ToSignal, async void 仅限生命周期
LINQ Where Select OrderBy GroupBy Any FirstOrDefault Sum ToList

7.10 报错自助翻译表#

报错/现象真实含义
CS0103 名称不存在拼错 / 没在作用域 / 没 using
CS0266 无法隐式转换多为 double→float,加 (float) 或字面量加 f
CS1061 没这个方法定义节点类型拿错了(GetNode 泛型不对),或方法名拼错
CS0120 非静态字段要求对象引用你在 static 上下文用了实例成员
CS7036 参数不匹配信号回调参数个数/类型和声明不一致
NullReferenceException引用为 null;节点字段没在 _Ready 赋值或路径错
ObjectDisposed/Invalid instance节点已 QueueFree 还在访问;IsInstanceValid 先查
”没反应”但不报错忘了 Build;信号没连上;节点 Process 关了;层/掩码没勾
改 Position 没用/编译不过Vector2 是 struct,整体赋值或用 with
暂停后按钮点不动ProcessMode 设 Always
await 后操作报错等待期间节点被释放,回来先验活
导出变量不出现不是 public 属性/字段、类型不被识别、缺 [GlobalClass](自定义 Resource)
中文乱码/路径怪文件/路径用英文,源文件 UTF-8

7.11 毕业标准#

能用自己的话回答这些问题,就算出师:

  1. 为什么 delta 要乘进移动距离?为什么 float/double 转换到处都是?
  2. _Ready、_Process、_PhysicsProcess 的分工和调用时机?
  3. 节点、场景、脚本、Resource 各自是什么关系?
  4. CharacterBody2D 和 RigidBody2D 怎么选?
  5. 信号 vs 直接调用 vs 组 vs Autoload,各在什么情况下用?
  6. struct 和 class 的赋值行为差异如何影响 Position/Velocity 的写法?
  7. override 到底重写了谁?接口和继承在伤害系统里各承担什么?
  8. 子弹从生成到命中经过哪些 API,顺序为什么不能乱?
  9. List/Dictionary/HashSet/Godot 集合分别什么时候用?
  10. 游戏卡死/掉帧/行为诡异时,你的排查顺序是什么?(监视器→远程树→可见碰撞→断点→print)

教程到此结束。语言只是锤子,做游戏才是目的——关掉这份文档,去把教程一的作业和你的第一个完整游戏做出来。

——教程二完——


《Godot C# 零基础》系列导航

⬅️ 上一篇:为 Godot 学 C# 06:集合、泛型、委托事件、LINQ 与异步 📑 返回系列总目录(第 01 篇)

为 Godot 学 C# 07:GDScript 到 C# API 对照速查表
https://021028.xyz/posts/default/176/
作者
021028
发布于
2026-10-14
许可协议
CC BY-NC-SA 4.0