STDIO
Tìm kiếm gần đây
    • Nội dung
    • QR Code
    • 0
    • 0
    • Sao chép

    CBP-5: Truyền và Lấy Thông Số từ Component

    Giao tiếp giữa Component và Entity thông qua Command trong mô hình CBP.
    20/08/2015
    18/09/2020
    7 phút đọc
    CBP-5: Truyền và Lấy Thông Số từ Component

    Giới thiệu

    Đây là 1 nội dung mới trong CBP:

    • Hỗ trợ thao tác với các Component bằng cách truyền và lấy giá trị từ các Component của Entity.
    • Hiện thực CPosition - Component quản lý tọa độ của Entity để minh họa cho vấn đề này.

    Tải project mẫu

    Đây là project ví dụ dùng trong bài viết: CBP-5-2019.zip.

    Project này có thể mở bằng Visual Studio 2019, project này chứa các file mã C++ của CBP, project có thể không tương thích với nhiều môi trường Visual Studio khác nhau nhưng có thể dành để tham khảo.

    CPosition

    Ý tưởng

    Đây là 1 dạng Component khá đặc biệt với chức năng chính là lưu trữ tọa độ của đối tượng. Giai đoạn phân tích có thể dẫn độc giả đến việc hiện thực Component di chuyển có kèm thuộc tính tọa độ thay vì Component tọa độ.

    Đối với Component tọa độ CPosition chỉ có thuộc tính tọa độ và phương thức truy vấn hay thay đổi trực tiếp giá trị mà CPosition nắm giữ get/set.

    Đối với Component di chuyển CMove chỉ có phương thức thay đổi tọa độ của Entity theo giá trị delta chênh lệch, gồm các bước cơ bản:

    1. Truy xuất tọa độ hiện tại của Entity.
    2. Thay đổi giá trị tọa độ truy xuất được theo delta.
    3. Thiết lập tọa độ mới cho Entity theo giá trị tính toán được.

    Lưu ý

    CMoveCPosition hoàn toàn không có liên hệ gì với nhau, 1 Entity hoàn toàn có thể sở hữu 1 trong 2 hoặc cả 2 Component trên. Trường hợp Entity tồn tại CMove nhưng không có CPosition thì xử lý của CMove xem như không ảnh hưởng đến Entity.

    Giải pháp ban đầu

    Để giải quyết vấn đề này có thể định nghĩa 1 bộ phương thức get/set cho CPosition. Lúc này, giải pháp như sau:

    // CPosition.h
    // class CPosition
    // public
    Vector2 getProperty();
    int     setProperty(Vector2 newPos);

    Vector2 là 1 trong những kiểu dữ liệu do tự định nghĩa, tất cả được gói trong FDataTypes.h, file chứa định nghĩa của những kiểu dữ liệu dạng struct, enum, union nhằm hỗ trợ các class.

    Đánh giá giải pháp, nhận thấy 1 số vấn đề nhãn tiền như sau:

    • Chỉ khả dụng cho CPosition, cần ép kiểu Component về CPosition nếu thao tác trên danh sách Component của Entity.
    • Chỉ dùng được cho 1 kiểu dữ liệu.

    Giải quyết vấn đề và hoàn thiện

    Bước 1: khai báo getProperty/setProperty ảo ở CBase

    // CBase.h
    // class CBase
    // public
    virtual Vector2 getProperty();
    virtual int     setProperty(Vector2 newPos);

    Tận dụng tính đa hình của lập trình hướng đối tượng, bước này giải quyết được vấn đề phương thức chỉ khả dụng cho CPosition và cần ép kiểu Component để sử dụng.

    CBase::getPropertyCBase::setProperty có thể không có chức năng, đối với mỗi Component, bộ phương thức này chỉ thao tác với những thuộc tính nhất định, với những kiểu dữ liệu nhất định, cách giải quyết này mặc định "không làm gì cả" khi lập trình viên gọi phương thức bằng đối tượng không phù hợp.

    Bước 2: chuẩn hóa đối số đầu vào

    // CBase.h
    // class CBase
    // public
    
    // To get component Property.
    // The data location will be auto released by destroy returning variable.
    // @param propertyFlag: the flag define the property you wanna get, listed in CBasePropertyFlag.
    virtual ComponentProperty getProperty(int propertyFlag);
    
    // To set component Property.
    // @param propertyData: a variable contain pointer to data location.
    // @param propertyFlag: the flag define the property you wanna set, listed in CBasePropertyFlag.
    virtual int setProperty(ComponentProperty propertyData, int propertyFlag) 
    throw(ComponentProperty::InvalidPointerType);

    Ở đây dùng 1 struct trung gian tên ComponentProperty lưu trữ địa chỉ dẫn đến dữ liệu và 1 cờ đánh dấu thuộc tính cần lấy hoặc thay đổi.

    ComponentProperty có khai báo như sau:

    struct ComponentProperty
    {
    enum Type
      {
          PTYPE_VOID = 0,
          PTYPE_VECTOR2
      };
    
    union
      {
          void*     ptrVoid;
          Vector2*  ptrVector2;
      };
    
    bool autoCleanup;
    Type typeFlag;
    
    ...
    };

    Về cơ bản, đây cũng là 1 hình thức ép kiểu dữ liệu, nhưng với cờ typeFlag đính kèm, luôn biết được kiểu thật của biến.

    Cờ thuộc tính của các Component được định nghĩa đơn giản theo kiểu enum, tương tự như Command. Ở đây có thay đổi 1 chút để thuận tiện cho trường hợp Component có kế thừa từ Component khác - 1 kiểu enum cho phép mở rộng thêm thông qua "kế thừa".

    class CPositionPropertyFlag : public CBase::CBasePropertyFlag
    {
    public:
       enum
       {
           PFLAG_POSITION = CBase::CBasePropertyFlag::NUM_OF_CBASE_PROPERTY_FLAG,
           NUM_OF_CPOSITION_PROPERTY_FLAG
       };
    };

    Vậy là khai báo của bộ phương thức get/set đã tương đối sẵn sàng, tiếp theo override lại bộ phương thức này cho CPosition như sau:

    // CPosition.h
    virtual ComponentProperty getProperty(int propertyFlag);
    virtual int setProperty(ComponentProperty propertyData, int propertyFlag) 
    throw(ComponentProperty::InvalidPointerType);
    
    // CPosition.cpp
    ComponentProperty CPosition::getProperty(int propertyFlag)
    {
       switch (propertyFlag)
       {
       case PropertyFlag::PFLAG_POSITION:
          return ComponentProperty(new Vector2(m_position), ComponentProperty::PTYPE_VECTOR2, true);
    
       default:
          return ComponentProperty(nullptr, ComponentProperty::PTYPE_VOID, false);
       }
    }
    
    int CPosition::setProperty(ComponentProperty propertyData, int propertyFlag) 
    throw(ComponentProperty::InvalidPointerType)
    {
        switch (propertyFlag)
        {
        case PropertyFlag::PFLAG_POSITION:
              if (propertyData.typeFlag == ComponentProperty::PTYPE_VECTOR2)
                  m_position = *propertyData.ptrVector2;
              else
                  throw ComponentProperty::InvalidPointerType();
              break;
    
        default:
              break;
        }
    }

    Bước 3: thêm bộ get/ set Property cho Entity

    // Entity.h
    // To get property from the component owned by this entity.
    // @param componentFlag: the flag define the component you gonna access.
    // @param propertyFlag: the flag define the property you wanna get.
    ComponentProperty getProperty(ID::COMPONENT componentFlag, int propertyFlag)
            throw(ID::Unexpected);
    
    // To set the component property.
    // @param componentFlag: the flag define the component you gonna access.
    // @param propertyData: the variable contain pointer to data location.
    // @param propertyFlag: the flag define the property you wanna set.
    int setProperty(ID::COMPONENT componentFlag, ComponentProperty propertyData, int propertyFlag)
            throw(ID::Unexpected, ComponentProperty::InvalidPointerType);

    Để thuận tiện cho việc truy xuất Component theo ID, chuyển thuộc tính m_components của Entity thành kiểu dữ liệu map<ID::Component, CBase*>. Mặt khác, thay đổi này sẽ đảm bảo mỗi Entity chỉ chứa 1 component mỗi loại.

    // Entity.h
    private:
            map<ID::COMPONENT, CBase*> m_components;

    Kèm với thay đổi này là 1 số thay đổi về định nghĩa phương thức trong Entity.cpp, có thể xem chi tiết hơn trong project ví dụ. Tiếp theo là định nghĩa của Entity get/set:

    // Entity.cpp
    ComponentProperty Entity::getProperty(ID::COMPONENT componentFlag, int propertyFlag)
    throw(ID::Unexpected)
    {
       try
       {
           return m_components.at(componentFlag)->getProperty(propertyFlag);
       }
       catch (out_of_range e)
       {
           throw ID::Unexpected();
       }
    }
    
    int Entity::setProperty(ID::COMPONENT componentFlag, 
                            ComponentProperty propertyData, int propertyFlag)
    throw(ID::Unexpected, ComponentProperty::InvalidPointerType)
    {
        try
        {
            m_components.at(componentFlag)->setProperty(propertyData, propertyFlag);
            return FSUCCESS;
        }
        catch (out_of_range e)
        {
             throw ID::Unexpected();
        }
        catch (ComponentProperty::InvalidPointerType e)
        {
             throw e;
        }
    return FFAILD;
    }

    Bỏ qua các xử lý về exception, bộ phương thức này chỉ hoạt động đơn thuần như 1 bộ tìm kiếm, gọi phương thức tương ứng của Component thích hợp.

    Thêm CPosition và thử nghiệm

    Bước thử nghiệm này, thêm CPosition cho Entity vừa thử nghiệm với CAnimationCBP-4, kết quả hướng đến là Entity sẽ thay đổi vị trí sau mỗi chu kỳ chớp tắt STDIO logo.

    Trong ví dụ có 1 số thay đổi như sau:

    // main.cpp
    void init(HINSTANCE instance, HWND handler)
    {
        ...
        CPosition::addComponentTo(g_entity, 50, 50);
    }
    // Entity.cpp
    int Entity::update(float delta)
    {
    ...
    if (s_timer >= 2.0f)
    {
        ComponentProperty newPos(new Vector2(rand() % 400 + 50, rand() % 400 + 50), 
                                 ComponentProperty::Type::PTYPE_VECTOR2, true);
        this->setProperty(ID::COMPONENT::POSITION, newPos, CPosition::PropertyFlag::PFLAG_POSITION);
    
        m_components.at(ID::COMPONENT::ANIMATION)->commandProcess(CAnimation::COMMAND::SHOW);
        s_timer = 0.0f;
    }
    ...
    }
    
    void Entity::draw(HDC hdc)
    {
        try
        {
             Vector2 drawPos = *this->getProperty(ID::COMPONENT::POSITION, 
                                                CPosition::PropertyFlag::PFLAG_POSITION).ptrVector2;
             ((CAnimation*)m_components.at(ID::COMPONENT::ANIMATION))->draw(hdc, drawPos);
        }
        catch (exception e)
        {
             OUTPUT("Entity::draw\nCannot draw\n" << std::string(e.what()) << endl);
        }
    }

    Vậy là quá trình chuẩn bị thử nghiệm đã hoàn thành và có thể chạy thử.

    Tổng kết

    Đến thời điểm này đã hoàn chỉnh cách lấy và sửa thông số của Component. Bộ phương thức này rất quan trọng trong khi làm việc với hệ thống đang định nghĩa. Nói nôm na, bộ phương thức này là 1 trong 2 công cụ liên lạc của Component với Entity và với thế giới game (công cụ còn lại là Command). Lại nhắc đến Command, bài viết sau sẽ hướng dẫn về 1 phần rất quan trọng, là bước cuối nhằm hoàn thiện hệ thống Command – Command Queue (hàng đợi chỉ lệnh).

    Bài chung series

    0 Bình luận
    Lập Trình Game

    Lập Trình Game

    Kiến thức, kỹ thuật, kinh nghiệm lập trình game.

    Đề xuất

    CBP-2: Khai Báo Component Cơ Sở và Entity Cơ Bản
    Tư tưởng của hệ thống Component - Entity trong Component Base ...
    CBP-3: Khai Báo Component Cơ Bản, Tích Hợp Component vào Entity
    Khai báo component CAnimation để thêm hình ảnh hiển thị cho Entity, đây ...

    Khám phá

    CBP-8: Component Điều Khiển và AI – Component Ra Lệnh
    Component ra lệnh - các Component có khả năng gửi Entity Command cho ...
    CBP-9: Bộ Khởi Tạo Entity – Factory và Hệ Thống ID
    Tổ chức lại CBP bằng cách tạo ra Factory, sản xuất Entity dựa vào các ID ...
    CBP-0: Giới Thiệu về Component Base Development
    Phương pháp lập trình Hướng thành phần (Component-base Development - ...
    CBP-6: Hàng Đợi Chỉ Lệnh
    Chuẩn hóa các thao tác nhận và xử lý chỉ lệnh từ Component trong mô hình ...
    CBP-1: Tổng Quan về Project Ví Dụ
    Giới thiệu về Component Base Programming (CBP) - sơ lược về project ví ...
    CBP-10: Hệ Thống Quản Lý Tập Trung Các Component Đặc Thù
    Hệ thống quản lý tập trung các Component sử dụng phương thức đặc trưng.
    CBP-4: Giao Tiếp với Entity – Hệ Thống Chỉ Lệnh
    Hướng dẫn về hệ thống chỉ lệnh, bước đầu hiện thực ứng dụng hệ thống chỉ ...
    CBP-7: Cài Đặt Phản Ứng Cho Entity
    Thiết lập phản ứng của Entity nhằm điều khiển các Component trong CBP.
    Khi bạn nhấn vào liên kết sản phẩm do STDIO đề xuất và mua hàng, STDIO có thể nhận được hoa hồng. Điều này hỗ trợ STDIO tạo thêm nhiều nội dung hữu ích. Tìm hiểu thêm.
    STDIO

    Trang chính

    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    Đã thông báo Bộ Công Thương
    ©STDIO, 2013 - 2020