using System.Collections.Generic; using UnityEngine; // Draws a minimap of the generated road as a 2D overlay in the top-left corner, // positioned just below the NN steering/throttle text. Shows white path lines // and a red dot for the current car position. Auto-refreshes when the road changes. public class MapOverlay : MonoBehaviour { public PathManager pathManager; public Transform carTransform; const int MAP_X = 5; const int MAP_Y = 42; // below the one-line NN text (~25px tall + padding) const int MAP_SIZE = 160; static Texture2D _whiteTex; static Texture2D _bgTex; static Texture2D _dotTex; static Texture2D WhiteTex { get { if (_whiteTex == null) { _whiteTex = new Texture2D(1, 1); _whiteTex.SetPixel(0, 0, Color.white); _whiteTex.Apply(); } return _whiteTex; } } List _nodes = new List(); float _minX, _maxX, _minZ, _maxZ; int _lastNodeCount = -1; float _lastFirstNodeX = float.MaxValue; // catches regen with same node count float _lastFirstNodeZ = float.MaxValue; bool _hasPath = false; void Awake() { if (pathManager == null) pathManager = FindObjectOfType(); _bgTex = new Texture2D(1, 1); _bgTex.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.55f)); _bgTex.Apply(); _dotTex = new Texture2D(4, 4); for (int y = 0; y < 4; y++) for (int x = 0; x < 4; x++) _dotTex.SetPixel(x, y, Color.red); _dotTex.Apply(); } void Update() { // Find car transform once it exists if (carTransform == null) { Car c = FindObjectOfType(); if (c != null) carTransform = c.transform; } // Re-cache path whenever it changes (road regen). // Check both count and the 10th node position — regen always produces the same // count (numSpans=100) but different positions for different seeds. if (pathManager != null && pathManager.carPath != null) { var nodes = pathManager.carPath.centerNodes; int n = nodes.Count; float fx = n > 10 ? nodes[10].pos.x : float.MaxValue; float fz = n > 10 ? nodes[10].pos.z : float.MaxValue; if (n != _lastNodeCount || fx != _lastFirstNodeX || fz != _lastFirstNodeZ) { _lastFirstNodeX = fx; _lastFirstNodeZ = fz; RefreshPath(); } } } void RefreshPath() { _nodes.Clear(); _minX = float.MaxValue; _maxX = float.MinValue; _minZ = float.MaxValue; _maxZ = float.MinValue; foreach (PathNode node in pathManager.carPath.centerNodes) { _nodes.Add(node.pos); if (node.pos.x < _minX) _minX = node.pos.x; if (node.pos.x > _maxX) _maxX = node.pos.x; if (node.pos.z < _minZ) _minZ = node.pos.z; if (node.pos.z > _maxZ) _maxZ = node.pos.z; } _lastNodeCount = _nodes.Count; _hasPath = _nodes.Count > 1; } Vector2 WorldToMap(Vector3 world) { float rangeX = _maxX - _minX; float rangeZ = _maxZ - _minZ; float range = Mathf.Max(rangeX, rangeZ, 1f); float pad = 6f; float inner = MAP_SIZE - pad * 2f; float px = MAP_X + pad + (world.x - _minX) / range * inner; float pz = MAP_Y + pad + (1f - (world.z - _minZ) / range) * inner; // flip Z → screen Y return new Vector2(px, pz); } static void DrawLine(Vector2 a, Vector2 b, Color color, float thickness = 1.5f) { Vector2 delta = b - a; if (delta.sqrMagnitude < 0.01f) return; float angle = Mathf.Atan2(delta.y, delta.x) * Mathf.Rad2Deg; float length = delta.magnitude; Color prev = GUI.color; GUI.color = color; Matrix4x4 saved = GUI.matrix; GUIUtility.RotateAroundPivot(angle, a); GUI.DrawTexture(new Rect(a.x, a.y - thickness * 0.5f, length, thickness), WhiteTex); GUI.matrix = saved; GUI.color = prev; } void OnGUI() { if (!_hasPath) return; // Dark background GUI.DrawTexture(new Rect(MAP_X - 1, MAP_Y - 1, MAP_SIZE + 2, MAP_SIZE + 2), _bgTex); // Path lines for (int i = 0; i < _nodes.Count - 1; i++) { Vector2 a = WorldToMap(_nodes[i]); Vector2 b = WorldToMap(_nodes[i + 1]); DrawLine(a, b, Color.white, 1.5f); } // Car dot if (carTransform != null) { Vector2 cp = WorldToMap(carTransform.position); GUI.DrawTexture(new Rect(cp.x - 3f, cp.y - 3f, 6f, 6f), _dotTex); } } }