Search…

CBP-7: Cài Đặt Phản Ứng Cho Entity

15/09/20206 min read
Thiết lập phản ứng của Entity nhằm điều khiển các Component trong CBP.

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 EntityEntity 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 được Entity Command điều khiển di chuyển. Căn cứ vào Command này, Entity sẽ điều khiển Component 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ển Component di chuyển, gia tốc cho Entity. Đây là 1 bộ phản ứng của Entity, 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ủa Entity cho phù hợp, bộ xử lý hiệu ứng thêm hiệu ứng quanh khu vực Entity (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ụng Command, 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: ReactCommandReactFunction, 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:

  1. So sánh Command nhận được với Command khóa.
  2. Nếu trùng với Command khóa thì push Command phản ứng vào Component 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 ReactCommandReactFunction. Để 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

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