Files
TestClient/Assets/UnityTestClient/UnityTestClient_Map.cs
2026-01-27 21:36:29 +01:00

1533 lines
64 KiB
C#

/*
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║
║ VYKRESLOVÁNÍ MAPY - UnityTestClient_Map.cs ║
║ ║
║ Tento soubor obsahuje veškeré vykreslování 3D mapy pomocí GameObjectů: ║
║ • Budovy (Buildings) - 3D kvádry s výškou ║
║ • Cesty (Pathways) - LineRenderer nebo extrudované polygony ║
║ • Oblasti (Areas) - Ploché 3D objekty (parky, voda) ║
║ • POI (Points of Interest) - Markery s ikonami ║
║ • Hráči - Capsule s barvou podle role/stavu ║
║ • Těla - Ležící capsule (po zabití) ║
║ • Úkoly - Žluté diamanty (markery) ║
║ • Opravné stanice - Oranžové věže (při sabotáži) ║
║ ║
║ GPS → UNITY PŘEVOD: ║
║ 1. Střed mapy (PlayAreaCenter) = Vector3(0, 0, 0) ║
║ 2. 1 metr GPS ≈ 1 Unity jednotka ║
║ 3. Latitude → Z osa (sever = +Z) ║
║ 4. Longitude → X osa (východ = +X) ║
║ 5. Haversine formula pro přesný výpočet vzdáleností ║
║ ║
║ VRSTVY (Y osa): ║
║ 0.00 - Zelený podklad ║
║ 0.01 - Oblasti (parky, voda) ║
║ 0.02 - Cesty ║
║ 0.XX - Budovy (výška podle dat) ║
║ 0.30 - Markery (úkoly, těla) ║
║ 0.50 - POI ║
║ 1.00 - Hráči ║
║ 5.00+ - Opravné stanice (věže) ║
║ ║
║ MAPOVÁ DATA: ║
║ Server stahuje data z OpenStreetMap (Overpass API) a posílá je klientovi. ║
║ Pokud Overpass API není dostupné, mapa se vykreslí bez detailů. ║
║ ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
*/
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using GeoSus.Client;
public partial class UnityTestClient
{
#region
// MAP PROMĚNNÉ
// ════════════════════════════════════════════════════════════════════════
#endregion
// ─────────────────────────────────────────────────────────────────────────
// Kontejnery pro mapové objekty
// ─────────────────────────────────────────────────────────────────────────
/// <summary>Rodičovský objekt pro celou mapu</summary>
protected GameObject mapContainer;
/// <summary>Kontejner pro budovy</summary>
protected GameObject buildingsContainer;
/// <summary>Kontejner pro cesty</summary>
protected GameObject pathwaysContainer;
/// <summary>Kontejner pro oblasti</summary>
protected GameObject areasContainer;
/// <summary>Kontejner pro POI</summary>
protected GameObject poisContainer;
/// <summary>Kontejner pro hráče</summary>
protected GameObject playersContainer;
/// <summary>Kontejner pro těla</summary>
protected GameObject bodiesContainer;
/// <summary>Kontejner pro úkoly</summary>
protected GameObject tasksContainer;
/// <summary>Kontejner pro opravné stanice</summary>
protected GameObject repairStationsContainer;
/// <summary>Emergency button marker (střed mapy)</summary>
protected GameObject emergencyButtonMarker;
/// <summary>Meeting lokace marker (během meetingu)</summary>
protected GameObject meetingLocationMarker;
// ─────────────────────────────────────────────────────────────────────────
// Mapování objektů na ID
// ─────────────────────────────────────────────────────────────────────────
/// <summary>Mapování ID hráče na GameObject</summary>
protected Dictionary<string, GameObject> playerObjects = new Dictionary<string, GameObject>();
/// <summary>Mapování ID těla na GameObject</summary>
protected Dictionary<string, GameObject> bodyObjects = new Dictionary<string, GameObject>();
/// <summary>Mapování ID úkolu na GameObject</summary>
protected Dictionary<string, GameObject> taskObjects = new Dictionary<string, GameObject>();
/// <summary>Mapování ID opravné stanice na GameObject</summary>
protected Dictionary<string, GameObject> repairStationObjects = new Dictionary<string, GameObject>();
// ─────────────────────────────────────────────────────────────────────────
// GPS referenční bod
// ─────────────────────────────────────────────────────────────────────────
/// <summary>Střed herní oblasti (GPS)</summary>
protected Position? mapCenter;
/// <summary>Poloměr herní oblasti (metry)</summary>
protected double mapRadius;
// ─────────────────────────────────────────────────────────────────────────
// Materiály
// ─────────────────────────────────────────────────────────────────────────
/// <summary>Materiál pro budovy</summary>
protected Material buildingMaterial;
/// <summary>Materiál pro cesty</summary>
protected Material pathwayMaterial;
/// <summary>Materiál pro oblasti</summary>
protected Material areaMaterial;
/// <summary>Materiál pro POI</summary>
protected Material poiMaterial;
/// <summary>Materiál pro hráče</summary>
protected Material playerMaterial;
/// <summary>Materiál pro těla</summary>
protected Material bodyMaterial;
/// <summary>Materiál pro úkoly</summary>
protected Material taskMaterial;
/// <summary>Materiál pro opravné stanice</summary>
protected Material repairMaterial;
#region
// INICIALIZACE MAPY
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Vytvoření kontejnerů a materiálů pro mapu.
/// Volá se při startu hry.
/// </summary>
protected void InitializeMapSystem()
{
// Vytvoření hlavního kontejneru
if (mapContainer == null)
{
mapContainer = new GameObject("MapContainer");
}
// Vytvoření sub-kontejnerů pro lepší organizaci
buildingsContainer = CreateContainer("Buildings");
pathwaysContainer = CreateContainer("Pathways");
areasContainer = CreateContainer("Areas");
poisContainer = CreateContainer("POIs");
playersContainer = CreateContainer("Players");
bodiesContainer = CreateContainer("Bodies");
tasksContainer = CreateContainer("Tasks");
repairStationsContainer = CreateContainer("RepairStations");
// Inicializace materiálů
InitializeMaterials();
// Vytvoření podlahy herní oblasti
CreateGroundPlane();
}
/// <summary>
/// Pomocná metoda pro vytvoření kontejneru
/// </summary>
private GameObject CreateContainer(string name)
{
var container = new GameObject(name);
container.transform.SetParent(mapContainer.transform);
return container;
}
/// <summary>
/// Inicializace materiálů pro různé typy objektů.
/// BUILD-SAFE: Všechny materiály jsou odvozeny z primitiv.
/// </summary>
private void InitializeMaterials()
{
// Budovy - různé barvy podle typu
buildingMaterial = CreateUnlitMaterial(new Color(0.7f, 0.65f, 0.6f)); // Světle šedá/béžová
// Cesty - tmavší šedá
pathwayMaterial = CreateUnlitMaterial(new Color(0.5f, 0.5f, 0.5f));
// Oblasti (parky) - zelená s průhledností
areaMaterial = CreateTransparentMaterial(new Color(0.3f, 0.7f, 0.3f, 0.4f));
// POI - jasně žlutá
poiMaterial = CreateUnlitMaterial(Color.yellow);
// Hráči - modrá
playerMaterial = CreateUnlitMaterial(new Color(0.2f, 0.5f, 1f));
// Těla - červená
bodyMaterial = CreateUnlitMaterial(new Color(0.8f, 0.2f, 0.2f));
// Úkoly - cyan/tyrkysová
taskMaterial = CreateUnlitMaterial(new Color(0f, 0.9f, 0.9f));
// Opravné stanice - oranžová
repairMaterial = CreateUnlitMaterial(new Color(1f, 0.6f, 0.1f));
}
/// <summary>
/// Vytvoří průhledný materiál
/// BUILD-SAFE: Používá materiál z primitivu jako základ
/// </summary>
private Material CreateTransparentMaterial(Color color)
{
var mat = CreateUnlitMaterial(color);
// Nastavení průhlednosti
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
mat.SetInt("_ZWrite", 0);
mat.renderQueue = 3000;
// Pro URP
if (mat.HasProperty("_Surface"))
{
mat.SetFloat("_Surface", 1); // 1 = Transparent
}
return mat;
}
/// <summary>
/// Nastaví materiál jako průhledný
/// </summary>
private void SetMaterialTransparent(Material mat)
{
mat.SetFloat("_Mode", 3); // Transparent
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
mat.SetInt("_ZWrite", 0);
mat.DisableKeyword("_ALPHATEST_ON");
mat.EnableKeyword("_ALPHABLEND_ON");
mat.DisableKeyword("_ALPHAPREMULTIPLY_ON");
mat.renderQueue = 3000;
}
/// <summary>
/// Nastaví materiál jako emisivní (svítící)
/// </summary>
private void SetMaterialEmissive(Material mat, Color emissionColor)
{
mat.EnableKeyword("_EMISSION");
mat.SetColor("_EmissionColor", emissionColor);
}
/// <summary>
/// Vytvoření podlahy herní oblasti
/// </summary>
private void CreateGroundPlane()
{
var ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
ground.name = "Ground";
ground.transform.SetParent(mapContainer.transform);
ground.transform.localPosition = Vector3.zero;
// Škálování podle poloměru (Plane má základní velikost 10x10)
float scale = (float)mapRadius * 0.3f; // Mírně větší než poloměr
ground.transform.localScale = new Vector3(scale, 1, scale);
// Materiál podlahy - použijeme Unlit shader pro konzistentní barvu
var groundMat = CreateUnlitMaterial(new Color(0.25f, 0.45f, 0.2f)); // Tráva
ground.GetComponent<Renderer>().material = groundMat;
// Vytvoříme emergency button marker uprostřed mapy
CreateEmergencyButtonMarker();
}
/// <summary>
/// Vytvoří výrazný marker pro emergency button (střed mapy)
/// </summary>
private void CreateEmergencyButtonMarker()
{
emergencyButtonMarker = new GameObject("EmergencyButton");
emergencyButtonMarker.transform.SetParent(mapContainer.transform);
emergencyButtonMarker.transform.localPosition = new Vector3(0, 0.1f, 0); // Střed mapy
// Velký červený kruh na zemi
var platform = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
platform.name = "Platform";
platform.transform.SetParent(emergencyButtonMarker.transform);
platform.transform.localPosition = Vector3.zero;
platform.transform.localScale = new Vector3(8f, 0.2f, 8f);
var platformMat = CreateEmissiveMaterial(new Color(0.8f, 0.2f, 0.2f), 1.5f);
platform.GetComponent<Renderer>().material = platformMat;
// Tlačítko uprostřed
var button = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
button.name = "Button";
button.transform.SetParent(emergencyButtonMarker.transform);
button.transform.localPosition = new Vector3(0, 0.5f, 0);
button.transform.localScale = new Vector3(3f, 0.5f, 3f);
var buttonMat = CreateEmissiveMaterial(Color.red, 3f);
button.GetComponent<Renderer>().material = buttonMat;
// Světelná koule nahoře
var light = GameObject.CreatePrimitive(PrimitiveType.Sphere);
light.name = "Light";
light.transform.SetParent(emergencyButtonMarker.transform);
light.transform.localPosition = new Vector3(0, 3f, 0);
light.transform.localScale = new Vector3(2f, 2f, 2f);
light.GetComponent<Renderer>().material = buttonMat;
}
/// <summary>
/// Vytvoří nebo aktualizuje marker pro meeting lokaci
/// </summary>
protected void UpdateMeetingLocationMarker(Position? location, bool isActive)
{
if (!isActive || location == null)
{
if (meetingLocationMarker != null)
{
Destroy(meetingLocationMarker);
meetingLocationMarker = null;
}
return;
}
if (meetingLocationMarker == null)
{
meetingLocationMarker = new GameObject("MeetingLocation");
meetingLocationMarker.transform.SetParent(mapContainer.transform);
// Velký pulsující kruh
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "Ring";
ring.transform.SetParent(meetingLocationMarker.transform);
ring.transform.localPosition = new Vector3(0, 0.1f, 0);
ring.transform.localScale = new Vector3(15f, 0.1f, 15f);
var ringMat = CreateEmissiveMaterial(new Color(1f, 1f, 0f, 0.6f), 2f);
ring.GetComponent<Renderer>().material = ringMat;
// Šipka dolů
var arrow = GameObject.CreatePrimitive(PrimitiveType.Cube);
arrow.name = "Arrow";
arrow.transform.SetParent(meetingLocationMarker.transform);
arrow.transform.localPosition = new Vector3(0, 10f, 0);
arrow.transform.localScale = new Vector3(2f, 8f, 0.5f);
var arrowMat = CreateEmissiveMaterial(Color.yellow, 4f);
arrow.GetComponent<Renderer>().material = arrowMat;
}
Vector3 pos = GPSToUnity(location.Value);
meetingLocationMarker.transform.position = pos;
}
/// <summary>
/// Cache pro základní materiál - získaný z primitivu při prvním volání
/// </summary>
private static Material _cachedBaseMaterial = null;
/// <summary>
/// Vytvoří materiál pro konzistentní barvu
/// BUILD-SAFE: Používá Sprites/Default shader který je vždy zahrnut
/// </summary>
private Material CreateUnlitMaterial(Color color)
{
// Sprites/Default shader je VŽDY zahrnut v Unity buildech
var shader = Shader.Find("Sprites/Default");
if (shader == null)
{
// Absolutní fallback - nemělo by nikdy nastat
Debug.LogError("[GeoSus] Sprites/Default shader nenalezen!");
var tempCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
var mat = new Material(tempCube.GetComponent<Renderer>().sharedMaterial);
DestroyImmediate(tempCube);
mat.color = color;
return mat;
}
var material = new Material(shader);
material.color = color;
return material;
}
#region
// GPS KONVERZE
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Konverze GPS souřadnic na Unity pozici.
/// Střed mapy je na (0, 0, 0).
///
/// MATEMATIKA:
/// - Latitude (zeměpisná šířka) → Z osa
/// - Longitude (zeměpisná délka) → X osa
/// - Používáme Haversine formuli pro přesnou vzdálenost
/// </summary>
/// <param name="position">GPS souřadnice</param>
/// <returns>Unity pozice ve 3D prostoru</returns>
protected Vector3 GPSToUnity(Position position)
{
if (!mapCenter.HasValue) return Vector3.zero;
// Výpočet vzdálenosti a směru od středu
// Zjednodušená verze - pro malé vzdálenosti je dost přesná
double latDiff = position.Lat - mapCenter.Value.Lat;
double lonDiff = position.Lon - mapCenter.Value.Lon;
// Převod stupňů na metry
// 1 stupeň latitude ≈ 111320 metrů
// 1 stupeň longitude závisí na latitude: 111320 * cos(latitude)
double metersPerDegreeLat = 111320.0;
double metersPerDegreeLon = 111320.0 * Math.Cos(mapCenter.Value.Lat * Math.PI / 180.0);
float x = (float)(lonDiff * metersPerDegreeLon);
float z = (float)(latDiff * metersPerDegreeLat);
return new Vector3(x, 0, z);
}
/// <summary>
/// Konverze Unity pozice zpět na GPS souřadnice.
/// Inverzní funkce k GPSToUnity.
/// </summary>
/// <param name="unityPos">Unity pozice</param>
/// <returns>GPS souřadnice</returns>
protected Position UnityToGPS(Vector3 unityPos)
{
if (!mapCenter.HasValue) return new Position(0, 0);
double metersPerDegreeLat = 111320.0;
double metersPerDegreeLon = 111320.0 * Math.Cos(mapCenter.Value.Lat * Math.PI / 180.0);
double latDiff = unityPos.z / metersPerDegreeLat;
double lonDiff = unityPos.x / metersPerDegreeLon;
return new Position(mapCenter.Value.Lat + latDiff, mapCenter.Value.Lon + lonDiff);
}
#region
// BUDOVY (BUILDINGS)
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Získá barvu budovy podle typu
/// </summary>
private Color GetBuildingColor(string buildingType)
{
if (string.IsNullOrEmpty(buildingType)) return new Color(0.7f, 0.65f, 0.6f);
switch (buildingType.ToLower())
{
case "residential":
case "apartments":
case "house":
return new Color(0.85f, 0.75f, 0.65f); // Teplá béžová
case "commercial":
case "retail":
case "shop":
return new Color(0.7f, 0.7f, 0.85f); // Světle modrá
case "industrial":
case "warehouse":
return new Color(0.6f, 0.55f, 0.5f); // Tmavě šedá
case "school":
case "university":
return new Color(0.9f, 0.85f, 0.7f); // Světle žlutá
case "hospital":
case "clinic":
return new Color(0.9f, 0.9f, 0.95f); // Bílá
case "church":
case "cathedral":
return new Color(0.8f, 0.75f, 0.85f); // Světle fialová
case "garage":
case "garages":
return new Color(0.5f, 0.5f, 0.5f); // Šedá
default:
return new Color(0.75f, 0.7f, 0.65f); // Výchozí béžová
}
}
/// <summary>
/// Vytvoření budovy z mapových dat.
/// Budova je vykreslena jako 3D kvádr s výškou.
/// </summary>
/// <param name="building">Data budovy z mapy</param>
protected void CreateBuildingObject(MapBuilding building)
{
// Vytvoření GameObjectu
var buildingObj = new GameObject($"Building_{building.Name ?? "Unknown"}");
buildingObj.transform.SetParent(buildingsContainer.transform);
// Výpočet středu budovy
Vector3 center = CalculatePolygonCenter(building.Outline);
buildingObj.transform.position = center;
// Vytvoření mesh pro budovu
MeshFilter meshFilter = buildingObj.AddComponent<MeshFilter>();
MeshRenderer meshRenderer = buildingObj.AddComponent<MeshRenderer>();
// Generování mesh z polygonu (výška implicitně 10m)
Mesh mesh = CreateExtrudedPolygonMesh(building.Outline, 10f);
meshFilter.mesh = mesh;
// Použijeme barvu podle typu budovy
Color buildingColor = GetBuildingColor(building.BuildingType);
meshRenderer.material = CreateUnlitMaterial(buildingColor);
// Přidání collideru pro interakci
buildingObj.AddComponent<MeshCollider>();
}
/// <summary>
/// Výpočet středu polygonu
/// </summary>
private Vector3 CalculatePolygonCenter(List<Position> points)
{
Vector3 center = Vector3.zero;
foreach (var point in points)
{
center += GPSToUnity(point);
}
return center / points.Count;
}
/// <summary>
/// Vytvoření 3D mesh z polygonu vytažením do výšky.
///
/// ALGORITMUS:
/// 1. Triangulace spodní podstavy (ear clipping)
/// 2. Kopie vrcholů nahoru pro horní podstavu
/// 3. Vytvoření bočních stěn spojením okrajů
/// </summary>
private Mesh CreateExtrudedPolygonMesh(List<Position> outline, float height)
{
Mesh mesh = new Mesh();
int vertexCount = outline.Count;
// Vertices - spodní a horní podstava
Vector3[] vertices = new Vector3[vertexCount * 2];
Vector3 center = CalculatePolygonCenter(outline);
for (int i = 0; i < vertexCount; i++)
{
Vector3 pos = GPSToUnity(outline[i]) - center;
vertices[i] = pos; // Spodní
vertices[i + vertexCount] = pos + Vector3.up * height; // Horní
}
// Triangles - jen boční stěny pro jednoduchost
List<int> triangles = new List<int>();
for (int i = 0; i < vertexCount; i++)
{
int next = (i + 1) % vertexCount;
// Boční stěna - dva trojúhelníky
triangles.Add(i);
triangles.Add(i + vertexCount);
triangles.Add(next);
triangles.Add(next);
triangles.Add(i + vertexCount);
triangles.Add(next + vertexCount);
}
// Horní podstava - zjednodušená triangulace (fan)
if (vertexCount >= 3)
{
for (int i = 1; i < vertexCount - 1; i++)
{
triangles.Add(vertexCount); // Střed (první bod horní)
triangles.Add(vertexCount + i);
triangles.Add(vertexCount + i + 1);
}
}
mesh.vertices = vertices;
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
#region
// CESTY (PATHWAYS)
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Vytvoření cesty z mapových dat.
/// Cesta je vykreslena jako LineRenderer nebo plochý polygon.
/// </summary>
/// <param name="pathway">Data cesty</param>
protected void CreatePathwayObject(MapPathway pathway)
{
var pathObj = new GameObject($"Path_{pathway.Name ?? "Unknown"}");
pathObj.transform.SetParent(pathwaysContainer.transform);
// Použijeme LineRenderer pro jednoduchost
LineRenderer line = pathObj.AddComponent<LineRenderer>();
line.material = pathwayMaterial;
line.widthMultiplier = GetPathWidth(pathway.PathType);
// Nastavení bodů cesty
line.positionCount = pathway.Points.Count;
for (int i = 0; i < pathway.Points.Count; i++)
{
Vector3 pos = GPSToUnity(pathway.Points[i]);
pos.y = 0.1f; // Mírně nad zemí
line.SetPosition(i, pos);
}
}
/// <summary>
/// Získání šířky cesty podle typu
/// </summary>
private float GetPathWidth(PathType type)
{
switch (type)
{
case PathType.Road: return 5f;
case PathType.Pedestrian: return 2f; // Sidewalk
case PathType.Path: return 1f; // Trail
case PathType.Footway: return 1.5f;
default: return 2f;
}
}
#region
// OBLASTI (AREAS)
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Vytvoření oblasti z mapových dat.
/// Oblast je vykreslena jako plochý 3D polygon.
/// </summary>
/// <param name="area">Data oblasti</param>
protected void CreateAreaObject(MapArea area)
{
var areaObj = new GameObject($"Area_{area.Name ?? "Unknown"}");
areaObj.transform.SetParent(areasContainer.transform);
MeshFilter meshFilter = areaObj.AddComponent<MeshFilter>();
MeshRenderer meshRenderer = areaObj.AddComponent<MeshRenderer>();
// Vytvoření plochého mesh
Mesh mesh = CreateFlatPolygonMesh(area.Outline);
meshFilter.mesh = mesh;
// Materiál podle typu
Material mat = new Material(areaMaterial);
mat.color = GetAreaColor(area.AreaType);
meshRenderer.material = mat;
areaObj.transform.position = new Vector3(0, 0.05f, 0); // Těsně nad zemí
}
/// <summary>
/// Vytvoření plochého mesh z polygonu
/// </summary>
private Mesh CreateFlatPolygonMesh(List<Position> outline)
{
Mesh mesh = new Mesh();
int vertexCount = outline.Count;
Vector3[] vertices = new Vector3[vertexCount];
Vector3 center = CalculatePolygonCenter(outline);
for (int i = 0; i < vertexCount; i++)
{
vertices[i] = GPSToUnity(outline[i]) - center;
}
// Triangulace - fan pattern
List<int> triangles = new List<int>();
if (vertexCount >= 3)
{
for (int i = 1; i < vertexCount - 1; i++)
{
triangles.Add(0);
triangles.Add(i);
triangles.Add(i + 1);
}
}
mesh.vertices = vertices;
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
return mesh;
}
/// <summary>
/// Získání barvy oblasti podle typu
/// </summary>
private Color GetAreaColor(MapAreaType type)
{
switch (type)
{
case MapAreaType.Park: return new Color(0.2f, 0.8f, 0.2f, 0.5f);
case MapAreaType.Garden: return new Color(0.3f, 0.7f, 0.3f, 0.5f);
case MapAreaType.Water: return new Color(0.2f, 0.5f, 0.9f, 0.7f);
case MapAreaType.Forest: return new Color(0.1f, 0.5f, 0.1f, 0.5f);
case MapAreaType.Grass: return new Color(0.4f, 0.8f, 0.4f, 0.5f);
case MapAreaType.Playground: return new Color(0.6f, 0.5f, 0.4f, 0.5f);
default: return new Color(0.5f, 0.5f, 0.5f, 0.5f);
}
}
#region
// POI (POINTS OF INTEREST)
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Vytvoření POI markeru - VÝRAZNÝ marker s ikonou a popisem.
/// POI jsou body zájmu pro orientaci na mapě.
/// </summary>
/// <param name="poi">Data POI</param>
protected void CreatePOIObject(MapPOI poi)
{
var poiObj = new GameObject($"POI_{poi.Name ?? "Unknown"}");
poiObj.transform.SetParent(poisContainer.transform);
Vector3 pos = GPSToUnity(poi.Location);
pos.y = 0.5f;
poiObj.transform.position = pos;
// Vytvoření vizuálu - větší a viditelnější
var visual = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
visual.transform.SetParent(poiObj.transform);
visual.transform.localPosition = new Vector3(0, 0.5f, 0);
visual.transform.localScale = new Vector3(1.2f, 0.5f, 1.2f);
// Barva podle typu - svítící materiál
Color poiColor = GetPOIColor(poi.POIType);
Material mat = CreateEmissiveMaterial(poiColor, 1.5f);
visual.GetComponent<Renderer>().material = mat;
// Ikona/symbol nahoře - malá koule
var icon = GameObject.CreatePrimitive(PrimitiveType.Sphere);
icon.transform.SetParent(poiObj.transform);
icon.transform.localPosition = new Vector3(0, 1.3f, 0);
icon.transform.localScale = new Vector3(0.8f, 0.8f, 0.8f);
icon.GetComponent<Renderer>().material = mat;
}
/// <summary>
/// Získání barvy POI podle typu
/// </summary>
private Color GetPOIColor(MapPOIType type)
{
switch (type)
{
case MapPOIType.Shop: return Color.magenta;
case MapPOIType.FoodDrink: return new Color(1f, 0.5f, 0f);
case MapPOIType.Transport: return Color.blue;
case MapPOIType.Landmark: return Color.yellow;
case MapPOIType.Health: return Color.green;
case MapPOIType.Culture: return Color.cyan;
case MapPOIType.Recreation: return new Color(0.5f, 1f, 0.5f);
default: return Color.white;
}
}
#region
// HRÁČI
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Barvy pro hráče - každý hráč má unikátní barvu
/// </summary>
private static readonly Color[] PlayerColors = new Color[]
{
Color.red,
Color.blue,
Color.green,
Color.yellow,
Color.cyan,
Color.magenta,
new Color(1f, 0.5f, 0f), // Orange
new Color(0.5f, 0f, 1f), // Purple
new Color(0f, 1f, 0.5f), // Mint
new Color(1f, 0.75f, 0.8f), // Pink
};
/// <summary>
/// Vytvoření nebo aktualizace objektu hráče - VÝRAZNÝ marker.
/// Hráč je vykreslován jako Capsule s barvou a jménem.
/// </summary>
/// <param name="playerId">UUID hráče</param>
/// <param name="position">GPS pozice</param>
/// <param name="state">Stav hráče</param>
protected void UpdatePlayerObject(string playerId, Position position, PlayerState state)
{
// Skip pro lokálního hráče - ten je vykreslen jinak
if (playerId == clientUuid) return;
GameObject playerObj;
if (!playerObjects.TryGetValue(playerId, out playerObj))
{
// Kontejner pro hráče
playerObj = new GameObject($"Player_{GetPlayerName(playerId)}");
playerObj.transform.SetParent(playersContainer.transform);
// Tělo hráče - větší capsule
var body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
body.name = "Body";
body.transform.SetParent(playerObj.transform);
body.transform.localPosition = new Vector3(0, 2f, 0);
body.transform.localScale = new Vector3(2f, 2.5f, 2f);
// Přidělení výrazné barvy
int colorIndex = playerObjects.Count % PlayerColors.Length;
var mat = CreateEmissiveMaterial(PlayerColors[colorIndex], 0.5f);
body.GetComponent<Renderer>().material = mat;
// Kroužek pod hráčem
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "Ring";
ring.transform.SetParent(playerObj.transform);
ring.transform.localPosition = new Vector3(0, 0.1f, 0);
ring.transform.localScale = new Vector3(4f, 0.1f, 4f);
var ringMat = CreateEmissiveMaterial(PlayerColors[colorIndex] * 0.7f, 0.3f);
ring.GetComponent<Renderer>().material = ringMat;
playerObjects[playerId] = playerObj;
}
// Aktualizace pozice
Vector3 unityPos = GPSToUnity(position);
playerObj.transform.position = unityPos;
// Viditelnost podle stavu
playerObj.SetActive(state == PlayerState.Alive);
}
/// <summary>
/// Aktualizace lokálního hráče (kurzor/avatar) - VELMI VÝRAZNÝ
/// </summary>
protected void UpdateLocalPlayerVisual()
{
// Vytvoření lokálního hráče pokud neexistuje
if (localPlayerObject == null)
{
// Hlavní objekt hráče
localPlayerObject = new GameObject("LocalPlayer");
localPlayerObject.transform.SetParent(playersContainer.transform);
// Tělo hráče - VELKÁ jasně zelená capsule
var body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
body.name = "Body";
body.transform.SetParent(localPlayerObject.transform);
body.transform.localPosition = new Vector3(0, 2.5f, 0);
body.transform.localScale = new Vector3(3f, 3f, 3f);
// Výrazná zelená barva se svícením
var bodyMat = CreateEmissiveMaterial(new Color(0.3f, 1f, 0.3f), 1.5f);
body.GetComponent<Renderer>().material = bodyMat;
// VELKÝ indikátor - kroužek kolem hráče
var indicator = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
indicator.name = "Indicator";
indicator.transform.SetParent(localPlayerObject.transform);
indicator.transform.localPosition = new Vector3(0, 0.15f, 0);
indicator.transform.localScale = new Vector3(8f, 0.15f, 8f);
// Žlutý svítící kroužek
var indicatorMat = CreateEmissiveMaterial(new Color(1f, 1f, 0f), 2f);
indicator.GetComponent<Renderer>().material = indicatorMat;
// Šipka směru
var arrow = GameObject.CreatePrimitive(PrimitiveType.Cube);
arrow.name = "Arrow";
arrow.transform.SetParent(localPlayerObject.transform);
arrow.transform.localPosition = new Vector3(0, 0.3f, 3f);
arrow.transform.localScale = new Vector3(1.5f, 0.3f, 3f);
arrow.GetComponent<Renderer>().material = bodyMat;
// Druhý vnější ring pro ještě lepší viditelnost
var outerRing = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
outerRing.name = "OuterRing";
outerRing.transform.SetParent(localPlayerObject.transform);
outerRing.transform.localPosition = new Vector3(0, 0.1f, 0);
outerRing.transform.localScale = new Vector3(12f, 0.1f, 12f);
var outerMat = CreateEmissiveMaterial(new Color(0.5f, 1f, 0.5f, 0.3f), 0.5f);
outerRing.GetComponent<Renderer>().material = outerMat;
}
// Pozice podle currentPlayerPosition (ne kamery!)
localPlayerObject.transform.position = new Vector3(
currentPlayerPosition.x,
0,
currentPlayerPosition.z
);
}
/// <summary>Objekt lokálního hráče</summary>
protected GameObject localPlayerObject;
#region
// TĚLA (BODIES)
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Vytvoření objektu těla mrtvého hráče.
/// Tělo je vykresleno jako ležící capsule.
/// </summary>
/// <param name="bodyId">ID těla</param>
/// <param name="position">GPS pozice</param>
/// <param name="victimName">Jméno oběti</param>
protected void CreateBodyObject(string bodyId, Position position, string victimName)
{
if (bodyObjects.ContainsKey(bodyId)) return;
// Kontejner pro tělo
var container = new GameObject($"Body_{victimName}");
container.transform.SetParent(bodiesContainer.transform);
Vector3 unityPos = GPSToUnity(position);
container.transform.position = unityPos;
// Ležící tělo
var bodyObj = GameObject.CreatePrimitive(PrimitiveType.Capsule);
bodyObj.name = "Corpse";
bodyObj.transform.SetParent(container.transform);
bodyObj.transform.localPosition = new Vector3(0, 0.3f, 0);
bodyObj.transform.rotation = Quaternion.Euler(90, 0, 0);
bodyObj.transform.localScale = new Vector3(1.5f, 2f, 1.5f);
// Výrazná červená barva se svícením
var bodyMat = CreateEmissiveMaterial(new Color(1f, 0.2f, 0.2f), 1.5f);
bodyObj.GetComponent<Renderer>().material = bodyMat;
// Výstražný kroužek
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "WarningRing";
ring.transform.SetParent(container.transform);
ring.transform.localPosition = new Vector3(0, 0.1f, 0);
ring.transform.localScale = new Vector3(6f, 0.1f, 6f);
var ringMat = CreateEmissiveMaterial(new Color(1f, 0f, 0f, 0.6f), 2f);
ring.GetComponent<Renderer>().material = ringMat;
// Výstražná značka nahoře
var warning = GameObject.CreatePrimitive(PrimitiveType.Cube);
warning.name = "Warning";
warning.transform.SetParent(container.transform);
warning.transform.localPosition = new Vector3(0, 4f, 0);
warning.transform.localScale = new Vector3(2f, 2f, 0.3f);
warning.GetComponent<Renderer>().material = bodyMat;
bodyObjects[bodyId] = container;
}
/// <summary>
/// Odstranění objektu těla (po reportu)
/// </summary>
protected void RemoveBodyObject(string bodyId)
{
if (bodyObjects.TryGetValue(bodyId, out var bodyObj))
{
Destroy(bodyObj);
bodyObjects.Remove(bodyId);
}
}
#region
// ÚKOLY (TASKS)
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Vytvoření markerů úkolů pro hráče.
/// Úkoly jsou vykresleny jako svítící válce.
/// </summary>
protected void CreateTaskMarkers()
{
if (client?.MyTasks == null) return;
foreach (var task in client.MyTasks)
{
CreateTaskObject(task);
}
}
/// <summary>
/// Vytvoření markeru úkolu - VÝRAZNÝ marker s popiskem
/// </summary>
private void CreateTaskObject(object taskObj)
{
// Dynamický přístup k vlastnostem
var taskType = taskObj.GetType();
string taskId = taskType.GetField("TaskId")?.GetValue(taskObj) as string ??
taskType.GetProperty("TaskId")?.GetValue(taskObj) as string ??
taskType.GetField("Id")?.GetValue(taskObj) as string ??
taskType.GetProperty("Id")?.GetValue(taskObj) as string ?? System.Guid.NewGuid().ToString();
string taskName = taskType.GetField("Name")?.GetValue(taskObj) as string ??
taskType.GetProperty("Name")?.GetValue(taskObj) as string ?? "Task";
// Získání lokace
object locationObj = taskType.GetField("Location")?.GetValue(taskObj) ??
taskType.GetProperty("Location")?.GetValue(taskObj);
Position location = locationObj is Position pos ? pos : new Position(0, 0);
if (taskObjects.ContainsKey(taskId)) return;
// Kontejner pro task marker
var container = new GameObject($"Task_{taskName}");
container.transform.SetParent(tasksContainer.transform);
Vector3 unityPos = GPSToUnity(location);
container.transform.position = unityPos;
// Hlavní marker - VĚTŠÍ svítící válec
var marker = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
marker.name = "Marker";
marker.transform.SetParent(container.transform);
marker.transform.localPosition = new Vector3(0, 2f, 0);
marker.transform.localScale = new Vector3(4f, 2f, 4f);
// Výrazný svítící cyan materiál
var mat = CreateEmissiveMaterial(new Color(0f, 1f, 1f), 2f);
marker.GetComponent<Renderer>().material = mat;
// Vrchní kužel jako "šipka dolů"
var cone = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
cone.name = "Cone";
cone.transform.SetParent(container.transform);
cone.transform.localPosition = new Vector3(0, 5f, 0);
cone.transform.localScale = new Vector3(2f, 1f, 2f);
cone.GetComponent<Renderer>().material = mat;
// Pulsující ring na zemi
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "Ring";
ring.transform.SetParent(container.transform);
ring.transform.localPosition = new Vector3(0, 0.1f, 0);
ring.transform.localScale = new Vector3(8f, 0.1f, 8f);
var ringMat = CreateEmissiveMaterial(new Color(0f, 0.8f, 0.8f, 0.5f), 1f);
ring.GetComponent<Renderer>().material = ringMat;
taskObjects[taskId] = container;
}
/// <summary>
/// Vytvoří svítící materiál
/// BUILD-SAFE: Používá Sprites/Default - emise simulována jasnou barvou
/// </summary>
private Material CreateEmissiveMaterial(Color color, float intensity = 1f)
{
// Simulujeme emisi zjasnením barvy
Color brightColor = new Color(
Mathf.Min(1f, color.r * (1f + intensity * 0.5f)),
Mathf.Min(1f, color.g * (1f + intensity * 0.5f)),
Mathf.Min(1f, color.b * (1f + intensity * 0.5f)),
color.a
);
return CreateUnlitMaterial(brightColor);
}
/// <summary>
/// Aktualizace stavu úkolu (dokončen = skryje marker)
/// </summary>
protected void UpdateTaskMarker(string taskId, bool completed)
{
if (taskObjects.TryGetValue(taskId, out var taskObj))
{
if (completed)
{
// Skryjeme celý marker
taskObj.SetActive(false);
}
}
}
#region
// OPRAVNÉ STANICE
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Vytvoření markerů opravných stanic.
/// Zobrazují se pouze při aktivní sabotáži.
/// </summary>
protected void CreateRepairStationMarkers()
{
if (currentSabotage?.RepairStations == null) return;
foreach (var station in currentSabotage.RepairStations)
{
CreateRepairStationObject(station.StationId, station.Name, station.Location);
}
}
/// <summary>
/// Vytvoření markeru opravné stanice - VELMI VÝRAZNÝ marker
/// </summary>
private void CreateRepairStationObject(string stationId, string stationName, Position stationPos)
{
if (string.IsNullOrEmpty(stationId)) stationId = System.Guid.NewGuid().ToString();
if (string.IsNullOrEmpty(stationName)) stationName = "Repair";
if (repairStationObjects.ContainsKey(stationId)) return;
Debug.Log($"[GeoSus] Creating repair station marker: {stationId} '{stationName}' @ {stationPos.Lat:F6}, {stationPos.Lon:F6}");
// Kontejner pro opravnou stanici
var container = new GameObject($"Repair_{stationName}");
container.transform.SetParent(repairStationsContainer.transform);
Vector3 unityPos = GPSToUnity(stationPos);
container.transform.position = unityPos;
// VELKÝ hlavní marker - věž
var tower = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
tower.name = "Tower";
tower.transform.SetParent(container.transform);
tower.transform.localPosition = new Vector3(0, 5f, 0);
tower.transform.localScale = new Vector3(3f, 5f, 3f);
// Výrazná oranžová/žlutá barva
var towerMat = CreateEmissiveMaterial(new Color(1f, 0.6f, 0f), 3f);
tower.GetComponent<Renderer>().material = towerMat;
// Výstražná koule nahoře
var beacon = GameObject.CreatePrimitive(PrimitiveType.Sphere);
beacon.name = "Beacon";
beacon.transform.SetParent(container.transform);
beacon.transform.localPosition = new Vector3(0, 11f, 0);
beacon.transform.localScale = new Vector3(4f, 4f, 4f);
var beaconMat = CreateEmissiveMaterial(new Color(1f, 0.8f, 0f), 5f);
beacon.GetComponent<Renderer>().material = beaconMat;
// Velký kroužek na zemi - blikající
var ring = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ring.name = "Ring";
ring.transform.SetParent(container.transform);
ring.transform.localPosition = new Vector3(0, 0.15f, 0);
ring.transform.localScale = new Vector3(12f, 0.15f, 12f);
var ringMat = CreateEmissiveMaterial(new Color(1f, 0.5f, 0f, 0.6f), 2f);
ring.GetComponent<Renderer>().material = ringMat;
// Druhá výstražná značka - šipka dolů
var arrow1 = GameObject.CreatePrimitive(PrimitiveType.Cube);
arrow1.name = "Arrow1";
arrow1.transform.SetParent(container.transform);
arrow1.transform.localPosition = new Vector3(0, 15f, 0);
arrow1.transform.localScale = new Vector3(2f, 4f, 0.5f);
arrow1.GetComponent<Renderer>().material = beaconMat;
repairStationObjects[stationId] = container;
}
/// <summary>
/// Aktualizace stavu opravné stanice
/// </summary>
protected void UpdateRepairStationMarker(string stationId, bool active)
{
if (repairStationObjects.TryGetValue(stationId, out var stationObj))
{
// Najdeme Beacon (kouli nahoře) pro blikání
var beacon = stationObj.transform.Find("Beacon");
var ring = stationObj.transform.Find("Ring");
if (beacon != null)
{
var renderer = beacon.GetComponent<Renderer>();
if (renderer != null)
{
float pulse = active ? Mathf.Abs(Mathf.Sin(Time.time * 8f)) : 0.5f;
Color emissionColor = new Color(1f, 0.8f, 0f) * (1f + pulse * 3f);
renderer.material.color = emissionColor;
}
}
if (ring != null)
{
var renderer = ring.GetComponent<Renderer>();
if (renderer != null)
{
float pulse = Mathf.Abs(Mathf.Sin(Time.time * 3f));
Color ringColor = active ? new Color(1f, 1f, 0f, 0.6f + pulse * 0.4f) : new Color(1f, 0.5f, 0f, 0.4f);
renderer.material.color = ringColor;
}
}
}
}
/// <summary>
/// Odstranění všech opravných stanic (po opravě sabotáže)
/// </summary>
protected void ClearRepairStationMarkers()
{
foreach (var kvp in repairStationObjects)
{
Destroy(kvp.Value);
}
repairStationObjects.Clear();
}
#region
// NAČTENÍ MAPY
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Zpracování přijatých mapových dat.
/// Vytvoří všechny 3D objekty mapy.
///
/// POZNÁMKA: MapDataPayload používá kompaktní formát:
/// - Buildings: List<double[]> kde každý double[] je [lat1,lon1,lat2,lon2,...]
/// - Pathways: List<double[]> podobně
/// - Areas: List<double[]> podobně
/// - POIs: List<double> ve formátu [lat,lon,type,lat,lon,type,...]
/// </summary>
/// <param name="mapData">Data z Overpass API</param>
protected void ProcessMapData(MapDataPayload mapData)
{
if (mapData == null) return;
// Uložení referenčních bodů
mapCenter = mapData.Center;
mapRadius = mapData.RadiusMeters;
// Inicializace systému
InitializeMapSystem();
// Vytvoření budov z kompaktního formátu
if (mapData.Buildings != null)
{
loadingMessage = "Vytvářím budovy...";
for (int i = 0; i < mapData.Buildings.Count; i++)
{
var coords = mapData.Buildings[i];
string buildingType = (mapData.BuildingTypes != null && i < mapData.BuildingTypes.Count)
? mapData.BuildingTypes[i] : null;
CreateBuildingFromCoords(coords, buildingType, i);
}
}
// Vytvoření cest z kompaktního formátu
if (mapData.Pathways != null)
{
loadingMessage = "Vytvářím cesty...";
for (int i = 0; i < mapData.Pathways.Count; i++)
{
var coords = mapData.Pathways[i];
PathType pathType = (mapData.PathwayTypes != null && i < mapData.PathwayTypes.Count)
? (PathType)mapData.PathwayTypes[i] : PathType.Footway;
CreatePathwayFromCoords(coords, pathType, i);
}
}
// Vytvoření oblastí z kompaktního formátu
if (mapData.Areas != null)
{
loadingMessage = "Vytvářím oblasti...";
for (int i = 0; i < mapData.Areas.Count; i++)
{
var coords = mapData.Areas[i];
MapAreaType areaType = (mapData.AreaTypes != null && i < mapData.AreaTypes.Count)
? (MapAreaType)mapData.AreaTypes[i] : MapAreaType.Other;
CreateAreaFromCoords(coords, areaType, i);
}
}
// Vytvoření POI z kompaktního formátu [lat,lon,type,lat,lon,type,...]
if (mapData.POIs != null && mapData.POIs.Count >= 3)
{
loadingMessage = "Vytvářím body zájmu...";
for (int i = 0; i + 2 < mapData.POIs.Count; i += 3)
{
double lat = mapData.POIs[i];
double lon = mapData.POIs[i + 1];
int typeIndex = (int)mapData.POIs[i + 2];
MapPOIType poiType = (MapPOIType)typeIndex;
CreatePOIFromCoords(lat, lon, poiType, i / 3);
}
}
loadingProgress = 1f;
loadingMessage = "Mapa připravena!";
}
/// <summary>
/// Vytvoření budovy z kompaktních souřadnic
/// </summary>
private void CreateBuildingFromCoords(double[] coords, string buildingType, int index)
{
if (coords == null || coords.Length < 6) return; // Potřeba alespoň 3 body
// Převod na List<Position>
List<Position> outline = new List<Position>();
for (int i = 0; i + 1 < coords.Length; i += 2)
{
outline.Add(new Position(coords[i], coords[i + 1]));
}
// Vytvoření MapBuilding objektu
var building = new MapBuilding
{
Id = index,
Outline = outline,
Name = buildingType ?? "Building",
BuildingType = buildingType
};
CreateBuildingObject(building);
}
/// <summary>
/// Vytvoření cesty z kompaktních souřadnic
/// </summary>
private void CreatePathwayFromCoords(double[] coords, PathType pathType, int index)
{
if (coords == null || coords.Length < 4) return; // Potřeba alespoň 2 body
// Převod na List<Position>
List<Position> points = new List<Position>();
for (int i = 0; i + 1 < coords.Length; i += 2)
{
points.Add(new Position(coords[i], coords[i + 1]));
}
// Vytvoření MapPathway objektu
var pathway = new MapPathway
{
Id = index,
Points = points,
PathType = pathType,
Name = pathType.ToString()
};
CreatePathwayObject(pathway);
}
/// <summary>
/// Vytvoření oblasti z kompaktních souřadnic
/// </summary>
private void CreateAreaFromCoords(double[] coords, MapAreaType areaType, int index)
{
if (coords == null || coords.Length < 6) return; // Potřeba alespoň 3 body
// Převod na List<Position>
List<Position> outline = new List<Position>();
for (int i = 0; i + 1 < coords.Length; i += 2)
{
outline.Add(new Position(coords[i], coords[i + 1]));
}
// Vytvoření MapArea objektu
var area = new MapArea
{
Id = index,
Outline = outline,
AreaType = areaType,
Name = areaType.ToString()
};
CreateAreaObject(area);
}
/// <summary>
/// Vytvoření POI z souřadnic
/// </summary>
private void CreatePOIFromCoords(double lat, double lon, MapPOIType poiType, int index)
{
var poi = new MapPOI
{
Id = index,
Location = new Position(lat, lon),
POIType = poiType,
Name = poiType.ToString()
};
CreatePOIObject(poi);
}
#region
// ÚKLID
// ════════════════════════════════════════════════════════════════════════
#endregion
/// <summary>
/// Vyčištění všech mapových objektů.
/// Volá se při ukončení hry nebo odpojení.
/// </summary>
protected void CleanupMapObjects()
{
// Zničení hlavního kontejneru zničí všechny potomky
if (mapContainer != null)
{
Destroy(mapContainer);
mapContainer = null;
}
// Vyčištění slovníků
playerObjects.Clear();
bodyObjects.Clear();
taskObjects.Clear();
repairStationObjects.Clear();
// Zničení lokálního hráče
if (localPlayerObject != null)
{
Destroy(localPlayerObject);
localPlayerObject = null;
}
}
/// <summary>
/// Aktualizace všech dynamických objektů na mapě.
/// Volá se každý frame v Update().
/// </summary>
protected void UpdateMapObjects()
{
if (client == null || currentState != AppState.InGame) return;
// Aktualizace pozic hráčů - bezpečně kopírujeme klíče
if (client.PlayerPositions != null)
{
try
{
// Použijeme ToArray() místo new List() - je rychlejší a bezpečnější
var playerIds = client.PlayerPositions.Keys.ToArray();
foreach (var playerId in playerIds)
{
if (client.PlayerPositions.TryGetValue(playerId, out var playerInfo))
{
UpdatePlayerObject(playerId, playerInfo.Position, playerInfo.State);
}
}
}
catch (System.InvalidOperationException)
{
// Dictionary byl změněn během iterace - přeskočíme tento frame
}
}
// Aktualizace lokálního hráče
UpdateLocalPlayerVisual();
// Aktualizace opravných stanic (blikání) - bezpečně kopírujeme klíče
if (currentSabotage != null)
{
try
{
var stationIds = repairStationObjects.Keys.ToArray();
foreach (var stationId in stationIds)
{
bool isActive = activeRepairStation == stationId;
UpdateRepairStationMarker(stationId, isActive);
}
}
catch (System.InvalidOperationException)
{
// Dictionary byl změněn během iterace - přeskočíme tento frame
}
}
// Aktualizace meeting location markeru
bool hasMeeting = currentMeeting != null && client?.CurrentLobbyState?.Phase == GamePhase.Meeting;
if (hasMeeting && currentMeeting != null)
{
UpdateMeetingLocationMarker(currentMeeting.MeetingLocation, true);
}
else
{
UpdateMeetingLocationMarker(null, false);
}
}
/// <summary>
/// Hledání nejbližší opravné stanice
/// </summary>
protected RepairStationInfo FindNearbyRepairStation(double maxDistance)
{
if (currentSabotage?.RepairStations == null) return null;
if (localPlayerObject == null) return null;
Position myPos = UnityToGPS(localPlayerObject.transform.position);
foreach (var station in currentSabotage.RepairStations)
{
double dist = CalculateDistance(myPos, station.Location);
if (dist <= maxDistance) return station;
}
return null;
}
/// <summary>
/// Výpočet vzdálenosti mezi dvěma GPS body (Haversine)
/// </summary>
protected double CalculateDistance(Position a, Position b)
{
const double R = 6371000; // Poloměr Země v metrech
double lat1 = a.Lat * Math.PI / 180;
double lat2 = b.Lat * Math.PI / 180;
double dLat = (b.Lat - a.Lat) * Math.PI / 180;
double dLon = (b.Lon - a.Lon) * Math.PI / 180;
double h = Math.Sin(dLat/2) * Math.Sin(dLat/2) +
Math.Cos(lat1) * Math.Cos(lat2) *
Math.Sin(dLon/2) * Math.Sin(dLon/2);
return 2 * R * Math.Asin(Math.Sqrt(h));
}
}