Under Construction

Prometheus Lab

Portfolio & Resources

Back to Blog
Unity14 min5 views

Coding Conventions

Author

Minh Khoa

Author

image.pngDưới đây là tổng hợp các quy tắc viết code chuẩn mực, phổ biến nhất mà các lập trình viên game (đặc biệt là trong môi trường Unity/C#) thường áp dụng. Việc tuân thủ các quy tắc này giúp code dễ đọc, dễ bảo trì và dễ dàng làm việc nhóm.


1. Class, Struct, Enum, và Interface

  • Quy tắc: Sử dụng PascalCase (Viết hoa chữ cái đầu của mỗi từ).

  • Interface: Bắt đầu bằng chữ I viết hoa.

  • Ví dụ:

    public class PlayerController : MonoBehaviour { }
    public struct WeaponStats { }
    public enum GameState { Playing, Paused, GameOver }
    public interface IDamageable { }
    
    

2. Tên Biến (Variables / Fields)

Phân loại tên biến rất quan trọng để khi nhìn vào code, dev biết ngay biến này thuộc phạm vi nào.

  • Private & Protected Fields (Biến nội bộ của class):

    • Quy tắc: Sử dụng camelCase (viết thường chữ cái đầu, các từ sau viết hoa) và thêm dấu gạch dưới _ ở đầu.

    • Ví dụ: _health, _moveSpeed, _playerRigidbody.

    • Lưu ý: Nếu bạn muốn hiển thị biến private trên Unity Inspector, hãy dùng [SerializeField].

      [SerializeField] private int _maxHealth = 100;
      
      
  • Public Fields:

    • Quy tắc: Sử dụng PascalCase (chuẩn C#) hoặc camelCase (rất nhiều dev Unity dùng để Inspector hiển thị đẹp hơn do Unity tự động tách từ).
    • Ví dụ: MaxHealth hoặc moveSpeed.
    • Lời khuyên: Hạn chế dùng public field để tránh bị class khác sửa đổi lung tung. Hãy dùng Properties hoặc [SerializeField] private.
  • Local Variables (Biến khai báo trong hàm) & Parameters (Tham số truyền vào):

    • Quy tắc: Sử dụng camelCase.

    • Ví dụ:

      public void TakeDamage(int damageAmount)
      {
          int currentDamage = damageAmount - armor;
      }
      
      

3. Hàm / Phương Thức (Methods / Functions)

  • Quy tắc: Sử dụng PascalCase. Tên hàm phải là một động từ thể hiện rõ hành động.
  • Ví dụ: MovePlayer(), CalculateScore(), SpawnEnemy(), UpdateUI().

4. Thuộc Tính (Properties - Getters/Setters)

  • Quy tắc: Sử dụng PascalCase.

  • Ví dụ:

    public int CurrentHealth { get; private set; }
    public bool IsGrounded => _isGrounded; // Expression-bodied property
    
    

5. Hằng Số (Constants) & Static Readonly

  • Quy tắc: Sử dụng PascalCase hoặc UPPER_SNAKE_CASE (Tất cả viết hoa, phân cách bằng dấu gạch dưới).

  • Ví dụ:

    public const int MaxPlayers = 4;
    // Hoặc
    public const float MAX_MOVE_SPEED = 10f;
    
    

6. Biến Boolean (Đúng / Sai)

  • Quy tắc: Nên bắt đầu bằng các tiền tố hỏi (như một câu hỏi Yes/No) như is, has, can, should. Điều này giúp code đọc trôi chảy như tiếng Anh.
  • Ví dụ:
    • isDead (Thay vì dead)
    • hasKey (Thay vì key)
    • canJump (Thay vì jumpable)
    • shouldSpawn

7. Sự Kiện (Events) & Delegates

  • Quy tắc: Sử dụng PascalCase. Tên Event thường bắt đầu bằng từ On.
  • Ví dụ: OnPlayerDeath, OnLevelCompleted.
  • Tên hàm xử lý sự kiện (Event Handlers): Nên đặt tên rõ ràng, ví dụ HandlePlayerDeath() hoặc OnLevelCompletedHandler().

8. Coroutines (Hàm bất đồng bộ của Unity)

  • Quy tắc: Thường thêm hậu tố Routine hoặc Coroutine để phân biệt rõ với các hàm chạy trong 1 frame thông thường.

  • Ví dụ:

    private IEnumerator FadeOutRoutine() { ... }
    private IEnumerator SpawnWavesCoroutine() { ... }
    
    

9. Tổ Chức Script và Comment

  • Tên File: Tên file .cs phải khớp hoàn toàn với tên Class chính bên trong. (VD: file GameManager.cs phải chứa class GameManager).
  • Namespace: Sử dụng Namespace để gom nhóm các tính năng lớn, tránh trùng lặp tên (VD: MaruGame.Core, MaruGame.UI).
  • Comment:
    • Sử dụng XML Comments (///) trên đầu các class/hàm public quan trọng để giải thích công dụng.
    • Với comment trong hàm (//), đừng giải thích code đang làm gì (vì code tự nó đã nói lên điều đó), hãy giải thích tại sao lại viết logic đó (giải thích business logic, trick fix bug, v.v.).

10. Quy Tắc Đặt Tên Thư Mục (Folder Naming)

Trong các dự án game lớn, việc quản lý tài nguyên và cấu trúc thư mục hợp lý là cực kỳ quan trọng.

  • Quy tắc chung: Sử dụng PascalCase giống như tên Class.
  • Kỹ thuật ghim thư mục lên đầu (Top-level Folders):
    • Thêm dấu gạch dưới _ vào trước tên các thư mục quan trọng hoặc bạn thường xuyên làm việc nhất.
    • Lý do: Unity Project Window (cũng như Windows/Mac) sắp xếp file theo thứ tự bảng chữ cái. Dấu _ đứng trước chữ A, do đó các thư mục này sẽ luôn tự động nổi lên vị trí trên cùng, giúp bạn điều hướng rất nhanh mà không cần cuộn chuột tìm kiếm.
    • Ví dụ: _Scripts, _Prefabs, _Scenes, _Art.
  • Quy tắc cho hệ thống con (Sub-systems): Nếu có các thư mục con phân chia theo tính năng, bạn có thể dùng dấu gạch ngang hoặc PascalCase liền nhau (VD: Core-Bounce-Balls hoặc CoreBounceBalls).

11. Đặt Tên Biến UI Components (Giao Diện)

Khi làm việc với UI (Canvas, Text, Button,...), việc nhận diện loại Component từ tên biến là rất quan trọng để tránh nhầm lẫn và lỗi null reference.

  • Quy tắc: Thêm hậu tố (Suffix) hoặc tiền tố (Prefix) tương ứng với loại Component.
  • Ví dụ (Sử dụng hậu tố là phổ biến nhất):
    • _playButton (hoặc playBtn)
    • _healthText (hoặc healthTxt)
    • _loadingBarImage (hoặc loadingImg)
    • _mainPanel / _inventoryDialog

12. Đặt Tên ScriptableObjects & Prefabs

  • ScriptableObjects (Các file lưu trữ dữ liệu tĩnh):
    • Tên Class và tên File thường đi kèm hậu tố Data, Config, Settings, Stats.
    • Ví dụ: WeaponData, EnemyStats, GameConfig.
  • Prefabs:
    • Sử dụng PascalCase. Với các project lớn, thường có thêm tiền tố viết hoa (phân cách bằng _) để gom nhóm các loại tài nguyên khi search.
    • Ví dụ: VFX_Explosion (Hiệu ứng), SFX_Jump (Âm thanh), UI_Popup_GameOver (Giao diện), ENV_PineTree (Môi trường), CHR_Player (Nhân vật).

13. Khử "Magic Numbers" và "Magic Strings"

  • Quy tắc: Không bao giờ gõ trực tiếp một con số cụ thể hoặc một chuỗi string cố định vào giữa các dòng logic code (VD: if (gameObject.tag == "Player") hay health -= 15;).

  • Giải pháp: Khai báo chúng dưới dạng các biến hằng số (const), readonly hoặc biến [SerializeField].

  • Ví dụ:

    // SAI
    Invoke("SpawnEnemy", 3f);
    
    // ĐÚNG
    private const string SPAWN_METHOD = nameof(SpawnEnemy);
    [SerializeField] private float _spawnDelay = 3f;
    
    // Nơi gọi hàm:
    Invoke(SPAWN_METHOD, _spawnDelay);
    
    

14. Trang Trí Inspector & Tổ Chức Class (Attributes & Regions)

Làm game không chỉ là viết code chạy được, mà còn phải làm ra công cụ (Inspector) dễ nhìn cho Game Designer thiết lập.

  • Attributes: Dùng [Header("...")], [Tooltip("...")], [Space] để phân chia các nhóm biến trên Unity Inspector.
  • RequireComponent: Luôn thêm [RequireComponent(typeof(TenComponent))] trên đầu Class nếu script của bạn bắt buộc phải có Component đó để chạy (VD: cần Rigidbody2D). Nó sẽ tự động gắn component đó vào GameObject và ngăn người khác lỡ tay xóa đi.
  • Regions: Dùng #region#endregion để thu gọn các đoạn code, giúp file ngăn nắp. Thường chia thành: Variables, Unity Methods (Awake/Start/Update), Public Methods, Private Methods.

Tóm tắt nhanh cho 1 Class mẫu:

using UnityEngine;

namespace MaruGame.Core
{
    public class PlayerStats : MonoBehaviour // PascalCase cho Class
    {
        public const int MAX_LEVEL = 99; // UPPER_SNAKE_CASE cho Hằng số

        [SerializeField] private float _baseHealth = 100f; // _camelCase cho Private Field
        [SerializeField] private float _moveSpeed = 5f;

        public float CurrentHealth { get; private set; } // PascalCase cho Property

        private bool _isDead = false; // Tiền tố 'is' cho Boolean

        public void TakeDamage(float damageAmount) // PascalCase cho Hàm, camelCase cho Tham số
        {
            if (_isDead) return;

            CurrentHealth -= damageAmount;

            if (CurrentHealth <= 0)
            {
                Die();
            }
        }

        private void Die()
        {
            _isDead = true;
            // Xử lý logic chết ở đây...
        }
    }
}