Lập trình như tự đá vào mặt mình, sớm hay muộn mũi bạn cũng sẽ chảy máu. Jim McCarthy
STDIO Hiện thực hàng đợi chỉ lệnh cho Entity và Component, nâng cấp phương thức update. Đây là một phần trong hệ thống do tôi sáng tạo dựa trên tư tưởng của Component Base Programming (CBP). Toàn bộ hệ thống được trình bày trong chuỗi gồm 10 bài viết với tiền tố CBP.
Nội dung bài viết

Giới thiệu

Sau CBP-4, chúng ta đã hiện thực được hệ thống chỉ lệnh và phương thức xử lý chỉ lệnh, trong bài viết này, chúng ta sẽ tiến hành chuẩn hóa thao tác nhận và xử lý chỉ lệnh của Component.

Đây là project ví dụ dùng trong bài viết: CBP-6_Example.

Project được khởi tạo trên nền Visual Studio 2013, sử dụng Fred Framework do tôi định nghĩa, độc giả không cần cài thêm DirectX hoặc OpenGL để sử dụng, đây là framework làm việc với win32.

Tiền đề bài viết

Đây là bài viết thứ 6 trong chuỗi bài viết của tôi về CBP thuộc STDIO :: www.stdio.vn.

Đối tượng hướng đến

Các lập trình viên:

  • Mong muốn tham khảo một phương pháp hiện thực của CBP.
  • Có kiến thức nền tảng tương đối tốt về C++.
  • Nắm được tư duy của CBP.
  • Đã nắm được cấu trúc project ở bài CBP-4.

Các lập trình viên khác có thể sử dụng làm tài liệu tham khảo.

Danh mục các bài viết CBP

Các bài viết liên quan

Vấn đề cần chỉnh sửa từ ví dụ ở CBP-4

Nếu đã từng làm game, hẳn độc giả sẽ biết một 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 một số trường hợp không mong muốn, thâm chí gây ra lỗi game.

Đơn cử một 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 ta ấn cả 2 phím cùng lúc thì sự kiện ấ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ữ một 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 đơn giản nhất là lọc các hoạt động bị thừa trong cùng một vòng lặp, và để có thể lọc thì ta cần một khu vực chứa các sự kiện xảy đến với thực thể, và tôi quyết định 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 tôi 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 Entity và Component, 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 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ủa chúng ta có những thay đổi như sau:

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;
};
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;
}
// 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 chúng ta đã có một 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 ta sẽ 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 một 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 một 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:

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;
};
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;
}
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

Qua bài viết này, chúng ta đã hoàn thành Command Queue cho Entity và Component, tạo được kênh giao tiếp với Entity. Bài viết sau, chúng ta 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ạn cần hỗ trợ các dự án kết nối không dây?

Quí doanh nghiệp, cá nhân cần hỗ trợ, hợp tác các dự án IoT, kết nối không dây. Vui lòng liên hệ, hoặc gọi trực tiếp 0942.111912.

  • TỪ KHÓA
  • Arduino
  • ESP32
  • ESP8266
  • Wifi
  • Bluetooth
  • Zigbee
  • Raspberry Pi
THẢO LUẬN
ĐÓNG