Thay thế cho mảng 2D trong cấu trúc bản đồ lát gạch


7

Sau khi tìm kiếm trong một thời gian dài, tôi ngạc nhiên khi câu hỏi này chưa được hỏi. Trong trò chơi 2D, lát gạch, bạn xử lý bản đồ như thế nào? Tôi rất vui khi có quan điểm của bạn trong bất kỳ ngôn ngữ nào, mặc dù tôi quan tâm nhiều hơn đến việc triển khai C ++.

Một mảng 2D, một vectơ 2D, một lớp xử lý một danh sách được liên kết với máy tính ad hoc để xử lý tọa độ, boost :: matrix ...? Bạn sử dụng giải pháp nào và tại sao?

Câu trả lời:


10

Một điều tôi đã làm cho một bản đồ theo phong cách RPG - đó là, những ngôi nhà bạn có thể vào, ngục tối, v.v. có 4 cấu trúc chính: Bản đồ, Khu vực, Khu vực và Ngói.

Một Ngói rõ ràng là một ngói.
Một khu vực, hoặc một đoạn, hoặc bất cứ điều gì, là một khu vực của gạch X bằng Y. Điều này có một mảng 2D.
Một khu vực là một bộ sưu tập các khu. Mỗi Khu vực có thể có các kích thước Vùng khác nhau - thế giới bên kia có thể sử dụng Vùng 32x32, trong khi một ngôi nhà có thể có một Vùng 1020. Chúng được lưu trữ trong từ điển (vì vậy tôi có thể có Vùng (-3, -2)).
Bản đồ là một tập hợp các Khu vực, tất cả đều được liên kết với nhau.

Tôi cảm thấy điều này cho phép tôi linh hoạt hơn thay vì có một bản đồ khổng lồ.


Yay, 3000 đại diện. :)
Vịt Cộng sản

Tôi không cảm thấy rằng điều này nhất thiết phải trả lời câu hỏi của OP. Đó là cách bạn lưu trữ nó, nhưng nó có trong một mảng không? Vectơ?
Kenneth Worden

5

Tôi sẽ giải thích những gì tôi làm cho một trường hợp cụ thể của bản đồ được lát gạch: đây là bản đồ vô hạn có hiệu quả, những bản đồ được tạo ra theo yêu cầu nhưng bạn cần lưu các sửa đổi cho nó:

Tôi xác định một số kích thước ô "N" và chia thế giới thành các hình vuông / hình khối "NxN" hoặc "NxNxN"

Một ô sẽ có một khóa duy nhất. Tôi tạo ra mỏ bằng cách băm hoặc sử dụng trực tiếp chuỗi được định dạng: "% i,% i,% i", x, y, z (trong đó x, y, z là tọa độ thế giới của điểm bắt đầu của ô chia cho N)

Việc lưu trữ các chỉ số gạch trong mảng là đơn giản vì bạn biết bạn có gạch NxN hoặc gạch NxNxN. Bạn cũng biết loại gạch của bạn chiếm bao nhiêu bit. Chỉ cần sử dụng một mảng tuyến tính. Nó làm cho việc tải và lưu / giải phóng các ô đơn giản hơn để xử lý.

Bất kỳ trình truy cập nào chỉ cần tạo khóa cho ô (để đảm bảo rằng nó được tải / tạo, sau đó lấy con trỏ tới nó), sau đó sử dụng một chỉ mục phụ để xem bên trong ô đó. để tìm giá trị gạch tại điểm cụ thể đó.

Trích xuất ô bằng khóa của nó, hiện tại tôi sử dụng một bản đồ / từ điển vì tôi thường xử lý toàn bộ các ô một lần (sẽ không muốn biết mức độ tệ hại của nó khi thực hiện tra cứu từ điển trên mỗi ô, eek).

Một điểm khác, tôi không giữ mob / người chơi trong dữ liệu di động. Các công cụ năng động tích cực cần hệ thống riêng của mình.


5

Câu hỏi có lẽ không được hỏi vì bạn không cần một giải pháp thay thế. Đối với tôi, đó là:

Tile tiles[MAP_HEIGHT][MAP_WIDTH]; nếu kích thước cố định.

std::vector<Tile> tiles(MAP_HEIGHT*MAP_WIDTH); nếu không thì.


3
Với C ++ TR1, người ta có thể sử dụng std :: mảng cho kích thước cố định với hình phạt hiệu năng "không" và có được ngữ nghĩa lặp lặp đẹp hơn tiềm năng.

1
Một lý do khác để sử dụng mảng 2d (sử dụng mảng C hoặc std :: vector hoặc std :: mảng hoặc tương đương trong các ngôn ngữ khác) là chúng khá nhỏ gọn. Tỷ lệ không gian được sử dụng để lưu trữ dữ liệu trò chơi (trên tổng kích thước của cấu trúc dữ liệu) gần bằng 100% với mảng 2d, nhưng thường thấp hơn nhiều với các mảng thưa thớt, danh sách được liên kết, bảng băm, mảng con trỏ hoặc các cấu trúc khác . Điều này ít quan trọng hơn trong các trò chơi trên PC nhưng nhiều nền tảng bị giới hạn bộ nhớ và các biểu diễn nhỏ gọn giúp giảm thời gian tải và bộ đệm cpu.
amitp

Bạn có ý nghĩa gì bởi "bạn không cần một sự thay thế?" Đôi khi, một danh sách được liên kết tốt hơn, đôi khi là mảng 1D, đôi khi là mảng 2D, v.v. Bạn có gợi ý rằng chỉ có một cách để làm một điều gì đó trong một chủ đề rộng lớn và phức tạp như lập trình trò chơi không? 😳
jdk1.0

@ jdk1.0 Tôi không thể nghĩ đến bất cứ lúc nào bạn muốn một danh sách được liên kết cho cấu trúc 2D. Và sự khác biệt giữa mảng 2D và 1D trong C ++ hầu như không liên quan vì dù sao bạn cũng sẽ nhận được 1 cục bộ nhớ liền kề. Lý do câu hỏi này không được hỏi nhiều là vì câu trả lời đơn giản và rõ ràng hầu như luôn luôn là câu trả lời hay nhất.
Kylotan

2

Nó sẽ phụ thuộc vào phong cách của trò chơi và bản đồ một cách trung thực. Đối với bản đồ hình chữ nhật tương đối nhỏ, có lẽ tôi chỉ cần gắn với một mảng 2d. Nếu bản đồ có hình dạng rất bất thường (nhiều khoảng trống), một trình bao bọc xung quanh các danh sách được liên kết cung cấp lập chỉ mục O (1) có thể là lựa chọn của tôi.

Một mảng được lập chỉ mục số nguyên cung cấp cho bạn một mảng 2147483647 ^ 2 2d. Điều đó khá lớn, nhưng vượt quá những gì bạn tải vào bộ nhớ. Nếu bản đồ có tỷ lệ lớn, một điều khác cần xem xét là chia bản đồ thành nhiều phần. Mỗi khối có kích thước cố định và chứa một mảng phụ có thể được tải / không tải khi cần để giữ bộ nhớ thấp hơn.


Không có lợi thế nào khi sử dụng cây bốn lá cho việc này thay vì chỉ đơn giản là phân chia bản đồ ở các kích thước cố định và rất nhiều sự phức tạp đã đạt được.

Ah, đã không giải thích rằng một phần chunk tốt hoạt động tốt hơn.
Khoan hồng

2

Nếu đó chỉ là một trò chơi lát gạch đơn giản trên lưới như trò chơi chiến lược theo lượt, thì một cái gì đó như thế này:

struct Tile
{
    // Stores the first entity (enemy, NPC, item, etc) on the tile.
    int first_entity;
    ...
};

struct Entity
{
    // Stores the next entity on the same tile or
    // the next free entity index to reclaim if
    // this entity has been freed/removed.
    int next;
    ...
};

struct Row
{
    // Stores all the tiles on the row.
    vector<Tile> tiles;

    // Stores all the entities on the row.
    vector<Entity> entities;

    // Points to the first free entity index
    // to reclaim on insertion.
    int first_free;
};

struct Map
{
    // Stores all the rows in the map.
    vector<Row> rows;
};

Một số người có thể tự hỏi tại sao tôi chọn lưu trữ các vectơ riêng cho từng hàng bản đồ. Đó là để cải thiện địa phương không gian khi chúng ta đi qua các thực thể đứng trên một ô nhất định. Khi chúng tôi lưu trữ một vectơ riêng cho mỗi hàng, thì tất cả các thực thể cho hàng đó có thể vừa với L1 hoặc L2, trong khi chúng thậm chí có thể không vừa với L3 nếu chúng tôi lưu trữ một thùng chứa thực thể cho tất cả các thực thể trong toàn bản đồ. Điều đó vẫn có xu hướng khá rẻ so với, lưu trữ một vectơ riêng trên mỗi ô.

Để có được, giả sử, gạch tại (102, 72), chúng tôi làm điều này:

Row& row = map.rows[72];
Tile& tile = row.tiles[102];

Để duyệt qua các thực thể trên ô, chúng tôi thực hiện:

int entity = tile.first_entity;
while (entity != -1)
{
    // Do something with the entity on the tile.
    ...

    // Advance to the next entity on the tile.
    entity = row.entities[entity].next;
}

Đương nhiên, việc triển khai loại "container riêng trên mỗi hàng" có lợi nhất, các mẫu truy cập ô của bạn nên cố gắng xử lý tất cả các cột quan tâm cho một hàng trước khi bạn chuyển sang hàng tiếp theo, không quá ngoằn ngoèo qua lại từ một hàng tiếp theo và trở lại một lần nữa.

Chèn một thực thể vào ô sẽ như thế này:

int Map::insert_entity(Entity ent, int col_idx, int row_idx)
{
     Row& row = rows[row_idx];

     int ent_idx = row.first_free;
     if (ent_idx != -1)
     {
          row.first_free = row.entities[ent_idx].next;
          row.entities[ent_idx] = ent;
     }
     else
     {
          ent_idx = static_cast<int>(row.entities.size());
          row.entities.push_back(ent);
     }

     Tile& tile = row.tiles[col_idx];
     row.entities[ent_idx].next = tile.first_entity;
     tile.first_entity = ent_idx;
     return ent_idx;
}

... và xóa:

void Map::remove_entity(int ent_idx, int col_idx, int row_idx)
{
     Row& row = rows[row_idx];
     Tile& tile = row.tiles[col_idx];
     if (tile.first_entity = ent_idx)
         tile.first_entity = row.entities[ent_idx].next;

     row.entities[ent_idx].next = row.first_free;
     row.first_free = ent_idx;
}

Lý do chính tôi thích giải pháp này là chúng tôi tránh lưu trữ quá nhiều vectơ (ví dụ: một vectơ trên mỗi ô: quá nhiều cho bản đồ lớn), nhưng không quá ít khi lặp qua các thực thể trên một ô cụ thể dẫn đến những bước tiến tuyệt vời trên địa chỉ bộ nhớ không gian và rất nhiều bộ nhớ cache bỏ lỡ. Một vector thực thể trên mỗi hàng tạo ra sự cân bằng tốt đẹp ở đó.

Điều này giả định rằng bạn có những thứ như tòa nhà, kẻ thù và vật phẩm và rương kho báu và người chơi đứng trên gạch và rất nhiều thời gian trong logic trò chơi đang truy cập vào các thực thể đứng trên các ô đó cũng như kiểm tra xem các thực thể đó đang ở đâu một gạch nhất định. Mặt khác, tôi sử dụng cách tiếp cận mảng 1D với một vectơ duy nhất cho tất cả các ô vì đó sẽ là cách hiệu quả nhất cho việc truy cập các ô. Sau đó, bạn có thể nhận được một ô bằng cách sử dụng: tiles[row*num_cols+col]Sử dụng mảng một chiều nếu nghi ngờ vì nó sẽ cho phép bạn duyệt mọi thứ theo thứ tự tuần tự đơn giản mà không cần các vòng lặp lồng nhau và chỉ yêu cầu một phân bổ heap để phân bổ toàn bộ.

Nói chung, mảng động riêng biệt trên mỗi hàng là thứ tôi đã tìm thấy để giảm bộ nhớ cache rất nhiều trong trường hợp lưới của bạn đang lưu trữ các phần tử bên trong nó. Tất nhiên, nếu nó không và lưới của bạn giống như một hình ảnh chứa pixel, thì việc sử dụng một mảng động riêng biệt trên mỗi hàng là vô nghĩa. Như một điểm chuẩn gần đây nơi tôi đã tối ưu hóa một cái gì đó giống như lưới theo cách này (trước khi nó chỉ sử dụng một mảng khổng lồ cho mọi thứ; tôi đã tối ưu hóa nó để lưu trữ một mảng động riêng biệt trên mỗi hàng sau khi thấy nhiều lỗi bộ nhớ cache trong vtune):

Trước:

--------------------------------------------
- test_grid
--------------------------------------------
time passed for 'insert': {1.799000 secs}
mem use after 'insert': 479,508,224 bytes

8560 cells, 1000000 rects
finished test_grid: {1.919000 secs}

Sau:

--------------------------------------------
- test_grid
--------------------------------------------
time passed for 'insert': {0.310000 secs}
mem use after 'insert': 410,546,720 bytes

8560 cells, 1000000 rects
finished test_grid: {0.361000 secs}

Và tôi đã sử dụng cùng một loại chiến lược được mô tả ở trên. Như một phần thưởng, bạn cũng có thể thấy nó giảm mức sử dụng bộ nhớ vì các vectơ lưu trữ các thực thể có xu hướng phù hợp chặt chẽ hơn nếu bạn lưu trữ một hàng trên mỗi hàng thay vì một cho toàn bộ bản đồ.

Lưu ý rằng thử nghiệm trên để chèn một triệu thực thể vào lưới có vẻ như mất nhiều thời gian và bộ nhớ rất nhiều ngay cả sau khi tối ưu hóa. Đó là bởi vì mỗi thực thể tôi đang chèn có nhiều ô, trung bình khoảng 100 ô cho mỗi thực thể (kích thước trung bình 10 x 10). Vì vậy, tôi đang chèn mỗi triệu triệu thực thể vào trung bình 100 ô lưới, điều này giống như chèn 100 triệu thực thể hơn 1 triệu thực thể. Đó là căng thẳng thử nghiệm một trường hợp bệnh lý. Nếu tôi chỉ chèn một triệu thực thể chiếm 1 ô, tôi có thể thực hiện việc đó trong một phần nghìn giây và chỉ cần sử dụng khoảng 16 megabyte bộ nhớ.

Trong trường hợp của tôi, tôi thậm chí thường phải làm cho các trường hợp bệnh lý có hiệu quả kể từ khi tôi làm việc trong VFX thay vì chơi game. Tôi không thể nói với các nghệ sĩ, "Làm cho nội dung của bạn theo cách này cho công cụ này,"vì toàn bộ quan điểm của VFX là để cho các nghệ sĩ tạo ra nội dung theo ý họ muốn. Sau đó, họ tối ưu hóa nó trước khi xuất sang công cụ yêu thích của họ, nhưng tôi phải xử lý những thứ không được tối ưu hóa, điều đó có nghĩa là tôi thường phải xử lý hiệu quả các trường hợp bệnh lý, như một quãng tám phải xử lý hiệu quả các hình tam giác lớn bao trùm toàn bộ khung cảnh kể từ đó các nghệ sĩ tạo ra nội dung như vậy và thường xuyên (thường xuyên hơn nhiều so với người ta có thể mong đợi). Vì vậy, dù sao đi nữa, bài kiểm tra trên đang thử nghiệm điều gì đó không bao giờ xảy ra và đó là lý do tại sao phải mất gần một phần ba giây để chèn một triệu thực thể, nhưng trong trường hợp của tôi, những điều "không bao giờ nên xảy ra" luôn luôn xảy ra. Vì vậy, trường hợp bệnh lý không phải là trường hợp hiếm gặp đối với tôi,

Là một phần thưởng phụ, điều này cũng cho phép bạn chèn và loại bỏ các thực thể cho nhiều hàng song song bằng cách sử dụng đa luồng mà không bị khóa, vì giờ đây bạn có thể làm điều đó một cách an toàn khi mỗi hàng có một thùng chứa thực thể riêng biệt với điều kiện hai luồng không cố gắng chèn / xóa công cụ đến / từ cùng một hàng.


0

Tôi đang chơi với một động cơ 3D trong đó thế giới là một lưới.

Tôi đang nhập một số bản đồ 2D + ô đã được tạo trước đây.

Và khi tôi làm, tôi chuyển đổi nó thành lưới 3D và đặt tất cả các gạch thành một kết cấu duy nhất.

Điều này rút ra đáng kể nhanh hơn.

Có lẽ tôi sẽ giữ bản đồ xung quanh trong một mảng 1D của w * h nếu tôi phải giữ khái niệm gạch của nó, nhưng câu trả lời của tôi là nó giải phóng để vượt ra khỏi 2Dness.

Và, nếu bạn gặp vấn đề về hiệu suất trong khi sử dụng bản vẽ GPU, hãy giữ biểu diễn đồ họa dưới dạng một kết cấu duy nhất - với lưới nếu nó có chiều cao thay đổi - thực sự có thể tăng tốc độ so với vẽ từng ô riêng lẻ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.