Простой двоичный код чтения-записи не работает

MS VS 2010, XNA 4.0
Итак, у меня есть класс Planet, и у него есть функции сохранения и загрузки.

public void SaveToFile(ContentManager content)
    {
        string path = content.RootDirectory + @"\Objects\Planets\" + this.name +@".planet"; 
        using (BinaryWriter bw = new BinaryWriter(File.Open(path, FileMode.Create)))
        {
            bw.Write(name);

            //sprite names
            bw.Write(planet.Sprite.Name); //"planet" is an animation.
            bw.Write(planet.HorFrames);   //and thats why it has a sprite
            bw.Write(planet.VerFrames);   //that has a name
            bw.Write((double)planet.FPS);

            bw.Write(asteroid1.Name);
            bw.Write(asteroid2.Name);
            bw.Write(asteroid3.Name);
            bw.Write(backgroundA.Name);

            //update related
            bw.Write((double)RSpeed);
            bw.Write((double)ASVariety);
            bw.Write((double)A1Chance);
            bw.Write((double)A2Chance);
            bw.Write((double)A3Chance);
            bw.Close();
        }
    }

public void LoadFromFile(ContentManager content, string name)
    {
        string path = content.RootDirectory + @"\Objects\Planets\" + name + @".planet";
        using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
        {
            this.name = br.ReadString();
            this.planet = new Animation(Cont.Texture2D(br.ReadString()), br.ReadInt32(), br.ReadInt32(), (float)br.ReadDecimal(), true);
            this.asteroid1 = Cont.Texture2D(br.ReadString());
            this.asteroid2 = Cont.Texture2D(br.ReadString());
            this.asteroid3 = Cont.Texture2D(br.ReadString());
            this.backgroundA = Cont.Texture2D(br.ReadString());
            this.RSpeed = (float)br.ReadDouble();
            this.ASVariety = (float)br.ReadDouble();
            this.A1Chance = (float)br.ReadDouble();
            this.A2Chance = (float)br.ReadDouble();
            this.A3Chance = (float)br.ReadDouble();

            bposB = new Vector2(backgroundA.Width - 1, 0);
            bspd = new Vector2(-RSpeed / 8f, 0);

            pEngine1 = new ParticleEngine(MSTime, 40, new Vector2(20, -30), new Vector2(2, 1), asteroid1, new Color(100, 100, 100));
            pEngine1.Follow(new Vector2(-200, Game1.window_height / 2));

            pEngine2 = new ParticleEngine(MSTime * 3, 40, new Vector2(20, -30), new Vector2(2, 1), asteroid2, new Color(100, 100, 100));
            pEngine2.Follow(new Vector2(-200, Game1.window_height / 2));

            br.Close();
        }

Это просто понять, не так ли?
Теперь, когда я пытаюсь загрузить спрайт для анимации планеты, я получаю сообщение об ошибке:
"Значение не может быть нулевым.
Параметр name: assetsName"
и это происходит в строке
this.planet = new Animation(Cont.Texture2D(br.ReadString()), br.ReadInt32(), br.ReadInt32(), ( float)br.ReadDecimal(), true);
Cont.Texture2D — это статическая функция, которая возвращает имя Texture2D, совпадающее с путем, из которого он был загружен, и выглядит следующим образом:

public static Texture2D Texture2D(string path)
    {
        Texture2D sprite = content.Load<Texture2D>(path);
        sprite.Name = path;
        return sprite;
    }

Итак, когда я сохраняю его, он сохраняет правильный путь. Тестовая функция выглядит следующим образом:

private void testFunction()
    {
        /*
        p = new Planet("Mars", 
            new Animation(Cont.Texture2D("Sprites\\Planets\\mars"), 8, 2, 0.9f, true),
            Cont.Texture2D("Sprites\\Backgrounds\\space"),
            Cont.Texture2D("Sprites\\Asteroids\\small\\small 1"),
            Cont.Texture2D("Sprites\\Asteroids\\medium\\medium 1"),
            Cont.Texture2D("Sprites\\Asteroids\\big\\big 1"),
            100, 50, 40, 20, 40, 40);
        p.SaveToFile(Content);
        */
        p = new Planet(Content, "Mars");
        tests = p.ToString();
    }

"тесты" - это просто тестовая строка. Теперь я сначала выполняю закомментированный код, чтобы он мог сохранить файл, а затем запускаю некомментированный код. Этот конструктор планеты просто вызывает функцию LoadFromFile и ничего больше. Кроме того, у меня есть сохраненный файл в моем контенте в моем проекте. Я сказал, чтобы этот файл рассматривался как контент (не компилировал его). Итак, код "видит" файл, это точно, но не может найти .png по пути, который был прочитан с mars.planet. Может быть, ошибка, если я сохраняю 2 строки одну за другой, а затем читатель не видит, где конец первой? Может быть, я сохраняю это неправильно?

Моя цель состоит в том, чтобы иметь двоичные файлы, которые будут загружаться и сохраняться, и в них будут планеты, ракеты, карты и т. д., которые представляют собой наборы имен и значений, которые необходимы для конструкторов этих классов.


person Monset    schedule 20.06.2015    source источник
comment
Вы пишете planet.FPS как double, но пытаетесь прочитать как decimal?   -  person cbr    schedule 21.06.2015
comment
В сообщении об ошибке сообщается, что существует параметр с именем assetName, значение которого равно null, когда требуется ненулевое значение. Наверняка это не имеет прямого отношения к BinaryReader. Что касается того, что это может быть, это невозможно сказать, если вы не предоставите хороший, минимальный, полный пример кода, достоверно воспроизводящий проблему.   -  person Peter Duniho    schedule 21.06.2015
comment
ФПС плавает. Двоичный писатель-читатель не имеет числа с плавающей запятой, поэтому, когда я сохраняю число с плавающей запятой, я сохраняю его как десятичное, а когда я загружаю его, я загружаю десятичное число и превращаю его в число с плавающей запятой: (float)br.ReadDecimal();.   -  person Monset    schedule 21.06.2015
comment
@PeterDuniho: у меня больше нет кода, который помог бы вам понять мою проблему. Я сделал Cont статическим классом, который может загружать текстуры/спрайты/музыку... и я загрузил его перед запуском тестовой функции, так что это означает, что он работает правильно. Я также проверил его на других путях спрайтов, и он работает. После подготовки класса Cont я вызвал тестовую функцию. Я не вижу, как я могу сделать этот более хороший, минимальный, полный пример кода, который надежно воспроизводит проблему.   -  person Monset    schedule 21.06.2015


Ответы (2)


Из-за ненужных сложностей с бинарным читателем-писателем я решил использовать Stream-писатель-читатель. Большое спасибо @Blas Soriano и @Peter Duniho.

Похоже, что бинарные писатели не могут записать 2 строки одну за другой так, чтобы бинарный читатель мог их прочитать, по крайней мере, не в моем случае. Я пробовал писать и читать 2 строки с помощью двоичного файла w-r в другом проекте, и там они, похоже, работают нормально. Я надеюсь, что никто не испытал такого рода вещи, которые произошли со мной. Еще раз спасибо Бласу и Питеру.

РЕДАКТИРОВАТЬ:
Кажется, что мой файл, который я включил в Содержимое решения, "Mars.planet", в его свойствах: Копировать в вывод, из 3 возможных вариантов: Копировать, если новее, Никогда Копировать, всегда копировать, выбрал «Копировать всегда», и это не позволяло вносить изменения в этот файл, поэтому я ДЕЙСТВИТЕЛЬНО изменил файл, сохранив его с помощью моей функции, но затем я выхожу из игры, поэтому файл возвращается к старая версия (не очень понимаю как), и эта версия действительно старая, в ней не было всех строк, которые мне сейчас нужны, поэтому, когда я комментирую код сохранения и раскомментирую код загрузки, запускаю игру, функция загрузки на самом деле будет загружать первую версию файла (версию с момента включения файла).

Теперь это работает, но не потому, что я использую Stream wr, а потому что я изменил свойство этого файла на: Не копировать. Я вернусь к Binary w-r.

person Monset    schedule 22.06.2015

В вашем testFunction() вы создаете новую планету и сохраняете ее в файл, не назначая имени спрайта (и я думаю, что оно не назначается в конструкторе анимации), поэтому при сохранении имя спрайта будет нулевым. Чтобы избежать этого, вам нужно присвоить planet.Sprite.Name перед сохранением в файл.

private void testFunction()
{
    Animation planetAnimation = new Animation(Cont.Texture2D("Sprites\\Planets\\mars"));
    planetAnimation.Sprite.Name = "Sprites\\Planets\\mars";
    p = new Planet("Mars", 
        planetAnimation , 8, 2, 0.9f, true),
        Cont.Texture2D("Sprites\\Backgrounds\\space"),
        Cont.Texture2D("Sprites\\Asteroids\\small\\small 1"),
        Cont.Texture2D("Sprites\\Asteroids\\medium\\medium 1"),
        Cont.Texture2D("Sprites\\Asteroids\\big\\big 1"),
        100, 50, 40, 20, 40, 40);
    p.SaveToFile(Content);
    /*
    p = new Planet(Content, "Mars");
    tests = p.ToString();
    */
}

Вместо этого вы можете изменить свои методы Load и Save, чтобы не сохранять эту строку дважды. Сначала удалите или прокомментируйте строку bw.Write(planet.Sprite.Name) в SaveToFile():

public void SaveToFile(ContentManager content)
{
    string path = content.RootDirectory + @"\Objects\Planets\" + this.name +@".planet"; 
    using (BinaryWriter bw = new BinaryWriter(File.Open(path, FileMode.Create)))
    {
        bw.Write(name);

        //sprite names
        //bw.Write(planet.Sprite.Name); //"planet" is an animation.
        bw.Write(planet.HorFrames);   //and thats why it has a sprite
        bw.Write(planet.VerFrames);   //that has a name
        bw.Write((double)planet.FPS);
...

Затем измените LoadFromFile(), чтобы повторно использовать строку:

public void LoadFromFile(ContentManager content, string name)
{
    string path = content.RootDirectory + @"\Objects\Planets\" + name + @".planet";
    using (BinaryReader br = new BinaryReader(File.OpenRead(path)))
    {
        this.name = br.ReadString();
        this.planet = new Animation(Cont.Texture2D(this.name), br.ReadInt32(), br.ReadInt32(), br.ReadDouble(), true);
        this.asteroid1 = Cont.Texture2D(br.ReadString());
        this.asteroid2 = Cont.Texture2D(br.ReadString());

Обратите внимание, что следуя предложению cubrr, я заменил десятичное число на двойное при чтении planet.FPS.


EDIT: Из вашего комментария кажется, что вы считаете, что нулевая строка должна быть назначена с помощью статического метода, но здесь есть две разные вещи.
Сначала взгляните на это имя внутри Cont.Texture2D():

public static Texture2D Texture2D(string path)
{
    Texture2D sprite = content.Load<Texture2D>(path);
    sprite.Name = path;
    return sprite;
}

Это имя Texture2D.Name
Теперь загляните внутрь SaveToFile():

bw.Write(name);

//sprite names
bw.Write(planet.Sprite.Name); //"planet" is an animation.

Здесь мы видим 2 разных имени, Planet.Name и Planet.Sprite.Name. Где вы присваиваете это второе имя? Вот почему я использовал planetAnimation.Sprite.Name = "Sprites\\Planets\\mars";.

person Blas Soriano    schedule 21.06.2015
comment
this.name означает название планеты. Извините, я не упомянул, что у планеты есть свое имя. Итак, название планеты — это просто имя, а, допустим, asteroid1.Name — это путь к спрайту. Как вы не заметили, каждый раз, когда я загружаю Texture2D, я делаю это с помощью Cont.Texture2D (строковый путь), и эта функция возвращает новый Texture2D с его именем в качестве пути, из которого он был загружен. - person Monset; 21.06.2015
comment
@Monset добавил пояснения, отредактировав мой ответ. Что касается FPS, вы можете использовать BinaryReader.ReadSingle() для чисел с плавающей запятой и BinaryReader.ReadDouble() для двойного. - person Blas Soriano; 21.06.2015
comment
как вы сказали, имя - это имя экземпляра класса Planet, а планета - это анимация планеты в классе Planet, которая имеет Texure2D с именем Sprite. Простите мою плохую систему именования переменных, я не уделял ей особого внимания в этом классе. Когда я сохраняю планету в файл, сначала я сохраняю имя планеты, а затем сохраняю путь к спрайту анимации планеты. В этом нет ошибок или недоразумений. Моя проблема заключается в следующем. другой сорт. - person Monset; 21.06.2015
comment
Я объясню это еще раз. Сначала я сохраняю только что созданную планету через тестовую функцию, затем комментирую это, затем пытаюсь загрузить сохраненный файл (файл действительно найден, это не тот файл, который не может быть найден), а функция LoadFromFile, в строке --- this.planet = new Animation(Cont.Texture2D(this.name),... --- переходит к функции Cont public static Texture2D (строковый путь), и эта функция не может найти спрайт, даже если этот точный спрайт был нашел, когда я создал планету, прежде чем сохранить ее. - person Monset; 21.06.2015
comment
@Monset Хорошо, теперь я понял. Это sprite.name не может быть нулевым, иначе вы получите исключение NullArgumentException при использовании bw.Write(planet.Sprite.Name);. Таким образом, проблема должна быть связана с самим содержимым строки. Я бы сделал 2 вещи: 1- Установите точку останова в этой строке, чтобы проверить эту строку. 2- Используйте шестнадцатеричный редактор для проверки двоичного файла. Посмотрите на этот снимок экрана. - person Blas Soriano; 21.06.2015
comment
Я устанавливаю точку останова с помощью throw new NotSupportedException(path), если Cont.Texture2D не может получить файл изображения, и кажется, что путь представляет собой пустую строку - person Monset; 21.06.2015
comment
Используете ли вы более одного экземпляра ContentManager? Внутри testFunction() я вижу Cont в закомментированном коде и Content в раскомментированном коде. - person Blas Soriano; 21.06.2015
comment
есть один ContentManager в классе Game1 (класс XNA по умолчанию для запуска вашей игры), и этот CM загружается в статический класс Cont - person Monset; 22.06.2015