Search…

CBP-8: Component Điều Khiển và AI – Component Ra Lệnh

15/09/20208 min read
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.

Giới thiệu

Cho đến CBP-7, đã có nhiều nội dung được thành hình:

  • 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, 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ũng chưa thật sự cá biệt, vì nếu xây dựng 2 Entity, sẽ xử lý các Entity Command như nhau, dù phản ứng của mỗi Entity có thể khác.

Bài viết này 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à 1 bước trong khâu lập trình Entity, ở CBP-7, lập trình phản ứng tương ứng cho Entity Command, còn ở bài này sẽ lập trình phản ứng cho sự kiện điều khiển của người chơi.

Tải project mẫu

Đây là project ví dụ dùng trong bài viết: CBP-8-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 quan tư tưởng

Các tính năng sắp hiện thực là 1 Component, 1 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 1 bộ điều khiển/AI, Component ra lệnh cần có phương thức update.

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 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ư ví dụ đã 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, 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, 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. Lập trình viên 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 1 số trường hợp cá biệt, lập trình viên có thể xây dựng 1 Component AI khổng lồ, thậm chí xây dựng cả 1 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à 1 sự hỗ trợ lớn cho lập trình viên, 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 lập trình viên 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.
  • CAI tùy biến: là loại Component cho phép lập trình viên 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, lập trình viên 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, ...). Lập trình viên cũng có thể cung cấp sẵn 1 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ỉ cần 1 bộ ánh xạ 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, 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, 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;
};

Đặc tả cho CController có thêm phương thức addKeyeraseKey giúp lập trình viên có thể tùy chỉnh keymap dễ dàng hơn khi khởi tạo.

2 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, 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 nhấ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, có 1 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

Bài viết này đã hiện thực mẫu 1 Component ra lệnh – thành phần không thể thiếu trong hệ thống CBP. Bài viết không hiện thực CAI.

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