Một cách tốt để lưu trữ dữ liệu tilemap là gì?


12

Tôi đang phát triển một nền tảng 2D với một số người bạn. Chúng tôi đã dựa trên Bộ công cụ khởi động nền tảng XNA sử dụng các tệp .txt để lưu trữ bản đồ ô vuông. Mặc dù điều này là đơn giản nhưng nó không cung cấp cho chúng ta đủ quyền kiểm soát và tính linh hoạt với thiết kế cấp. Một số ví dụ: đối với nhiều lớp nội dung, nhiều tệp được yêu cầu, mỗi đối tượng được cố định trên lưới, không cho phép xoay đối tượng, số lượng ký tự hạn chế, v.v. Vì vậy, tôi đang nghiên cứu cách lưu trữ dữ liệu cấp độ và tập tin bản đồ.

Điều này chỉ liên quan đến việc lưu trữ hệ thống tệp của các bản đồ ô vuông, không liên quan đến cấu trúc dữ liệu mà trò chơi sẽ sử dụng trong khi trò chơi đang chạy. Bản đồ ô vuông được tải vào một mảng 2D, vì vậy câu hỏi này là về nguồn nào để điền vào mảng đó.

Lý do cho DB: Từ quan điểm của tôi, tôi thấy ít dư thừa dữ liệu bằng cách sử dụng cơ sở dữ liệu để lưu trữ dữ liệu ô. Gạch ở cùng vị trí x, y có cùng đặc điểm có thể được sử dụng lại từ cấp này sang cấp khác. Có vẻ như nó đủ đơn giản để viết một phương thức để lấy tất cả các ô được sử dụng ở một mức cụ thể từ cơ sở dữ liệu.

Lý do cho JSON / XML: Các tệp có thể chỉnh sửa trực quan, các thay đổi có thể được theo dõi qua SVN dễ dàng hơn rất nhiều. Nhưng có nội dung lặp đi lặp lại.

Có bất kỳ nhược điểm nào (thời gian tải, thời gian truy cập, bộ nhớ, v.v.) so với cái khác không? Và những gì thường được sử dụng trong ngành công nghiệp?

Hiện tại các tập tin trông như thế này:

....................
....................
....................
....................
....................
....................
....................
.........GGG........
.........###........
....................
....GGG.......GGG...
....###.......###...
....................
.1................X.
####################

1 - Điểm bắt đầu của người chơi, X - Thoát cấp ,. - Không gian trống, # - Nền tảng, G - Gem


2
Bạn đang sử dụng "định dạng" nào? Chỉ nói "tệp văn bản" chỉ có nghĩa là bạn không lưu dữ liệu nhị phân. Khi bạn nói "không đủ kiểm soát và linh hoạt", cụ thể bạn đang gặp phải vấn đề gì? Tại sao sự đảo lộn giữa XML và SQLite? Điều đó sẽ quyết định câu trả lời. blog.stackoverflow.com/2011/08/gorilla-vs-shark
Tetrad

Tôi sẽ chọn JSON vì nó dễ đọc hơn.
Den

3
Tại sao mọi người nghĩ về việc sử dụng SQLite cho những điều này? Đó là một cơ sở dữ liệu quan hệ ; Tại sao mọi người nghĩ rằng một cơ sở dữ liệu quan hệ tạo ra một định dạng tệp tốt?
Nicol Bolas

1
@StephenTierney: Tại sao câu hỏi này đột nhiên chuyển từ XML so với SQLite sang JSON so với bất kỳ loại cơ sở dữ liệu nào? Câu hỏi của tôi là cụ thể tại sao không phải là "Cách tốt để lưu trữ dữ liệu tilemap là gì?" Câu hỏi X so với Y này là tùy ý và vô nghĩa.
Nicol Bolas

1
@Kylotan: Nhưng nó không hoạt động tốt. Rất khó để chỉnh sửa, vì bạn chỉ có thể chỉnh sửa cơ sở dữ liệu thông qua các lệnh SQL. Thật khó để đọc, vì các bảng không thực sự là một cách hiệu quả để xem xét một tilemap và hiểu được những gì đang diễn ra. Và trong khi nó có thể thực hiện tra cứu, quy trình tra cứu lại quá phức tạp. Làm cho một cái gì đó hoạt động là rất quan trọng, nhưng nếu nó sẽ làm cho quá trình thực sự phát triển trò chơi trở nên khó khăn hơn nhiều, thì nó không đáng để đi theo con đường ngắn.
Nicol Bolas

Câu trả lời:


13

Vì vậy, đọc qua câu hỏi cập nhật của bạn, có vẻ như bạn quan tâm nhất đến "dữ liệu dư thừa" trên đĩa, với một số lo ngại thứ yếu về thời gian tải và những gì ngành công nghiệp sử dụng.

Trước hết, tại sao bạn lo lắng về dữ liệu dư thừa? Ngay cả đối với một định dạng cồng kềnh như XML, việc thực hiện nén và giảm kích thước các cấp của bạn sẽ khá đơn giản. Bạn có nhiều khả năng chiếm không gian với kết cấu hoặc âm thanh hơn dữ liệu cấp độ của bạn.

Thứ hai, một định dạng nhị phân nhiều khả năng sẽ tải nhanh hơn định dạng dựa trên văn bản mà bạn phải phân tích cú pháp. Chắc chắn là như vậy nếu bạn có thể thả nó vào bộ nhớ và có cấu trúc dữ liệu của bạn ở đó. Tuy nhiên, có một số nhược điểm đó. Đối với một, các tệp nhị phân bên cạnh không thể gỡ lỗi (đặc biệt là đối với những người tạo nội dung). Bạn không thể khác hoặc phiên bản chúng. Nhưng chúng sẽ nhỏ hơn, và tải nhanh hơn.

Những gì một số công cụ làm (và những gì tôi coi là một tình huống lý tưởng) là thực hiện hai đường dẫn tải. Để phát triển, bạn sử dụng một định dạng dựa trên văn bản của một số loại. Nó thực sự không quan trọng bạn sử dụng định dạng cụ thể nào, miễn là bạn sử dụng một thư viện vững chắc. Để phát hành, bạn chuyển sang tải phiên bản nhị phân (nhỏ hơn, tải nhanh hơn, có thể bị tước các thực thể gỡ lỗi). Công cụ của bạn để chỉnh sửa các cấp nhổ cả hai. Nó hoạt động nhiều hơn chỉ là một định dạng tệp, nhưng bạn có thể tận dụng tốt nhất cả hai thế giới.

Tất cả những gì đang được nói, tôi nghĩ rằng bạn đang nhảy súng một chút.

Bước một cho những vấn đề này là luôn mô tả vấn đề bạn đang giải quyết triệt để. Nếu bạn biết dữ liệu nào bạn cần, thì bạn sẽ biết dữ liệu nào bạn cần lưu trữ. Từ đó là một câu hỏi tối ưu hóa có thể kiểm chứng.

Nếu bạn có trường hợp sử dụng để kiểm tra, thì bạn có thể kiểm tra một vài phương pháp lưu trữ / tải dữ liệu khác nhau để đo cho bất cứ điều gì bạn cho là quan trọng (thời gian tải, sử dụng bộ nhớ, kích thước đĩa, v.v.). Không biết bất cứ điều gì, thật khó để cụ thể hơn.


8
  • XML: Dễ dàng chỉnh sửa bằng tay nhưng một khi bạn bắt đầu tải nhiều dữ liệu, nó sẽ chậm lại.
  • SQLite: Điều này có thể tốt hơn nếu bạn muốn lấy nhiều dữ liệu cùng một lúc hoặc thậm chí chỉ là các đoạn nhỏ. Tuy nhiên, trừ khi bạn đang sử dụng điều này ở nơi khác trong trò chơi của mình, tôi nghĩ rằng điều đó quá mức cho bản đồ của bạn (và có thể là quá phức tạp).

Đề nghị của tôi là sử dụng một định dạng tệp nhị phân tùy chỉnh. Với trò chơi của tôi, tôi chỉ có một SaveMapphương pháp đi qua và lưu từng trường bằng cách sử dụng a BinaryWriter. Điều này cũng cho phép bạn chọn nén nó nếu bạn muốn và cung cấp cho bạn quyền kiểm soát nhiều hơn đối với kích thước tệp. tức là Lưu shortthay vì intnếu bạn biết nó sẽ không lớn hơn 32767. Trong một vòng lặp lớn, lưu một cái gì đó shortthay vì intcó thể có nghĩa là kích thước tệp nhỏ hơn rất nhiều.

Ngoài ra, nếu bạn đi theo lộ trình này, tôi khuyên bạn nên biến đầu tiên trong tệp là số phiên bản.

Ví dụ, hãy xem xét một lớp bản đồ (rất đơn giản):

class Map {
    private const short MapVersion = 1;
    public string Name { get; set; }

    public void SaveMap(string filename) {
        //set up binary writer
        bw.Write(MapVersion);
        bw.Write(Name);
        //close/dispose binary writer
    }
    public void LoadMap(string filename) {
        //set up binary reader
        short mapVersion = br.ReadInt16();
        Name = br.ReadString();
        //close/dispose binary reader
    }
}

Bây giờ, giả sử bạn muốn thêm một tài sản mới vào bản đồ của bạn, nói một Listsố Platformđối tượng. Tôi chọn cái này vì nó liên quan nhiều hơn một chút.

Trước hết, bạn tăng MapVersionvà thêm List:

private const short MapVersion = 2;
public string Name { get; set; }
public List<Platform> Platforms { get; set; }

Sau đó, cập nhật phương thức lưu:

public void SaveMap(string filename) {
    //set up binary writer
    bw.Write(MapVersion);
    bw.Write(Name);
    //Save the count for loading later
    bw.Write(Platforms.Count);
    foreach(Platform plat in Platforms) {
        //For simplicities sake, I assume Platform has it's own
        // method to write itself to a file.
        plat.Write(bw);
    }
    //close/dispose binary writer
}

Sau đó, và đây là nơi bạn thực sự thấy được lợi ích, hãy cập nhật phương thức tải:

public void LoadMap(string filename) {
    //set up binary reader
    short mapVersion = br.ReadInt16();
    Name = br.ReadString();
    //Create our platforms list
    Platforms = new List<Platform>();
    if (mapVersion >= 2) {
        //Version is OK, let's load the Platforms
        int mapCount = br.ReadInt32();
        for (int i = 0; i < mapCount; i++) {
            //Again, I'm going to assume platform has a static Read
            //  method that returns a Platform object
            Platforms.Add(Platform.Read(br));
        }
    } else {
        //If it's an older version, give it a default value
        Platforms.Add(new Platform());
    }
    //close/dispose binary reader
}

Như bạn có thể thấy, LoadMapsphương pháp không chỉ có thể tải phiên bản mới nhất của bản đồ mà cả các phiên bản cũ hơn! Khi nó tải các bản đồ cũ hơn, bạn có quyền kiểm soát các giá trị mặc định mà nó sử dụng.


8

Truyện ngắn

Một giải pháp thay thế gọn gàng cho vấn đề này là lưu trữ các cấp của bạn trong một bitmap, một pixel cho mỗi ô. Sử dụng RGBA, điều này dễ dàng cho phép bốn kích thước khác nhau (lớp, id, xoay, màu, v.v.) được lưu trữ trên một hình ảnh duy nhất.

Câu chuyện dài

Câu hỏi này khiến tôi nhớ lại khi Notch livestream, anh ấy sẽ tham gia Ludum Dare vài tháng trước và tôi muốn chia sẻ trong trường hợp bạn không biết anh ấy đã làm gì. Tôi nghĩ rằng nó thực sự thú vị.

Về cơ bản, anh ta đã sử dụng bitmap để lưu trữ các cấp độ của mình. Mỗi pixel trong bitmap tương ứng với một "ô" trên thế giới (không thực sự là một ô trong trường hợp của anh ta vì đây là một trò chơi phát sóng nhưng đủ gần). Một ví dụ về một trong những cấp độ của anh ấy:

nhập mô tả hình ảnh ở đây

Vì vậy, nếu bạn sử dụng RGBA (8 bit cho mỗi kênh), bạn có thể sử dụng mỗi kênh làm một lớp khác nhau và tối đa 256 loại gạch cho mỗi kênh. Như vậy đã đủ chưa? Hoặc ví dụ, một trong các kênh có thể giữ xoay vòng cho ô như bạn đã đề cập. Bên cạnh đó, việc tạo một trình soạn thảo cấp độ để làm việc với định dạng này cũng khá tầm thường.

Và tốt nhất, bạn thậm chí không cần bất kỳ bộ xử lý nội dung tùy chỉnh nào vì bạn chỉ có thể tải nó vào XNA như bất kỳ Texture2D nào khác và đọc lại các pixel trong một vòng lặp.

IIRC, anh ta đã sử dụng một chấm đỏ thuần túy trên bản đồ để báo hiệu cho kẻ thù và tùy thuộc vào giá trị của kênh alpha cho pixel đó, nó sẽ xác định loại kẻ thù (ví dụ: giá trị alpha là 255 có thể là một con dơi trong khi 254 sẽ là một zombie hoặc một cái gì đó khác).

Một ý tưởng khác là chia nhỏ các đối tượng trò chơi của bạn thành các đối tượng được cố định trong lưới và những đối tượng có thể di chuyển "tinh xảo" giữa các ô. Giữ các ô cố định trong bitmap và các đối tượng động trong một danh sách.

Thậm chí có thể có một cách để ghép nhiều thông tin hơn vào 4 kênh đó. Nếu ai đó có bất kỳ ý tưởng về điều này, cho tôi biết. :)


3

Bạn có thể có thể thoát khỏi với hầu hết mọi thứ. Thật không may, các máy tính hiện đại quá nhanh đến mức lượng dữ liệu tương đối nhỏ này sẽ không thực sự tạo ra sự khác biệt đáng kể về thời gian, ngay cả khi nó được lưu trữ ở định dạng dữ liệu cồng kềnh và được xây dựng tồi, cần đọc một lượng lớn xử lý.

Nếu bạn muốn làm điều "đúng", bạn tạo định dạng nhị phân như Drackir đề xuất, nhưng nếu bạn đưa ra một lựa chọn khác vì một lý do ngớ ngẩn nào đó thì có lẽ nó sẽ không quay lại và cắn bạn (ít nhất là không thực hiện khôn ngoan).


1
Vâng, nó chắc chắn phụ thuộc vào kích thước. Tôi có một bản đồ khoảng 161 000 gạch. Mỗi lớp có 6 lớp. Ban đầu tôi có nó bằng XML, nhưng nó là 82 MB và đã lấy phải tải mãi mãi . Bây giờ tôi lưu nó ở định dạng nhị phân và khoảng 5mb trước khi nén và tải trong khoảng 200ms. :)
Richard Marskell - Drackir

2

Xem câu hỏi này: 'XML nhị phân' cho dữ liệu trò chơi? Tôi cũng sẽ chọn JSON hoặc YAML hoặc thậm chí XML, nhưng điều đó có khá nhiều chi phí. Đối với SQLite, tôi sẽ không bao giờ sử dụng nó để lưu trữ các cấp. Cơ sở dữ liệu quan hệ rất tiện lợi nếu bạn muốn lưu trữ nhiều dữ liệu có liên quan (ví dụ: có các liên kết / khóa ngoại quan hệ) và nếu bạn muốn hỏi một số lượng lớn các truy vấn khác nhau, việc lưu trữ các tilemaps dường như không thực hiện các loại này mọi thứ và sẽ thêm phức tạp không cần thiết.


2

Vấn đề cơ bản với câu hỏi này là nó liên kết hai khái niệm không liên quan gì đến nhau:

  1. Mô hình lưu trữ tệp
  2. Biểu diễn trong bộ nhớ của dữ liệu

Bạn có thể lưu trữ các tệp của mình dưới dạng văn bản thuần túy ở một số định dạng tùy ý, JSON, Lua script, XML, định dạng nhị phân tùy ý, v.v. Và không ai trong số đó sẽ yêu cầu việc thể hiện dữ liệu trong bộ nhớ của bạn theo bất kỳ dạng cụ thể nào.

Công việc của mã tải cấp độ của bạn là chuyển đổi mô hình lưu trữ tệp thành biểu diễn trong bộ nhớ của bạn. Ví dụ: bạn nói: "Tôi thấy ít dữ liệu dư thừa hơn bằng cách sử dụng cơ sở dữ liệu để lưu trữ dữ liệu ô vuông." Nếu bạn muốn dự phòng ít hơn, đó là điều mà mã tải cấp của bạn nên tìm đến. Đó là điều mà đại diện trong bộ nhớ của bạn sẽ có thể xử lý.

Nó là không phải một cái gì đó định dạng tập tin của bạn cần phải được quan tâm. Và đây là lý do: những tập tin đó phải đến từ một nơi nào đó.

Hoặc là bạn sẽ viết chúng bằng tay, hoặc bạn sẽ sử dụng một loại công cụ tạo và chỉnh sửa dữ liệu cấp độ. Nếu bạn đang viết tay chúng, thì điều quan trọng nhất bạn cần là một định dạng dễ đọc và sửa đổi. Dự phòng dữ liệu không phải là một cái gì đó định dạng của bạn thậm chí cần phải xem xét, bởi vì bạn sẽ được dành phần lớn của bạn thời gian chỉnh sửa các tập tin. Bạn có muốn thực sự phải sử dụng thủ công bất kỳ cơ chế nào bạn đưa ra để cung cấp dự phòng dữ liệu không? Sẽ không phải là sử dụng tốt hơn thời gian của bạn để chỉ có trình tải cấp của bạn xử lý nó?

Nếu bạn có một công cụ tạo ra chúng, thì định dạng thực tế chỉ quan trọng (tốt nhất) vì mục đích dễ đọc. Bạn có thể đặt bất cứ thứ gì trong các tập tin này. Nếu bạn muốn bỏ qua dữ liệu dư thừa trong tệp, chỉ cần thiết kế một định dạng có thể làm như vậy và làm cho công cụ của bạn sử dụng chính xác khả năng đó. Định dạng tilemap của bạn có thể bao gồm nén RLE (mã hóa độ dài chạy), nén trùng lặp, bất kỳ kỹ thuật nén nào bạn muốn, bởi vì đó là của bạn định dạng tilemap .

Vấn đề được giải quyết.

Cơ sở dữ liệu quan hệ (RDB) tồn tại để giải quyết vấn đề thực hiện các tìm kiếm phức tạp trên các bộ dữ liệu lớn có chứa nhiều trường dữ liệu. Loại tìm kiếm duy nhất bạn từng làm trong bản đồ ô vuông là "lấy ô ở vị trí X, Y". Bất kỳ mảng có thể xử lý đó. Sử dụng cơ sở dữ liệu quan hệ để lưu trữ và duy trì dữ liệu tilemap sẽ là cực kỳ quá mức cần thiết, và không thực sự có giá trị gì.

Ngay cả khi bạn xây dựng một số nén vào biểu diễn trong bộ nhớ của mình, bạn vẫn có thể dễ dàng đánh bại RDB về hiệu năng và dấu chân bộ nhớ. Có, bạn sẽ phải thực sự thực hiện nén đó. Nhưng nếu kích thước dữ liệu trong bộ nhớ là mối quan tâm mà bạn sẽ xem xét RDB, thì có lẽ bạn thực sự muốn thực hiện các loại nén cụ thể. Bạn sẽ nhanh hơn RDB và bộ nhớ thấp hơn cùng một lúc.


1
  • XML: thực sự đơn giản để sử dụng, nhưng chi phí trở nên khó chịu khi cấu trúc bị sâu.
  • SQLite: thuận tiện hơn cho các cấu trúc lớn hơn.

Đối với dữ liệu thực sự đơn giản, có lẽ tôi chỉ cần đi theo lộ trình XML, nếu bạn đã quen với việc tải và phân tích cú pháp XML.

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.