1965 字
10 分钟
为 Godot 学 C# 02:运算符、流程控制与方法

教程二 · 第 2 章:运算符、流程控制、方法#

2.1 运算符#

算术#

int a = 10, b = 3;
GD.Print(a + b); // 13
GD.Print(a / b); // 3!整数除整数还是整数(截断)
GD.Print(a % b); // 1,取余:判断整除、循环索引超有用
GD.Print(10f / 3f);// 3.333...,想要小数结果至少一边是 float/double
a++; // 等同 a = a + 1
a += 5; // 等同 a = a + 5,-= *= /= 同理
GD.Print(2 + 3 * 4); // 14,先乘除后加减,拿不准就加括号

Godot 场景:冷却归零、每 N 波刷怪:

_attackCooldown -= dt;
if (_enemiesKilled % 5 == 0) GD.Print("每杀 5 个触发一次");

比较与逻辑#

bool isLow = hp < 30;
bool canAct = hp > 0 && isGrounded; // && 与:两边都真
bool hit = wasAttacked || steppedTrap; // || 或:任一为真
bool notGrounded = !isGrounded; // ! 非
if (hp >= 0 && hp <= 100) { } // 区间判断要写两遍,不能 0 <= hp <= 100

短路特性:a && b,a 为假时 b 根本不计算。利用它防 null:

if (_target != null && _target.IsInsideTree()) { } // 安全:先确认存在

三元运算符:二选一的简写#

string stateName = isGrounded ? "地面" : "空中";
// 等价于
// string stateName;
// if (isGrounded) stateName = "地面"; else stateName = "空中";
Velocity = Velocity with { X = dir != 0 ? dir * speed : Velocity.X };

别嵌套三元,难读。

位运算(了解即可)#

Godot 碰撞层用 32 位掩码,会看到这些:

SetCollisionLayerValue(2, true); // 打开第 2 层
uint mask = CollisionMask;
bool hitsLayer3 = (mask & (1u << 2)) != 0; // 检查第 3 位

<< 移位、& 与、| 或、^ 异或。现在眼熟即可,直接用 SetCollisionLayerValue 更不容易错。

2.2 if / else#

public void TakeDamage(int amount)
{
if (_invincible)
return; // 提前返回,少写 else 嵌套
_hp -= amount;
if (_hp <= 0)
{
_hp = 0;
Die();
}
else if (amount >= 30)
{
PlayHurtAnim("heavy");
}
else
{
PlayHurtAnim("light");
}
}
  • 条件必须是 bool:C# 不像 C/Python,if (hp) 直接编译错误,要写 if (hp > 0)
  • 单行可以不写花括号,但新手建议永远写花括号,以后加语句不会踩坑
  • 提前 return / continue 降低嵌套,是写清楚逻辑的第一技巧

模式匹配:现代 C# 的 if 超能力#

教程一里球撞挡板写过 if (collider is Paddle paddle):

Node collider = collisionInfo.GetCollider();
if (collider is Paddle paddle) // 类型检查 + 转换一步完成
{
BounceFromPaddle(paddle); // paddle 已当作 Paddle 类型可用
}
else if (collider is StaticBody2D)
{
_direction.Y = -_direction.Y;
}

老写法(要记住,看别人代码会遇到):

if (collider is Paddle)
{
var paddle = (Paddle)collider; // 先判断再强转,啰嗦
}

is 还能配合属性:

if (collider is Enemy { Hp: <= 0 }) return; // 是敌人且血量已为 0

switch 表达式(多分支算一个值时比 if-else 干净):

float damageMult = element switch
{
DamageType.Physical => 1.0f,
DamageType.Fire => 1.5f,
DamageType.Ice => 0.8f,
_ => 1.0f // _ 是"其他所有情况"
};

2.3 循环#

while:先看条件#

int bullets = 3;
while (bullets > 0)
{
FireOneShot();
bullets--;
}

for:次数明确#

for (int i = 0; i < 5; i++)
{
GD.Print($"第 {i} 发"); // 0,1,2,3,4
}

结构:for(初始化; 继续条件; 每轮收尾)。倒着来:for (int i = list.Count - 1; i >= 0; i--)——边遍历边删除时必须倒序(第 6 章讲)。

foreach:遍历一切”可枚举”#

foreach (Node child in GetChildren())
{
child.QueueFree();
}
foreach (var node in GetTree().GetNodesInGroup("enemy"))
{
if (node is Enemy enemy)
enemy.TakeDamage(10);
}

最常用、最不容易出错的循环。限制:遍历过程中不能直接增删集合元素(会抛 InvalidOperationException)。

跳出控制#

break; // 立刻结束整个循环
continue; // 跳过本轮剩余语句,进入下一轮

MoveAndSlideCollide 的碰撞遍历你在 Pong 里见过:

var collisions = MoveAndSlideCollide();
for (int i = 0; i < collisions.Count; i++)
{
var c = collisions[i];
// ...
}

2.4 方法(函数)#

方法是打包一段逻辑、起个名字、可传参、可返回结果的机制。

// 返回类型 方法名(参数列表)
private int CalcDamage(int baseAtk, float critRate)
{
if (GD.Randf() < critRate)
return baseAtk * 2; // return 把结果送回调用处并立刻结束方法
return baseAtk;
}
private void Die() // void:不返回任何东西
{
EmitSignal(SignalName.Died);
QueueFree();
}

调用:

int dmg = CalcDamage(20, 0.25f);

参数传递的几个要点#

值参数是副本(值类型传进去,外面不变):

void AddOne(int x) { x++; }
AddOne(hp); // hp 本身不变

引用类型传的是”遥控器”(节点、自定义类传进去,改成员等于改本体):

void Kill(Player p) { p.Hp = 0; } // 外面那个 Player 真的被改

out / ref(需要”多个返回值”时):

if (PhysicsIntersectPoint(point, out var enemy))
{
enemy.TakeDamage(10); // Godot 很多 TryGet 风格 API 都是 out 参数
}

TryXxx(参数, out 结果) 返回 bool 表示成不成功、结果走 out,是 C# 生态的通用惯例。Godot 的射线检测、字典取值都会见到。

方法重载:同名不同参数#

public void Heal(int amount) { _hp += amount; }
public void Heal(float percent) { _hp += (int)(MaxHp * percent); } // 同名不同参,合法

调用处按参数类型自动匹配。Godot API 大量重载,例如 DrawCircle / DrawArc 参数个数不同的版本。

默认参数与命名参数#

private void SpawnEnemy(string type = "slime", int count = 1, float delay = 0f)
SpawnEnemy(); // slime, 1, 0
SpawnEnemy("bat", 3);
SpawnEnemy(count: 5, delay: 2f); // 命名参数可跳过默认值,顺序随意

方法拆多大合适#

经验:一个方法一个意图,超过一屏(约 40 行)就该想办法拆。Godot 脚本里典型结构:

public override void _PhysicsProcess(double delta)
{
ApplyGravity(delta);
HandleInput();
UpdateStateMachine();
MoveAndSlide();
}

_PhysicsProcess 只做编排,每步一个名字清楚的私有方法——读代码像读目录。

2.5 局部函数#

方法里还能定义临时方法(复杂计算拆步又不想污染类时用,少见但要认识):

public int Combo(int a, int b)
{
int Square(int x) => x * x; // 表达式体方法
return Square(a) + Square(b);
}

=> 叫表达式体成员,单行方法的简写:

public bool IsDead => _hp <= 0; // 属性的表达式体,第 4 章讲
private void Stop() => Velocity = Vector2.Zero;

2.6 静态:属于类而不是某个对象#

GD.Print、Mathf.Clamp、Input.IsActionPressed——注意这些调用不需要先 new 对象:

GD.Print("x"); // 静态方法:类名.方法名
Mathf.Clamp(y, 0f, 540f);
Input.GetAxis("left", "right");

static 成员挂在类上、全程序共享一份。自己写工具方法也可以:

public static class GameMath
{
public static float Knockback(float strength, float mass) => strength / mass * 10f;
}
// 调用:GameMath.Knockback(50, 2);

⚠️ Godot 节点生命周期由引擎管理,不要自己 new Node 派生类当工具(new Player() 不在场景树里,什么都不会发生)。静态工具类只放纯计算。

2.7 一个综合例子:平台跳跃输入处理#

把本章东西串起来(对照教程一第 4 章代码重读一遍,现在每行都该认识):

public override void _PhysicsProcess(double delta)
{
float dt = (float)delta;
if (!IsOnFloor())
Velocity = Velocity with { Y = Velocity.Y + Gravity * dt };
float dir = Input.GetAxis("move_left", "move_right");
Velocity = Velocity with { X = dir * MoveSpeed };
if (IsOnFloor()) _coyote = CoyoteTime;
else _coyote -= dt;
if (Input.IsActionJustPressed("jump")) _buffer = JumpBufferTime;
else _buffer -= dt;
if (_buffer > 0f && _coyote > 0f)
{
Velocity = Velocity with { Y = JumpVelocity };
_buffer = 0f;
_coyote = 0f;
}
MoveAndSlide();
}

新的没有了——游戏代码 80% 是”取值、判断、改字段、调引擎方法”的排列组合。语言部分到本章其实已经能读大部分游戏脚本,剩下的核心是面向对象(第 4–5 章)。

2.8 作业#

  1. 写方法 ClampHp(int hp):用 if-else 把任意整数夹在 0–100 返回;再用 Mathf.Clamp 重写一遍(体会库函数的意义)。
  2. 写 GetScoreGrade(int score) 用 switch 表达式返回 “S/A/B/C”(自己定分段)。
  3. 用 foreach 遍历 GetChildren(),打印每个子节点的名字和类型(child.GetType().Name)。
  4. 实现一个方法 BounceOffCeiling():只在 Y 速度向下时反转 Y 速度(注意 Godot 里 y 向下为正,想想条件该怎么写)。

第 3 章:数组、字符串、空值、异常——处理”一串数据”和”出错怎么办”。


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

⬅️ 上一篇:为 Godot 学 C# 01:变量与类型 📑 返回系列总目录(第 01 篇) ➡️ 下一篇:为 Godot 学 C# 03:数组、字符串、空值与异常

为 Godot 学 C# 02:运算符、流程控制与方法
https://021028.xyz/posts/default/171/
作者
021028
发布于
2026-10-14
许可协议
CC BY-NC-SA 4.0