I need to instantiate an object at runtime, where the number of objects are based on a txt file (number of lines). All objects need to be clickable (onClick() event) and when they are pressed a tooltip must appear. Tooltips are specific ( Object1 -> Tooltip1, Object2 -> Tooltip2). Tooltips are just a Panel and they consist in some other Panels and Buttons. One of these create a new Panel. Also these Panel are specific (Tooltip1 -> Panel1 and so on). I create a prefab for each of three objects.
So, Object1 - onClick() -> Tooltip1 - onClick() -> Panel1. At runtime.
How can I keep reference of an object create at runtime?
My first solution:
Instantiate Object prefab:
for (int i = 0; i < numberOfObject; i++)
{
var instance = Instantiate(m_ObjectPrefab);
instance.name = "Object_" + m_nameObject[i];
Instantiate one Tooltip for each Object:
var instancePanelTooltip = Instantiate(m_panelTooltipPrefab, m_canvas.transform);
instancePanelTooltip.name = "Panel_Tooltip_" + m_nameObject[i];
Instantiate one Panel for each Tooltip:
var instancePanel = Instantiate(m_panelPrefab, m_canvas.transform);
instancePanel.name = "Panel_" + m_nameObject[i];
instancePanel.SetActive(false);
instancePanelTooltip.SetActive(false);
Add event handler for Object
DetectClickOnObject scriptDetectClickPanelTooltip = instance.AddComponent<DetectClickOnObject>();
scriptDetectClickPanelTooltip.SetTooltip(instancePanelTooltip);
Add event handler for button on Tooltip
DetectClickOnObject scriptDetectClickPanel = buttonOpenPanel.AddComponent<DetectClickOnObject>();
scriptDetectClickPanel.SetPanel(instancePanel);
}
Issue with this solution:
I will instantiate 3 object (Object, Tooltip, Panel) for each line of file. For Object it is okay, but it is not with Tooltip and Panel, because only one Tooltip is active among all (same for Panel).
I just avoid the problem of reference because I create in the same place all objects (one for each element), but what can I do if I need to get access to Tooltip2 or Panel3 without a reference (I am trying to avoid Find and similar).
Conclusion of the first solution: Solution is working, but I think there is a better way of doing that (avoid create so much object and keep reference in the right way).
My second solution (guidelines):
Problems with second solution: Referring to 1) I tried to follow that, but I ended up with nothing. I am lost between singleton, static and something else. Referring to 2) I think it can be easy, I just need to cut away some of the first solution. Referring to 3) I can not do more if I do not have a class reference manager.
What I am looking for:
@Behnam Sattar suggestion: As DataModell class,
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;
}
}
As DataManager,
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;
}
}
Now I need to register Event associate to GameObject instantiate before. I want to do that in my EventManager. How can I point to dataCollectionPOI
in EventManager class created and feeded in DataManager? Thanks for your time.
答案 0 :(得分:1)
Based on my understanding your question is mostly a design question. In first part of this answer, I'm giving you a suggestion for doing the design and keeping a reference to your objects. In second part I'll give you some tips regarding performance.
[I'm using RootObject instead of Object to refer you the main GameObject you create.]
Design
Let's break down our needs and then come up with a solution for each.
First we want to read some text file and then get some data from that. This data will be used later for creation of GameObjects. For now let's just focus on the data itself.
What we want here is a manager class which reads the file for us and stores the data in some form. We access this manager later and ask for our data in order to create the GameObjects.
This manager class stores our data in a collection of data objects [notice here we're talking about plain objects and not Unity's GameObjects]. You need to design this data class based on each line of text you have. Optionally, you can also keep the references to GameObjects here too.
Assume you're reading three string values from each line, named ValueOne
, ValueTwo
, and ValueThree
, and you want to keep reference to three GameObjects called RootObject
, ToolTip
and Panel
. For such purpose you can define following class:
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;
}
}
Then in your manager class you can create a collection (a List for example) for holding your data. Your manager class should read the text file at some point and populate this list with instances of DataModel. It will be something like this:
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);
}
}
}
After you call the method on manager to read the data, you'll have your data prepared for you whenever you want to use it.
It's time to create objects based on the data and save the reference.
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.
}
Done. Now you have a manager class which has reference to all the data and also GameObjects which are created based on this data.
Performance
It might not be a good idea to do this all in runtime as it might cause frame drops in your game. If this is a problem you can try object pooling. If you search for that you should be able to find great tutorials on how to perform object pooling.
If you ended up not using object pooling, you can still remedy any performance drops by instantiating only one GameObject per frame. This can be done using Coroutines. You just need to do a yield return new WaitForEndOfFrame()
in your loop of instantiation.
Final Note
Keep in mind that this all a suggestion and I don't think there is one single best answer to your question. Make sure you try to understand what tools are at your disposal and try them all before deciding on one solution. :)
答案 1 :(得分:1)
基本上,您需要在 Runtime
创建一个对象并为该特定对象设置数据。
这是最简单的方法
首先创建脚本第一个可以在运行时Create
一个对象,第二个是与预制件附加的数据设置器脚本
因此,当您在运行时创建对象时,您可以选择 dataSetter
的引用并设置特定数据。
希望下面给出的代码对您有用。
GameObject object = Instantiate(gameObject,parent)
DataSetter s = object.GetComponent<DataSetter>().toolTip = "YourTooltip";
在 DataSetter
脚本工具提示中是一个字符串变量。
您还可以声明文本变量并为其分配文本。
答案 2 :(得分:0)
您基本上是在正确的轨道上,但是您可以分离(或“对象化”;)哪些脚本保留哪些引用。在这些预制件中保留用于管理预制件内部工作的脚本。
一种适合工具提示的单例模式绝对是可行的方法。对于无法使用单例的地方(例如在可单击对象中),请使用类似于依赖项注入(保存在Finds
上)和预存储的引用:
var obj = Instantiate(...);
obj.GetComponent<SomeManagerScript>.SomeDependency = SingletonInstance;
obj.GetComponent<StoredReferenceScript>.TextPanel.Text = "Some Text";
对于事件,您可以通过几种方式处理它们。第一,每个可点击项管理自己的绑定/功能;或两个,每个可点击项都会通知全局管理员该操作已被单击,然后该管理员确定要采取的措施。