Search…

CBP-9: Bộ Khởi Tạo Entity – Factory và Hệ Thống ID

15/09/20207 min read
Tổ chức lại CBP bằng cách tạo ra Factory, sản xuất Entity dựa vào các ID truyền vào.

Giới thiệu

Hàm init trong file main.cpp được dùng để để đặc tả Entity, hiện tại cũng chỉ có 1 Entity. Thử tưởng tượng hàm init sẽ trở thành thế nào nếu đặc tả tất cả Entity trong một game tại đó? Chỉ tưởng tượng một hàm có thể dài vài ngàn dòng thôi cũng thấy rùng mình.

Lúc này cần một cơ chế thông minh hơn, quy củ hơn để gói gọn các phương pháp khởi tạo Entity, để làm việc đó, tạo ra một lớp quản lý khởi tạo Entity - lớp Factory.

Tải project mẫu

Đây là project ví dụ dùng trong bài viết: CBP-9-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ần 1 hàm có thể tạo ra nhiều loại Entity dựa vào các ID của từng loại Entity truyền vào hàm dựa vào cách thiết kế của Factory Pattern và vì chỉ có một Factory mang chức năng tạo ra các Entity, nên kết hợp thêm Singleton Pattern để tăng tính đúng đắn cho Factory, sau này có thể một số Entity sẽ sinh ra một số Entity khác trong Component của nó – chẳng hạn hoạt động bắn - cần truy cập Factory từ những nơi đó.

Những lưu ý ban đầu

  • Tùy vào thể loại game mà cách hiện thực các phương thức tạo Entity sẽ khác nhau.
    • Đối với những game như RPG, các Entity có xu hướng lặp lại về cơ chế hoạt động, AI, tính chất, chỉ thay đổi về hình ảnh và một số thuộc tính chỉ số. Loại này thường tạo các phương thức khởi tạo chung, 1 phương thức cho nhiều Entity, tất nhiên sẽ cần truyền đối số vào để thay đổi thuộc tính của Entity.
    • Một số game có những Entity rất khác nhau, mỗi hình ảnh là đại diện cho một loại Entity hoàn toàn khác, với AI hoàn toàn riêng biệt (hoặc gần như vậy). Những Entity này đòi hỏi thao tác khởi tạo và thiết lập chi tiết nên các phương thức tạo thường không yêu cầu đối số nữa mà sẽ tạo hẳn luôn một phương thức mới cho mỗi loại Entity. Ban đầu có thể dựa vào việc đọc tập tin lưu trữ thông số Entity và truyền vào phương thức tạo để debug và cân bằng game. Nhưng một khi chắc chắn game đã ổn định thì một số thuộc tính static có thể giúp tăng performance cho game (dù vậy, điều này không thường xảy ra đối với các game có yêu cầu bảo trì, nâng cấp và cân bằng liên tục).
  • Factory #include các Component, trong một số Component lại #include Factory.
  • Hiện thực Factory theo cách thứ 2 có thể tạo ra một file cpp dài vài ngàn, thậm chí vài chục ngàn dòng, điều này cũng bình thường vì cách quản lý từng phương thức, sắp xếp chúng và tận dụng sự hỗ trợ của IDE dễ dàng có được một file mã nguồn đẹp và dễ tìm kiếm. Vấn đề là khi file header của Factory dài hơn 300 dòng, có lẽ cần suy nghĩ việc chia Factory ra nhiều Factory khác, chuyên biệt hơn và tích hợp lại vào Factory chung.
  • Mỗi lần gọi đến phương thức tạo Entity của Factory, nếu ID truyền vào là hợp lệ, một Entity sẽ được tạo ra, và được đưa về nhờ vào kiểu trả về của phương thức. Các Entity này không liên quan gì đến các Entity đã được tạo trước đó.

Entity ID

Đây là chìa khóa cho việc sản xuất Entity. Factory dựa theo ID được truyền vào để trả về Entity phù hợp.

Nghe có vẻ phức tạp, thực chất, đây cũng chỉ là một danh sách các ID được định nghĩa theo kiểu enum được đặt trong ID::Entity. Nhưng có một điều cần lưu ý là danh sách ID có thể rất dài, tùy vào độ phức tạp của project, có thể là 50, cũng có thể là 500, vì thế để có được một sự quản lý tốt nhất, tiện lợi cho việc tìm kiếm, thêm vào, xóa bỏ những ID, nên sắp xếp tên của ID theo bảng chữ cái alphabet, và các phương thức tương ứng trong Factory cũng thế.

Hiện thực Factory

Với những lưu ý trên, khai báo lớp Factory như sau:

class Factory
{
public:
	// To create and return an Entity.
	// @param entityID: The ID to specify the Entity gonna create.
	Entity* create(ID::Entity entityID);

	static Factory* getInstance();

	// Must be call before use this Factory.
	static void init();

	// Call to release this Factory.
	static void release();

private:
	Factory();
	~Factory();

	// The list of method used to create each type of Entity.
	// Sorted by A->Z.
	Entity* create_STDIO_LOGO();
	Entity* create_STDIO_LOGO_CONTROLABLE();

private:
	static Factory* m_instance;
};

Các phương thức init, release, getInstancem_instance được dùng để hiện thực Singleton cho Factory.

Nhằm đảm bảo sự đơn giản và dễ hiểu, sử dụng những phương thức và thuộc tính này như một quy tắc riêng, một phần khác cũng để cho toàn bộ nhóm làm việc biết được có những lớp Singleton nào trong project thông qua danh sách các lớp được gọi init không đối ở đầu chương trình.

Tại create, cho 1 cấu trúc switch gọi phương thức tương ứng với ID::Entity được truyền vào, các phương thức được gọi có tên tương ứng với tên của ID, và được định nghĩa trong tầm vực private, các phương thức được sắp xếp tăng dần theo chuỗi ký tự của tên phương thức.

Chi tiết hiện thực từng phương thức tạo Entity cụ thể tương tự như hàm init tại main.cpp, hay chính xác hơn là chuyển chúng vào đây.

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

Có một số thay đổi tại mã nguồn như sau:

File main.cpp

Entity* g_entity1;
Entity* g_entity2;
void update(float delta)
{
	g_entity1->update(delta);
	g_entity2->update(delta);
}
void draw(HDC hdc)
{
	g_entity1->draw(hdc);
	g_entity2->draw(hdc);
}
void init(HINSTANCE instance, HWND handler)
{
	FTexture::init(instance, handler);
	Factory::init();

	g_entity1 = Factory::getInstance()->create(ID::STDIO_LOGO);
	g_entity2 = Factory::getInstance()->create(ID::STDIO_LOGO_CONTROLABLE);
}
void release()
{
	FTexture::release();
	Factory::release();

	SAFE_DELETE(g_entity1);
	SAFE_DELETE(g_entity2);
}

Kết quả là 2 logo STDIO, 1 cái nằm yên tại vị trí (250, 250), còn 1 cái nhảy ngẫu nhiên khi nhấn phím S, biến mất khi nhấn phím H.

Thực ra Factory không được gọi trong init, đối với 1 game mà nói, Factory cần được gọi trong file cpp của map, khi đang load map. Đối với ví dụ nhỏ này không hiện thực phần map, tuy nhiên vẫn mong độc giả lưu ý, tránh khởi tạo quá nhiều các Entity trong init "để dùng dần".

Tổng kết

Bài viết này hiện thực thêm một lớp hỗ trợ cho lập trình viên trong quá trình khởi tạo Entity, thực ra đây chỉ là một cấu trúc nhằm gói gọn các khởi tạo của Entity trong quá trình sử dụng, về căn bản phần định nghĩa từng Entity không thay đổi.

Đây chỉ là bề nổi của một Factory, khi tiến vào những project phức tạp hơn, độc giả có thể gặp những trường hợp khác, như khởi tạo sao chép Entity (Entity tạo ra không chỉ giống về chủng loại, mà cả các chỉ số khác cũng giữ nguyên so với đối tượng sao chép, ví dụ như HP, MP, vị trí, trạng thái, ...).

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