2

我最近一直在尝试统一对象池,以同时加快多个游戏对象的实例化。

然而,由于这些是相当复杂的对象,当它们回到池中时,我需要重置它们。

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

所以在伪代码中,我会有一个带有public MyScriptableData dataand的类public MyScriptableData defaults

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

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

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

我有3个主要问题:

A)我不确定如何实际实现这一点。在我看来,在第 2 步中,默认值将被更改。因此,重置为默认值将无济于事。我想过使用创建我的可编写脚本对象的新实例

data = ScriptableObject.CreateInstance<MyScriptableData>();

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

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

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

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

尝试的解决方案(但错误,见下文):我为 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 方法来进行设置,并且对象在用完时被销毁。

当我重新使用实例化/销毁时,我没有注意到性能有显着变化。感谢所有的回复!

4

3 回答 3

4

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

OnEnable()
{
    default = data;
}

OnDisable()
{
    data = default;
}

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

于 2016-06-16T09:20:09.807 回答
2

当您创建对象时如何,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# 使用反射复制基类属性

希望这可以帮助

于 2016-06-16T01:07:46.857 回答
-9

笔记!!

从大约 2014 年开始,通常您不需要在 Unity 中进行池化。Unity 极大地提高了性能,因此对于典型的游戏场景,这不是必需的。

请注意,OP 仅通过删除他们的手动池尝试就消除了该问题。

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

  • 垃圾收集

  • 内存处理

  • 池状处理触发启发式

  • 预制和实例化过程

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

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

于 2016-06-16T16:58:38.447 回答