Unity C# 常用语法速查

这篇按 Unity 写脚本时最常查的 C# 写法整理:字段怎么暴露、集合怎么查、事件怎么解绑、null 怎么防、哪些写法别放进 Update。


快速索引

按需求查写法

我想做什么 常用写法
Inspector 可调但外部不能乱改 [SerializeField] private
外部只读内部可改 public int Hp { get; private set; }
常量和只读值 constreadonly
安全访问 Dictionary TryGetValue
遍历时删除 List 元素 倒序 for
表示能力 interface
表示有限状态 enum
订阅事件 +=
取消事件 -=
安全触发委托 ?.Invoke()
判断组件是否存在 TryGetComponent
判断 Unity 对象是否销毁 if (obj == null)
低频筛选集合 LINQ
高频筛选集合 手写 for

0. 字段、属性、常量

1
2
3
4
5
6
7
8
9
10
[SerializeField] private int maxHp = 100;     // Inspector 可调,外部不能直接改
[SerializeField] private Transform target; // Inspector 拖对象引用

private int currentHp; // 运行时内部状态

public int CurrentHp => currentHp; // 外部只读
public bool IsDead => currentHp <= 0; // 计算属性

private const int MaxLevel = 99; // 编译期常量
private readonly float spawnRadius = 5f; // 初始化后不再改
1
2
public int Score { get; private set; }        // 外部可读,只有本类能改
public string PlayerName { get; set; } // 外部可读可写,慎用

不建议:

1
2
public int hp;                                // 外部任意脚本都能改
public bool isDead; // 容易和 hp 状态不同步

1. class / struct / interface / enum

1
2
3
4
public class Inventory                       // 引用类型,适合有状态对象
{
private readonly List<ItemData> items = new();
}
1
2
3
4
5
public readonly struct DamageInfo            // 值类型,适合小型只读数据
{
public readonly int Amount;
public readonly Vector3 HitPoint;
}
1
2
3
4
public interface IDamageable                 // 表示“可受伤”能力
{
void TakeDamage(int amount);
}
1
2
3
4
5
6
7
8
public enum GameState                        // 有限状态,替代魔法字符串
{
Boot,
MainMenu,
Playing,
Paused,
GameOver
}

2. List 常用操作

1
2
3
4
5
6
7
8
9
10
private readonly List<GameObject> enemies = new(); // 缓存敌人列表

enemies.Add(enemy); // 添加元素
enemies.Remove(enemy); // 删除指定元素
enemies.Clear(); // 清空列表

if (enemies.Count > 0) // 访问前检查数量
{
GameObject first = enemies[0];
}

遍历时删除:

1
2
3
4
5
6
7
for (int i = enemies.Count - 1; i >= 0; i--) // 倒序删除,不会跳过元素
{
if (enemies[i] == null)
{
enemies.RemoveAt(i);
}
}

3. Dictionary 常用操作

1
2
3
4
5
6
7
8
9
10
11
12
13
private readonly Dictionary<int, ItemData> itemMap = new(); // id -> 数据

itemMap[item.Id] = item; // 新增或覆盖

if (itemMap.TryGetValue(id, out ItemData data)) // 安全查找
{
Debug.Log(data.Name);
}

if (itemMap.ContainsKey(id)) // 只判断 key 是否存在
{
itemMap.Remove(id);
}

不建议:

1
ItemData data = itemMap[id];                  // key 不存在会抛异常

4. 委托、Action、事件

1
2
3
public Action<int> OnHpChanged;               // 简单回调,不限制外部 Invoke

OnHpChanged?.Invoke(currentHp); // 有订阅者才触发
1
2
3
4
5
6
7
public event Action<int> HpChanged;           // 推荐事件,外部只能订阅/取消

private void SetHp(int value)
{
currentHp = value;
HpChanged?.Invoke(currentHp); // 只有类内部能触发
}

生命周期里订阅:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void OnEnable()
{
player.HpChanged += OnHpChanged; // 启用时订阅
}

private void OnDisable()
{
player.HpChanged -= OnHpChanged; // 禁用时取消
}

private void OnHpChanged(int hp)
{
hpText.text = hp.ToString();
}

少用:

1
button.onClick.AddListener(() => DoSomething()); // 匿名函数不好移除

5. null 与 Unity 对象判断

1
2
3
4
5
if (target == null)                           // 普通空引用 / Unity 对象已销毁
{
Debug.LogWarning("target 未设置", this);
return;
}
1
2
3
4
if (TryGetComponent(out Rigidbody rb))        // 获取组件并判断是否存在
{
rb.AddForce(Vector3.up, ForceMode.Impulse);
}
1
2
string nameText = playerName ?? "Unknown";    // null 时给默认值
player?.TakeDamage(10); // player 不为空才调用

Unity 特别注意:

1
2
3
4
5
6
UnityEngine.Object obj = target;

if (obj == null) // Unity 重载了 ==,可判断已销毁对象
{
return;
}

6. LINQ 使用边界

低频可用:

1
2
3
var aliveEnemies = enemies
.Where(e => e != null && e.activeInHierarchy)
.ToList(); // 打开面板、初始化、编辑器工具可接受

高频改手写:

1
2
3
4
5
6
7
8
9
10
aliveEnemies.Clear();                         // 复用列表,减少 GC

for (int i = 0; i < enemies.Count; i++)
{
GameObject enemy = enemies[i];
if (enemy != null && enemy.activeInHierarchy)
{
aliveEnemies.Add(enemy);
}
}

7. 字符串与格式化

1
Debug.Log($"hp={currentHp}, max={maxHp}");     // 普通日志可用插值

热路径慎用:

1
2
3
4
5
6
private readonly StringBuilder builder = new(); // 高频拼接可复用

builder.Clear();
builder.Append("Score: ");
builder.Append(score);
scoreText.text = builder.ToString();

8. 异常处理

1
2
3
4
5
6
7
8
try
{
LoadConfig(path);
}
catch (IOException ex)
{
Debug.LogError($"配置读取失败 path={path}\n{ex}"); // 带上下文和异常
}

不建议:

1
2
3
4
5
6
7
8
try
{
LoadConfig(path);
}
catch
{
// 什么都不写,问题会被吞掉
}

9. 命名速查

1
2
3
4
5
6
7
PlayerHealth                 # 类名 PascalCase
TakeDamage # 方法 PascalCase
currentHp # 私有字段 camelCase
maxHp # SerializeField 字段 camelCase
CurrentHp # 属性 PascalCase
IDamageable # 接口 I 开头
OnHpChanged # 事件处理方法 On + 事件名

10. Unity 脚本常用模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using UnityEngine;

public sealed class PlayerMover : MonoBehaviour
{
[SerializeField] private Transform target; // Inspector 拖目标
[SerializeField] private float moveSpeed = 5f; // 移动速度

private void Awake()
{
if (target == null)
{
Debug.LogWarning("target 未设置", this);
}
}

private void Update()
{
if (target == null)
{
return;
}

transform.position = Vector3.MoveTowards(
transform.position,
target.position,
moveSpeed * Time.deltaTime
);
}
}

11. 常见坑速查

1
2
3
4
5
6
7
public 字段到处暴露                  # 状态容易被外部乱改
Dictionary[key] 直接取 # key 不存在直接异常
foreach 里删除 List # 集合被修改会异常
OnEnable 订阅事件但不 OnDisable 取消 # 场景切换后旧对象还响应
Update 里 LINQ # 容易产生 GC
catch 后不打印 ex # 报错线索被吞
Unity 对象 Destroy 后继续访问 # MissingReferenceException

系列导航