2.7-C#编程规范与代码风格

2.7 C#编程规范与代码风格

在游戏开发中,良好的编程规范和代码风格不仅能提高代码的可读性和可维护性,还能帮助团队成员更好地协作。本节将介绍C#编程中的重要规范和最佳实践。

目录

命名规范

类名和接口名

使用PascalCase(首字母大写)命名类和接口:

// 游戏相关的类名示例
public class PlayerController { }
public class EnemySpawner { }
public class WeaponSystem { }

// 接口名前加I前缀
public interface IInteractable { }
public interface IDamageable { }
public interface IInventoryItem { }

方法名

使用PascalCase命名方法,动词开头:

public class Player
{
  // 动作方法
  public void MoveToPosition(Vector3 position) { }
  public void RotateTowards(Vector3 target) { }
  
  // 状态检查方法
  public bool IsGrounded() { }
  public bool CanAttack() { }
  
  // 获取/设置方法
  public int GetHealth() { }
  public void SetHealth(int value) { }
}

变量和参数

使用camelCase(首字母小写)命名变量和参数:

```csharp

public class GameManager
{
// 私有字段
private int currentScore;
private float gameTime;
private bool isGamePaused;

// 属性
public int CurrentLevel { get; private set; }

// 方法参数
public void AddScore(int scoreValue, bool isBonus = false)
{
    int multiplier = isBonus ? 2 : 1;
    currentScore += scoreValue * multiplier;
}

}


### 常量和只读字段

使用PascalCase命名常量和只读字段,全大写用于特殊配置:

  ```csharp
public class GameConfig
{
    // 游戏常量
    public const float GravityForce = 9.81f;
    public const int MaxPlayers = 4;
    
    // 配置常量
    public const string CONFIG_FILE_PATH = "config.json";
    public const string SAVE_DIRECTORY = "SaveData";
    
    // 只读字段
    private readonly float startingHealth;
    private readonly Vector3 spawnPoint;
}

代码组织

类的结构

按照以下顺序组织类的成员:

public class Enemy
{
  #region Fields
  // 1. 常量和静态字段
  private const float MAX_HEALTH = 100f;
  private static int enemyCount;
  
  // 2. 私有字段
  private float currentHealth;
  private bool isAlive;
  #endregion
  
  #region Properties
  // 3. 公共属性
  public float Health { get; private set; }
  public bool IsActive { get; set; }
  #endregion
  
  #region Events
  // 4. 事件
  public event Action<float> OnHealthChanged;
  public event Action OnDeath;
  #endregion
  
  #region Constructor
  // 5. 构造函数
  public Enemy(float health)
  {
      currentHealth = health;
      isAlive = true;
      enemyCount++;
  }
  #endregion
  
  #region Public Methods
  // 6. 公共方法
  public void TakeDamage(float damage)
  {
      if (!isAlive) return;
      
      currentHealth -= damage;
      OnHealthChanged?.Invoke(currentHealth);
      
      CheckDeath();
  }
  #endregion
  
  #region Private Methods
  // 7. 私有方法
  private void CheckDeath()
  {
      if (currentHealth <= 0 && isAlive)
      {
          isAlive = false;
          OnDeath?.Invoke();
          enemyCount--;
      }
  }
  #endregion
}

文件组织

每个文件应该只包含一个主要的类:

Assets/
  ├─ Scripts/
  │   ├─ Player/
  │   │   ├─ PlayerController.cs
  │   │   ├─ PlayerInput.cs
  │   │   └─ PlayerAnimation.cs
  │   ├─ Enemy/
  │   │   ├─ EnemyController.cs
  │   │   ├─ EnemyAI.cs
  │   │   └─ EnemySpawner.cs
  │   └─ UI/
  │       ├─ UIManager.cs
  │       ├─ HealthBar.cs
  │       └─ MainMenu.cs
  └─ ...

代码格式化

缩进和空格

使用4个空格进行缩进,在适当的地方使用空行分隔代码块:

public class WeaponSystem
{
    private List<Weapon> weapons;
    private int currentWeaponIndex;
    
    public WeaponSystem()
    {
        weapons = new List<Weapon>();
        currentWeaponIndex = 0;
    }
    
    public void AddWeapon(Weapon weapon)
    {
        if (weapon == null)
            throw new ArgumentNullException(nameof(weapon));
        
        weapons.Add(weapon);
    }
    
    public void SwitchWeapon(int index)
    {
        if (index < 0 || index >= weapons.Count)
            return;
        
        currentWeaponIndex = index;
        OnWeaponSwitched?.Invoke(CurrentWeapon);
    }
    
    public Weapon CurrentWeapon => 
        weapons.Count > 0 ? weapons[currentWeaponIndex] : null;
}

大括号和换行

始终使用大括号,即使是单行语句:

public class PlayerMovement
{
    public void Move(Vector3 direction)
    {
        if (direction.magnitude > 1f)
        {
            direction.Normalize();
        }
        
        if (IsGrounded())
        {
            ApplyMovement(direction);
        }
        else
        {
            ApplyAirMovement(direction);
        }
    }
    
    private void ApplyMovement(Vector3 direction)
    {
        // 即使是单行,也使用大括号
        if (direction == Vector3.zero)
        {
            return;
        }
        
        transform.position += direction * speed * Time.deltaTime;
    }
}

最佳实践

使用属性而不是公共字段

public class Character
{
    // 不好的做法
    public float health;
    
    // 好的做法
    private float health;
    public float Health
    {
        get => health;
        set
        {
            health = Mathf.Clamp(value, 0f, maxHealth);
            OnHealthChanged?.Invoke(health);
        }
    }
}

避免魔法数字

public class CombatSystem
{
    // 不好的做法
    public void CalculateDamage(float baseDamage)
    {
        float damage = baseDamage * 1.5f;  // 魔法数字
    }
    
    // 好的做法
    private const float CRITICAL_MULTIPLIER = 1.5f;
    public void CalculateDamage(float baseDamage)
    {
        float damage = baseDamage * CRITICAL_MULTIPLIER;
    }
}

使用有意义的异常处理

public class SaveSystem
{
  public void SaveGame(GameData data)
  {
      try
      {
          string json = JsonUtility.ToJson(data);
          File.WriteAllText(savePath, json);
      }
      catch (IOException ex)
      {
          Debug.LogError($"Failed to save game: {ex.Message}");
          throw new SaveGameException("Could not save game data", ex);
      }
      catch (Exception ex)
      {
          Debug.LogError($"Unexpected error while saving: {ex.Message}");
          throw;
      }
  }
}

遵循单一职责原则

// 不好的做法
public class PlayerManager
{
    public void UpdatePlayer() { }
    public void RenderHealthBar() { }  // UI相关
    public void SavePlayerData() { }   // 数据相关
}

// 好的做法
public class PlayerManager
{
    private PlayerUI ui;
    private PlayerData data;
    
    public void UpdatePlayer() { }
}

public class PlayerUI
{
    public void RenderHealthBar() { }
}

public class PlayerData
{
    public void SaveData() { }
}

使用适当的访问修饰符

public class InventorySystem
{
    // 只在类内部使用的字段
    private List<Item> items;
    
    // 允许子类访问但不公开
    protected virtual void OnItemAdded(Item item) { }
    
    // 公开的API
    public bool AddItem(Item item)
    {
        if (items.Count >= maxItems)
            return false;
            
        items.Add(item);
        OnItemAdded(item);
        return true;
    }
}

代码审查清单

就像游戏测试清单一样,代码审查也需要一个清单:

  1. 命名检查

    • 命名是否清晰且符合规范
    • 是否避免使用魔法数字
    • 是否使用有意义的变量名
  2. 结构检查

    • 代码是否组织合理
    • 是否遵循单一职责原则
    • 是否避免代码重复
  3. 性能检查

    • 是否避免不必要的对象创建
    • 是否合理使用缓存
    • 是否优化循环和条件判断
  4. 安全性检查

    • 是否进行输入验证
    • 是否处理异常情况
    • 是否保护敏感数据
  5. 可维护性检查

    • 是否有适当的注释
    • 是否遵循DRY原则
    • 是否易于测试

实战练习

练习1:重构游戏管理器

将一个混乱的游戏管理器类重构为符合规范的代码:

// 重构前
public class GameManager
{
    public static GameManager instance;
    public int score = 0;
    public bool gameStarted = false;
    public void StartGame() { gameStarted = true; }
    public void EndGame() { gameStarted = false; }
    public void AddScore(int s) { score += s; }
}

// 重构后
public class GameManager : MonoBehaviour
{
    #region Singleton
    private static GameManager instance;
    public static GameManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<GameManager>();
                if (instance == null)
                {
                    GameObject go = new GameObject("GameManager");
                    instance = go.AddComponent<GameManager>();
                }
            }
            return instance;
        }
    }
    #endregion
    
    #region Events
    public event Action<int> OnScoreChanged;
    public event Action<GameState> OnGameStateChanged;
    #endregion
    
    #region Properties
    private int score;
    public int Score
    {
        get => score;
        private set
        {
            if (score != value)
            {
                score = value;
                OnScoreChanged?.Invoke(score);
            }
        }
    }
    
    public GameState CurrentState { get; private set; }
    #endregion
    
    #region Public Methods
    public void StartGame()
    {
        if (CurrentState != GameState.MainMenu)
            return;
            
        CurrentState = GameState.Playing;
        OnGameStateChanged?.Invoke(CurrentState);
    }
    
    public void EndGame()
    {
        if (CurrentState != GameState.Playing)
            return;
            
        CurrentState = GameState.GameOver;
        OnGameStateChanged?.Invoke(CurrentState);
    }
    
    public void AddScore(int points)
    {
        if (CurrentState != GameState.Playing || points <= 0)
            return;
            
        Score += points;
    }
    #endregion
    
    #region Private Methods
    private void Awake()
    {
        if (instance != null && instance != this)
        {
            Destroy(gameObject);
            return;
        }
        
        instance = this;
        DontDestroyOnLoad(gameObject);
        
        InitializeGame();
    }
    
    private void InitializeGame()
    {
        CurrentState = GameState.MainMenu;
        Score = 0;
    }
    #endregion
}

public enum GameState
{
    MainMenu,
    Playing,
    Paused,
    GameOver
}

练习2:创建规范的组件系统

实现一个符合C#规范的组件系统:

public interface IComponent
{
 void Initialize();
 void Update();
 void Cleanup();
}

public abstract class GameComponent : IComponent
{
 protected bool isInitialized;
 protected GameObject owner;
 
 protected GameComponent(GameObject owner)
 {
     this.owner = owner;
 }
 
 public virtual void Initialize()
 {
     if (isInitialized)
         return;
         
     isInitialized = true;
 }
 
 public abstract void Update();
 
 public virtual void Cleanup()
 {
     isInitialized = false;
 }
}

public class HealthComponent : GameComponent
{
 #region Events
 public event Action<float> OnHealthChanged;
 public event Action OnDeath;
 #endregion
 
 #region Properties
 private float currentHealth;
 public float CurrentHealth
 {
     get => currentHealth;
     private set
     {
         if (currentHealth != value)
         {
             currentHealth = Mathf.Clamp(value, 0f, maxHealth);
             OnHealthChanged?.Invoke(currentHealth);
             
             if (currentHealth <= 0f)
             {
                 OnDeath?.Invoke();
             }
         }
     }
 }
 
 private readonly float maxHealth;
 public float MaxHealth => maxHealth;
 #endregion
 
 #region Constructor
 public HealthComponent(GameObject owner, float maxHealth) : base(owner)
 {
     this.maxHealth = maxHealth;
     this.currentHealth = maxHealth;
 }
 #endregion
 
 #region Public Methods
 public override void Initialize()
 {
     base.Initialize();
     CurrentHealth = maxHealth;
 }
 
 public override void Update()
 {
     // 实现生命值随时间恢复等逻辑
 }
 
 public void TakeDamage(float damage)
 {
     if (damage <= 0f)
         return;
         
     CurrentHealth -= damage;
 }
 
 public void Heal(float amount)
 {
     if (amount <= 0f)
         return;
         
     CurrentHealth += amount;
 }
 #endregion
}

public class ComponentManager
{
 #region Fields
 private readonly Dictionary<Type, IComponent> components;
 private readonly GameObject owner;
 #endregion
 
 #region Constructor
 public ComponentManager(GameObject owner)
 {
     this.owner = owner;
     components = new Dictionary<Type, IComponent>();
 }
 #endregion
 
 #region Public Methods
 public T AddComponent<T>() where T : IComponent, new()
 {
     Type type = typeof(T);
     
     if (components.ContainsKey(type))
     {
         throw new InvalidOperationException(
             $"Component of type {type.Name} already exists.");
     }
     
     T component = new T();
     components.Add(type, component);
     component.Initialize();
     
     return component;
 }
 
 public T GetComponent<T>() where T : IComponent
 {
     Type type = typeof(T);
     
     if (components.TryGetValue(type, out IComponent component))
     {
         return (T)component;
     }
     
     return default;
 }
 
 public void RemoveComponent<T>() where T : IComponent
 {
     Type type = typeof(T);
     
     if (components.TryGetValue(type, out IComponent component))
     {
         component.Cleanup();
         components.Remove(type);
     }
 }
 
 public void UpdateComponents()
 {
     foreach (var component in components.Values)
     {
         component.Update();
     }
 }
 #endregion
}

总结

在本节中,我们学习了:

  1. C#的命名规范和约定
  2. 代码组织和文件结构
  3. 代码格式化规则
  4. 编程最佳实践
  5. 在游戏开发中的实际应用

通过实战练习,我们实现了:

  1. 一个规范的游戏管理器
  2. 一个完整的组件系统

良好的编程规范和代码风格不仅能提高代码质量,还能提升团队协作效率。在实际游戏开发中,我们应该始终遵循这些规范。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值