Coding Conventions
Minh Khoa
Author
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ữ
Iviế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ụ:
MaxHealthhoặcmoveSpeed. - Lời khuyên: Hạn chế dùng
publicfield để tránh bị class khác sửa đổi lung tung. Hãy dùngPropertieshoặ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ặcOnLevelCompletedHandler().
8. Coroutines (Hàm bất đồng bộ của Unity)
-
Quy tắc: Thường thêm hậu tố
RoutinehoặcCoroutineđể 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
.csphải khớp hoàn toàn với tên Class chính bên trong. (VD: fileGameManager.csphải chứa classGameManager). - 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.).
- Sử dụng XML Comments (
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.
- Thêm dấu gạch dưới
- 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-BallshoặcCoreBounceBalls).
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ặcplayBtn)_healthText(hoặchealthTxt)_loadingBarImage(hoặcloadingImg)_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.
- Tên Class và tên File thường đi kèm hậu tố
- 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).
- 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
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")hayhealth -= 15;). -
Giải pháp: Khai báo chúng dưới dạng các biến hằng số (
const),readonlyhoặ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
#regionvà#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...
}
}
}