Dướ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.
Tên File: Tên file .csphả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.
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 và #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...
}
}
}