Câu trả lời là luôn luôn sử dụng một mảng hoặc std :: vector. Các loại như danh sách được liên kết hoặc std :: map thường hoàn toàn khủng khiếp trong các trò chơi và điều đó chắc chắn bao gồm các trường hợp như bộ sưu tập các đối tượng trò chơi.
Bạn nên lưu trữ các đối tượng (không phải con trỏ tới chúng) trong mảng / vector.
Bạn muốn bộ nhớ tiếp giáp. Bạn thực sự thực sự muốn nó. Việc lặp lại bất kỳ dữ liệu nào trong bộ nhớ không liền kề sẽ tạo ra rất nhiều lỗi nhớ cache nói chung và loại bỏ khả năng trình biên dịch và CPU thực hiện tìm nạp trước bộ đệm hiệu quả. Điều này một mình có thể giết chết hiệu suất.
Bạn cũng muốn tránh phân bổ bộ nhớ và thỏa thuận. Chúng rất chậm, ngay cả với bộ cấp phát bộ nhớ nhanh. Tôi đã thấy các trò chơi tăng gấp 10 lần FPS chỉ bằng cách loại bỏ vài trăm phân bổ bộ nhớ cho mỗi khung hình. Có vẻ như nó không tệ đến thế, nhưng nó có thể.
Cuối cùng, hầu hết các cấu trúc dữ liệu mà bạn quan tâm để quản lý các đối tượng trò chơi có thể được gắn hiệu quả hơn nhiều trên một mảng hoặc một vectơ so với chúng có thể với một cây hoặc một danh sách.
Ví dụ, để xóa các đối tượng trò chơi, bạn có thể sử dụng trao đổi và pop. Dễ dàng thực hiện với một cái gì đó như:
std::swap(objects[index], objects.back());
objects.pop_back();
Bạn cũng có thể đánh dấu các đối tượng là đã xóa và đưa chỉ mục của chúng vào danh sách miễn phí cho lần tiếp theo bạn cần tạo một đối tượng mới, nhưng thực hiện trao đổi và pop là tốt hơn. Nó cho phép bạn thực hiện một vòng lặp đơn giản trên tất cả các đối tượng sống mà không phân nhánh ngoài vòng lặp. Đối với tích hợp vật lý đạn và tương tự, đây có thể là một hiệu suất tăng đáng kể.
Quan trọng hơn, bạn có thể tìm thấy các đối tượng với một cặp tra cứu bảng đơn giản từ một duy nhất ổn định đang sử dụng cấu trúc bản đồ vị trí.
Các đối tượng trò chơi của bạn có một chỉ mục trong mảng chính của chúng. Chúng có thể được tra cứu rất hiệu quả chỉ với chỉ số này (nhanh hơn nhiều so với bản đồ hoặc thậm chí là bảng băm). Tuy nhiên, chỉ mục không ổn định do hoán đổi và bật khi xóa đối tượng.
Một bản đồ vị trí yêu cầu hai lớp gián tiếp, nhưng cả hai đều là các tra cứu mảng đơn giản với các chỉ số không đổi. Họ rất nhanh . Rất nhanh.
Ý tưởng cơ bản là bạn có ba mảng: danh sách đối tượng chính, danh sách chỉ định của bạn và danh sách miễn phí cho danh sách gián tiếp. Danh sách đối tượng chính của bạn chứa các đối tượng thực tế của bạn, trong đó mỗi đối tượng biết ID duy nhất của riêng nó. ID duy nhất bao gồm một chỉ mục và thẻ phiên bản. Danh sách chỉ định đơn giản là một mảng các chỉ mục cho danh sách đối tượng chính. Danh sách miễn phí là một chồng các chỉ số vào danh sách gián tiếp.
Khi bạn tạo một đối tượng trong danh sách chính, bạn sẽ tìm thấy một mục không được sử dụng trong danh sách không xác định (sử dụng danh sách miễn phí). Mục trong danh sách chỉ định chỉ đến mục không được sử dụng trong danh sách chính. Bạn khởi tạo đối tượng của mình ở vị trí đó và đặt ID duy nhất của nó thành chỉ mục của mục nhập danh sách không xác định mà bạn đã chọn và thẻ phiên bản hiện có trong thành phần danh sách chính, cộng với một.
Khi bạn phá hủy một đối tượng, bạn thực hiện trao đổi và bật như bình thường, nhưng bạn cũng tăng số phiên bản. Sau đó, bạn cũng thêm chỉ mục danh sách không xác định (một phần của ID duy nhất của đối tượng) vào danh sách miễn phí. Khi di chuyển một đối tượng như là một phần của trao đổi và pop, bạn cũng cập nhật mục nhập của nó trong danh sách chỉ định đến vị trí mới của nó.
Ví dụ mã giả:
Object:
int index
int version
other data
SlotMap:
Object objects[]
int slots[]
int freelist[]
int count
Get(id):
index = indirection[id.index]
if objects[index].version = id.version:
return &objects[index]
else:
return null
CreateObject():
index = freelist.pop()
objects[count].index = id
objects[count].version += 1
indirection[index] = count
Object* object = &objects[count].object
object.initialize()
count += 1
return object
Remove(id):
index = indirection[id.index]
if objects[index].version = id.version:
objects[index].version += 1
objects[count - 1].version += 1
swap(objects[index].data, objects[count - 1].data)
Lớp cảm ứng cho phép bạn có một mã định danh ổn định (chỉ mục vào lớp cảm ứng, trong đó các mục không di chuyển) cho một tài nguyên có thể di chuyển trong quá trình nén (danh sách đối tượng chính).
Thẻ phiên bản cho phép bạn lưu trữ ID vào một đối tượng có thể bị xóa. Ví dụ: bạn có id (10,1). Đối tượng có chỉ số 10 bị xóa (giả sử, viên đạn của bạn chạm vào một đối tượng và bị phá hủy). Đối tượng ở vị trí đó của bộ nhớ trong danh sách đối tượng chính sau đó có số phiên bản bị lỗi, cho nó (10,2). Nếu bạn cố gắng tra cứu lại (10,1) từ ID cũ, việc tra cứu sẽ trả về đối tượng đó thông qua chỉ số 10, nhưng có thể thấy rằng số phiên bản đã thay đổi, do đó ID không còn hợp lệ.
Đây là cấu trúc dữ liệu nhanh nhất tuyệt đối mà bạn có thể có với ID ổn định cho phép các đối tượng di chuyển trong bộ nhớ, điều này rất quan trọng đối với vị trí dữ liệu và sự gắn kết bộ đệm. Điều này nhanh hơn bất kỳ việc thực hiện bảng băm nào có thể; một bảng băm ít nhất cần phải tính toán một hàm băm (nhiều hướng dẫn hơn tra cứu bảng) và sau đó phải tuân theo chuỗi băm (một danh sách được liên kết trong trường hợp khủng khiếp của std :: unordered_map hoặc danh sách địa chỉ mở trong bất kỳ triển khai không ngu ngốc nào của bảng băm), và sau đó phải thực hiện so sánh giá trị trên mỗi khóa (không đắt hơn, nhưng có thể rẻ hơn, so với kiểm tra thẻ phiên bản). Một bảng băm rất tốt (không phải là bảng trong bất kỳ triển khai STL nào, vì STL bắt buộc một bảng băm tối ưu hóa cho các trường hợp sử dụng khác nhau so với trò chơi của bạn cho danh sách đối tượng trò chơi) có thể tiết kiệm được một lần,
Có nhiều cải tiến khác nhau mà bạn có thể thực hiện đối với thuật toán cơ sở. Ví dụ, sử dụng một cái gì đó như std :: deque cho danh sách đối tượng chính; thêm một lớp gián tiếp, nhưng cho phép các đối tượng được chèn vào danh sách đầy đủ mà không làm mất hiệu lực bất kỳ con trỏ tạm thời nào bạn có được từ slotmap.
Bạn cũng có thể tránh lưu trữ chỉ mục bên trong đối tượng, vì chỉ mục có thể được tính từ địa chỉ bộ nhớ của đối tượng (đối tượng này) và thậm chí tốt hơn là chỉ cần thiết khi xóa đối tượng trong trường hợp bạn đã có id của đối tượng (và do đó chỉ số) như là một tham số.
Xin lỗi vì đã viết lên; Tôi không cảm thấy đó là mô tả rõ ràng nhất có thể. Nó muộn và thật khó để giải thích mà không mất nhiều thời gian hơn tôi có trên các mẫu mã.