Files
BlueArchiveMiniGame/Assets/Scripts/Test/AutoMap/AutoMapGenerator.cs
2025-10-13 07:35:57 +08:00

463 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Assets/Scripts/Test/AutoMap/AutoMapGenerator.cs
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Collections.Generic;
using System.Resources;
public class AutoMapGenerator : MonoBehaviour
{
[Header("地图区域设置")]
public string areaName = "目标区域";
public double southLat = 39.9100;
public double northLat = 39.9300;
public double westLon = 116.3800;
public double eastLon = 116.4100;
[Header("资源引用 - 如为空将自动创建")]
public Material buildingMaterial;
public Material roadMaterial;
public Material parkMaterial;
public Material waterMaterial;
public Material groundMaterial;
[Header("生成设置")]
public float buildingHeight = 8f;
public float roadWidth = 6f;
[Header("调试选项")]
public bool showDebugInfo = true;
public bool logMissingBuildings = true;
public bool includeAllWays = true; // 新增
private OSMParser osmParser;
private ResourceManager resourceManager;
void Start()
{
InitializeComponents();
StartCoroutine(DownloadAndGenerateMap());
}
void InitializeComponents()
{
// 确保有必要的组件
osmParser = GetComponent<OSMParser>();
if (osmParser == null) osmParser = gameObject.AddComponent<OSMParser>();
// 设置解析器选项
osmParser.includeAllWays = includeAllWays;
// 初始化资源管理器
resourceManager = new ResourceManager();
resourceManager.InitializeResources(this);
// 调试:显示现有材质
if (showDebugInfo)
{
resourceManager.LogExistingMaterials();
}
}
// 添加一个编辑器菜单项来清理和重新创建材质
[ContextMenu("重新初始化材质")]
public void ReinitializeMaterials()
{
#if UNITY_EDITOR
Debug.Log("重新初始化材质...");
// 强制重新创建所有材质
resourceManager = new ResourceManager();
// 清空现有材质引用
buildingMaterial = null;
roadMaterial = null;
parkMaterial = null;
waterMaterial = null;
groundMaterial = null;
// 重新初始化
resourceManager.InitializeResources(this);
Debug.Log("材质重新初始化完成");
#endif
}
// 添加一个方法来检查材质状态
[ContextMenu("检查材质状态")]
public void CheckMaterialStatus()
{
Debug.Log("=== 材质状态检查 ===");
Debug.Log($"建筑材质: {buildingMaterial?.name ?? ""}");
Debug.Log($"道路材质: {roadMaterial?.name ?? ""}");
Debug.Log($"公园材质: {parkMaterial?.name ?? ""}");
Debug.Log($"水域材质: {waterMaterial?.name ?? ""}");
Debug.Log($"地面材质: {groundMaterial?.name ?? ""}");
resourceManager.LogExistingMaterials();
}
IEnumerator DownloadAndGenerateMap()
{
Debug.Log($"开始下载 {areaName} 地图数据...");
string overpassQuery = BuildOverpassQuery();
string encodedQuery = UnityWebRequest.EscapeURL(overpassQuery);
string url = $"https://overpass-api.de/api/interpreter?data={encodedQuery}";
if (showDebugInfo)
{
Debug.Log($"目标区域: {areaName}");
Debug.Log($"经纬度范围: {southLat},{westLon} 到 {northLat},{eastLon}");
}
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
string osmData = request.downloadHandler.text;
Debug.Log($"成功下载地图数据! 长度: {osmData.Length} 字符");
GenerateMap(osmData);
}
else
{
Debug.LogError($"下载失败: {request.error}");
CreateTestMap();
}
}
}
string BuildOverpassQuery()
{
return $@"
[out:xml][timeout:45];
(
way[""building""]({southLat},{westLon},{northLat},{eastLon});
way[""highway""]({southLat},{westLon},{northLat},{eastLon});
way[""leisure""]({southLat},{westLon},{northLat},{eastLon});
way[""waterway""]({southLat},{westLon},{northLat},{eastLon});
way[""natural""][""natural""~""water""]({southLat},{westLon},{northLat},{eastLon});
);
(._;>;);
out body;";
}
void GenerateMap(string osmData)
{
ClearExistingMap();
if (string.IsNullOrEmpty(osmData) || osmData.Length < 100)
{
Debug.LogError("OSM数据无效创建测试地图");
CreateTestMap();
return;
}
OSMData parsedData = osmParser.ParseOSMXML(osmData);
CreateMapElements(parsedData);
Debug.Log("地图生成完成!");
}
void CreateMapElements(OSMData data)
{
// 创建地面
CreateGround(data);
int totalBuildings = 0;
int generatedBuildings = 0;
int skippedBuildings = 0;
// 创建各种地图元素
foreach (var way in data.ways)
{
var points = GetWayPoints(way, data);
if (way.IsBuilding)
{
totalBuildings++;
// 放宽建筑生成条件
if (points.Count >= 3)
{
CreateBuilding(way, points);
generatedBuildings++;
}
else if (points.Count == 2)
{
// 对于只有2个点的建筑创建简化版本
CreateSimpleBuilding(way, points);
generatedBuildings++;
}
else
{
Debug.LogWarning($"跳过建筑 {way.id}: 只有 {points.Count} 个点");
skippedBuildings++;
}
}
else if (way.IsRoad && points.Count >= 2)
{
CreateRoad(way, points);
}
else if (way.IsPark && points.Count >= 3)
{
CreatePark(way, points);
}
else if (way.IsWater && points.Count >= 3)
{
CreateWater(way, points);
}
}
Debug.Log($"建筑生成统计: 总计{totalBuildings}, 生成{generatedBuildings}, 跳过{skippedBuildings}");
}
// 创建简化建筑用于只有2个点的情况
void CreateSimpleBuilding(OSMWay way, List<Vector3> points)
{
if (points.Count != 2) return;
GameObject building = new GameObject($"SimpleBuilding_{way.id}");
building.transform.SetParent(transform);
// 计算矩形的四个点
Vector3 direction = (points[1] - points[0]).normalized;
Vector3 perpendicular = new Vector3(-direction.z, 0, direction.x); // 垂直方向
float width = 10f; // 默认宽度
List<Vector3> footprint = new List<Vector3>
{
points[0] - perpendicular * width * 0.5f,
points[1] - perpendicular * width * 0.5f,
points[1] + perpendicular * width * 0.5f,
points[0] + perpendicular * width * 0.5f
};
var meshFilter = building.AddComponent<MeshFilter>();
var renderer = building.AddComponent<MeshRenderer>();
meshFilter.mesh = CreateBuildingMesh(footprint, buildingHeight);
renderer.material = buildingMaterial;
building.AddComponent<MeshCollider>();
Debug.Log($"创建简化建筑: {way.id} (从{points.Count}个点生成)");
}
// 改进的节点获取方法
List<Vector3> GetWayPoints(OSMWay way, OSMData data)
{
var points = new List<Vector3>();
var nodeDict = CreateNodeDictionary(data);
int foundNodes = 0;
foreach (long nodeId in way.nodeRefs)
{
if (nodeDict.ContainsKey(nodeId))
{
points.Add(nodeDict[nodeId].unityPosition);
foundNodes++;
}
}
if (foundNodes < way.nodeRefs.Count && way.IsBuilding)
{
Debug.LogWarning($"建筑 {way.id}: 引用 {way.nodeRefs.Count} 个节点,只找到 {foundNodes} 个");
}
return points;
}
Dictionary<long, OSMNode> CreateNodeDictionary(OSMData data)
{
var dict = new Dictionary<long, OSMNode>();
foreach (var node in data.nodes)
dict[node.id] = node;
return dict;
}
void CreateGround(OSMData data)
{
if (data.nodes.Count == 0) return;
GameObject ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
ground.name = "MapGround";
ground.transform.SetParent(transform);
Bounds bounds = CalculateBounds(data);
ground.transform.position = new Vector3(bounds.center.x, -0.1f, bounds.center.z);
ground.transform.localScale = new Vector3(bounds.size.x / 10f, 1, bounds.size.z / 10f);
ground.GetComponent<Renderer>().material = groundMaterial;
}
void CreateBuilding(OSMWay way, List<Vector3> footprint)
{
GameObject building = new GameObject($"Building_{way.id}");
building.transform.SetParent(transform);
var meshFilter = building.AddComponent<MeshFilter>();
var renderer = building.AddComponent<MeshRenderer>();
meshFilter.mesh = CreateBuildingMesh(footprint, buildingHeight);
renderer.material = buildingMaterial;
building.AddComponent<MeshCollider>();
}
void CreateRoad(OSMWay way, List<Vector3> path)
{
GameObject road = new GameObject($"Road_{way.id}");
road.transform.SetParent(transform);
var lineRenderer = road.AddComponent<LineRenderer>();
lineRenderer.positionCount = path.Count;
lineRenderer.SetPositions(path.ToArray());
lineRenderer.startWidth = roadWidth;
lineRenderer.endWidth = roadWidth;
lineRenderer.material = roadMaterial;
lineRenderer.textureMode = LineTextureMode.Tile;
}
void CreatePark(OSMWay way, List<Vector3> area)
{
GameObject park = new GameObject($"Park_{way.id}");
park.transform.SetParent(transform);
var ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
ground.transform.SetParent(park.transform);
ground.name = "ParkGround";
Bounds bounds = CalculateBounds(area);
ground.transform.position = bounds.center;
ground.transform.localScale = new Vector3(bounds.size.x / 10f, 1, bounds.size.z / 10f);
ground.GetComponent<Renderer>().material = parkMaterial;
}
void CreateWater(OSMWay way, List<Vector3> area)
{
GameObject water = new GameObject($"Water_{way.id}");
water.transform.SetParent(transform);
var surface = GameObject.CreatePrimitive(PrimitiveType.Plane);
surface.transform.SetParent(water.transform);
surface.name = "WaterSurface";
Bounds bounds = CalculateBounds(area);
surface.transform.position = bounds.center + Vector3.up * 0.1f;
surface.transform.localScale = new Vector3(bounds.size.x / 10f, 1, bounds.size.z / 10f);
surface.GetComponent<Renderer>().material = waterMaterial;
}
Mesh CreateBuildingMesh(List<Vector3> footprint, float height)
{
Mesh mesh = new Mesh();
var vertices = new List<Vector3>();
var triangles = new List<int>();
int count = footprint.Count;
// 底面顶点
vertices.AddRange(footprint);
// 顶面顶点
foreach (var point in footprint)
vertices.Add(point + Vector3.up * height);
// 侧面
for (int i = 0; i < count; i++)
{
int next = (i + 1) % count;
triangles.Add(i); triangles.Add(next); triangles.Add(i + count);
triangles.Add(next); triangles.Add(next + count); triangles.Add(i + count);
}
// 底面和顶面
var bottomTris = TriangulatePolygon(footprint, false);
var topTris = TriangulatePolygon(footprint, true);
triangles.AddRange(bottomTris);
foreach (int tri in topTris) triangles.Add(tri + count);
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
return mesh;
}
List<int> TriangulatePolygon(List<Vector3> vertices, bool clockwise)
{
var triangles = new List<int>();
if (vertices.Count < 3) return triangles;
for (int i = 1; i < vertices.Count - 1; i++)
{
if (clockwise)
{
triangles.Add(0); triangles.Add(i); triangles.Add(i + 1);
}
else
{
triangles.Add(0); triangles.Add(i + 1); triangles.Add(i);
}
}
return triangles;
}
Bounds CalculateBounds(OSMData data)
{
if (data.nodes.Count == 0) return new Bounds();
Bounds bounds = new Bounds(data.nodes[0].unityPosition, Vector3.zero);
foreach (var node in data.nodes)
bounds.Encapsulate(node.unityPosition);
return bounds;
}
Bounds CalculateBounds(List<Vector3> points)
{
if (points.Count == 0) return new Bounds();
Bounds bounds = new Bounds(points[0], Vector3.zero);
foreach (var point in points)
bounds.Encapsulate(point);
return bounds;
}
void CreateTestMap()
{
Debug.Log("创建测试地图...");
ClearExistingMap();
// 创建测试地面
GameObject ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
ground.name = "TestGround";
ground.transform.SetParent(transform);
ground.transform.localScale = new Vector3(10, 1, 10);
ground.GetComponent<Renderer>().material = groundMaterial;
// 创建几个测试建筑
for (int i = 0; i < 5; i++)
{
var building = GameObject.CreatePrimitive(PrimitiveType.Cube);
building.name = $"TestBuilding_{i}";
building.transform.SetParent(transform);
building.transform.position = new Vector3(i * 15, 4, 0);
building.transform.localScale = new Vector3(10, 8, 10);
building.GetComponent<Renderer>().material = buildingMaterial;
}
Debug.Log("测试地图创建完成!");
}
void ClearExistingMap()
{
foreach (Transform child in transform)
DestroyImmediate(child.gameObject);
}
[ContextMenu("重新生成地图")]
public void RegenerateMap()
{
StartCoroutine(DownloadAndGenerateMap());
}
}