using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using Unity.VisualScripting; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.UIElements; using static UnityEditor.PlayerSettings; using static UnityEngine.GraphicsBuffer; [ExecuteAlways] public class GameManager : MonoBehaviour { public Transform ConnectionParent; public Transform NodeParent; public GameObject NodePrefab; public static GameManager Instance { get; private set; } public bool regenerateOnChange = false; [HideInInspector] public float maxConnectionLength = 6; [HideInInspector] public int nodeCount = 100; [HideInInspector] public int hoverRadiusCon = 50; [HideInInspector] public int hoverRadiusNode = 50; [HideInInspector] public int selectedLevel = -1; [SerializeField] [HideInInspector] public List connections = new List(); [SerializeField] [HideInInspector] public List nodes = new List(); [SerializeField] [HideInInspector] public List levels = new List(); [Serializable] public class Connection { public Node nodeA, nodeB; public bool allowed = true; public bool hovered = false; public LineRenderer lineRenderer; } void Awake() { if (Instance != null && Instance != this) { if (Application.isPlaying) Destroy(gameObject); return; } Instance = this; if (Application.isPlaying) DontDestroyOnLoad(gameObject); } public void GenerateAlongSphere() { connections.Clear(); nodes.Clear(); for (int i = NodeParent.childCount - 1; i >= 0; i--) DestroyImmediate(NodeParent.GetChild(i).gameObject); float radius = 20f; float goldenRatio = (1f + Mathf.Sqrt(5f)) / 2f; float angleIncrement = 2f * Mathf.PI * goldenRatio; for (int i = 0; i < nodeCount; i++) { float t = (float)i / nodeCount; // von 0 bis 1 float inclination = Mathf.Acos(1f - 2f * t); float azimuth = angleIncrement * i; float x = Mathf.Sin(inclination) * Mathf.Cos(azimuth); float y = Mathf.Sin(inclination) * Mathf.Sin(azimuth); float z = Mathf.Cos(inclination); Vector3 pos = new Vector3(x, y, z) * radius; var auto = PrefabUtility.InstantiatePrefab(NodePrefab, NodeParent) as GameObject; auto.transform.localPosition = pos; nodes.Add(auto.GetComponent()); } } public void GenerateConnections() { connections.Clear(); foreach (LineRenderer line in ConnectionParent.GetComponentsInChildren()) DestroyImmediate(line.gameObject); foreach (Node nodeA in nodes) { if (nodeA == null) continue; foreach (Node nodeB in nodes) { if (nodeB == null) continue; bool conExists = false; if (nodeA == nodeB || Math.Abs(Vector3.Distance(nodeA.transform.position, nodeB.transform.position)) > maxConnectionLength) continue; foreach (Connection con in connections) { if ((con.nodeA == nodeA && con.nodeB == nodeB) || (con.nodeA == nodeB && con.nodeB == nodeA)) { conExists = true; break; } } if (!conExists) { var dummy = new GameObject("dummy"); dummy.transform.SetParent(ConnectionParent); var newCon = new Connection { nodeA = nodeA, nodeB = nodeB, lineRenderer = dummy.AddComponent() }; newCon.lineRenderer.enabled = false; newCon.lineRenderer.material = new Material(Shader.Find("Sprites/Default")); newCon.lineRenderer.positionCount = 3; connections.Add(newCon); } } } } private void Update() { foreach (var con in connections) { if(Application.isPlaying && !con.allowed) { con.lineRenderer.enabled = false; continue; } float width = (con.hovered ? 0.6f : 0.3f) * (con.allowed ? 1f : 0.3f); con.lineRenderer.startColor = con.allowed ? con.nodeA.transform.GetChild(0).GetComponent().sharedMaterial.color : new Color(0.2f, 0.2f, 0.2f); con.lineRenderer.endColor = con.allowed ? con.nodeB.transform.GetChild(0).GetComponent().sharedMaterial.color : new Color(0.2f, 0.2f, 0.2f); con.lineRenderer.startWidth = width; con.lineRenderer.endWidth = width; con.lineRenderer.SetGreatCircleArc(con.nodeA.transform.position, con.nodeB.transform.position, 5, 20.5f); con.lineRenderer.enabled = true; } } public void LoadLevelData(int index) { if(index >= levels.Count) { Debug.LogWarning("LevelIndex out of range"); return; } connections.Clear(); nodes.Clear(); for (int i = NodeParent.childCount - 1; i >= 0; i--) DestroyImmediate(NodeParent.GetChild(i).gameObject); foreach (LineRenderer line in ConnectionParent.GetComponentsInChildren()) DestroyImmediate(line.gameObject); foreach(LevelData.NodeData nodeData in levels[index].nodes) { var auto = PrefabUtility.InstantiatePrefab(NodePrefab, NodeParent) as GameObject; auto.transform.localPosition = nodeData.position; nodes.Add(auto.GetComponent()); auto.GetComponent().Owner = nodeData.owner; } foreach (LevelData.ConnectionData conData in levels[index].connections) { var dummy = new GameObject("dummy"); dummy.transform.SetParent(ConnectionParent); var newCon = new Connection { nodeA = nodes[conData.nodeAIndex], nodeB = nodes[conData.nodeBIndex], allowed = conData.allowed, lineRenderer = dummy.AddComponent() }; newCon.lineRenderer.enabled = false; newCon.lineRenderer.material = new Material(Shader.Find("Sprites/Default")); newCon.lineRenderer.positionCount = 3; connections.Add(newCon); } selectedLevel = index; maxConnectionLength = levels[index].maxConnectionLength; nodeCount = nodes.Count; Debug.Log("Loaded Level " + index); } public void SaveLevelData(int index = -1) { LevelData data = new LevelData(); data.maxConnectionLength = maxConnectionLength; // Nodes speichern foreach (var node in nodes) { data.nodes.Add(new LevelData.NodeData { position = node.transform.localPosition, owner = node.Owner }); } // Connections speichern foreach (var con in connections) { int idxA = nodes.IndexOf(con.nodeA); int idxB = nodes.IndexOf(con.nodeB); if (idxA >= 0 && idxB >= 0) { data.connections.Add(new LevelData.ConnectionData { nodeAIndex = idxA, nodeBIndex = idxB, allowed = con.allowed }); } } if (index == -1 || index >= levels.Count) levels.Add(data); else levels[index] = data; int newIndex = index < 0 ? levels.Count - 1 : index; selectedLevel = newIndex; Debug.Log("Saved Level " + newIndex); } } [Serializable] public class LevelData { [Serializable] public class NodeData { public Vector3 position; public int owner; } [Serializable] public class ConnectionData { public int nodeAIndex; public int nodeBIndex; public bool allowed = true; } public float maxConnectionLength = 0; public List nodes = new List(); public List connections = new List(); } #if UNITY_EDITOR [CustomEditor(typeof(GameManager))] public class GameManagerEditor : Editor { int clickedConIdx = -1; public override void OnInspectorGUI() { DrawDefaultInspector(); GameManager gm = (GameManager)target; GUILayout.Label("Generation Parameters", EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); gm.nodeCount = EditorGUILayout.IntSlider("Node Count", gm.nodeCount, 0, 200); gm.maxConnectionLength = EditorGUILayout.Slider("Max Connection Length", gm.maxConnectionLength, 0, 100); if (EditorGUI.EndChangeCheck() && gm.regenerateOnChange) { gm.GenerateAlongSphere(); gm.GenerateConnections(); SceneView.RepaintAll(); } GUILayout.Space(10); GUILayout.Label("Connection Gizmos", EditorStyles.boldLabel); gm.hoverRadiusCon = EditorGUILayout.IntSlider("Hover Radius (Connection)", gm.hoverRadiusCon, 0, 100); gm.hoverRadiusNode = EditorGUILayout.IntSlider("Hover Radius (Node)", gm.hoverRadiusNode, 0, 100); GUILayout.BeginHorizontal(); if (GUILayout.Button("Distribute Nodes")) { gm.GenerateAlongSphere(); SceneView.RepaintAll(); } if (GUILayout.Button("Generate Connections")) { gm.GenerateConnections(); SceneView.RepaintAll(); } if (GUILayout.Button("Generate Both")) { gm.GenerateAlongSphere(); gm.GenerateConnections(); SceneView.RepaintAll(); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button("Fetch all Nodes")) { Node[] allNodes = gm.NodeParent.GetComponentsInChildren(); Debug.Log("Added " + (allNodes.Length - gm.nodeCount) + " new nodes"); gm.nodes = allNodes.ToList(); gm.nodeCount = allNodes.Length; } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUILayout.BeginVertical(); for(int i = 0; i < gm.levels.Count; i++) { if (gm.selectedLevel == i) GUILayout.Label("▷ Level " + i, EditorStyles.boldLabel); else GUILayout.Label(" Level " + i); } GUILayout.EndVertical(); GUILayout.BeginVertical(); for (int i = 0; i < gm.levels.Count; i++) { if (GUILayout.Button("Load")) gm.LoadLevelData(i); } GUILayout.EndVertical(); GUILayout.BeginVertical(); for (int i = 0; i < gm.levels.Count; i++) { GUILayout.BeginHorizontal(); if (gm.selectedLevel == i) if (GUILayout.Button("Save")) gm.SaveLevelData(i); if (GUILayout.Button("Delete")) { gm.levels.Remove(gm.levels[i]); if (gm.selectedLevel == i) gm.selectedLevel = -1; if (gm.selectedLevel > i) gm.selectedLevel--; if(gm.levels.Count <= 0) gm.selectedLevel = -1; } GUILayout.EndHorizontal(); } GUILayout.EndVertical(); GUILayout.EndHorizontal(); if (GUILayout.Button("Save as new")) gm.SaveLevelData(); } private void OnSceneGUI() { Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual; GameManager gm = FindFirstObjectByType(); if (gm == null) return; GameObject selected = Selection.activeGameObject; bool allowHover = false; if (selected == gm.gameObject) allowHover = true; else { Node node = selected?.GetComponent(); if (node != null && gm.nodes.Contains(node)) allowHover = true; } if (!allowHover) return; if (gm.connections == null || gm.connections.Count == 0) return; Camera cam = SceneView.lastActiveSceneView.camera; if (cam == null) return; Vector2 mouse = Event.current.mousePosition; mouse.y = cam.pixelHeight - mouse.y; Vector3 mousePos = new Vector3(mouse.x, mouse.y, 0); int closestIdx = -1; float closestDist = float.MaxValue; bool cancelHover = false; // Hover-Ermittlung Connections for (int i = 0; i < gm.connections.Count; i++) { gm.connections[i].hovered = false; if (cancelHover) continue; var con = gm.connections[i]; Vector3 a = cam.WorldToScreenPoint(con.nodeA.transform.position); Vector3 b = cam.WorldToScreenPoint(con.nodeB.transform.position); Vector3 ab = b - a; Vector3 am = mousePos - a; float t = Mathf.Clamp01(Vector3.Dot(am, ab) / ab.sqrMagnitude); Vector3 proj = a + t * ab; float dist = Vector3.Distance(mousePos, proj); if (Vector3.Distance(a, mousePos) <= gm.hoverRadiusNode || Vector3.Distance(b, mousePos) <= gm.hoverRadiusNode) cancelHover = true; if (dist < closestDist && dist < gm.hoverRadiusCon) { closestDist = dist; closestIdx = i; } } if (closestIdx < 0) cancelHover = true; if (!cancelHover) gm.connections[closestIdx].hovered = true; // Klick-Behandlung Event e = Event.current; if (e.type == EventType.MouseDown && e.button == 0 && !cancelHover) { clickedConIdx = closestIdx; } if (e.type == EventType.MouseUp && e.button == 0) { if (closestIdx == clickedConIdx && !cancelHover) { gm.connections[closestIdx].allowed = !gm.connections[closestIdx].allowed; e.Use(); // Event als verarbeitet markieren } clickedConIdx = -1; } // SceneView kontinuierlich aktualisieren SceneView.RepaintAll(); } } #endif public static class LineRendererExtensions { // Zeichnet eine Quadratic Bezier Kurve (Start, Control, End) mit N Punkten public static void SetQuadraticBezier(this LineRenderer line, Vector3 start, Vector3 control, Vector3 end, int segments) { line.positionCount = segments + 1; for (int i = 0; i <= segments; i++) { float t = i / (float)segments; // Quadratic Bezier Formel Vector3 point = (1 - t) * (1 - t) * start + 2 * (1 - t) * t * control + t * t * end; line.SetPosition(i, point); } } public static void SetGreatCircleArc(this LineRenderer line, Vector3 start, Vector3 end, int segments, float radius) { // Normalize to sphere radius Vector3 startNorm = start.normalized * radius; Vector3 endNorm = end.normalized * radius; line.positionCount = segments + 1; for (int i = 0; i <= segments; i++) { float t = i / (float)segments; // spherical linear interpolation (slerp) between start and end Vector3 point = Vector3.Slerp(startNorm, endNorm, t); line.SetPosition(i, point); } } }