Giới thiệu
Bài viết trước đã hiện thực gần hoàn chỉnh CAnimation
và đã kiểm tra được hoạt động của Component này. Bài viết này sẽ thêm hệ thống chỉ lệnh cho CAnimation
và hiện thực dùng chỉ lệnh để điều khiển hình ảnh hiển thị của Entity
.
Tải project mẫu
Đây là project ví dụ dùng trong bài viết: CBP-4-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.
Ý tưởng về hệ thống chỉ lệnh (Command)
Component hoạt động liên tục và song song với các Component khác nhằm điều khiển Entity
, chính vì Entity
không tự quản lý những hoạt động của chính nó, nên để điều khiển một Entity
, không chỉ cần hệ thống điều khiển tầng cao (điều khiển Entity
) mà còn cần một hệ thống khác, hỗ trợ Entity
điều khiển tầng thấp hơn (các Component). Đây chính là mục đích của hệ thống chỉ lệnh.
Căn cứ vào Entity Command nhận được, Entity
sẽ không gửi hoặc gửi (một hoặc nhiều) Component Command cho (một hoặc nhiều) Component nhằm điều khiển phản ứng của Entity như đã giới thiệu ở CBP-2.
Hiện thực Command
Thực chất Command chỉ là một loại mã đánh dấu một hoạt động của Component, nên tính chất của Command chỉ cần phân biệt được với các Command khác là đủ. Ta có thể sử dụng macro, enum hoặc bất kỳ kiểu khai báo nào khác để có một hệ thống Command gồm những mã phân biệt là được, vì thực chất vẫn phải xử lý từng loại Command một cách riêng biệt trong phương thức commandProcess
.
Tuy nhiên, nếu xây dựng tập trung thì hệ thống Command có thể sẽ phình lên và rất khó kiểm soát khi ta có quá nhiều Component, thêm nữa là Developer cũng sẽ không biết có thể gửi Command nào cho đúng. Chính vì vậy, ta cũng cần có cách thông báo cho Developer biết là có thể gửi những Command nào cho Component mà họ muốn thao tác.
Cách của tôi là khai báo những Command mà Component sẽ sử dụng bên trong khai báo lớp của Component đó, và dựa theo hệ thống gợi ý của IDE để làm việc.
Phân tích mẫu các Component Command cho CAnimation
CAnimation
theo tôi xử lý một số Command cơ bản như sau:
- Thay đổi hình ảnh hiển thị của
Entity
. - Lật ảnh (trái-phải hoặc trên-dưới).
- Ẩn/hiện hình ảnh.
Ngoài ra nếu có nhu cầu, CAnimation
có thể có thêm một số Command đặc biệt nữa như hiệu ứng thu phóng, fade in/fade out, ... cơ chế CBP rất dễ nâng cấp, nên không cần phải lường trước những điều này.
Trong phạm vi bài viết chỉ hiện thực Command ẩn/hiện hình ảnh.
Khai báo COMMAND cho CAnimation
Thêm COMMAND
cho CAnimation
và có được một khai báo như sau:
class CAnimation : public CBase { public: enum COMMAND { CHANGE_ANIMATION = 0, FACE_LEFT, FACE_RIGHT, SHOW, HIDE }; static CAnimation* addComponentTo(Entity* object, const char* resourcePath); virtual int update(float deltaTime); virtual int commandProcess(int command); int draw(); private: CAnimation(); ~CAnimation(); private: vector<FTexture*> m_animate; unsigned int m_currentAnimationIndex; };
Ở đây tôi chỉ tạm đặt Command CHANGE_ANIMATION
để thay đổi hình ảnh hiển thị, thực chất một Entity
có thể có nhiều hơn 2 loại hoạt ảnh, lúc này ta có thể thay đổi cấu trúc của COMMAND
lại cho phù hợp.
Mục đích của enum COMMAND
là khi sử dụng ở tầng development sẽ gọi theo dạng như sau:
componentAnimation->commandProcess(CAnimation::COMMAND::CHANGE_ANIMATION);
Nên ta có thể tùy nghi sử dụng enum, struct, class… để định nghĩa COMMAND
cho từng class Component, miễn sao đảm bảo được cách gọi Command để truyền vào phương thức commandProcess
như trên.
Nâng cấp COMMAND
một chút cho cách tạo một hệ thống Command dùng cho nhiều animation, định nghĩa như sau:
struct COMMAND { enum CHANGE_ANIMATION_TO { STAND = 0, MOVE, JUMP, HIT, NUM_OF_ANIMATION_INDEX }; enum { FACE_LEFT = CHANGE_ANIMATION_TO::NUM_OF_ANIMATION_INDEX, FACE_RIGHT, SHOW, HIDE }; };
Lúc này cách gọi Command có dạng:
componentAnimation->commandProcess(CAnimation::COMMAND::CHANGE_ANIMATION_TO::STAND); componentAnimation->commandProcess(CAnimation::COMMAND::FACE_LEFT);
Khi sử dụng có thể dựa vào hệ thống gợi ý của IDE để gọi Command. Tuy nhiên do framework còn khá đơn giản và chỉ giới thiệu về CBP là chính nên bài viết không sử dụng cấu trúc này cho ví dụ.
Nên suy nghĩ trước về hệ thống Command của từng Component để đưa ra cấu trúc hợp lý cho COMMAND
, điều này không những giúp dễ dàng xử lý các chỉ lệnh trong commandProcess
mà còn giúp cho câu chỉ lệnh đưa vào hàm trở nên có nghĩa. Như ví dụ trên dễ dàng dịch được nghĩa của chỉ lệnh CAnimation command: change animation to stand ở ngôn ngữ giao tiếp hằng ngày.
Thử nghiệm xử lý Component Command
Sau đây là hiện thực và thử nghiệm Command SHOW
và HIDE
của CAnimation
:
Cần định nghĩa xử lý của CAnimation
đối với 2 Command này trong CAnimation::commandProcess
:
int CAnimation::commandProcess(int command) { switch (command) { case CAnimation::COMMAND::HIDE: m_visible = false; break; case CAnimation::COMMAND::SHOW: m_visible = true; break; // Other commands default: break; } return 0; }
Tại đây xuất hiện một thuộc tính mới: m_visible
, đây là thuộc tính kiểu bool tôi định nghĩa thêm cho CAnimation
nhằm hỗ trợ xử lý cho 2 Command SHOW
và HIDE
.
Chỉ set giá trị cho m_visible
thôi là chưa đủ, cần xử lý thao tác vẽ dựa trên thuộc tính này thì SHOW
và HIDE
mới thực sự hoạt động. Thay đổi phương thức draw
thành như sau:
int CAnimation::draw(HDC hdc) { if (m_visible) { m_animate.at(m_currentAnimationIndex)->draw(hdc); } return 0; }
Và không quên đặt giá trị mặc định cho m_visible
tại constructor:
CAnimation::CAnimation() { m_typeID = ID::COMPONENT::ANIMATION; m_visible = true; m_animate.clear(); }
Giờ tạo một biến đếm thời gian tại khu vực update
của Entity
để xử lý Command theo chu kỳ. Lưu ý rằng đây chỉ là code tạm nhằm thử nghiệm hệ thống Command, không được trực tiếp gọi phương thức commandProcess
cho các Command mới.
if (s_timer >= 1.0f) { m_components.at(0)->commandProcess(CAnimation::COMMAND::HIDE); } if (s_timer >= 2.0f) { m_components.at(0)->commandProcess(CAnimation::COMMAND::SHOW); s_timer = 0.0f; }
Lúc này chỉ có duy nhất CAnimation
trong Entity
, nên có thể trực tiếp gọi đến Component này trong danh sách tại index 0. Và như vậy, kết quả mong đợi là logo của STDIO sẽ chớp tắt với chu kỳ 2 giây.
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ù