Search…

CBP-6: Hàng Đợi Chỉ Lệnh

15/09/20206 min read
Chuẩn hóa các thao tác nhận và xử lý chỉ lệnh từ Component trong mô hình CBP.

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 updatedraw, 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 EntityComponent, 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:

  1. Thêm thuộc tính đại diện cho Command Queue trong Entity.
  2. Thêm bộ phương thức push/pop của Command.
  3. Chỉnh sửa phương thức update của Entity.

Đ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 EntityComponent, 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

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