C# Cho Game Dev #4: Methods, Parameters & Enum
tutorial

C# Cho Game Dev #4: Methods, Parameters & Enum

October 26, 2024
6 min read
#CSharp#Programming#Game Development#Course
💻 C# Cho Game Dev4 / 7

Tại sao cần Methods?

Bạn viết code tính damage ở 5 chỗ khác nhau. Khi cần sửa công thức, phải sửa cả 5 chỗ. Methods giải quyết vấn đề này: viết một lần, gọi nhiều nơi.

Cú Pháp Cơ Bản

// Định nghĩa method
static void SayHello()
{
    Console.WriteLine("Hello, Game Dev!");
}

// Gọi method
SayHello();  // "Hello, Game Dev!"
SayHello();  // Gọi lại bao nhiêu lần cũng được

Cấu trúc: [access] [static] returnType MethodName(parameters)

  • static — tạm thời luôn dùng, sẽ hiểu rõ hơn ở bài OOP
  • void — method không trả về giá trị
  • Tên method dùng PascalCase (chữ cái đầu viết hoa)

Parameters — Truyền dữ liệu vào

static void TakeDamage(int amount)
{
    Console.WriteLine($"Nhận {amount} damage!");
}

TakeDamage(30);   // "Nhận 30 damage!"
TakeDamage(50);   // "Nhận 50 damage!"

Nhiều parameters

static void Attack(string attacker, string target, int damage)
{
    Console.WriteLine($"{attacker} tấn công {target}, gây {damage} damage!");
}

Attack("Player", "Goblin", 25);

Default parameters

static void Heal(int amount = 20, bool showEffect = true)
{
    Console.WriteLine($"Hồi {amount} HP");
    if (showEffect) Console.WriteLine("* Hiệu ứng hồi máu *");
}

Heal();           // amount=20, showEffect=true
Heal(50);         // amount=50, showEffect=true
Heal(30, false);  // amount=30, showEffect=false

Default params phải đặt sau params bắt buộc.

Return — Trả về giá trị

static int CalculateDamage(int baseDmg, float multiplier)
{
    return (int)(baseDmg * multiplier);
}

int damage = CalculateDamage(30, 1.5f);
Console.WriteLine($"Damage: {damage}");  // 45
  • Kiểu return phải match với kiểu khai báo (int ở đây)
  • return kết thúc method ngay lập tức

Return boolean — kiểm tra điều kiện

static bool CanAfford(int gold, int price)
{
    return gold >= price;
}

if (CanAfford(500, 300))
    Console.WriteLine("Mua thành công!");
else
    Console.WriteLine("Không đủ tiền!");

Return với early exit

static float GetHealthPercent(int current, int max)
{
    if (max <= 0) return 0f;  // tránh chia cho 0, thoát sớm
    return (float)current / max * 100f;
}

Ref & Out — Thay đổi biến bên ngoài

Ref — truyền tham chiếu

static void ApplyDamage(ref int health, int damage)
{
    health -= damage;
    if (health < 0) health = 0;
}

int playerHP = 100;
ApplyDamage(ref playerHP, 30);
Console.WriteLine(playerHP);  // 70 — biến gốc bị thay đổi

Không có ref, method chỉ nhận bản copy của giá trị.

Out — trả về nhiều giá trị

static void GetMinMax(int[] numbers, out int min, out int max)
{
    min = numbers[0];
    max = numbers[0];
    foreach (int n in numbers)
    {
        if (n < min) min = n;
        if (n > max) max = n;
    }
}

int[] scores = { 100, 450, 200, 800, 50 };
GetMinMax(scores, out int lowest, out int highest);
Console.WriteLine($"Min: {lowest}, Max: {highest}");  // Min: 50, Max: 800

out bắt buộc method phải gán giá trị trước khi kết thúc.

Method Overloading — Cùng tên, khác params

static int Damage(int base_dmg)
{
    return base_dmg;
}

static int Damage(int base_dmg, float critMultiplier)
{
    return (int)(base_dmg * critMultiplier);
}

static int Damage(int base_dmg, int bonusDmg, bool isElemental)
{
    int total = base_dmg + bonusDmg;
    if (isElemental) total = (int)(total * 1.3f);
    return total;
}

// Compiler tự chọn method phù hợp dựa trên arguments
Console.WriteLine(Damage(30));              // 30
Console.WriteLine(Damage(30, 2.0f));        // 60
Console.WriteLine(Damage(30, 10, true));    // 52

Overloading cho phép cùng 1 tên method nhưng nhận các kiểu/số lượng parameter khác nhau.

Enum — Tập hợp hằng số có tên

Enum giúp thay thế "magic numbers" bằng tên có ý nghĩa.

Cú pháp

enum WeaponType
{
    Sword,    // 0
    Bow,      // 1
    Staff,    // 2
    Dagger,   // 3
    Axe       // 4
}

WeaponType equipped = WeaponType.Sword;
Console.WriteLine(equipped);        // "Sword"
Console.WriteLine((int)equipped);   // 0

Dùng trong switch

enum GameState { MainMenu, Playing, Paused, GameOver }

static void HandleState(GameState state)
{
    switch (state)
    {
        case GameState.MainMenu:
            Console.WriteLine("Hiện menu chính");
            break;
        case GameState.Playing:
            Console.WriteLine("Game đang chạy");
            break;
        case GameState.Paused:
            Console.WriteLine("Game tạm dừng");
            break;
        case GameState.GameOver:
            Console.WriteLine("Game kết thúc");
            break;
    }
}

HandleState(GameState.Playing);  // "Game đang chạy"

Gán giá trị cụ thể

enum Rarity
{
    Common = 1,
    Uncommon = 2,
    Rare = 3,
    Epic = 4,
    Legendary = 5
}

static int GetDropRate(Rarity rarity)
{
    return rarity switch
    {
        Rarity.Common    => 50,
        Rarity.Uncommon  => 30,
        Rarity.Rare      => 15,
        Rarity.Epic      => 4,
        Rarity.Legendary => 1,
        _ => 0
    };
}

Console.WriteLine($"Legendary drop rate: {GetDropRate(Rarity.Legendary)}%");

Tại sao dùng Enum thay vì string?

// BAD — magic strings, dễ typo
string state = "playin";  // typo, không ai bắt lỗi

// GOOD — enum, compiler bắt lỗi
GameState state = GameState.Playin;  // COMPILE ERROR!

Enum cho bạn autocomplete trong IDE và compile-time error khi nhập sai. Trong Unity, enum cực kỳ phổ biến.

Ví Dụ Tổng Hợp: Combat System

enum Element { None, Fire, Water, Grass }

static float GetElementMultiplier(Element attacker, Element defender)
{
    if (attacker == Element.Fire && defender == Element.Grass) return 2.0f;
    if (attacker == Element.Water && defender == Element.Fire) return 2.0f;
    if (attacker == Element.Grass && defender == Element.Water) return 2.0f;
    if (attacker == defender && attacker != Element.None) return 0.5f;
    return 1.0f;
}

static int CalculateFinalDamage(int baseDmg, Element atkElement, Element defElement, bool isCrit)
{
    float multiplier = GetElementMultiplier(atkElement, defElement);
    float critBonus = isCrit ? 1.5f : 1.0f;
    int finalDmg = (int)(baseDmg * multiplier * critBonus);
    return finalDmg;
}

static void PrintAttackResult(string attacker, string defender, int damage,
    Element atkElem, Element defElem, bool isCrit)
{
    float mult = GetElementMultiplier(atkElem, defElem);
    string effectiveness = mult > 1 ? "Hiệu quả cao!" : mult < 1 ? "Không hiệu quả..." : "";
    string crit = isCrit ? " CRITICAL!" : "";

    Console.WriteLine($"{attacker}{defender}: {damage} damage{crit} {effectiveness}");
}

// Sử dụng
int dmg = CalculateFinalDamage(40, Element.Fire, Element.Grass, true);
PrintAttackResult("Charmander", "Bulbasaur", dmg, Element.Fire, Element.Grass, true);
// Output: Charmander → Bulbasaur: 120 damage CRITICAL! Hiệu quả cao!

Bài Tập

Bài 1: Math Utils Viết 3 method: Max(int a, int b) trả về số lớn hơn, Clamp(int value, int min, int max) giới hạn giá trị trong khoảng, và Map(float value, float fromMin, float fromMax, float toMin, float toMax) ánh xạ giá trị từ range này sang range khác.

Bài 2: Shop System Tạo enum ItemType { Weapon, Armor, Potion, Scroll }. Viết method GetPrice(ItemType type, Rarity rarity) trả về giá = basePrice * rarityMultiplier. Viết method TryBuy(ref int gold, ItemType type, Rarity rarity) trả về bool và trừ gold nếu đủ tiền.

Bài 3: Dice Roller Viết method overloaded Roll() (1d6 mặc định), Roll(int sides) (1 xúc xắc n mặt), Roll(int count, int sides) (nhiều xúc xắc). In kết quả mỗi viên và tổng.


Bài trước: C# #3: Collections Bài tiếp: C# #5: OOP — Class, Object & Constructor