Newer
Older
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using System.Linq;
using UnityEngine.UI;
public class FactExplorer : MonoBehaviour
{
#region InspectorVariables
[Header("PrefabComponents")]
[SerializeField] private Transform factParentsUI;
[SerializeField] private Transform mainFactUI;
[SerializeField] private Transform factChildrenUI;
[SerializeField] private Transform linesUI;
[Header("Prefabs")]
[SerializeField] private GameObject factSpotPrefab;
[SerializeField] private GameObject parentLine;
[SerializeField] private GameObject childLine;
#endregion InspectorVariables
private Fact mainFact;
private List<Fact> parentFacts;
private List<Fact> childFacts;
#endregion Variables
#region UnityMethods
private void Update()
{
DestroyIfClickedOutside();
}
public void Initialize(Fact fact, Vector3 factPosition)
{
mainFact = fact;
parentFacts = GetParentFacts();
childFacts = GetChildFacts();
Debug.Log($"Parents of {mainFact.Label}: {string.Join(", ", parentFacts.Select(cf => cf.Label))}");
Debug.Log($"Children of {mainFact.Label}: {string.Join(", ", childFacts.Select(cf => cf.Label))}");
UpdateFactExplorerUI();
MoveToPreferredPosition(factPosition);
#endregion UnityMethods
#region Implementation
private List<Fact> GetParentFacts()
{
//TODO: TSC how to get all current facts without this dumb workaround
var allFacts = DisplayFacts.displayedFacts.Values.Select(x => x.GetComponent<FactWrapper>().fact);
List<Fact> _parentFacts = new();
// get all facts that reference the mainFact
foreach (Fact f in allFacts)
if (GetReferencedPids(f).Contains(mainFact.Id)) // if f contains reference to mainFact
_parentFacts.Add(f);
return _parentFacts;
}
private List<Fact> GetChildFacts()
{
List<Fact> childFacts = new();
// get all properties of Type mainFactType that are strings and start with "pid"
foreach (string factId in GetReferencedPids(mainFact))
childFacts.Add(StageStatic.stage.factState[factId]);
return childFacts;
}
private void UpdateFactExplorerUI()
{
SpawnUIFacts(factParentsUI, parentFacts);
SpawnUIFacts(mainFactUI, new List<Fact>() { mainFact });
SpawnUIFacts(factChildrenUI, childFacts);
// Force rebuild of FactExplorer layout, since the positions of the factObjects will be wrong otherwise
LayoutRebuilder.ForceRebuildLayoutImmediate(transform.GetComponent<RectTransform>());
SpawnParentLines(factParentsUI.gameObject, mainFactUI);
SpawnChildLines(factChildrenUI.gameObject, mainFactUI);
}
private void DestroyIfClickedOutside()
{
// Destroy on tab press or left click outside of FactExplorer
if (Input.GetKeyDown(KeyCode.Tab)
|| (Input.GetMouseButtonDown(0) && !RectTransformUtility.RectangleContainsScreenPoint(transform.GetComponent<RectTransform>(), Input.mousePosition, null)))
{
Destroy(gameObject);
}
}
private void MoveToPreferredPosition(Vector3 prefPos)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(transform.GetComponent<RectTransform>());
// calculate opimal position
var deltaPos = mainFactUI.position - prefPos;
transform.position -= deltaPos;
// clamp position, so that no parts of the FactExplorer are out of screen
RectTransform feRect = GetComponent<RectTransform>();
Vector2 apos = feRect.anchoredPosition;
apos.x = Mathf.Clamp(apos.x, 0, Screen.width - feRect.sizeDelta.x);
apos.y = Mathf.Clamp(apos.y, -Screen.height + feRect.sizeDelta.y, 0);
feRect.anchoredPosition = apos;
}
#endregion Implementation
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#region Spawner
private void SpawnUIFacts(Transform uiParent, List<Fact> toSpawn)
{
// if uiParent has no children: deactivate it
if (toSpawn.Count == 0)
uiParent.gameObject.SetActive(false);
foreach (Fact f in toSpawn)
{
var spot = Instantiate(factSpotPrefab, uiParent);
// TODO: this link to DisplayFacts is not ideal: maybe refactor to SciptableObject or such
var display = f.instantiateDisplay(DisplayFacts.prefabDictionary[f.GetType()], spot.transform);
display.transform.localPosition = Vector3.zero;
}
}
private void SpawnParentLines(GameObject parent, Transform mainFactUI)
{
var mainTransform = mainFactUI.GetComponent<RectTransform>();
var factWidth = mainTransform.rect.width;
// transform.positions are weird due to LayoutGroups => manually calculate offset
float xOffset = -factParentsUI.GetComponent<RectTransform>().rect.width / 2 + factWidth / 2;
float yOffset = transform.GetComponent<VerticalLayoutGroup>().spacing;
parent.ForAllChildren(par =>
{
// position at the bottom center of par rect
var position = par.transform.TransformPoint(new Vector2(0, par.GetComponent<RectTransform>().rect.yMin));
var line = Instantiate(parentLine, position, Quaternion.identity, par.transform);
var uiLine = line.GetComponent<UILine>();
uiLine.points = new List<Vector2>() { Vector2.zero, new Vector2(-xOffset, -yOffset) };
xOffset += factWidth + factParentsUI.GetComponent<HorizontalLayoutGroup>().spacing;
});
}
private void SpawnChildLines(GameObject parent, Transform mainFactUI)
{
var mainTransform = mainFactUI.GetComponent<RectTransform>();
var factWidth = mainTransform.rect.width;
// transform.positions are weird due to LayoutGroups => manually calculate offset
float xOffset = -factChildrenUI.GetComponent<RectTransform>().rect.width / 2 + factWidth / 2;
float yOffset = -transform.GetComponent<VerticalLayoutGroup>().spacing;
parent.ForAllChildren(par =>
{
// position at the top center of par rect
var position = par.transform.TransformPoint(new Vector2(0, par.GetComponent<RectTransform>().rect.yMax));
var line = Instantiate(childLine, position, Quaternion.identity, par.transform);
var uiLine = line.GetComponent<UILine>();
uiLine.points = new List<Vector2>() {
Vector2.zero,
new Vector2(0, -yOffset/2),
new Vector2(-xOffset, -yOffset/2),
new Vector2(-xOffset, -yOffset)
};
xOffset += factWidth + factChildrenUI.GetComponent<HorizontalLayoutGroup>().spacing;
});
}
#endregion Spawner
#region Helper
/// <summary>
/// Check all fields of a Fact for references to other facts and return their ids
/// </summary>
/// <param name="f"></param>
/// <returns>An Enumerable containing the ids of facts referenced by the input fact</returns>
private static IEnumerable<string> GetReferencedPids(Fact f)
{
return (f.GetType().GetFields()
.Where(field => field.FieldType == typeof(string) && field.Name.ToLower().StartsWith("pid"))
).Select(fi => (string)fi.GetValue(f));
}
#endregion Helper
}