【问题标题】:How to maintain reference to object instantiate at runtime in Unity如何在 Unity 运行时维护对对象实例化的引用
【发布时间】:2019-04-17 00:06:13
【问题描述】:

我需要在运行时实例化一个对象,其中对象的数量基于一个 txt 文件(行数)。所有对象都必须是可点击的(onClick() 事件),并且当它们被按下时,必须出现工具提示。工具提示是特定的(Object1 -> Tooltip1,Object2 -> Tooltip2)。工具提示只是一个面板,它们包含在其他一些面板和按钮中。其中之一创建一个新面板。这些面板也是特定的(Tooltip1 -> Panel1 等等)。我为三个对象中的每一个创建了一个预制件。

所以,Object1 - onClick() -> Tooltip1 - onClick() -> Panel1。在运行时。

如何保持在运行时创建的对象的引用?

我的第一个解决方案

  1. 创建一个 Empty 对象并使用公共变量(Object prefab)为其分配一个脚本。
  2. 实例化对象预制件:

    for (int i = 0; i < numberOfObject; i++)
    {
       var instance = Instantiate(m_ObjectPrefab);
       instance.name = "Object_" + m_nameObject[i];
    
  3. 为每个对象实例化一个工具提示:

        var instancePanelTooltip = Instantiate(m_panelTooltipPrefab, m_canvas.transform);
        instancePanelTooltip.name = "Panel_Tooltip_" + m_nameObject[i];
    
  4. 为每个工具提示实例化一个面板:

       var instancePanel = Instantiate(m_panelPrefab, m_canvas.transform);
       instancePanel.name = "Panel_" + m_nameObject[i];
       instancePanel.SetActive(false);
       instancePanelTooltip.SetActive(false);
    
  5. 为对象添加事件处理程序

        DetectClickOnObject scriptDetectClickPanelTooltip = instance.AddComponent<DetectClickOnObject>();
        scriptDetectClickPanelTooltip.SetTooltip(instancePanelTooltip);
    
  6. 为 Tooltip 上的按钮添加事件处理程序

        DetectClickOnObject scriptDetectClickPanel = buttonOpenPanel.AddComponent<DetectClickOnObject>();
        scriptDetectClickPanel.SetPanel(instancePanel);
    }
    

此解决方案的问题:

  1. 我将为文件的每一行实例化 3 个对象(对象、工具提示、面板)。 Object 可以,但 Tooltip 和 Panel 不行,因为只有一个 Tooltip 处于活动状态(Panel 也一样)。

  2. 我只是避免了引用问题,因为我在同一个地方创建了所有对象(每个元素一个),但是如果我需要在没有引用的情况下访问 Tooltip2 或 Panel3(我是尽量避免 Find 和类似的)。

第一个解决方案的结论:解决方案有效,但我认为有更好的方法(避免创建太多对象并以正确的方式保持引用)。

我的第二个解决方案(指南):

  1. 我正在尝试创建一个类来保持对所有在运行时创建的对象的引用。
  2. 我想为每一行创建一个对象实例,但我只需要一个工具提示和面板用于所有对象,并根据单击的对象更改属性。所以 Object 是在运行时创建的,但 Tooltip 和 Panel 已经在场景中但没有激活。
  3. 我需要一个注册事件管理器来在运行时在对象上添加 onClick() 事件,并且它需要根据单击的对象处理要在工具提示和面板上设置的属性。

第二种解决方案的问题: 参考 1)我尝试关注 that,但最终一无所获。我在单身,静态和其他东西之间迷失了。参考2)我认为这很容易,我只需要删除一些第一个解决方案。参考 3) 如果我没有类参考管理器,我将无能为力。

我在寻找什么:

  1. 第一个解决方案这么糟糕吗?如果我看代码我很反感,它与优雅(或类似的东西)相去甚远。
  2. 您能否建议我如何使用参考管理器在运行时跟踪参考创建?以及如何使用?

@Behnam Sattar 建议: 作为 DataModell 类,

public class DataModelPOI
{
    public string m_namePOI { get; private set; }
    public string m_locationPOI { get; private set; }
    public Vector2d m_positionPOI { get; private set; }

    public GameObject m_gameObject_POI;
    public GameObject m_gameObjectTooltip;
    public GameObject m_gameObjectPanel;

    public DataModelPOI(string namePOI, string locationPOI, Vector2d positionPOI)
    {
        this.m_namePOI = namePOI;
        this.m_locationPOI = locationPOI;
        this.m_positionPOI = positionPOI;
    }
}

作为数据管理员,

public class POIManager : MonoBehaviour
{
    List<DataModelPOI> dataCollectionPOI = new List<DataModelPOI>();

    void Start()
    {
        ReadFile();

        SpawnPOI();
    }

    void Update()
    {
        int count = dataCollectionPOI.Count;
        for (int i = 0; i < count; i++)
        {
            UpdatePOIPosition();
        }
    }

    void ReadFile()
    {
        TakeDataFromFile();
        for (int i = 0; i < ength; i++)
        {
            DataModelPOI dataPOI = new DataModelPOI(m_namePoi[i], m_namePoi[i], _locations[i]);
            dataCollectionPOI.Add(dataPOI);
        }
    }

    private void SpawnPOI()
    {
        for (int i = 0; i < dataCollectionPOI.Count; i++)
        {
            DataModelPOI dataPOI = dataCollectionPOI[i];
            var instance = Instantiate(m_POIPrefab);
            instance.name = "POI_" + m_namePoi[i];
            dataPOI.m_gameObject_POI = instance;
            dataPOI.m_gameObjectTooltip = m_panelTooltipPOI;
            dataPOI.m_gameObjectPanel = m_panelPOI;
        }
    }

现在我需要先注册 Event 关联到 GameObject 实例化。我想在我的 EventManager 中这样做。如何在 DataManager 中创建和提供的 EventManager 类中指向 dataCollectionPOI?感谢您的宝贵时间。

【问题讨论】:

    标签: c# unity3d reference


    【解决方案1】:

    根据我的理解,您的问题主要是设计问题。在这个答案的第一部分,我给你一个建议,让你做设计并保持对你的对象的引用。在第二部分中,我将为您提供一些有关性能的提示。

    [我使用 RootObject 而不是 Object 来指代您创建的主要 GameObject。]

    设计

    让我们分解我们的需求,然后为每个需求提出解决方案。

    首先我们要读取一些文本文件,然后从中获取一些数据。此数据稍后将用于创建游戏对象。现在让我们只关注数据本身。

    我们想要的是一个管理器类,它为我们读取文件并以某种形式存储数据。我们稍后会访问此管理器并要求我们提供数据以创建游戏对象。

    这个管理器类将我们的数据存储在数据对象的集合中[注意这里我们讨论的是普通对象而不是 Unity 的游戏对象]。你需要根据你拥有的每一行文本来设计这个数据类。或者,您也可以在此处保留对 GameObjects 的引用。

    假设您正在从每一行读取三个字符串值,分别名为 ValueOneValueTwoValueThree,并且您希望保留对名为 RootObjectToolTipPanel 的三个游戏对象的引用.为此,您可以定义以下类:

    public class DataModel {
    
      // Values read from text file.
      public string valueOne { get; private set; }
      public string valueTwo { get; private set; }
      public string valueThree { get; private set; }
    
      // Placeholders for GameObjecs created at runtime.
      public GameObject rootObject;
      public GameObject tooltipObject;
      public GameObject panelObject;
    
      public DataModel(string valueOne, string valueTwo, string valueThree){
        this.valueOne = valueOne;
        this.valueTwo = valueTwo;
        this.valueThree = valueThree;
      }
    }
    

    然后在您的经理类中,您可以创建一个集合(例如 List)来保存您的数据。您的管理器类应该在某个时候读取文本文件并使用 DataModel 实例填充此列表。它会是这样的:

    public class DataManager {
    
      List<DataModel> dataCollection = new List<DataModel>();
    
      public void ReadFile() {
    
        // Here you need to read the file and get the values you need.
        // The actual code should be different from what I'm putting here.
    
        foreach(string line in lines) {
          // You get valueOne, valueTwo and valueThree
          // from each line and maybe prepare them 
          // (maybe you need conversion from string to int)
    
          DataModel data = new DataModel(valueOne, valueTwo, valueThree);
          dataCollection.Add(data);
        }
      }
    }
    

    在你调用 manager 上的方法读取数据后,你就可以随时为你准备好数据。

    是时候根据数据创建对象并保存引用了。

    for (int i = 0; i++; i <= manager.dataCollection.Count) {
      DataModel data = manager.dataCollection[i];
      data.rootObject = instantiate() // You instantiate the root GameObject here.
      data.tooltip = instantiate() // You instantiate the tooltop GameObject here.
      data.panel = instantiate() // You instantiate the panel GameObject here.
    }
    

    完成。现在你有一个管理器类,它引用了所有数据以及基于这些数据创建的游戏对象。

    性能

    在运行时执行这一切可能不是一个好主意,因为它可能会导致游戏中的帧丢失。如果这是一个问题,您可以尝试对象池。如果您搜索它,您应该能够找到有关如何执行对象池的精彩教程。

    如果您最终没有使用对象池,您仍然可以通过每帧仅实例化一个 GameObject 来弥补任何性能下降。这可以使用协程来完成。你只需要在你的实例化循环中做一个yield return new WaitForEndOfFrame()

    最后说明

    请记住,这只是一个建议,我认为您的问题没有一个最佳答案。在决定一种解决方案之前,请确保您尝试了解可以使用哪些工具并尝试所有工具。 :)

    【讨论】:

    • 感谢您的回答! FIFS(先到先得)。我试着听从你的建议。我创建了一个 DataModel 类和一个 DataManager 类。 DataModel 与您编写的内容接近。在 DataManager 类中,我读取所有数据,将元素添加到 DataModel 对象,然后生成所有实例生成。现在在我的 EventManager 类中,我需要引用在 DataManager 中创建的 dataCollection 对象,我该怎么做?
    • 您可以通过数据管理器类上的方法公开它。像这样:` public List GetData() { return new List(this.dataColllection); } ` 请注意,这里我正在制作原始列表的副本,其中包含对原始数据模型类的引用。这样可以保证调用该方法的消费者不会乱用原来的列表。
    • 显然我需要在脚本中定义一个 DataManager 类的对象,我想在其中使用暴露的方法。
    • 是的,您可以定义一个单例,也可以将类转换为 MonoBehaviour 并将其附加到场景中的 GameObject。
    【解决方案2】:

    您基本上走在正确的轨道上,但您可以分离(或“对象化”;)哪些脚本保留哪些引用。将管理预制件内部工作的脚本保存在这些预制件中。

    工具提示的类似单例模​​式绝对是要走的路。对于不能使用单例的地方,例如在可点击的对象中,使用更类似于依赖注入的东西(以节省 Finds)和预存储参考:

    var obj = Instantiate(...);
    obj.GetComponent<SomeManagerScript>.SomeDependency = SingletonInstance;
    obj.GetComponent<StoredReferenceScript>.TextPanel.Text = "Some Text";
    

    对于事件,您可以通过几种方式处理它们。一,每个可点击管理自己的绑定/功能;或者两个,每个可点击通知一个全局管理者它被点击了,管理者决定要采取的行动。

    【讨论】:

    • 感谢您的回答!我会花一些时间考虑您的建议,然后我会回来提供一些反馈。
    【解决方案3】:

    基本上,您需要在Runtime 创建一个对象并为该特定对象设置数据。

    这是最简单的方法

    首先创建脚本第一个可以在运行时Create 一个对象,第二个是您的数据设置器脚本,附加到您的预制件中

    因此,当您在运行时创建对象时,您可以选择dataSetter 的引用并设置特定数据。

    希望下面给出的代码对你有用。

    GameObject object  = Instantiate(gameObject,parent)
    DataSetter s = object.GetComponent<DataSetter>().toolTip = "YourTooltip";
    

    DataSetter 脚本工具提示中是一个字符串变量。 您还可以声明文本变量并为其分配文本。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-06
      • 2018-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-20
      • 2022-01-17
      • 1970-01-01
      相关资源
      最近更新 更多