Search…

CBP-4: Giao Tiếp với Entity – Hệ Thống Chỉ Lệnh

15/09/20206 min read
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ỉ lệnh cơ bản để điều khiển CAnimation trong mô hình CBP.

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 enumstruct, 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 SHOWHIDE 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 SHOWHIDE.

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ì SHOWHIDE 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

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024