Giới thiệu
Bài viết hướng dẫn độc giả hiện thực hệ thống cài đặt phản ứng cho Entity
. Đây là nhân tố quyết định, giúp các lập trình viên có thể dễ dàng lập trình các Entity
như ý, hệ thống cài đặt phản ứng phong phú, chi tiết cũng có thể giúp tạo ra được rất nhiều loại Entity
khác nhau.
Tải project mẫu
Đây là project ví dụ dùng trong bài viết: CBP-7-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.
Thế nào là phản ứng của Entity?
Phản ứng của Entity
là cách gọi những thao tác xử lý của Entity
đối với Command
mà nó nhận được. Có thể là thao tác pushCommand
cho 1 Component
, có thể là thực thi 1 hàm do lập trình viên định nghĩa.
Phản ứng của Entity
được cài đặt lúc khởi tạo Entity
và Entity
sẽ xem đó là cơ chế hoạt động trong suốt thời gian mà nó tồn tại. Ví dụ:
- Khi nhấn 1 phím di chuyển,
Entity
sẽ nhận đượcEntity
Command
điều khiển di chuyển. Căn cứ vàoCommand
này,Entity
sẽ điều khiểnComponent
hoạt ảnh, đổi hoạt ảnh sang di chuyển, chuyển hướng Animation sang hướng di chuyển (lật sang trái, sang phải), điều khiểnComponent
di chuyển, gia tốc choEntity
. Đây là 1 bộ phản ứng củaEntity
, sử dụng Component Command. - Khi
Entity
rơi vào trạng thái chết,Component
hoạt ảnh sẽ chuyển hoạt ảnh củaEntity
cho phù hợp, bộ xử lý hiệu ứng thêm hiệu ứng quanh khu vựcEntity
(nếu có),Entity
được đưa ra khỏi bộ update bình thường để sẵn sàng hủy khi có thể. Đây là 1 bộ phản ứng vừa sử dụngCommand
, vừa sử dụng phương thức được định nghĩa ngoài.
Phân loại phản ứng
Phản ứng sử dụng Component Command
Đây là kiểu phản ứng đơn giản đang sử dụng cho phương thức update
của Entity
. Kết quả của phản ứng này là push 1 Component Command vào danh sách Command của 1 Component xác định. Dạng phản ứng này thường dùng để định nghĩa logic cơ bản của đối tượng.
Phản ứng sử dụng phương thức tự định nghĩa
Đây là kiểu phản ứng ứng dụng con trỏ hàm, các hàm phản ứng do lập trình viên định nghĩa có dạng trả về là void
và đối số là 1 con trỏ đến Entity
phản ứng. Với dạng này, lập trình viên dễ dàng thao tác với những phương thức được định nghĩa trong Entity
như get
/set
Property hoặc lấy Entity
phản ứng làm đối số truyền vào những hàm khác (như khi hủy Entity
). Dạng phản ứng này thường được dùng để định nghĩa các thao tác xử lý như va chạm, cổng dịch chuyển, ...
Cài đặt phản ứng
Phương thức commandProcess
cho Entity
nhằm tổng quát hóa quá trình xử lý Entity Command. Bước này tối ưu xử lý cho danh sách phản ứng với std::map.
Với sự hỗ trợ của 2 struct mới: ReactCommand
và ReactFunction
, có thể lưu trữ các phản ứng dưới dạng mảng 1 chiều, đối với các phản ứng sử dụng phương thức được định nghĩa ngoài, dùng con trỏ hàm để giải quyết, mọi chuyện thật sự rất đơn giản:
- So sánh
Command
nhận được vớiCommand
khóa. - Nếu trùng với
Command
khóa thì pushCommand
phản ứng vàoComponent
có ID tương ứng hoặc thực thi phương thức đã định. Nếu không thì bỏ qua.
File Entity.h
class Entity { ... public: typedef void(*EntityReactFunction)(Entity* reactor); ... struct ReactCommand { Entity::COMMAND command; ID::COMPONENT componentID; int reactCommand; // @param _command: The Entity Command cause this react. // @param _componentID: The Component ID mark the component get react. // @param _reactCommand: The given command. ReactCommand(Entity::COMMAND _command, ID::COMPONENT _componentID, int _reactCommand) { command = _command; componentID = _componentID; reactCommand = _reactCommand; } }; struct ReactFunction { Entity::COMMAND command; EntityReactFunction reactFunction; // @param _command: The Entity Command cause this react. // @param _reactCommand: The . ReactFunction(Entity::COMMAND _command, EntityReactFunction _reactFunction) { command = _command; reactFunction = _reactFunction; } }; ... // To process the received Entity Command. // @param: the processing command. int commandProcess(Entity::COMMAND command); private: ... vector<ReactCommand> m_reactCommands; vector<ReactFunction> m_reactFunctions; };
File Entity.cpp
int Entity::commandProcess(COMMAND command) { for (ReactCommand react : m_reactCommands) { if (command == react.command) m_components.at(react.componentID)->pushCommand(react.reactCommand); } for (ReactFunction react : m_reactFunctions) { if (command == react.command) react.reactFunction(this); } return FSUCCESS; }
Vậy là hệ thống phản ứng của Entity
đã hoàn chỉnh, việc còn lại là định nghĩa phương thức thêm phản ứng vào danh sách phản ứng.
File Entity.h
class Entity { ... public: ... // To add new react to this Entity. // IMPORTANT: The reacts are orderly // the ReactFunction(s) are called after ReactCommand(s). // @param react: the new pushed react. int settingReact(ReactCommand react); int settingReact(ReactFunction react); ... };
File Entity.cpp
int Entity::settingReact(ReactCommand react) { m_reactCommands.push_back(react); return FSUCCESS; } int Entity::settingReact(ReactFunction react) { m_reactFunctions.push_back(react); return FSUCCESS; }
File main.cpp
void init(HINSTANCE instance, HWND handler) { FTexture::init(instance, handler); g_entity = new Entity(); CAnimation::addComponentTo(g_entity, "Resource\\logo.bmp"); CPosition::addComponentTo(g_entity, 50, 50); g_entity->settingReact(Entity::ReactCommand(Entity::COMMAND::HIDE, ID::COMPONENT::ANIMATION, CAnimation::COMMAND::HIDE)); g_entity->settingReact(Entity::ReactCommand(Entity::COMMAND::SHOW, ID::COMPONENT::ANIMATION, CAnimation::COMMAND::SHOW)); g_entity->settingReact(Entity::ReactFunction(Entity::SHOW, [](Entity* entity) { ComponentProperty newPos(new Vector2(rand() % 400 + 50, rand() % 400 + 50), ComponentProperty::Type::PTYPE_VECTOR2, true); entity->setProperty(ID::COMPONENT::POSITION, newPos, CPosition::PropertyFlag::PFLAG_POSITION); } )); }
Đây là bước thiết lập phản ứng cho Entity
đã sử dụng cả 2 cách thiết lập, sử dụng ReactCommand
và ReactFunction
. Để rút gọn, trong bước thiết lập ReactFunction
đã sử dụng lambda, có thể định nghĩa phương thức như bình thường và truyền vào hàm dưới dạng con trỏ hàm.
File Entity.cpp
int Entity::update(float delta) { while (true) { Entity::COMMAND command = popCommand(); if (command == NULL_COMMAND) break; commandProcess(command); } // Component updating. for (Pair_ID_ptrCBase componentPair : m_components) componentPair.second->update(delta); return 0; }
Ở bước này xóa đi phần giả lập phản ứng để thử nghiệm ở các bài viết trước, thay vào đó là vòng lặp xử lý Entity Command. Mục đích hướng tới của thử nghiệm này là làm cho project hoạt động giống như ví dụ trước sau tất cả những thay đổi trên.
Tổng kết
Bài viết này là 1 phần rất quan trọng trong hệ thống CBP, những phản ứng này kết hợp với các Component càng đơn giản, càng sơ khai, các Command càng chi tiết thì khả năng tùy biến của lập trình viên đối với Entity
càng cao, tuy nhiên kéo theo đó là bước lập trình Entity càng dài, càng phức tạp, người thiết kế cần có sự đánh giá đúng đắn về project đang làm để cân bằng hợp lý.
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ù