教程二 · 第 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; // 编译器看出是 intvar speed = 300f; // floatvar pos = new Vector2(0, 0); // Vector2var 不是”随便什么类型”(那是别的语言的 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 是 classvar 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 运算 |
bool | true/false | IsOnFloor() 结果 |
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(); // 5v.Normalized(); // 单位向量Vector2.Right; // (1,0);还有 Up (0,-1,Godot 2D 的 y 朝下!)、Down、LeftVector2.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..1Colors.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 可隐式转 StringNameif (side == "right") { } // 比较也能用字符串字面量Input.IsActionPressed("jump");// API 接受 StringName,直接写字符串字面量即可StringName 是引擎内部的”驻留字符串”(同样的文本只存一份,比较是整数级速度)。日常写 "jump" 字面量会隐式转换,只有把字符串存起来反复比较时才需要显式声明 StringName。初学不用纠结,看到 API 签名里是 StringName 知道它不是新东西就行。
1.10 命名约定(C# 世界的规矩)
| 成员 | 约定 | 例子 |
|---|---|---|
| 类、枚举、方法、属性 | PascalCase | PlayerController、TakeDamage、MoveSpeed |
| 私有字段 | _camelCase | _hp、_isGrounded |
| 局部变量、参数 | camelCase | newSpeed、deltaTime |
| 常量 | PascalCase 或全大写 | MaxHp(C# 惯例是 Pascal,老派写法 MAX_HP 也能见) |
| Godot 信号方法 | 生成代码固定 XxxEventHandler | DiedEventHandler |
Godot 的 C# API 是 PascalCase(Position、GetNode、MoveAndSlide),而 GDScript 是 snake_case(position、get_node)。看 GDScript 教程时心里自动做翻译:global_position → GlobalPosition。
1.11 作业
- 声明名字、等级、经验值、暴击率(float)、是否在队伍中(bool)五个变量并打印。
- 验证值类型:创建 Vector2 变量 a,
var b = a,修改 b,打印 a,确认没被波及。 - 故意制造三个编译错误:漏分号、float 不加 f、拼错变量名,Build 并逐行读懂报错信息。
- 写一个枚举
DamageType { Physical, Fire, Ice },[Export]到脚本上,回编辑器确认检查器出现了下拉框。
第 2 章:运算、判断、循环、方法——让代码真正”算起来”。
《Godot C# 零基础》系列导航
⬅️ 上一篇:Godot C# 零基础教程 09:导出发布与学习路线图 📑 返回系列总目录(第 01 篇) ➡️ 下一篇:为 Godot 学 C# 02:运算符、流程控制与方法