2

在过去的几天里,我一直在研究一种方法,可以在保存时将我的 xna 游戏的 1.44 亿个图块表示压缩到一个非常小的尺寸。成功地实现了这一点后,我现在发现自己对如何将它们从文件中分块取回感到困惑。

在我拥有的文件中。

  1. 一个整数(使用 7BitEncodedInt 方法将其压缩为字节)
  2. 一个字节

压缩整数表示瓦片的数量,后面的字节确定瓦片的类型。这一切都很好,而且效果很好。最重要的是,它将文件大小平均缩小到 50mb。

问题是我目前正在读回整个文件。从文件中我得到了这个。

  1. 每个图块的索引值(只是我抓取图块时的基本迭代)
  2. 每个图块的类型为字节值
  3. 表示该图块纹理的字节值(这很难解释,但在每个图块的基础上都是必要的)

所有这一切的最终结果是我设法保存了文件并且只使用了大约 50mb。但是通过将整个东西重新加载进去,它会在内存上扩展到近 1.5gigs。我真的不能再牺牲瓷砖信息了。所以我需要一种仅根据玩家位置加载地图部分的方法。目标是在 100-200mb 范围内

我一直在研究内存映射文件,使用四叉树,几乎所有我能找到的用于分块加载文件的东西。虽然这些选项看起来都不错,但我不确定哪个是最好的,或者如果考虑到这种情况,可能还有另一个更好的选项。所有这一切的另一个问题是这些解决方案似乎都非常复杂(特别是因为这是我第一次使用它们),虽然我不反对将自己投入到一些冗长的编码中,但我想知道它会做什么我需要它在手之前。

我的问题是,考虑到我在拉入文件时必须如何处理文件,并且需要根据玩家的位置来完成它,那么最好的方法是什么?我只是在这里寻找一些方向。代码总是受欢迎的,但不是必需的。

4

2 回答 2

0

您希望在 Tile 类中具有固定长度的变量并实现如下内容:

这是一个集合类 (People) 的示例,它可以基于从序列化到文件的集合中的索引获取值。

Person 是作为 People 集合基础的类。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FileStreamDatabaseTest
{
    class Program
    {
        static void Main(string[] args)
        {
            People.OpenCollection();
            People.Test_WillOverwriteData();
            People.CloseCollection();
            Console.ReadLine();
        }
    }

    public class Person
    {
        // define maxium variable sizes for serialisation
        protected static int pMaxLength_FirstName = 64;
        protected static int pMaxLength_Age = 10;
        public static int MaxObjectSize
        {
            get
            {
                // return the sum of all the maxlegnth variables to define the entire object size for serialisation
                return pMaxLength_FirstName + pMaxLength_Age;
            }
        }

        // define each object that will be serialised as follows 
        protected string pFirstName;
        public string Firstname
        {
            set
            {
                // ensure the new value is not over max variable size
                if (value.Length > pMaxLength_FirstName)
                    throw new Exception("the length of the value is to long.");

                pFirstName = value;
            }
            get
            {
                return pFirstName;
            }
        }
        protected int pAge;
        public int Age
        {
            get
            {
                return pAge;
            }
            set
            {
                pAge = value;
            }
        }

        public byte[] Serialise()
        {
            // Output string builder
            StringBuilder Output = new StringBuilder();

            // Append firstname value
            Output.Append(Firstname);

            // Add extra spaces to end of string until max length is reached
            if (Firstname.Length < pMaxLength_FirstName)
                for (int i = Firstname.Length; i < pMaxLength_FirstName; i++)
                    Output.Append(" ");

            // Append age value as string
            Output.Append(Age.ToString());

            // Add extra spaces to end of string until max length is reached
            int AgeLength = Age.ToString().Length;
            if (AgeLength < pMaxLength_Age)
                for (int i = AgeLength; i < pMaxLength_Age; i++)
                    Output.Append(" ");

            // Return the output string as bytes using ascii encoding
            return System.Text.Encoding.ASCII.GetBytes(Output.ToString());
        }

        public void Deserialise(byte[] SerialisedData)
        {
            string Values = System.Text.Encoding.ASCII.GetString(SerialisedData);

            pFirstName = Values.Substring(0, pMaxLength_FirstName).Trim();
            pAge = int.Parse(Values.Substring(pMaxLength_FirstName, pMaxLength_Age).Trim());
        }
    }

    public static class People
    {
        private static string tileDatasource = @"c:\test.dat";
        private static System.IO.FileStream FileStream;

        public static void OpenCollection()
        {
            FileStream = new System.IO.FileStream(tileDatasource, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite, System.IO.FileShare.None);
        }

        public static void CloseCollection()
        {
            FileStream.Close();
            FileStream.Dispose();
            FileStream = null;
        }

        public static void SaveCollection(Person[] People)
        {
            FileStream.SetLength(People.Length * Person.MaxObjectSize);
            FileStream.Position = 0;

            foreach (Person PersonToWrite in People)
            {
                // call serialise to get bytes
                byte[] OutputBytes = PersonToWrite.Serialise();

                // write the output buffer
                // note: this will always be the same size as each variable should 
                //       append spaces until its max size is reached
                FileStream.Write(OutputBytes, 0, OutputBytes.Length);
            }
        }

        public static Person GetValue(int Index)
        {
            // set the stream position to read the object by multiplying the requested index with the max object size
            FileStream.Position = Index * Person.MaxObjectSize;

            // read the data
            byte[] InputBytes = new byte[Person.MaxObjectSize];
            FileStream.Read(InputBytes, 0, Person.MaxObjectSize);

            // deserialise
            Person PersonToReturn = new Person();
            PersonToReturn.Deserialise(InputBytes);

            // retun the person
            return PersonToReturn;
        }

        public static void Test_WillOverwriteData()
        {
            long StartTime;
            long EndTime;
            TimeSpan TimeTaken;

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Creating 2,000,000 test people... ");
            StartTime = DateTime.Now.Ticks;
            Person[] People = new Person[2000000];
            for (int i = 0; i < 2000000; i++)
            {
                People[i] = new Person();
                People[i].Firstname = "TestName." + i;
                People[i].Age = i;
            }
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Serialising Collection to disk... ");
            StartTime = DateTime.Now.Ticks;
            SaveCollection(People);
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Redundancy Test... ");
            StartTime = DateTime.Now.Ticks;
            bool Parsed = true;
            int FailedCount = 0;
            for (int i = 0; i < 2000000; i++)
            {
                if (GetValue(i).Age != i)
                {
                    Parsed = false;
                    FailedCount++;
                }
            }
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> " + (Parsed ? "PARSED" : "FAILED (" + FailedCount + " failed index's"));
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Reading 10,000 index's at once... ");
            StartTime = DateTime.Now.Ticks;
            Person[] ChunkOfPeople = new Person[10000];
            for (int i = 0; i < 10000; i++)
                ChunkOfPeople[i] = GetValue(i);
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");


            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Reading 100,000 index's at once... ");
            StartTime = DateTime.Now.Ticks;
            ChunkOfPeople = new Person[100000];
            for (int i = 0; i < 100000; i++)
                ChunkOfPeople[i] = GetValue(i);
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");

            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("*** Reading 1,000,000 index's at once... ");
            StartTime = DateTime.Now.Ticks;
            ChunkOfPeople = new Person[1000000];
            for (int i = 0; i < 1000000; i++)
                ChunkOfPeople[i] = GetValue(i);
            EndTime = DateTime.Now.Ticks;
            TimeTaken = new TimeSpan(EndTime - StartTime);
            Console.WriteLine("-> Completed in " + TimeTaken.TotalSeconds + " seconds");
        }
    }     
}
于 2011-06-19T08:00:57.683 回答
0

有许多选项,并非所有选项都适合您的特定项目:

  • 不要对所有数据使用单个文件。将地图划分为较小的“房间”,并将每个房间存储在自己的文件中。仅加载玩家开始的“房间”,并抢先加载相邻的“房间”并卸载旧的“房间”。
  • 减少您需要存储的瓷砖数量。使用程序生成来创建区域的布局。如果您有一个 10x10 的房间,地板由单一瓷砖类型制成,则不要存储 100 个单独的瓷砖,而是使用一个特定的标记,上面写着“这个区域有一个 10x10 的地板,上面有这个瓷砖”。如果是墙,则保存开始和结束位置以及纹理类型。如果您在空地中间有一个多瓷砖装饰物,并且它的位置与故事无关,则将其随机放置在该场地中(并将随机数生成器的种子保存在地图文件中,以便下次它会出现在同一个地方)。
于 2011-06-19T08:35:37.693 回答