Files
ID/Assets/Code/Lab6/GraphGrid.cs
T
2026-03-01 00:29:25 +03:00

627 lines
23 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace Code.Lab6
{
public class GraphGrid : Graph
{
public GameObject obstaclePrefab;
public string mapName = "arena.map";
public bool get8Vicinity = false;
public float cellSize = 1f;
[Range(0, Mathf.Infinity)]
public float defaultCost = 1f;
private const string MapsDir = "Resources/Maps";
private int _numCols;
private int _numRows;
private GameObject[] _vertexObj;
private bool[,] _mapVertices;
public bool useWeightedMap = true;
public Texture2D weightMapTexture;
public float minWeight = 0.5f;
public float maxWeight = 5f;
private Dictionary<int, int> _gridIdToVertexIndex;
private int GridToId(int x, int y)
{
return _numCols * y + x;
}
private Vector2Int IdToGrid(int id)
{
return new Vector2Int(id % _numCols, id / _numCols);
}
private void LoadMapWithWeights(string filename)
{
var path = Application.dataPath + "/" + MapsDir + "/" + filename;
try
{
var strmRdr = new StreamReader(path);
using (strmRdr)
{
strmRdr.ReadLine();
var line = strmRdr.ReadLine();
if (line != null && line.StartsWith("height"))
_numRows = int.Parse(line.Split(' ')[1]);
line = strmRdr.ReadLine();
if (line != null && line.StartsWith("width"))
_numCols = int.Parse(line.Split(' ')[1]);
strmRdr.ReadLine();
Vertices = new List<Vertex>();
Neighbours = new List<List<Vertex>>();
Costs = new List<List<float>>();
_vertexObj = new GameObject[_numRows * _numCols];
_mapVertices = new bool[_numRows, _numCols];
_gridIdToVertexIndex = new Dictionary<int, int>();
Debug.Log($"Loading weighted map {_numRows}x{_numCols}");
for (var row = 0; row < _numRows; row++)
{
line = strmRdr.ReadLine();
if (string.IsNullOrEmpty(line)) continue;
for (var col = 0; col < _numCols && col < line.Length; col++)
{
var cellChar = line[col];
var isGround = cellChar != '#';
_mapVertices[row, col] = isGround;
var position = new Vector3(col * cellSize, 0f, row * cellSize);
var gridId = GridToId(col, row);
GameObject obj;
if (isGround && vertexPrefab != null)
{
obj = Instantiate(vertexPrefab, position, Quaternion.identity);
}
else if (!isGround && obstaclePrefab != null)
{
obj = Instantiate(obstaclePrefab, position, Quaternion.identity);
}
else
{
obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.position = position;
}
if (obj == null) continue;
obj.name = $"{cellChar}_{gridId}";
obj.transform.parent = transform;
var scale = obj.transform.localScale;
scale.x = cellSize;
scale.z = cellSize;
scale.y = isGround ? 0.1f : 1f;
obj.transform.localScale = scale;
_vertexObj[gridId] = obj;
if (!isGround) continue;
var v = obj.GetComponent<Vertex>();
if (v == null)
v = obj.AddComponent<Vertex>();
v.id = gridId;
v.neighbours = new List<Edge>();
v.weight = GetWeightFromChar(cellChar);
var renderer1 = obj.GetComponent<Renderer>();
if (renderer1 != null)
{
renderer1.material.color = cellChar switch
{
'r' => new Color(0.8f, 0.2f, 0.2f),
'm' => new Color(0.6f, 0.4f, 0.2f),
'h' => new Color(0.2f, 0.8f, 0.2f),
_ => new Color(0.8f, 0.8f, 0.8f)
};
}
var vertexIndex = Vertices.Count;
Vertices.Add(v);
Neighbours.Add(new List<Vertex>());
Costs.Add(new List<float>());
_gridIdToVertexIndex[gridId] = vertexIndex;
}
}
foreach (var kvp in _gridIdToVertexIndex)
{
var gridId = kvp.Key;
var vertexIndex = kvp.Value;
var gridPos = IdToGrid(gridId);
SetNeighboursWithWeights(gridPos.x, gridPos.y, vertexIndex);
}
Debug.Log($"Weighted map loaded. {Vertices.Count} walkable vertices");
}
}
catch (Exception e)
{
Debug.LogError($"Failed to load weighted map: {e.Message}");
}
}
private float GetWeightFromChar(char c)
{
return c switch
{
'h' => 0.5f,
'.' => 1f,
'm' => 3f,
'r' => 5f,
'#' => float.MaxValue,
_ => 1f
};
}
private float CalculateVertexWeight(int row, int col, Texture2D weightTexture)
{
if (weightTexture != null)
{
var texX = Mathf.FloorToInt((float)col / _numCols * weightTexture.width);
var texY = Mathf.FloorToInt((float)row / _numRows * weightTexture.height);
var pixel = weightTexture.GetPixel(texX, texY);
var gray = pixel.grayscale;
return Mathf.Lerp(minWeight, maxWeight, gray);
}
var centerX = _numCols / 2f;
var centerY = _numRows / 2f;
var distFromCenter = Vector2.Distance(new Vector2(col, row), new Vector2(centerX, centerY));
var maxDist = Mathf.Max(_numCols, _numRows) / 2f;
var centerWeight = Mathf.Lerp(minWeight, maxWeight, distFromCenter / maxDist);
var noise = Mathf.PerlinNoise(col * 0.1f, row * 0.1f);
var noiseWeight = Mathf.Lerp(minWeight, maxWeight, noise);
var riverX = _numCols * 0.3f + Mathf.Sin(row * 0.3f) * 5f;
var distToRiver = Mathf.Abs(col - riverX);
var riverWeight = distToRiver < 3f ? maxWeight : minWeight;
var finalWeight = (centerWeight * 0.3f + noiseWeight * 0.3f + riverWeight * 0.4f);
return Mathf.Clamp(finalWeight, minWeight, maxWeight);
}
protected void SetNeighboursWithWeights(int x, int y, int vertexIndex)
{
var currentVertex = Vertices[vertexIndex];
Neighbours[vertexIndex].Clear();
Costs[vertexIndex].Clear();
currentVertex.neighbours.Clear();
Vector2Int[] directions;
if (get8Vicinity)
{
directions = new[]
{
new Vector2Int(x, y - 1), new Vector2Int(x + 1, y - 1),
new Vector2Int(x + 1, y), new Vector2Int(x + 1, y + 1),
new Vector2Int(x, y + 1), new Vector2Int(x - 1, y + 1),
new Vector2Int(x - 1, y), new Vector2Int(x - 1, y - 1)
};
}
else
{
directions = new[]
{
new Vector2Int(x, y - 1), new Vector2Int(x + 1, y),
new Vector2Int(x, y + 1), new Vector2Int(x - 1, y)
};
}
const float diagonalMultiplier = 1.4f;
foreach (var dir in directions)
{
if (dir.x < 0 || dir.x >= _numCols || dir.y < 0 || dir.y >= _numRows)
continue;
if (!_mapVertices[dir.y, dir.x])
continue;
var neighbourGridId = GridToId(dir.x, dir.y);
if (!_gridIdToVertexIndex.TryGetValue(neighbourGridId, out var neighbourIndex))
continue;
var neighbourVertex = Vertices[neighbourIndex];
var baseCost = (currentVertex.weight + neighbourVertex.weight) / 2f;
if (dir.x != x && dir.y != y)
{
baseCost *= diagonalMultiplier;
}
Neighbours[vertexIndex].Add(neighbourVertex);
Costs[vertexIndex].Add(baseCost);
currentVertex.neighbours.Add(new Edge(neighbourVertex, baseCost));
}
}
public void GenerateLargeWeightedMap(int width, int height)
{
_numCols = width;
_numRows = height;
Vertices = new List<Vertex>();
Neighbours = new List<List<Vertex>>();
Costs = new List<List<float>>();
_vertexObj = new GameObject[width * height];
_mapVertices = new bool[height, width];
_gridIdToVertexIndex = new Dictionary<int, int>();
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
_mapVertices[y, x] = true;
var position = new Vector3(x * cellSize, 0f, y * cellSize);
var gridId = GridToId(x, y);
var obj = Instantiate(vertexPrefab, position, Quaternion.identity);
obj.name = $"Vertex_{gridId}";
obj.transform.parent = transform;
var v = obj.GetComponent<Vertex>();
if (v == null)
v = obj.AddComponent<Vertex>();
v.id = gridId;
v.neighbours = new List<Edge>();
v.weight = CalculateVertexWeight(y, x, null);
var renderer1 = obj.GetComponent<Renderer>();
if (renderer1 != null)
{
var t = Mathf.InverseLerp(minWeight, maxWeight, v.weight);
renderer1.material.color = Color.Lerp(Color.white, Color.black, t);
}
var vertexIndex = Vertices.Count;
Vertices.Add(v);
Neighbours.Add(new List<Vertex>());
Costs.Add(new List<float>());
_gridIdToVertexIndex[gridId] = vertexIndex;
}
}
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var gridId = GridToId(x, y);
if (_gridIdToVertexIndex.TryGetValue(gridId, out var vertexIndex))
{
SetNeighboursWithWeights(x, y, vertexIndex);
}
}
}
Debug.Log($"Generated large weighted map: {width}x{height} ({Vertices.Count} vertices)");
}
public override void Load()
{
if (useWeightedMap)
{
LoadMapWithWeights(mapName);
}
else
{
LoadMap(mapName);
}
}
private void LoadMap(string filename)
{
var path = Application.dataPath + "/" + MapsDir + "/" + filename;
try
{
var strmRdr = new StreamReader(path);
using (strmRdr)
{
strmRdr.ReadLine();
var line = strmRdr.ReadLine();
if (line != null && line.StartsWith("height"))
{
_numRows = int.Parse(line.Split(' ')[1]);
}
else
{
Debug.LogError("Invalid map format: height line not found");
return;
}
line = strmRdr.ReadLine();
if (line != null && line.StartsWith("width"))
{
_numCols = int.Parse(line.Split(' ')[1]);
}
else
{
Debug.LogError("Invalid map format: width line not found");
return;
}
strmRdr.ReadLine();
var totalCells = _numRows * _numCols;
Vertices = new List<Vertex>();
Neighbours = new List<List<Vertex>>();
Costs = new List<List<float>>();
_vertexObj = new GameObject[totalCells];
_mapVertices = new bool[_numRows, _numCols];
_gridIdToVertexIndex = new Dictionary<int, int>();
Debug.Log($"Loading map {_numRows}x{_numCols} (total cells: {totalCells})");
for (var row = 0; row < _numRows; row++)
{
line = strmRdr.ReadLine();
if (string.IsNullOrEmpty(line) || line.Length < _numCols)
{
Debug.LogError($"Map line {row} is too short or missing");
continue;
}
for (var col = 0; col < _numCols; col++)
{
var cellChar = line[col];
var isGround = (cellChar == '.' || cellChar == 'G' || cellChar == ' ');
_mapVertices[row, col] = isGround;
var position = new Vector3(col * cellSize, 0f, row * cellSize);
var gridId = GridToId(col, row);
GameObject obj;
if (isGround && vertexPrefab != null)
{
obj = Instantiate(vertexPrefab, position, Quaternion.identity);
}
else if (!isGround && obstaclePrefab != null)
{
obj = Instantiate(obstaclePrefab, position, Quaternion.identity);
}
else
{
obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.position = position;
var renderer1 = obj.GetComponent<Renderer>();
if (renderer1 != null)
{
renderer1.material.color = isGround ? Color.green : Color.red;
}
}
if (obj == null) continue;
obj.name = $"{(isGround ? "Ground" : "Obstacle")}_{gridId}";
obj.transform.parent = transform;
var scale = obj.transform.localScale;
scale.x = cellSize;
scale.z = cellSize;
scale.y = isGround ? 0.1f : 1f;
obj.transform.localScale = scale;
_vertexObj[gridId] = obj;
if (!isGround) continue;
var v = obj.GetComponent<Vertex>();
if (v == null)
v = obj.AddComponent<Vertex>();
v.id = gridId;
v.neighbours = new List<Edge>();
var vertexIndex = Vertices.Count;
Vertices.Add(v);
Neighbours.Add(new List<Vertex>());
Costs.Add(new List<float>());
_gridIdToVertexIndex[gridId] = vertexIndex;
}
}
Debug.Log($"Created {Vertices.Count} walkable vertices");
foreach (var kvp in _gridIdToVertexIndex)
{
var gridId = kvp.Key;
var vertexIndex = kvp.Value;
var gridPos = IdToGrid(gridId);
SetNeighbours(gridPos.x, gridPos.y, vertexIndex);
}
Debug.Log($"Map loaded successfully. {Vertices.Count} walkable vertices");
}
}
catch (Exception e)
{
Debug.LogError($"Failed to load map: {e.Message}");
Debug.LogException(e);
}
}
protected void SetNeighbours(int x, int y, int vertexIndex)
{
var currentVertex = Vertices[vertexIndex];
var gridId = currentVertex.id;
Neighbours[vertexIndex].Clear();
Costs[vertexIndex].Clear();
currentVertex.neighbours.Clear();
Vector2Int[] directions;
if (get8Vicinity)
{
directions = new[]
{
new Vector2Int(x, y - 1), new Vector2Int(x + 1, y - 1), new Vector2Int(x + 1, y), new Vector2Int(x + 1, y + 1), new Vector2Int(x, y + 1), new Vector2Int(x - 1, y + 1), new Vector2Int(x - 1, y), new Vector2Int(x - 1, y - 1)
};
}
else
{
directions = new[]
{
new Vector2Int(x, y - 1), new Vector2Int(x + 1, y), new Vector2Int(x, y + 1), new Vector2Int(x - 1, y)
};
}
var diagonalCost = defaultCost * 1.4f;
foreach (var dir in directions)
{
if (dir.x < 0 || dir.x >= _numCols || dir.y < 0 || dir.y >= _numRows)
continue;
if (!_mapVertices[dir.y, dir.x])
continue;
var neighbourGridId = GridToId(dir.x, dir.y);
if (!_gridIdToVertexIndex.TryGetValue(neighbourGridId, out var neighbourIndex))
continue;
var neighbourVertex = Vertices[neighbourIndex];
var cost = defaultCost;
if (dir.x != x && dir.y != y)
{
cost = diagonalCost;
}
Neighbours[vertexIndex].Add(neighbourVertex);
Costs[vertexIndex].Add(cost);
currentVertex.neighbours.Add(new Edge(neighbourVertex, cost));
}
}
public override Vertex GetNearestVertex(Vector3 position)
{
var col = Mathf.RoundToInt(position.x / cellSize);
var row = Mathf.RoundToInt(position.z / cellSize);
col = Mathf.Clamp(col, 0, _numCols - 1);
row = Mathf.Clamp(row, 0, _numRows - 1);
if (!_mapVertices[row, col])
{
return FindNearestWalkable(col, row);
}
var gridId = GridToId(col, row);
return _gridIdToVertexIndex.TryGetValue(gridId, out var vertexIndex) ? Vertices[vertexIndex] : null;
}
private Vertex FindNearestWalkable(int startCol, int startRow)
{
var queue = new Queue<Vector2Int>();
var explored = new HashSet<Vector2Int>();
queue.Enqueue(new Vector2Int(startCol, startRow));
explored.Add(new Vector2Int(startCol, startRow));
const int maxSearchRadius = 20;
var steps = 0;
while (queue.Count > 0 && steps < maxSearchRadius * maxSearchRadius)
{
var pos = queue.Dequeue();
if (_mapVertices[pos.y, pos.x])
{
var gridId = GridToId(pos.x, pos.y);
if (_gridIdToVertexIndex.TryGetValue(gridId, out var vertexIndex))
{
return Vertices[vertexIndex];
}
}
var neighbours = new[]
{
new Vector2Int(pos.x, pos.y - 1),
new Vector2Int(pos.x + 1, pos.y),
new Vector2Int(pos.x, pos.y + 1),
new Vector2Int(pos.x - 1, pos.y)
};
foreach (var n in neighbours)
{
if (n.x < 0 || n.x >= _numCols || n.y < 0 || n.y >= _numRows || !explored.Add(n)) continue;
queue.Enqueue(n);
}
steps++;
}
Debug.LogWarning($"No walkable vertex found near ({startCol}, {startRow})");
return Vertices.Count > 0 ? Vertices[0] : null;
}
public override Vertex[] GetNeighbours(Vertex v)
{
if (!v || Vertices == null || Neighbours == null || !_gridIdToVertexIndex.TryGetValue(v.id, out var vertexIndex) || vertexIndex < 0 || vertexIndex >= Neighbours.Count || Neighbours[vertexIndex] == null)
return Array.Empty<Vertex>();
return Neighbours[vertexIndex].ToArray();
}
public override Edge[] GetEdges(Vertex v)
{
if (!v || v.neighbours == null)
return Array.Empty<Edge>();
return v.neighbours.ToArray();
}
public override Vertex GetVertexObj(int id)
{
if (!_gridIdToVertexIndex.TryGetValue(id, out var vertexIndex)) return null;
if (vertexIndex >= 0 && vertexIndex < Vertices.Count)
return Vertices[vertexIndex];
return null;
}
}
}