如何在Unity中对GameObject进行序列化和保存

5

我有一个游戏,在这个游戏中,玩家会拾取武器,并将其作为GameObject变量传递给我的玩家对象中的“MainHandWeapon”。我正在尝试保存该武器以便在场景更改时保留它。我的处理方法如下:

public class Player_Manager : Character, Can_Take_Damage {

    // The weapon the player has.
    public GameObject MainHandWeapon;

    public void Save()
    {
        // Create the Binary Formatter.
        BinaryFormatter bf = new BinaryFormatter();
        // Stream the file with a File Stream. (Note that File.Create() 'Creates' or 'Overwrites' a file.)
        FileStream file = File.Create(Application.persistentDataPath + "/PlayerData.dat");
        // Create a new Player_Data.
        Player_Data data = new Player_Data ();
        // Save the data.
        data.weapon = MainHandWeapon;
        data.baseDamage = BaseDamage;
        data.baseHealth = BaseHealth;
        data.currentHealth = CurrentHealth;
        data.baseMana = BaseMana;
        data.currentMana = CurrentMana;
        data.baseMoveSpeed = BaseMoveSpeed;
        // Serialize the file so the contents cannot be manipulated.
        bf.Serialize(file, data);
        // Close the file to prevent any corruptions
        file.Close();
    }
}

[Serializable]
class Player_Data
{
    [SerializeField]
    private GameObject _weapon;
    public GameObject weapon{
        get { return _weapon; }
        set { _weapon = value; }
    }

    public float baseDamage;
    public float baseHealth;
    public float currentHealth;
    public float baseMana;
    public float currentMana;
    public float baseMoveSpeed;
}

但是我在设置后一直收到这个错误信息:
SerializationException: Type UnityEngine.GameObject is not marked as Serializable.

我到底做错了什么?

这是一个类似的问题的链接,已得到回答。希望这能对您有所帮助。 - Danny Herbert
Joey,你必须“手动”完成它。就是这么简单。在Unity中根本没有序列化或“物体保存” - 就是这么简单。(请注意,所谓的“Serialization”类几乎完全无用;它不是为此目的而设计的。) - Fattie
注意:你说:“我正在尝试通过场景更改来保存那个武器...”为了做到这一点,你可以非常简单地使用DontDestroyOnLoad。角色/任何物品在场景之间移动是非常普遍的。完全没有必要保存或序列化它。 - Fattie
3个回答

18

经过多个实验后,我得出结论:Unity无法使用BinaryFormatter序列化GameObject。尽管Unity在其API文档中声称可以,但实际上是不可能的。

如果你想在不移除_weapon GameObject的情况下消除错误,你应该替换...

[SerializeField]
private GameObject _weapon;
[NonSerialized]
private GameObject _weapon;

这将允许你的代码在不抛出异常的情况下运行,但是你不能_weapon GameObject进行反序列化。你可以反序列化其他字段。

或者

你可以将GameObject序列化为xml。这样可以毫无问题地序列化GameObject,并以人类可读格式保存数据。如果你关心安全或不希望玩家在自己的设备上修改分数,你可以加密、将其转换为二进制Base-64格式,然后再将其保存到磁盘中。

using UnityEngine;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System;
using System.Runtime.Serialization;
using System.Xml.Linq;
using System.Text;

public class Player_Manager : MonoBehaviour
{

    // The weapon the player has.
    public GameObject MainHandWeapon;

    void Start()
    {
        Save();
    }

    public void Save()
    {
        float test = 50;

        Debug.Log(Application.persistentDataPath);

        // Stream the file with a File Stream. (Note that File.Create() 'Creates' or 'Overwrites' a file.)
        FileStream file = File.Create(Application.persistentDataPath + "/PlayerData.dat");
        // Create a new Player_Data.
        Player_Data data = new Player_Data();
        //Save the data.
        data.weapon = MainHandWeapon;
        data.baseDamage = test;
        data.baseHealth = test;
        data.currentHealth = test;
        data.baseMana = test;
        data.currentMana = test;
        data.baseMoveSpeed = test;

        //Serialize to xml
        DataContractSerializer bf = new DataContractSerializer(data.GetType());
        MemoryStream streamer = new MemoryStream();

        //Serialize the file
        bf.WriteObject(streamer, data);
        streamer.Seek(0, SeekOrigin.Begin);

        //Save to disk
        file.Write(streamer.GetBuffer(), 0, streamer.GetBuffer().Length);

        // Close the file to prevent any corruptions
        file.Close();

        string result = XElement.Parse(Encoding.ASCII.GetString(streamer.GetBuffer()).Replace("\0", "")).ToString();
        Debug.Log("Serialized Result: " + result);

    }
}


[DataContract]
class Player_Data
{
    [DataMember]
    private GameObject _weapon;

    public GameObject weapon
    {
        get { return _weapon; }
        set { _weapon = value; }
    }

    [DataMember]
    public float baseDamage;
    [DataMember]
    public float baseHealth;
    [DataMember]
    public float currentHealth;
    [DataMember]
    public float baseMana;
    [DataMember]
    public float currentMana;
    [DataMember]
    public float baseMoveSpeed;
}

6
经过数小时的实验,我得出结论:Unity无法使用BinaryFormatter序列化GameObject。虽然Unity在其API文档中声称可以实现,但实际上并不行。我完全同意您的观点。 - Fattie
1
@JoeBlow 还有许多其他人遇到了同样的问题。要么是文档中有错误,要么是Unity花时间编写代码但未能测试它。 - Programmer
你的解决方案可以序列化 List<T> 类或 Object[] foo 吗?它也能正常工作吗?我尝试了你的解决方案,它只在一个特定的类上起作用,但对于像这两个例子中的数组类型却不行。 - David Dimalanta
1
我真的不知道。这个答案仅适用于 GameObject;。你应该测试一下并查看结果。 - Programmer
1
@程序员 我很高兴、宽慰和难过地看到你也对Unity的一贯不一致感到愤怒。这证实了我长期以来的想法,即他们是粗心和漠不关心的。 - Confused
请注意,虽然你可以序列化GameObject,但很可能无法正确地再次反序列化它... 你不能简单地通过序列化程序在C#层级上创建GameObject的实例,并期望底层的Unity C++引擎能够捕捉到它 => 反序列化后,你要么会得到错误,要么会得到无效/无用的GameObject实例。Unity本身使用复杂的YAML序列化,其中使用GUID引用(这些ID存储在所有资源/脚本等的.meta文件中) - undefined

3

Unity不允许您这样做是因为一个Gameobject包括所有附加到它的脚本,例如mesh renderers、colliders等。

如果您想序列化Transform,可以通过创建一个新的Vector3位置、Vector3缩放、Vector4四元数并将其序列化出来,然后在反序列化时将这些数据提供给一个新的Transform(例如)。

但是,试图序列化与mesh renderer相关联的实际mesh数据将证明是相当复杂的任务。最好只序列化表示mesh ID的int或其他内容,然后在加载时将其转换为正确的引用。


那么这是什么意思?“- 所有从UnityEngine.Object继承的类,比如GameObject、Component、MonoBehaviour、Texture2D、AnimationClip。”按照这个说法,我应该能够保存一个GameObject对吧?还是我理解错了?如果不想保存GameObject,我只需要保存脚本就可以了吗?如果不行,我感觉他们的说明非常误导人。 - JoeyL
你的报价来自哪里,@JoeyL? - Zze
@JoeyL 其他资源:http://answers.unity3d.com/questions/803671/i-need-to-save-and-load-gameobjects-how-do-i-do-th.html http://answers.unity3d.com/questions/998574/save-serialize-gameobjects.html https://www.reddit.com/r/Unity3D/comments/3ic3id/game_object_serialization_in_unity_5/ - Zze
2
他们的意思是在编辑器中将其序列化为内部保存格式。 - aeroson

2
理想情况下,您应该拥有一个包含武器数据(攻击力、耐久度等)并对该对象进行序列化的对象。这样可以使您的保存游戏文件更小,并且更符合面向对象编程(OOP)的规范,因为您可以从该类继承。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接