Unity:需要将 return 上的池对象重置为池。也许使用 ScriptableObject?

Unity: Need to reset a pooled object on return to pool. Perhaps using ScriptableObject?

我最近一直在尝试在 unity 中使用对象池来加速多个游戏对象的实例化。

但是,由于这些对象相当复杂,我需要在它们返回池中时重置它们。

我读到使用 ScriptableObject 可能是存储默认值以便轻松重置的好方法。但是为了做到这一点,我需要在运行时加载一个新的 ScriptableObject 来存储对象的实际值。

所以在伪代码中,我有一个 class 和 public MyScriptableData datapublic MyScriptableData defaults

1) 使用默认的 ScriptableObject 创建新的池化对象 data = defaults;;

2) 在对象的生命周期内做一些改变脚本化对象值的事情

3) 停用,然后 return 池对象到池,将 scriptableObject 重置为其默认值(再次 data = defaults;)。

我有 3 个主要问题:

A) 我不确定如何实际实现它。在我看来,在第 2 步中,默认值将被更改。因此,重置为默认值将无济于事。我考虑过使用

创建我的可编写脚本对象的新实例

data = ScriptableObject.CreateInstance<MyScriptableData>();

但是我如何从 defaults 中复制默认值,确保从不更改默认值?我希望默认值可以在统一编辑器中作为资产进行编辑。

B) 如果我使用CreateInstance,性能会不会很差?我做这个对象池的全部原因是为了降低对象实例化的性能成本。我不想通过实例化可编写脚本的对象来重新引入缓慢的代码。

C) 这个方法可以吗?还是有更好的方法在返回池之前重置对象?

根据一些答案进行编辑:我已经有一个包含一长串字段的设置,然后将这些字段的默认值存储在字典中。但是我发现每次我想要 add/change/remove 一个字段时,我都必须在几个地方更改代码

尝试的解决方案(但错误,见下文):我为 ScriptableObject 创建了一个扩展方法:

using UnityEngine;
using System.Reflection;

public static class ScriptableObjectExtension {

    public static T ShallowCopy<T> (this T orig) where T : ScriptableObject {
        T copiedObject = ScriptableObject.CreateInstance<T> ();
        FieldInfo[] myObjectFields = orig.GetType ().GetFields (
                                         BindingFlags.NonPublic | BindingFlags.Public |
                                         BindingFlags.Instance);

        foreach (FieldInfo fi in myObjectFields) {
            fi.SetValue (copiedObject, fi.GetValue (orig));
        }
        return copiedObject;
    }
}

最终解决方案:

上面的脚本用于克隆可编写脚本的对象,但是,我似乎在使用该解决方案时走错了路。

下面的几个人指出,对于大多数应用程序来说,合并并不是那么重要。我最初尝试加入池化,因为根据分析器,我的帧率约为 30-15 fps,我认为池化有助于改善这一点。

根据评论,我深入挖掘了一下,发现有一个名为 LogStringToConsole 的进程。我对自己说,这会不会像我的 Debug.Log 语句减慢速度一样简单!?我删除了它们,巨大的尖峰消失了。显然 Debug.Log 会导致巨大的性能问题。现在我的帧率远高于 60fps。因此,我决定不对这些对象进行池化(但在另一种情况下,我仍然对更简单的对象使用池化,这些对象每秒生成几次)。这意味着我根本不需要担心这里的脚本化对象。我现在有并实例化加载一个预制件和一个 Init 方法来设置东西,并且对象在用完时被销毁。

当我重新使用 instantiate/destroy 时,我没有注意到性能有明显变化。感谢大家的回复!

创建对象时如何,在Awake()Start()中将您想要的默认值保存到一堆变量中或将其存储在字典中,

之后要重置该值,只需创建一个名为 Reset() 的方法,然后为所有变量分配您之前存储的默认值。

例如

// method 1
Dictionary<string, object> defaultValues = new Dictionary<string, object>();
int speed = 10;
List<float> scores = new List<float>() {1.5f, 3.4f};

// method 2
SomeClass something = new SomeClass();
SomeClass defaultSomething = new SomeClass();

// and if the type can use const
string sth = "abc";
const string defaultSth = "abc";

void Awake()
{
    defaultValues.Add("speed", speed); 
    defaultValues.Add("scores", new List<float>(scores)); // new list because it is reference type, 
    //and you dont want to store reference to the list because it will be edited during runtime  

    defaultSomething = something.Clone(); // edit, but you need to implement clone by yourself for that class or do something that will make other instance of the class with same value
}

void Reset()
{
    speed = (int) defaultValues["speed"];
    scores = (List<float>) defaultValues["scores"];

    something = defaultSomething.Clone();
    sth = defaultSth;
}

缺点是每个实例都会存储自己的默认变量占用内存,以后可以改成static或者const

另一种方法是创建一个仅用于存储默认值的实例(不要在运行时修改它)并使用 C# 反射复制所有成员值

C# Using Reflection to copy base class properties

希望对您有所帮助

如果您只需要在对象停用时重置对象的值,难道您不能简单地使用:

OnEnable()
{
    default = data;
}

OnDisable()
{
    data = default;
}

这将允许您在它激活时存储/分配默认数据,并在它停用时将其数据重置回默认值。

注意!!

从大约 2014 年开始,您通常不需要在 Unity 中池化。 Unity 显着提高了性能,因此对于典型的游戏场景,没有必要。

请注意,OP 只是通过取消他们的手拼尝试来解决问题。

近年来,Unity 极大地改进了他们的

  • 垃圾收集

  • 内存处理

  • 池式处理触发启发式

  • 预制和实例化过程

视频游戏中的对象创建在现代硬件上完全微不足道; 像“子弹”这样的典型场景可能在一秒钟内只有十几个项目;并且你在未来的安静帧中有大量的奢侈时间来做 gc 等。在 Unity 的早期,你必须手动编写池来实现典型的游戏多对象需求,例如子弹。由于 Unity 的努力,现在对于典型的游戏场景(子弹、多个 NPC 等)来说完全没有必要。

如果出于某种原因,您可以合并,但就性能而言,这完全没有必要,对于典型的视频游戏需求。 2D 或 3D。