首页 > 解决方案 > 如何使用 ScriptableObject 进行关卡的预制转换?

问题描述

我有 3 种预制件:A、B 和 C

在设计关卡时,在播放模式下,我在关卡中添加和定位它们。

如何创建一个 ScriptableObject 来保存对这些预制件及其转换的所有实例的引用?

具体来说,在 PlayMode 中,Scriptable 对象应动态响应预制件在 PlayMode 中的位置和旋转变化。

我无法想象如何做到这一点,尽管这似乎是对 Scriptable Objects 的一种很好的使用。

标签: unity3dscriptable-object

解决方案


您可以将所需的信息存储在专门的课程中,例如

[Serializable]
public class InstanceInformation
{
    public GameObject UsedPrefab;
    public Vector3 Position;
    public Quaternion Rotation;
    public Vector3 Scale;

    public InstanceInformation(GameObject usedPrefab, Transform transform)
    {
        UsedPrefab = usedPrefab;
        Rotation = transform.rotation;
        Position = transform.position;
        Scale = transform.localScale;
    }

    public void UpdateValues(Transform transform)
    {
        Rotation = transform.rotation;
        Position = transform.position;
        Scale = transform.localScale;
    }
}

并且在您的 ScriptableObject 中有一个

[CreateAssetMenu]
public class LevelData : ScriptableObject
{
    public List<InstanceInformation> instances = new List<InstanceInformation>();
}

然后每次实例化一个预制件时,您也会在其中创建一个相应的条目instances

所以稍后在你的经理脚本中,你实例化你所做的事情,例如

// Reference this via the Inspector
public LevelData scriptable;

// For keeping a link for the currently Instantiated stuff
// so everytime you manipulate them later on you can update the according data entry
private Dictionary<GameObject, InstanceInformation> objectToInstanceInformation = new Dictionary<GameObject, InstanceInformation>();

...

var obj = Instantiate(aPrefab, aPosition, aRotation);

var instanceInfo = new InstanceInfo(aPrefab, obj.transform);

// Add the reference to the ScriptableObject list
scriptable.instances.Add(instanceInfo);
// And also keep track of the reference linked to the actual instance
objectToInstanceInformation.Add(obj, instanceInfo);

现在你可以重复或在某个时刻打电话

public void SaveInstanceInformations()
{
    foreach(var kvp in objectToInstanceInformation)
    {
        var obj = kvp.key;
        var instanceInfo = kvp.value;

        instanceInfo.UpdateValues(obj.transform);
    }
}

因为InstanceInformation是一个类,因此一个引用类型在此处自动更改值也会更改instancesScriptableObject 中的相应条目!


所以稍后当你想加载状态时,你可以简单地做

foreach (var instance in scriptable.instances)
{
    var obj = Instantiate(instance.UsedPrefab, instance.Position, instance.Rotation);
    obj.transform.localScale = instance.Scale;
    objectToInstanceInformation.Add(obj, instance);
}

最后,为了能够持久存储这些数据,您可以使用BinaryFormatter类似

[CreateAssetMenu]
public class LevelData : ScriptableObject
{
    // here you can set a filename the data shall be stored to
    [SerializeField] private string fileName = "level.dat";

    public List<InstanceInformation> instances = new List<InstanceInformation>();

    // called when the object is loaded
    private void OnEnable()
    {
        // try to load the data from drive
        // in the editor use the Application.streamingAssetsPath
        // in a build use Application.persistentDataPath
        var folder = Application.isEditor ? Application.streamingAssetsPath : Application.persistentDataPath;
        if(!File.Exists(Path.Combine(folder, fileName)))
        {
            // on the first run the folder and file will not exist
            // in the editor do nothing (the file simply doesn't exist yet)
            // in a build copy the content from the streaming assets folder to the persistent data path (if exists)
            var fallbackFolder = Application.streamingAssetsPath;
            if(!Driectoy.Exists(fallbackFolder)) return;

            if(!File.Exists(Path.Combine(fallbackFolder, fileName))) return;

            // copy fallback file to persistent file
            File.Copy(Path.Combine(fallbackFolder, FileName), Path.Combine(folder, fileName));
        } 

        // load the list
        using(var file = File.Open(Path.Combine(folder, fileName), FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            var binaryFormatter = new BinaryFormatter();
            instances = (List<InstanceInformation>)binaryFormatter.Deserialize(file);
        }
    }

    // called when the object is destroyed (you app ended)
    private void OnDestroy()
    {
        // save the data to drive
        // in the editor use the Application.streamingAssets
        // in a build use Application.persistentDataPath
        var folder = Application.isEditor ? Application.streamingAssets : Application.persistentDataPath;
        if(!Directoy.Exists(folder)) Directory.CreateDirectory(folder);

        using(var file = File.Open(Path.Combine(folder, fileName), FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
        {
            var binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(file, instances);
        }
    }
}

推荐阅读