Menu
CoddyTech

Thành phần nhân vật trong trò chơi

Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 69 trên 70.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống nhân vật trong trò chơi bằng mẫu kiến trúc dựa trên thành phần! Thay vì tạo các hệ phân cấp kế thừa cứng nhắc, bạn sẽ kết hợp các nhân vật bằng cách gắn những thành phần độc lập, mỗi thành phần xử lý một hành vi cụ thể. Đây là cách tiếp cận linh hoạt được sử dụng rộng rãi trong phát triển trò chơi.

Bạn sẽ sắp xếp code trong năm tệp:

  • IComponent.cs: Định nghĩa một giao diện IComponent trong không gian tên Game.Components. Mỗi thành phần phải có phương thức Update() để in ra hoạt động của thành phần.
  • HealthComponent.cs: Tạo lớp HealthComponent triển khai IComponent. Lớp này cần theo dõi điểm máu hiện tại và tối đa, cùng các phương thức TakeDamage(int amount) và Heal(int amount). Điểm máu không bao giờ được thấp hơn 0 hoặc cao hơn mức tối đa. Thêm sự kiện event Action<int> HealthChanged, được kích hoạt mỗi khi điểm máu thay đổi và truyền giá trị điểm máu mới. Phương thức Update() cần in ra trạng thái điểm máu hiện tại.
  • InventoryComponent.cs: Tạo lớp InventoryComponent triển khai IComponent. Lớp này quản lý danh sách tên vật phẩm với các phương thức AddItem(string item) và RemoveItem(string item) (trả về bool để cho biết thao tác có thành công hay không). Phương thức Update() cần in ra nội dung túi đồ.
  • Character.cs: Tạo lớp Character trong không gian tên Game, lớp này lưu một tập hợp các thành phần. Nhân vật cần có thuộc tính Name và các phương thức sau:
    • AddComponent(IComponent component) - gắn một thành phần vào nhân vật
    • GetComponent<T>() với điều kiện T : IComponent - trả về thành phần đầu tiên thuộc kiểu T hoặc null nếu không tìm thấy
    • UpdateAll() - gọi Update() trên từng thành phần đã gắn
  • Program.cs: Kết hợp mọi thứ bằng cách tạo một nhân vật, gắn các thành phần và xử lý lệnh.

Bạn sẽ nhận các đầu vào sau:

  • Tên nhân vật
  • Điểm máu tối đa ban đầu cho HealthComponent
  • Số lệnh cần xử lý
  • Với mỗi lệnh: hành động, theo sau là dữ liệu liên quan

Định dạng lệnh:

damage
{amount}

heal
{amount}

add_item
{itemName}

remove_item
{itemName}

update

Định dạng đầu ra:

  • Khi điểm máu thay đổi (thông qua sự kiện): Health changed: {newHealth}
  • Khi thêm vật phẩm: Added: {itemName}
  • Khi xóa vật phẩm: Removed: {itemName} hoặc Item not found: {itemName}
  • Phương thức Update() của HealthComponent: Health: {current}/{max}
  • Phương thức Update() của InventoryComponent: Inventory: {item1}, {item2}, ... hoặc Inventory: empty

Ví dụ, nếu đầu vào là:

Hero
100
7
add_item
Sword
add_item
Shield
damage
30
heal
10
remove_item
Potion
remove_item
Sword
update

Đầu ra sẽ là:

Added: Sword
Added: Shield
Health changed: 70
Health changed: 80
Item not found: Potion
Removed: Sword
Health: 80/100
Inventory: Shield

Hãy chú ý rằng nhân vật không kế thừa từ lớp đặc biệt nào, mà chỉ lưu các thành phần cung cấp hành vi. Phương thức GetComponent<T>() dùng kiểu tổng quát để truy xuất an toàn theo kiểu, còn các sự kiện thông báo khi điểm máu thay đổi. Cách kết hợp này cho phép bạn tự do phối hợp các thành phần!

Tự làm thử

using System;
using Game;
using Game.Components;

class Program
{
    public static void Main(string[] args)
    {
        // Đọc tên nhân vật
        string characterName = Console.ReadLine();
        
        // Đọc mức máu tối đa ban đầu
        int maxHealth = Convert.ToInt32(Console.ReadLine());
        
        // Đọc số lượng lệnh
        int numCommands = Convert.ToInt32(Console.ReadLine());
        
        // TODO: Tạo Character với tên đã cho
        
        // TODO: Tạo HealthComponent với maxHealth và InventoryComponent
        
        // TODO: Đăng ký sự kiện HealthChanged để in ra: Health changed: {newHealth}
        
        // TODO: Thêm cả hai thành phần vào nhân vật
        
        // TODO: Xử lý từng lệnh
        for (int i = 0; i < numCommands; i++)
        {
            string command = Console.ReadLine();
            
            // TODO: Xử lý từng loại lệnh:
            // - "damage": đọc lượng, gọi TakeDamage trên thành phần máu
            // - "heal": đọc lượng, gọi Heal trên thành phần máu
            // - "add_item": đọc tên vật phẩm, gọi AddItem trên thành phần kho đồ
            // - "remove_item": đọc tên vật phẩm, gọi RemoveItem trên thành phần kho đồ
            // - "update": gọi UpdateAll trên nhân vật
        }
    }
}

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C# online