2074 字
10 分钟
为 Godot 学 C# 01:变量与类型

教程二 · 第 1 章:准备、变量与类型#

教程二解决”教程一里每句代码为什么这么写”。不打开 Visual Studio 建控制台项目——所有例子都假设运行在 Godot 的脚本里,输出统一用 GD.Print。练习方式:建一个 playground.tscn,挂 Playground.cs,把例子粘进 _Ready(),Build,F6,看输出面板。

using Godot;
public partial class Playground : Node
{
public override void _Ready()
{
// 你的实验代码写在这里
GD.Print("hello");
}
}

1.1 C# 是什么、编译是怎么回事#

  • C#(读作 “C sharp”)是微软 2000 年推出的静态、强类型、面向对象语言,现在开源跨平台(Linux/macOS 上同样运行)
  • 静态类型:每个变量的类型在编译时就确定,编译器先把代码翻译成程序集(DLL),Godot 再加载。好处:拼错方法名、参数类型不对,Build 阶段就报错,不用等玩家点到那里才崩
  • 你写的 .cs 文件 → dotnet build → .godot/mono/temp/bin/.../*.dll → Godot 运行。这就是为什么”改完代码必须 Build”

编译错误长这样,别害怕,它把位置和原因都说清楚了:

Playground.cs(8,21): error CS0103: 当前上下文中不存在名称"hp"

意思:Playground.cs 第 8 行第 21 列,名叫 hp 的东西不存在(多半拼错了)。

1.2 一个 C# 文件的骨架#

using Godot; // ① using:引入命名空间(工具箱)
using System; // 想用 Console、Math 等基础类时引入
public partial class Playground : Node // ② 声明类
{
private int _hp = 100; // ③ 字段(这个对象记得住的数据)
public override void _Ready() // ④ 方法(能执行的动作)
{
int local = 10; // ⑤ 局部变量
GD.Print(local);
}
}
  • 命名空间(namespace):类太多时的文件夹。Godot 的类在 Godot 命名空间,所以要 using Godot; 才能直接写 Node;不 using 就得写全名 Godot.Node
  • 类(class):数据+方法的打包单位。Godot 规定一个脚本文件一个主类,类名=文件名
  • 语句以分号结尾;代码块用花括号;区分大小写(Hp 和 hp 是两个东西)
  • 注释:// 单行、/* 多行 */

1.3 变量:声明、赋值、var#

int hp = 100; // 声明:类型 名字 = 初始值;
float speed = 300f; // 注意!C# 里带小数点的字面量默认是 double
// 给 float 变量赋值要加 f 后缀——Godot 新手第一大坑
string playerName = "小功杭";
bool isAlive = true;
hp = 80; // 重新赋值不用再写类型
hp = hp - 20;

var:让编译器推断类型

var hp = 100; // 编译器看出是 int
var speed = 300f; // float
var pos = new Vector2(0, 0); // Vector2

var 不是”随便什么类型”(那是别的语言的 dynamic/var 变体),它只是省略手写类型,类型依然锁死。建议:等号右边类型一眼可见时用 var(更简洁),可见性差时写全类型。

1.4 值类型和引用类型(Godot 里必须懂的一条)#

C# 的类型分两大家族,行为有本质区别。

值类型:变量里直接装数据,赋值是”复制一份”。

int a = 10;
int b = a;
b = 20;
GD.Print(a); // 10,a 不受影响

Godot 的 Vector2、Vector3、Color、float、int、bool 都是值类型(结构体 struct):

var p1 = new Vector2(1, 2);
var p2 = p1; // 复制
p2.X = 99;
GD.Print(p1); // (1, 2)——p1 没变!

这解释了为什么不能这样改位置:

// 错误示范(编译都过不了):
Position.X = 10f;
// 原因:Position 是属性,返回的是位置的"副本",改副本毫无意义

正确写法——整体赋一个新 Vector2:

Position = new Vector2(10f, Position.Y);
// 或 with 表达式(C# 10+):只改一个轴,其他照抄
Position = Position with { X = 10f };
Velocity = Velocity with { Y = -520f };

引用类型:变量里装的是”指向对象的地址”,赋值是共享同一个对象。

class(包括所有 Godot 节点、自定义 Resource、string 以外的普通类)都是引用类型:

var player1 = new PlayerData(); // 假设 PlayerData 是 class
var player2 = player1;
player2.Hp = 0;
GD.Print(player1.Hp); // 0——同一份对象

对照 Godot:GetNode<Player>("Player") 拿到的是场景里那个玩家本体,你改它的属性,场景里真的变。而 GlobalPosition 取出来是副本。

速记:struct 是”物”,class 是”东西的遥控器”。

1.5 内置数值类型速查#

类型含义Godot 中的常见位置
int整数(约 ±21 亿)伤害值、金币数、生命值
float单精度小数Vector2 分量、速度、delta 转换后
double双精度小数_Process(double delta) 的 delta、Math 运算
booltrue/falseIsOnFloor() 结果
long大整数时间戳、TicksMsec 超界时
// float 与 double 互转必须显式
float dt = (float)delta; // double → float,强转
double precise = hp; // int → double,隐式(不会丢精度)
int rounded = (int)3.9; // 强转是"截断",结果 3,不是四舍五入
int rounded2 = Mathf.RoundToInt(3.9f); // 4,想要四舍五入用它

运算隐式提升规则:float 和 double 一起算结果是 double,所以 speed * delta(float × double)会得到 double,塞回 Vector2 运算要 (float)。教程一里你已经见过这个 cast 无数次了。

1.6 Godot 常用结构体#

var v = new Vector2(3f, 4f);
v.Length(); // 5
v.Normalized(); // 单位向量
Vector2.Right; // (1,0);还有 Up (0,-1,Godot 2D 的 y 朝下!)、Down、Left
Vector2.Lerp(Vector2.Zero, v, 0.5f); // 线性插值:两点之间走一半
v.Rotated(Mathf.Pi / 2); // 旋转 90°
v * 2f; // 标量乘
v + Vector2.One; // 逐分量加
var c = new Color(1f, 0f, 0f); // 红,RGB 0..1
Colors.White; Color.Color8(255, 0, 0); // 两种快捷写法
c.A; // alpha 通道

2D 坐标系:x 向右、y 向下,角度 0 指向右,顺时针为正。从数学课本切过来最容易错的就是 y 和角度方向。

1.7 常量与只读#

private const int MaxHp = 100; // 编译期常量,全大写命名是惯例
public override void _Ready()
{
readonly int startAmmo = 30; // 只能在构造/声明时赋值一次
// startAmmo = 20; // 报错
}

Godot 中更常见的”常量”其实是 [Export] 字段——它能在编辑器调,比 const 灵活,调参不用重编译。

1.8 枚举:一组命名的整数#

玩家状态、伤害类型别用魔法数字,用枚举:

public enum PlayerState { Idle, Run, Jump, Fall, Hurt, Dead }
// 底层依次是 0,1,2,3,4,5,也可手动赋值
private PlayerState _state = PlayerState.Idle;
private void ChangeState(PlayerState next)
{
_state = next;
GD.Print(_state); // 直接打印就是名字 "Run"
if (_state == PlayerState.Dead) { /* ... */ }
}

Godot 自带枚举(在检查器看到的下拉选项都是):Key.Space、MouseButton.Left、Tween.TransitionType.Quad、Control.LayoutPreset.FullRect。用 IDE 的自动补全浏览枚举值,比文档快。

[GlobalClass] + enum + [Export] 可以在检查器里做下拉框:

public enum EnemyKind { Melee, Ranged, Bomber }
[Export] public EnemyKind Kind = EnemyKind.Melee; // Inspector 里直接选

1.9 StringName 与字符串的特殊关系#

Godot C# 里信号参数、节点组名、动作名常用 StringName 而不是 string:

StringName side = "left"; // string 可隐式转 StringName
if (side == "right") { } // 比较也能用字符串字面量
Input.IsActionPressed("jump");// API 接受 StringName,直接写字符串字面量即可

StringName 是引擎内部的”驻留字符串”(同样的文本只存一份,比较是整数级速度)。日常写 "jump" 字面量会隐式转换,只有把字符串存起来反复比较时才需要显式声明 StringName。初学不用纠结,看到 API 签名里是 StringName 知道它不是新东西就行。

1.10 命名约定(C# 世界的规矩)#

成员约定例子
类、枚举、方法、属性PascalCasePlayerController、TakeDamage、MoveSpeed
私有字段_camelCase_hp、_isGrounded
局部变量、参数camelCasenewSpeed、deltaTime
常量PascalCase 或全大写MaxHp(C# 惯例是 Pascal,老派写法 MAX_HP 也能见)
Godot 信号方法生成代码固定 XxxEventHandlerDiedEventHandler

Godot 的 C# API 是 PascalCase(Position、GetNode、MoveAndSlide),而 GDScript 是 snake_case(position、get_node)。看 GDScript 教程时心里自动做翻译:global_position → GlobalPosition。

1.11 作业#

  1. 声明名字、等级、经验值、暴击率(float)、是否在队伍中(bool)五个变量并打印。
  2. 验证值类型:创建 Vector2 变量 a,var b = a,修改 b,打印 a,确认没被波及。
  3. 故意制造三个编译错误:漏分号、float 不加 f、拼错变量名,Build 并逐行读懂报错信息。
  4. 写一个枚举 DamageType { Physical, Fire, Ice },[Export] 到脚本上,回编辑器确认检查器出现了下拉框。

第 2 章:运算、判断、循环、方法——让代码真正”算起来”。


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

⬅️ 上一篇:Godot C# 零基础教程 09:导出发布与学习路线图 📑 返回系列总目录(第 01 篇) ➡️ 下一篇:为 Godot 学 C# 02:运算符、流程控制与方法

为 Godot 学 C# 01:变量与类型
https://021028.xyz/posts/default/170/
作者
021028
发布于
2026-10-14
许可协议
CC BY-NC-SA 4.0