sdsandbox-rl-scripts/Scripts/MapOverlay.cs

157 lines
4.8 KiB
C#
Executable File

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<Vector3> _nodes = new List<Vector3>();
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<PathManager>();
_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<Car>();
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);
}
}
}