1739 字
9 分钟
为 Godot 学 C# 07:GDScript 到 C# API 对照速查表
教程二 · 第 7 章:Godot ↔ C# 对照速查表
写代码时随时翻。左列是你在 GDScript 教程里常见的写法(网上 90% 教程是 GDScript),右列是 C# 对应物。
7.1 GDScript → C# 翻译规则
| GDScript | C# |
|---|---|
extends CharacterBody2D | public 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 = $Sprite | private Sprite2D _s = null!; 然后 _Ready 里 GetNode<Sprite2D>("Sprite") |
$Sprite / $"../Player" | GetNode<Sprite2D>("Sprite") |
@unique %Sprite | GetNode<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_position | GlobalPosition |
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_rad | Mathf.Sin / Mathf.Pi / Mathf.DegToRad |
Vector2.RIGHT | Vector2.Right |
await get_tree().create_timer(1).timeout | await 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}" |
null | null(节点另需 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.Dictionaryif (result.Count > 0 && result["collider"].As<Node>() is Enemy e) { }7.4 输入速查
float x = Input.GetAxis("move_left", "move_right"); // -1/0/1Vector2 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 => { };
// Tweenvar 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 ToList7.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 毕业标准
能用自己的话回答这些问题,就算出师:
- 为什么
delta要乘进移动距离?为什么 float/double 转换到处都是? _Ready、_Process、_PhysicsProcess的分工和调用时机?- 节点、场景、脚本、Resource 各自是什么关系?
- CharacterBody2D 和 RigidBody2D 怎么选?
- 信号 vs 直接调用 vs 组 vs Autoload,各在什么情况下用?
- struct 和 class 的赋值行为差异如何影响 Position/Velocity 的写法?
- override 到底重写了谁?接口和继承在伤害系统里各承担什么?
- 子弹从生成到命中经过哪些 API,顺序为什么不能乱?
- List/Dictionary/HashSet/Godot 集合分别什么时候用?
- 游戏卡死/掉帧/行为诡异时,你的排查顺序是什么?(监视器→远程树→可见碰撞→断点→print)
教程到此结束。语言只是锤子,做游戏才是目的——关掉这份文档,去把教程一的作业和你的第一个完整游戏做出来。
——教程二完——
《Godot C# 零基础》系列导航
⬅️ 上一篇:为 Godot 学 C# 06:集合、泛型、委托事件、LINQ 与异步 📑 返回系列总目录(第 01 篇)
为 Godot 学 C# 07:GDScript 到 C# API 对照速查表
https://021028.xyz/posts/default/176/