Tôi không quan tâm nếu nó hoạt động trên bộ máy của bạn ! Chúng tôi không vận chuyển chúng. Ted Nelson
STDIO Bài viết giới thiệu một mảng mới trong hệ thống CBP do tôi định nghĩa: Component ra lệnh - các Component có khả năng gửi Entity Command cho Entity sở hữu nó. Bao gồm 2 thành phần chính là các Component về thiết bị điều khiển và AI.
Nội dung bài viết

Giới thiệu

Cho đến CBP-7, chúng ta đã cùng nhau đi qua rất nhiều nội dung, hiện thực Component, hiện thực hàng đợi chỉ lệnh, xây dựng cơ chế xử lý chỉ lệnh, phân biệt Entity Command và Component Command, xây dựng phản ứng cho Entity… Tuy nhiên thực tế độc giả có thể thấy: chúng ta vẫn đang sử dụng những Entity Command thử nghiệm được gửi trực tiếp từ vòng lặp game, Entity thử nghiệm của chúng ta cũng chưa thật sự cá biệt, vì nếu xây dựng 2 Entity, chúng sẽ xử lý các Entity Command như nhau, dù phản ứng của mỗi Entity có thể khác.

Nội dung bài viết này, chúng ta sẽ thực hiện bước cuối cùng trong khâu “lập trình Entity” – định nghĩa Component ra lệnh.

Nói nôm na, đây cũng là một bước trong khâu “lập trình Entity”, ở CBP-7, chúng ta lập trình phản ứng tương ứng cho Entity Command, còn ở bài này, chúng ta sẽ lập trình phản ứng cho sự kiện điều khiển của người chơi.

Đây là project ví dụ dùng trong bài viết: CBP-8_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ứ 8 của tôi trong chuỗi bài viết 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 của hệ thống ở CBP-2.

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

Tổng quan tư tưởng

Trước hết tôi muốn khẳng định: thứ mà chúng ta sắp hiện thực là một Component, một Component có Command, get/ set Property hẳn hoi, và vì tính chất đòi hỏi kiểm tra và xử lý liên tục của một bộ điều khiển / AI, Component ra lệnh cần có phương thức update.

Tôi chia Component ra lệnh thành 2 loại:

  • Controller: thao tác với các thiết bị vật lý nhằm nhận lệnh điều khiển từ người dùng.
  • Artificial Intelligence (AI): tự động tính toán và gửi chỉ lệnh cho Entity theo cơ chế được lập trình sẵn.

(Trong nội dung bài viết, tôi chỉ hiện thực Controller, phần AI độc giả có thể tự do sáng tạo hoặc hiện thực như tôi đã làm với phản ứng của Entity – gọi hàm định nghĩa ngoài – kết hợp với cách update của Controller sau đây).

Điểm chung:

  • Là Component được phép gửi Entity Command cho Entity sở hữu nó.
  • Được thiết đặt khi khởi tạo Entity.
  • Có thể thay đổi trong quá trình hoạt động.

CController là dạng đơn giản, chúng ta có thể hiện thực tương tự như đã làm với phản ứng của Entity, tuy nhiên điểm khác biệt là CController không phản ứng dựa theo Entity Command mà phản ứng dựa theo trạng thái của thiết bị điều khiển (bàn phím, chuột, gamepad…), và CController gửi đi Entity Command chứ không phải Component Command.

CAI phức tạp hơn vì cần định nghĩa xử lý cụ thể cho tất cả các trường hợp có thể xảy ra với Entity để đảm bảo sự ổn định khi hoạt động. Tùy vào độ phức tạp của project mà lập trình viên có thể định nghĩa CAI thích hợp, đối với tôi, CAI chia thành 2 loại nhỏ:

  • CAI toàn năng: là loại Component định nghĩa toàn bộ hệ thống AI trong nội bộ Component. Developer chỉ (và chỉ được) nhập dữ liệu tại phương thức addComponentTo để đặc tả hoạt động. AI loại này thường không linh hoạt và kém đa dạng, thích hợp cho các tựa game RPG, Adventure, Puzzle có cơ chế AI đơn giản. Trong một số trường hợp cá biệt, Programmer có thể xây dựng một Component AI “khổng lồ”, thậm chí xây dựng cả một cấu trúc file mới (tương tự XML chẳng hạn) để định nghĩa ra những AI đủ sức xử lý những vấn đề phức tạp thì đây sẽ là một sự hỗ trợ lớn cho Developer, tuy nhiên họ cũng cần tốn thời gian giải thích trọn vẹn về cơ chế mà họ thiết kế cho Developer trước khi được đưa vào sử dụng, chưa kể đến những vấn đề về logic có thể xảy ra (tôi đã từng làm như vậy, và theo kinh nghiệm của tôi thì độc giả không nên thử).
  • CAI tùy biến: là loại Component cho phép Developer tự định nghĩa cơ chế hoạt động của AI thông qua Entity được cung cấp vào phương thức AI (sử dụng con trỏ hàm tương tự như phản ứng của Entity). Bằng cách này, Developer có thể linh hoạt cơ chế làm việc cho Entity, loại Component này thường kết hợp với những Component khác có chức năng chứa thuộc tính cho Entity (phạm vi hoạt động/ update/ kích hoạt…). Programmer cũng có thể cung cấp sẵn một số cơ chế AI đơn giản (di chuyển đơn giản, rượt đuổi…) để sử dụng cho Component này.

CController

Phân tích

Đây là Component đại diện cho bộ điều khiển của người dùng. Như đã đề cập, Component này khá đơn giản, chúng ta chỉ cần một bộ “map” sự kiện phím – Entity command là được.

Trong trường hợp cần nhiều chức năng hơn, độc giả có thể nâng cấp thêm phần thực thi hàm theo Command, ứng dụng con trỏ hàm như tôi đã làm với phản ứng của Entity.

Đó là về cơ chế hoạt động, về Command, tôi cho rằng các Component ra lệnh bao gồm 2 chức năng cơ bản là “tạm dừng” / “tiếp tục”, các chỉ lệnh này cho biết khi nào thì người dùng được phép điều khiển hay AI được phép hoạt động, đây là 2 chỉ lệnh cơ bản và cần thiết, tùy vào từng tựa game cụ thể mà chúng ta có thể phát triển thêm những chỉ lệnh khác.

Tổng hợp các phân tích trên, tôi khai báo CController như sau:

class CController : public CBase
{
public:
	typedef pair<int, int> Keycode_Command_Pair;

	class PropertyFlag : public CBase::PropertyFlag
	{
	public:
		enum
		{
			COMMAND_MAP = NUM_OF_CBASE_PROPERTY_FLAG,
			NUM_OF_CCONTROLLER_PROPERTY_FLAG
		};
	};

	enum COMMAND
	{
		PAUSE = 0,
		RESUME
	};

static CController* addComponentTo(Entity* object);
static	CController* addComponentTo(Entity* object, map<int, int> commandMap);

	int update(float deltaTime);
	virtual int commandProcess(int command);
	virtual ComponentProperty	getProperty(int propertyFlag);

	// To add or remove a control key.
	// pass command as CBase::COMMAND::NULL_COMMAND to remove control key.
	virtual int	setProperty(ComponentProperty propertyData, int propertyFlag) 
    throw(ComponentProperty::InvalidPointerType);

	// To add a new control key.
	// @param keyCode: Virtual keycode of the key.
	//		it's marked as VK_<Keyname>
	//		if it's numpad number: VK_NUMPAD<number>
	//		if it's alphabet or number key then pass UPPER CASE char: 'S'
	// @param command: Entity Command bind with the key.
	int addKey(int keyCode, int command);

	// To erase all control action bind with the key.
	// @param keyCode: Virtual keycode of the key.
	//		it's marked as VK_<Keyname>
	//		if it's numpad number: VK_NUMPAD<number>
	//		if it's alphabet or number key then pass UPPER CASE char: 'S'
	int eraseKey(int keyCode);

private:
	CController();
	~CController();

private:
	map<int, int> m_CommandMap;
	bool	m_isPaused;
};

Tôi đặc tả cho CController có thêm phương thức addKey eraseKey giúp Developer có thể tùy chỉnh keymap dễ dàng hơn khi khởi tạo.

Hai phương thức trên tất nhiên có thể dùng sau khi khởi tạo, tuy nhiên để giữ cho cấu trúc Component không bị sai lệch, tôi overload lại bộ phương thức get/ set Property cho phù hợp.

Định nghĩa duy nhất cần chú ý duy nhất của Component này đó là phương thức update:

int CController::update(float deltaTime)
{
	CBase::update(deltaTime);

	if (!m_isPaused)
		for each (Keycode_Command_Pair commandPair in m_CommandMap)
			if (FKeyboard::isKeyDown(commandPair.first))
				m_owner->pushCommand((Entity::COMMAND)commandPair.second);
}

Dù là phần được chú ý nhất, nhưng cách hiện thực vẫn vô cùng đơn giản. Chỉ cần gọi update của CBase để xử lý những Component Command được nhận được trong vòng lặp trước, sau đó kiểm tra từng phím được thiết lập trong keymap xem chúng có được ấn không thì gửi đi Entity Command tương ứng cho Entity sở hữu nó.

Lưu ý

Các phím điều khiển giao diện người dùng (đóng/ mở thùng đồ, ẩn/ hiện thông tin nhân vật, quái vật…) thuộc về Entity UI, các UI này lấy thông tin từ Entity nhân vật.

Cài đặt CController và thử nghiệm

Để thử nghiệm CController, tôi có một số thay đổi về mã nguồn như sau, toàn bộ đều thuộc main.cpp:

void init(HINSTANCE instance, HWND handler)
{
	...
	auto controller = CController::addComponentTo(g_entity);

	...
	controller->addKey('S', Entity::COMMAND::SHOW);
	controller->addKey('H', Entity::COMMAND::HIDE);
}
void update(float delta)
{
	g_entity->update(delta);
}

Điều này có nghĩa là Logo STDIO sẽ không tự động ẩn hiện và nhảy lung tung nữa, mà sẽ hoạt động theo phím điều khiển SH.

Lời kết

Qua bài viết này, tôi đã hiện thực mẫu cho độc giả một Component ra lệnh – thành phần không thể thiếu trong hệ thống CBP do tôi định nghĩa. Ở đây tôi không hiện thực CAI, nhưng tôi tin rằng những độc giả có thể bám trụ đến bài viết này đều có đủ khả năng tự định nghĩa lấy một CAI hoàn chỉnh.

Nếu độc giả cảm thấy cần gợi ý hoặc muốn yêu cầu tôi mở rộng thêm phần định nghĩa CAI, có thể để lại comment phía dưới, tôi sẽ đáp ứng trong thời gian sớm nhất.

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