using System; using System.Collections.Generic; using System.Dynamic; using Unity.VisualScripting; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.UIElements; using static UnityEngine.GraphicsBuffer; [ExecuteAlways] public class GameManager : MonoBehaviour { [HideInInspector] public static GameManager instance; public Transform NodeParent; public GameObject NodePrefab; [Header("Node Generation")] [Range(0, 20)] public float maxConnectionLength = 6; [Range(0, 1000)] public int nodeCount = 100; [Range(0, 100)] public float hoverRadius = 50; [Space] [Range(0,10)] public int editorConnectionAllowedWidth = 3; public Color editorConnectionAllowedColor = Color.white; [Space] [Range(0, 10)] public int editorConnectionHoveredWidth = 2; public Color editorConnectionHoveredColor = Color.gray; [Space] [Range(0,10)] public int editorConnectionForbiddenWidth = 1; public Color editorConnectionForbiddenColor = Color.black; private List nodes = new List(); [SerializeField] public List connections = new List(); [Serializable] public class Connection { public Node nodeA, nodeB; public bool allowed; public LineRenderer lineRenderer; ~Connection() { DestroyImmediate(lineRenderer.transform.parent); } } 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 (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 = Instantiate(new GameObject(), transform); dummy.name = "Dummy"; 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); } } } } #if UNITY_EDITOR void OnDrawGizmos() { if (connections == null) return; // Linien respektieren den Z-Buffer Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual; foreach (var con in connections) { if(!con.allowed) continue; Handles.color = Color.red; Handles.DrawLine(con.nodeA.transform.position, con.nodeB.transform.position); } } #endif private void Update() { foreach (var con in connections) { if (!con.allowed) { con.lineRenderer.enabled = false; continue; } con.lineRenderer.enabled = true; con.lineRenderer.startColor = con.nodeA.transform.GetChild(0).GetComponent().material.color; con.lineRenderer.endColor = con.nodeB.transform.GetChild(0).GetComponent().material.color; con.lineRenderer.startWidth = 0.3f; con.lineRenderer.endWidth = 0.3f; con.lineRenderer.SetGreatCircleArc(con.nodeA.transform.position, con.nodeB.transform.position, 5, 20.5f); con.lineRenderer.enabled = true; } } } #if UNITY_EDITOR [CustomEditor(typeof(GameManager))] public class GameManagerEditor : Editor { int clickedConIdx = -1; public override void OnInspectorGUI() { DrawDefaultInspector(); GameManager gm = (GameManager)target; GUILayout.BeginHorizontal(); if (GUILayout.Button("Generate Sphere")) gm.GenerateAlongSphere(); if (GUILayout.Button("Generate Connections")) gm.GenerateConnections(); GUILayout.EndHorizontal(); if (GUILayout.Button("Generate")) { gm.GenerateAlongSphere(); gm.GenerateConnections(); } } private void OnSceneGUI() { Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual; GameManager gm = (GameManager)target; 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; // Hover-Ermittlung for (int i = 0; i < gm.connections.Count; i++) { 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 (dist < closestDist && dist < gm.hoverRadius) { closestDist = dist; closestIdx = i; } } // Klick-Behandlung Event e = Event.current; if (e.type == EventType.MouseDown && e.button == 0 && closestIdx >= 0) { clickedConIdx = closestIdx; } if (e.type == EventType.MouseUp && e.button == 0) { if (closestIdx == clickedConIdx && closestIdx != -1) { gm.connections[closestIdx].allowed = !gm.connections[closestIdx].allowed; e.Use(); // Event als verarbeitet markieren } clickedConIdx = -1; } // Zeichnen for (int i = 0; i < gm.connections.Count; i++) { var con = gm.connections[i]; Handles.color = (i == closestIdx && !con.allowed) ? gm.editorConnectionHoveredColor : con.allowed ? gm.editorConnectionAllowedColor : gm.editorConnectionForbiddenColor; float thickness = (i == closestIdx) ? gm.editorConnectionHoveredWidth : con.allowed ? gm.editorConnectionAllowedWidth : gm.editorConnectionForbiddenWidth; Handles.DrawLine(con.nodeA.transform.position, con.nodeB.transform.position, thickness); } // 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); } } }