基于C#的游戏开发代码规范指南

张开发
2026/4/8 5:16:48 15 分钟阅读

分享文章

基于C#的游戏开发代码规范指南
一、团队开发的核心编程原则贯穿全程的基本准则是代码规范的底层逻辑核心围绕简单、一致、迭代展开KISS原则Keep it Simple, stupid保持代码简洁优先使用Unity内置API如对象池、瓦片地图不重复造轮子、最优秀的代码是 “无需编写的代码”YAGNI 原则You aren’t gonna need it仅实现当下需要的功能不提前开发未来可能用到的特性根因解决避免 “贴补丁式” 代码遇到问题先排查根本原因而非简单规避错误增量迭代打造整洁代码是一个动态、迭代的过程代码库需要持续的维护因此要预留出专门的时间确保优化工作落地不追求 “一次性完美”一致性至上团队统一编码风格从命名到文件夹组织保持一致这是规范的核心二、需遵守的规范2.1 命名规则私有成员变量下划线前缀 驼峰命名法private int _currentHealth; private float _moveSpeed; private Transform _targetTransform; // 配合SerializeField可在编辑器可见但保持封装性 [SerializeField] private int _maxHealth 100;常量全大写字母单词间用下划线分隔private const int MAX_ENEMY_COUNT 50; private const string PLAYER_TAG Player;类/结构体/枚举帕斯卡命名法枚举优先使用单数名词带 [Flags] 特性的位运算枚举用复数// 类 public class PlayerController : MonoBehaviour { // ... } // 结构体 public struct PlayerStats { public int Level; public float Attack; } // 枚举 public enum GameState { MainMenu, Playing, Paused } // 位运算枚举 [Flags] public enum DamageTypes { None 0, Physical 1, Magical 2, True 4 }接口额外添加 I 前缀 帕斯卡命名法public interface IDamageable { void TakeDamage(int damage); }公共属性 / 方法 / 事件帕斯卡命名法布尔属性 / 方法以 Is/Has/Should 等前缀开头如 IsAlive、HasItem事件用现在 / 过去分词命名如 DoorOpening、DoorOpenedpublic class PlayerStats : MonoBehaviour { // 公共属性 public int CurrentHealth { get; private set; } // 布尔属性/方法以 Is/Has/Should 开头 public bool IsAlive CurrentHealth 0; // 表达式体属性 public bool HasWeapon _currentWeapon ! null; // 公共方法 public void Heal(int amount) { CurrentHealth Mathf.Min(CurrentHealth amount, _maxHealth); } // 事件用现在/过去分词命名 public event System.Action DoorOpened; public event System.Actionfloat DoorOpening; // 触发事件的方法 public void OnDoorOpen() { DoorOpened?.Invoke(); } }局部变量 / 方法参数驼峰命名法可结合 var 关键字简化声明参数命名需清晰表意禁止单字母无意义命名循环计数器除外public void Attack(Transform targetTransform, int damageValue) { // 局部变量 Vector3 targetPosition targetTransform.position; // 使用 var 简化类型上下文清晰时 var bullet Instantiate(bulletPrefab, transform.position, Quaternion.identity); // 禁止i、j、k 这种无意义命名循环计数器除外 // 正确 for (int i 0; i _enemyList.Count; i) { // ... } }命名空间帕斯卡命名法按项目功能层级划分与文件夹结构对应避免类名冲突namespace MyGame.AI { public class EnemyAI : MonoBehaviour { // ... } } namespace MyGame.UI { public class UIManager : MonoBehaviour { // ... } }2.2 代码格式化规范大括号与缩进采用 Allman 风格大括号另起一行统一缩进为 4 空格禁止混用制表符即使单行语句也不省略大括号嵌套语句必须保留大括号// 错误示范 if (Input.GetKeyDown(KeyCode.Space)) Jump();// 正确示范 if (Input.GetKeyDown(KeyCode.Space)) { Jump(); }空格规范运算符前后、循环 / 条件关键字与括号间加单个空格函数参数逗号后加空格括号内、函数名与括号间无空格// 错误示范 int damagebaseDamage-defense; // 运算符无空格 if(damage0){ ... } // 关键字与括号无空格 void TakeDamage(int damage,float multiplier){ ... } // 逗号后无空格// 正确示范 int damage baseDamage - defense; if (damage 0) { ... } void TakeDamage(int damage, float multiplier) { ... }垂直间距相关方法 / 字段分组字段与方法、类与接口间用空行分隔避免过度空行// 错误示范 private int _currentHealth; private float _moveSpeed; private void Update() // 无空行分隔 { // ... }// 正确示范 private int _currentHealth; private float _moveSpeed; private void Update() { // ... }switch 语句多层if else语句要使用switch语句缩进 case 块必须包含 default 分支覆盖所有可能情况// 错误示范 if (gameState GameState.Playing) { ... } else if (gameState GameState.Paused) { ... }// 正确示范 switch (gameState) { case GameState.Playing: StartGame(); break; default: Debug.LogError(未知状态); break; }属性单行只读属性用表达式体复杂属性用 {get;set;}优先用属性封装私有字段// 错误示范 // 直接公开字段无封装 public int currentHealth; // 单行属性冗余写法 public int IsAlive { get { return currentHealth 0; } }// 正确示范 // 单行只读表达式体 public bool IsAlive _currentHealth 0; // 复杂属性封装私有字段 public int CurrentHealth { get _currentHealth; set _currentHealth Mathf.Clamp(value, 0, _maxHealth); }2.3 类与方法设计规范类的设计一个 MonoBehaviour 对应一个文件文件名与类名完全一致遵循单一职责原则避免 “上帝类”一个类只负责一件事固定成员顺序字段 → 属性 → 事件 / 委托 → MonoBehaviour 生命周期方法 → 公共方法 → 私有方法// 固定成员顺序 public class PlayerController : MonoBehaviour { // 1. 字段 private int _currentHealth; // 2. 属性 public bool IsAlive _currentHealth 0; // 3. 事件 public event Action OnDeath; // 4. 生命周期方法 private void Awake() { ... } // 5. 公共方法 public void TakeDamage(int damage) { ... } // 6. 私有方法 private void Die() { ... } }方法的设计方法小而专一个方法只做一个动作名称精准反映功能减少参数数量避免标志位参数拆分功能为独立方法避免副作用方法仅做名称描述的事out/ref 仅用于核心功能拓展方法放在静态类中命名为「类型 Extensions」如 TransformExtensions用 this 关键字标记扩展类型// 静态类类型Extensions public static class TransformExtensions { // this 标记扩展类型 public static void ResetLocalTransform(this Transform transform) { transform.localPosition Vector3.zero; transform.localRotation Quaternion.identity; transform.localScale Vector3.one; } } // 调用方式直接用Transform实例调用 transform.ResetLocalTransform();2.4 Unity专属序列化规范用 [SerializeField] 让私有字段在 Inspector 显示替代公共字段保证封装性[SerializeField] private int _maxHealth; // 私有序列化封装性好 public int MaxHealth _maxHealth; // 公共属性访问数值字段用 [Range] 限制范围多字段用 [Serializable] 类 / 结构体分组[Range(0, 100)] public int health; [Serializable] public struct PlayerStats { public int atk; public int def; } [SerializeField] private PlayerStats _stats;需要被外部访问的序列化字段通过公共属性和公共方法访问不直接访问私有字段[SerializeField] private int _currentHealth; public int CurrentHealth _currentHealth; // 属性封装访问 public void TakeDamage(int dmg) _currentHealth - dmg;2.5 注释规范注释解释「为什么」而非「是什么」优先让代码自我解释命名混乱代码的注释无意义先重构再写注释普通注释用 //单独占行// 后加单个空格// 限制最大生命值不超过上限 _currentHealth Mathf.Min(_currentHealth, _maxHealth);公共方法 / 类用 XML 摘要注释/// 支持 IDE 智能提示/// summary /// 玩家受到伤害返回实际造成的伤害值 /// /summary /// param namedamage基础伤害/param /// returns最终伤害/returns public int TakeDamage(int damage) { // ... }序列化字段用 [Tooltip] 替代注释在 Inspector 直接显示说明[Tooltip(玩家最大生命值范围0-200)] [Range(0, 200)] [SerializeField] private int _maxHealth;及时更新 TODO 注释删除过期内容移除注释掉的代码// TODO: 后续优化伤害计算逻辑 // 已完成功能直接删除注释代码三、如何编写出干净的代码单一职责每个方法应描述一个动作或回答一个问题而不应同时做两者。方法的好名字应反映其功能// 错误示范 // 同时做移动、跳跃、受伤功能混乱 private void UpdatePlayer() { Move(); Jump(); TakeDamage(10); }// 正确示范 // 只做移动 private void Move() { ... } // 只做跳跃 private void Jump() { ... } // 只做受伤计算 public int TakeDamage(int damage) { ... }DRY原则Don’t repeat yourself避免重复或冗余的逻辑这样做可以减少调试和维护的成本// 错误示范 private void PlayExplosionA(Vector3 hitPosition) { explosionA.transform.position hitPosition; explosionA.Stop(); explosionA.Play(); AudioSource.PlayClipAtPoint(soundA, hitPosition); } private void PlayExplosionB(Vector3 hitPosition) { explosionB.transform.position hitPosition; explosionB.Stop(); explosionB.Play(); AudioSource.PlayClipAtPoint(soundB, hitPosition); }// 正确示范 private void PlayFXWithSound(ParticleSystem particle, AudioClip clip, Vector3 hitPosition) { particle.transform.position hitPosition; particle.Stop(); particle.Play(); AudioSource.PlayClipAtPoint(clip, hitPosition); }四、参考文献《使用C#风格指南编写简洁且可扩展的游戏代码Unity 6版》以上是我根据官方文档整理的学习笔记由于个人水平有限文中可能存在理解不当或错误之处。如果您发现任何问题欢迎在评论区指正我会虚心学习并及时修改非常感谢

更多文章