1533 lines
64 KiB
C#
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));
|
|
}
|
|
}
|