Giới thiệu
Bài viết CBP-4 đã hiện thực được hệ thống chỉ lệnh và phương thức xử lý chỉ lệnh. Bài viết này tiến hành chuẩn hóa thao tác nhận và xử lý chỉ lệnh của Component
.
Tải project mẫu
Đây là project ví dụ dùng trong bài viết: CBP-6-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.
Vấn đề cần cập nhật từ ví dụ ở CBP-4
Nếu đã từng làm game, độc giả sẽ biết 1 vòng lặp game đều có 2 bước update
và draw
, và các sự kiện về thao tác điều khiển của người chơi có thể đến vào bất cứ lúc nào, cho nên việc gọi trực tiếp phương thức commandProcess
có thể dẫn đến 1 số trường hợp không mong muốn, thậm chí gây ra lỗi game.
Đơn cử 1 trường hợp, giả sử game có 2 phím điều khiển cho việc di chuyển sang trái, nếu nhấn cả 2 phím cùng lúc thì sự kiện nhấn của 2 phím sẽ gọi đến xử lý 2 lần cho việc di chuyển, nếu vận tốc di chuyển thay đổi theo gia tốc thì lúc này nhân vật sẽ gia tốc nhanh hơn bình thường, và nếu giữ 1 phím và nhấp phím còn lại có thể dẫn đến bất hợp lý trong animation của nhân vật.
Giải pháp
Đối với vấn đề này, giải pháp là lọc các hoạt động bị thừa trong cùng 1 vòng lặp, và để có thể lọc thì ta cần 1 khu vực chứa các sự kiện xảy đến với thực thể, sử dụng cơ chế hàng đợi cho hệ thống vì tính có thứ tự của hàng đợi sẽ hữu dụng trong việc lập trình các hoạt động của Entity
cũng như phản ứng của Component
.
Trong bài viết này sẽ không đi đến giải quyết vấn đề cụ thể nêu trên mà chỉ phác thảo nên hệ thống hàng đợi cho các Command trong Entity
và Component
, làm tiền đề cho các xử lý cụ thể mà độc giả có thể ứng dụng.
Hàng đợi chỉ lệnh của Entity
Điểm qua các công việc cần làm để hiện thực Command Queue cho Entity
:
- Thêm thuộc tính đại diện cho Command Queue trong
Entity
. - Thêm bộ phương thức
push
/pop
củaCommand
. - Chỉnh sửa phương thức
update
củaEntity
.
Điểm qua các công việc này, mã nguồn có những cập nhật như sau:
File Entity.h
class Entity { ... public: enum COMMAND { NULL_COMMAND = -1 }; ... // To push an Entity Command into command queue. // Possible pushed command are listed in Entity::COMMAND. // The commands are orderly. // @param command: the command to be pushed in command queue. int pushCommand(Entity::COMMAND command); // To get the next command in queue. // @return: the popped command. NULL_COMMAND which mean the queue was empty. Entity::COMMAND popCommand(); private: ... vector<Entity::COMMAND> m_commandQueue; };
File Entity.cpp
int Entity::pushCommand(COMMAND command) { m_commandQueue.push_back(command); return FSUCCESS; } Entity::COMMAND Entity::popCommand() { if (m_commandQueue.empty()) return NULL_COMMAND; Entity::COMMAND result = m_commandQueue.front(); m_commandQueue.erase(m_commandQueue.begin()); return result; } int Entity::update(float delta) { // Entity Command Processing. while (true) { switch (popCommand()) { case HIDE: { m_components.at(ID::COMPONENT::ANIMATION)->commandProcess(CAnimation::COMMAND::HIDE); continue; } case SHOW: { 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); continue; } default: continue; case NULL_COMMAND: break; } break; } // Component updating. for (Pair_ID_ptrCBase componentPair : m_components) componentPair.second->update(delta); return 0; }
File main.cpp
// UPDATE REGION void update(float delta) { // Testing entity controller. static float s_timer = 0.0f; s_timer += delta; if (s_timer >= 1.0f) { g_entity->pushCommand(Entity::COMMAND::HIDE); } if (s_timer >= 2.0f) { g_entity->pushCommand(Entity::COMMAND::SHOW); s_timer = 0.0f; } // =========================== g_entity->update(delta); }
Nhờ hiện thực Command Queue, nay đã có 1 kênh giao tiếp giữa các thao tác điều khiển, thế giới game với Entity
. Đến thời điểm này, project ví dụ đã có thể hoạt động, tuy nhiên công việc vẫn chưa hoàn thành, tiếp theo hiện thực hàng đợi chỉ lệnh cho Component
.
Hàng đợi chỉ lệnh của Component
Tương tự như Entity
, Component
cũng cần 1 hàng đợi chỉ lệnh của riêng nó để xử lý những thao tác chẳng hạn như kết hợp phím, nhưng mục tiêu chính hướng tới là tất cả các command của Component
đều được xử lý trong cùng 1 vị trí của vòng lặp game.
Hàng đợi chỉ lệnh và thao tác lấy chỉ lệnh, xử lý, là điểm chung của các Component
, nên ta sẽ hiện thực tại CBase
, công việc tương tự như đối với Entity
.
Các thay đổi trong mã nguồn như sau:
File Base.h
class CBase { public: ... enum COMMAND { NULL_COMMAND = -1 }; ... // To push an Component Command into command queue. // Possible pushed command are listed in each <Component class name>::COMMAND. // The commands are orderly. // @param command: the command to be pushed in command queue. int pushCommand(int command); // To get the next command in queue. // @return: the popped command. NULL_COMMAND which mean the queue was empty. int popCommand(); protected: ... vector<int> m_commandQueue; };
File Base.cpp
int CBase::update(float deltaTime) { while (true) { int command = popCommand(); if (command == CBase::COMMAND::NULL_COMMAND) break; commandProcess(command); } return 0; } int CBase::pushCommand(int command) { m_commandQueue.push_back(command); return FSUCCESS; } int CBase::popCommand() { if (m_commandQueue.empty()) return CBase::COMMAND::NULL_COMMAND; int result = m_commandQueue.front(); m_commandQueue.erase(m_commandQueue.begin()); return result; }
File Entity.cpp
int Entity::update(float delta) { ... m_components.at(ID::COMPONENT::ANIMATION)->pushCommand(CAnimation::COMMAND::HIDE); ... m_components.at(ID::COMPONENT::ANIMATION)->pushCommand(CAnimation::COMMAND::SHOW); ... }
Điểm lớn nhất cần lưu ý ở đây là phương thức update
của CBase
đã được định nghĩa để lấy command trong danh sách và xử lý, nên nếu độc giả muốn overload lại phương thức này để xử lý cho các Component
khác thì phải gọi phương thức update
của CBase
ở đầu phương thức update
của Component
khác.
Tổng kết
Bài viết này đã hoàn thành Command Queue cho Entity
và Component
, tạo được kênh giao tiếp với Entity
. Bài viết sau sẽ khai thác Command Queue, tạo ra các phản ứng cho Entity
và bước đầu lập trình cho Entity
.
Bài chung series
- CBP-0: Giới Thiệu về Component Base Development
- CBP-1: Tổng Quan về Project Ví Dụ
- CBP-2: Khai Báo Component Cơ Sở và Entity Cơ Bản
- CBP-3: Khai Báo Component Cơ Bản, Tích Hợp Component vào Entity
- CBP-4: Giao Tiếp với Entity – Hệ Thống Chỉ Lệnh
- CBP-5: Truyền và Lấy Thông Số từ Component
- CBP-6: Hàng Đợi Chỉ Lệnh
- CBP-7: Cài Đặt Phản Ứng cho Entity
- CBP-8: Component Điều Khiển và AI – Component Ra Lệnh
- CBP-9: Bộ Khởi Tạo Entity – Factory và Hệ Thống ID
- CBP-10: Hệ Thống Quản Lý Tập Trung Các Component Đặc Thù