|
|
|
@@ -3,9 +3,8 @@ using System;
|
|
|
|
using System.Collections;
|
|
|
|
using System.Collections;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Globalization;
|
|
|
|
using System.Globalization;
|
|
|
|
using UnityEditor;
|
|
|
|
using TMPro;
|
|
|
|
using UnityEngine;
|
|
|
|
using UnityEngine;
|
|
|
|
using UnityEngine.Localization.Pseudo;
|
|
|
|
|
|
|
|
using UnityEngine.UI;
|
|
|
|
using UnityEngine.UI;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -13,8 +12,8 @@ namespace Subsystems{
|
|
|
|
[System.Serializable]
|
|
|
|
[System.Serializable]
|
|
|
|
public class BuildingSettings
|
|
|
|
public class BuildingSettings
|
|
|
|
{
|
|
|
|
{
|
|
|
|
public Material ResidentalBuildingsMat;
|
|
|
|
public Material ResidentialBuildingsMat;
|
|
|
|
public float ResidentalBuildingHeight;
|
|
|
|
public float ResidentialBuildingHeight;
|
|
|
|
public Material CommercialBuildingsMat;
|
|
|
|
public Material CommercialBuildingsMat;
|
|
|
|
public float CommercialBuildingHeight;
|
|
|
|
public float CommercialBuildingHeight;
|
|
|
|
public Material IndustrialBuildingsMat;
|
|
|
|
public Material IndustrialBuildingsMat;
|
|
|
|
@@ -56,7 +55,6 @@ namespace Subsystems{
|
|
|
|
public Material GrassMat;
|
|
|
|
public Material GrassMat;
|
|
|
|
public Material WaterMat;
|
|
|
|
public Material WaterMat;
|
|
|
|
public Material DefaultMat;
|
|
|
|
public Material DefaultMat;
|
|
|
|
public Material GlobalSkybox; // Sem v Unity přetáhneš svůj Panoramic materiál
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public class GameManager_Map
|
|
|
|
public class GameManager_Map
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@@ -67,6 +65,43 @@ namespace Subsystems{
|
|
|
|
private PathwaySettings _pathwaySettings;
|
|
|
|
private PathwaySettings _pathwaySettings;
|
|
|
|
private AreaSettings _areaSettings;
|
|
|
|
private AreaSettings _areaSettings;
|
|
|
|
private const float _metersPerUnit = 1f;
|
|
|
|
private const float _metersPerUnit = 1f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── Layer Y separation (single source of truth for vertical stacking) ───
|
|
|
|
|
|
|
|
// Areas at the bottom, paths above areas, buildings extruded upward from
|
|
|
|
|
|
|
|
// their own base, POIs floating well above everything else. Z-fighting
|
|
|
|
|
|
|
|
// happens when adjacent geometry shares a Y; these constants keep each
|
|
|
|
|
|
|
|
// logical layer at a distinct elevation.
|
|
|
|
|
|
|
|
private const float kAreaBaseY = 0.10f;
|
|
|
|
|
|
|
|
private const float kPathY = 0.30f;
|
|
|
|
|
|
|
|
private const float kBuildingBaseY = 0.50f;
|
|
|
|
|
|
|
|
private const float kPoiY = 2.00f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Render-queue forcing was tried in P3 to disambiguate same-Y geometry
|
|
|
|
|
|
|
|
// but turned out to be the cause of the "blank map in mobile game view,
|
|
|
|
|
|
|
|
// fine in scene view" regression: forcing transparent-class shaders
|
|
|
|
|
|
|
|
// (default queue 3000+) into the Geometry range (2000-2150) breaks
|
|
|
|
|
|
|
|
// their depth-write/blend assumptions on mobile shader paths. The
|
|
|
|
|
|
|
|
// editor's scene view masks it because it uses different render paths
|
|
|
|
|
|
|
|
// and post-process is off there. Queue forcing removed in P8;
|
|
|
|
|
|
|
|
// disambiguation is now via Y-layering + per-area Y-stagger alone,
|
|
|
|
|
|
|
|
// which the depth buffer resolves correctly even on weak mobile GPUs.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── Marker sizing (top-down camera, units = meters) ─────────────────
|
|
|
|
|
|
|
|
// The camera's orthographic size pushes "1 meter" to a small fraction
|
|
|
|
|
|
|
|
// of the screen. Markers need to be visibly larger than buildings'
|
|
|
|
|
|
|
|
// footprints for instant recognition.
|
|
|
|
|
|
|
|
private const float kMarkerHeight = 8f; // pillar height
|
|
|
|
|
|
|
|
private const float kMarkerRadius = 3f; // pillar radius (cylinder X/Z)
|
|
|
|
|
|
|
|
private const float kMarkerY = 4f; // base Y so pillar centers ~mid-height
|
|
|
|
|
|
|
|
private const float kLabelY = 9f; // text label sits above pillar top
|
|
|
|
|
|
|
|
private const float kLabelFontSize = 14f; // 3D text size in world units
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Runtime marker collections
|
|
|
|
|
|
|
|
private Dictionary<string, GameObject> _taskMarkers = new Dictionary<string, GameObject>();
|
|
|
|
|
|
|
|
private Dictionary<string, GameObject> _bodyMarkers = new Dictionary<string, GameObject>();
|
|
|
|
|
|
|
|
private Dictionary<string, GameObject> _playerAvatars = new Dictionary<string, GameObject>();
|
|
|
|
|
|
|
|
private List<GameObject> _sabotageMarkers = new List<GameObject>();
|
|
|
|
|
|
|
|
|
|
|
|
public GameManager_Map(GameClient gameClient, GameObject mapCenterPoint, BuildingSettings buildingSettings, PathwaySettings pathwaySettings, AreaSettings areaSettings)
|
|
|
|
public GameManager_Map(GameClient gameClient, GameObject mapCenterPoint, BuildingSettings buildingSettings, PathwaySettings pathwaySettings, AreaSettings areaSettings)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
_gameClient = gameClient;
|
|
|
|
_gameClient = gameClient;
|
|
|
|
@@ -75,8 +110,25 @@ namespace Subsystems{
|
|
|
|
_pathwaySettings = pathwaySettings;
|
|
|
|
_pathwaySettings = pathwaySettings;
|
|
|
|
_areaSettings = areaSettings;
|
|
|
|
_areaSettings = areaSettings;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsSceneReady => _mapCenterPoint != null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Called from OnSceneLoaded when Client.unity is loaded so the
|
|
|
|
|
|
|
|
/// MapCenterPoint (which lives in Client.unity) can be wired at runtime.</summary>
|
|
|
|
|
|
|
|
public void SetMapCenterPoint(GameObject go) { _mapCenterPoint = go; }
|
|
|
|
public void BuildMap()
|
|
|
|
public void BuildMap()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_mapCenterPoint == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Debug.LogWarning("[Map] BuildMap skipped: MapCenterPoint is not yet bound.");
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_gameClient?.CurrentLobbyState?.MapData == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
Debug.LogWarning("[Map] BuildMap skipped: no MapData in CurrentLobbyState.");
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ClearChildren();
|
|
|
|
ClearChildren();
|
|
|
|
_centerPosition = _gameClient.CurrentLobbyState.MapData.Center;
|
|
|
|
_centerPosition = _gameClient.CurrentLobbyState.MapData.Center;
|
|
|
|
GameObject buildingsRoot = new GameObject("Buildings");
|
|
|
|
GameObject buildingsRoot = new GameObject("Buildings");
|
|
|
|
@@ -85,8 +137,8 @@ namespace Subsystems{
|
|
|
|
GameObject pathRoot = new GameObject("Pathways");
|
|
|
|
GameObject pathRoot = new GameObject("Pathways");
|
|
|
|
pathRoot.transform.parent = _mapCenterPoint.transform;
|
|
|
|
pathRoot.transform.parent = _mapCenterPoint.transform;
|
|
|
|
|
|
|
|
|
|
|
|
//GameObject areaRoot = new GameObject("Areas");
|
|
|
|
GameObject areaRoot = new GameObject("Areas");
|
|
|
|
//areaRoot.transform.parent = _mapCenterPoint.transform;
|
|
|
|
areaRoot.transform.parent = _mapCenterPoint.transform;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var building in _gameClient.CurrentLobbyState.MapData.GetBuildings())
|
|
|
|
foreach (var building in _gameClient.CurrentLobbyState.MapData.GetBuildings())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@@ -105,20 +157,137 @@ namespace Subsystems{
|
|
|
|
GameObject p = BuildPathwayMesh(path);
|
|
|
|
GameObject p = BuildPathwayMesh(path);
|
|
|
|
p.transform.parent = pathRoot.transform;
|
|
|
|
p.transform.parent = pathRoot.transform;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*foreach (var area in _gameClient.CurrentLobbyState.MapData.GetAreas())
|
|
|
|
foreach (var area in _gameClient.CurrentLobbyState.MapData.GetAreas())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
GameObject a = BuildAreaMesh(area);
|
|
|
|
GameObject a = BuildAreaMesh(area);
|
|
|
|
a.transform.parent = areaRoot.transform;
|
|
|
|
a.transform.parent = areaRoot.transform;
|
|
|
|
}*/
|
|
|
|
|
|
|
|
//TODO: POIs
|
|
|
|
|
|
|
|
// NASTAVENÍ SKYBOXU
|
|
|
|
|
|
|
|
if (_areaSettings.GlobalSkybox != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
RenderSettings.skybox = _areaSettings.GlobalSkybox;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
|
|
|
|
|
|
|
|
GameObject poiRoot = new GameObject("POIs");
|
|
|
|
|
|
|
|
poiRoot.transform.parent = _mapCenterPoint.transform;
|
|
|
|
|
|
|
|
int poiCount = 0;
|
|
|
|
|
|
|
|
foreach (var poi in _gameClient.CurrentLobbyState.MapData.GetPOIs())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Debug.LogWarning("Skybox material není přiřazen v AreaSettings!");
|
|
|
|
GameObject p = BuildPOIMarker(poi);
|
|
|
|
|
|
|
|
if (p != null) { p.transform.parent = poiRoot.transform; poiCount++; }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Diagnostic - if the user reports "map missing in game view" but
|
|
|
|
|
|
|
|
// the counts here are non-zero, the bug is camera/culling related,
|
|
|
|
|
|
|
|
// not a build issue.
|
|
|
|
|
|
|
|
int buildings = _gameClient.CurrentLobbyState.MapData.GetBuildings()?.Count ?? 0;
|
|
|
|
|
|
|
|
int paths = _gameClient.CurrentLobbyState.MapData.GetPathways()?.Count ?? 0;
|
|
|
|
|
|
|
|
int areas = _gameClient.CurrentLobbyState.MapData.GetAreas()?.Count ?? 0;
|
|
|
|
|
|
|
|
Debug.Log($"[Map] BuildMap done: {buildings} buildings, {paths} paths, " +
|
|
|
|
|
|
|
|
$"{areas} areas, {poiCount} POIs. MapCenterPoint={_mapCenterPoint.name} " +
|
|
|
|
|
|
|
|
$"layer={_mapCenterPoint.layer} pos={_mapCenterPoint.transform.position} " +
|
|
|
|
|
|
|
|
$"scale={_mapCenterPoint.transform.localScale}");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Build a tall, brightly-colored pillar for a Point of Interest with
|
|
|
|
|
|
|
|
/// a 3D text label above it (e.g. "FOOD", "SHOP"). The label is laid
|
|
|
|
|
|
|
|
/// flat on the XZ plane facing UP so it reads correctly under the
|
|
|
|
|
|
|
|
/// orthogonal top-down camera.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private GameObject BuildPOIMarker(MapPOI poi)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (poi == null) return null;
|
|
|
|
|
|
|
|
var color = ColorForPOI(poi.POIType);
|
|
|
|
|
|
|
|
string label = LabelForPOI(poi.POIType);
|
|
|
|
|
|
|
|
var pos = poi.Location.ToLocalVector3(_centerPosition);
|
|
|
|
|
|
|
|
return CreateMarkerWithLabel($"POI_{poi.POIType}_{poi.Id}", pos, color, label);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Shared marker builder: tall colored cylinder pillar + 3D text label
|
|
|
|
|
|
|
|
/// above it. Used by POIs, tasks, bodies, and sabotage stations so
|
|
|
|
|
|
|
|
/// they all share a visual language ("colored pillar with a name").
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private GameObject CreateMarkerWithLabel(string name, Vector3 worldPos, Color color, string label)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var go = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
|
|
|
|
|
|
|
|
go.name = name;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Strip the auto-added collider - markers are visual only.
|
|
|
|
|
|
|
|
var col = go.GetComponent<Collider>();
|
|
|
|
|
|
|
|
if (col != null) UnityEngine.Object.Destroy(col);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go.transform.position = worldPos + Vector3.up * kMarkerY;
|
|
|
|
|
|
|
|
// Cylinder's default unit is 2 tall, 1 wide. Scale Y by half of
|
|
|
|
|
|
|
|
// kMarkerHeight (built-in is 2 units), X/Z by kMarkerRadius.
|
|
|
|
|
|
|
|
go.transform.localScale = new Vector3(kMarkerRadius, kMarkerHeight * 0.5f, kMarkerRadius);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var mr = go.GetComponent<MeshRenderer>();
|
|
|
|
|
|
|
|
if (mr != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// One .material access -> single clone of the primitive's
|
|
|
|
|
|
|
|
// default mat. Don't touch renderQueue (P3 regression cause).
|
|
|
|
|
|
|
|
var inst = mr.material;
|
|
|
|
|
|
|
|
if (inst != null) inst.color = color;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3D text label - lays flat on top of the pillar facing up.
|
|
|
|
|
|
|
|
// Parented to the marker so it follows position changes.
|
|
|
|
|
|
|
|
var labelGO = new GameObject("Label");
|
|
|
|
|
|
|
|
labelGO.transform.SetParent(go.transform, worldPositionStays: false);
|
|
|
|
|
|
|
|
// Local Y offset: pillar's local scale Y is kMarkerHeight/2, but
|
|
|
|
|
|
|
|
// the cylinder primitive is 2 units tall in local space, so its
|
|
|
|
|
|
|
|
// top is at local +1. Label sits a hair above that.
|
|
|
|
|
|
|
|
labelGO.transform.localPosition = new Vector3(0, 1.05f, 0);
|
|
|
|
|
|
|
|
// Rotate 90 around X so the text quad's normal points +Y (toward
|
|
|
|
|
|
|
|
// the top-down camera). The default TMP forward is +Z.
|
|
|
|
|
|
|
|
labelGO.transform.localRotation = Quaternion.Euler(90f, 0f, 0f);
|
|
|
|
|
|
|
|
// Compensate for the cylinder's non-uniform parent scale so the
|
|
|
|
|
|
|
|
// text size in world units matches kLabelFontSize regardless of
|
|
|
|
|
|
|
|
// how the pillar was scaled.
|
|
|
|
|
|
|
|
labelGO.transform.localScale = new Vector3(
|
|
|
|
|
|
|
|
1f / kMarkerRadius,
|
|
|
|
|
|
|
|
1f / (kMarkerHeight * 0.5f),
|
|
|
|
|
|
|
|
1f / kMarkerRadius);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var tmp = labelGO.AddComponent<TextMeshPro>();
|
|
|
|
|
|
|
|
tmp.text = label;
|
|
|
|
|
|
|
|
tmp.fontSize = kLabelFontSize;
|
|
|
|
|
|
|
|
tmp.color = Color.white;
|
|
|
|
|
|
|
|
tmp.fontStyle = FontStyles.Bold;
|
|
|
|
|
|
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
|
|
|
|
|
|
tmp.outlineColor = Color.black;
|
|
|
|
|
|
|
|
tmp.outlineWidth = 0.25f;
|
|
|
|
|
|
|
|
// Reasonable bounds so the text mesh isn't auto-clipped.
|
|
|
|
|
|
|
|
var rt = tmp.rectTransform;
|
|
|
|
|
|
|
|
rt.sizeDelta = new Vector2(20, 4);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return go;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static Color ColorForPOI(MapPOIType type)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
case MapPOIType.FoodDrink: return new Color(1.00f, 0.55f, 0.00f); // orange
|
|
|
|
|
|
|
|
case MapPOIType.Shop: return new Color(0.20f, 0.60f, 1.00f); // blue
|
|
|
|
|
|
|
|
case MapPOIType.Health: return new Color(0.96f, 0.27f, 0.27f); // red
|
|
|
|
|
|
|
|
case MapPOIType.Transport: return new Color(0.85f, 0.85f, 0.20f); // yellow
|
|
|
|
|
|
|
|
case MapPOIType.Culture: return new Color(0.65f, 0.30f, 0.95f); // purple
|
|
|
|
|
|
|
|
case MapPOIType.Landmark: return new Color(0.95f, 0.85f, 0.40f); // gold
|
|
|
|
|
|
|
|
case MapPOIType.Recreation: return new Color(0.30f, 0.85f, 0.30f); // green
|
|
|
|
|
|
|
|
default: return new Color(0.75f, 0.75f, 0.80f); // muted grey
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static string LabelForPOI(MapPOIType type)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
case MapPOIType.FoodDrink: return "FOOD";
|
|
|
|
|
|
|
|
case MapPOIType.Shop: return "SHOP";
|
|
|
|
|
|
|
|
case MapPOIType.Health: return "HEALTH";
|
|
|
|
|
|
|
|
case MapPOIType.Transport: return "TRANSIT";
|
|
|
|
|
|
|
|
case MapPOIType.Culture: return "CULTURE";
|
|
|
|
|
|
|
|
case MapPOIType.Landmark: return "LANDMARK";
|
|
|
|
|
|
|
|
case MapPOIType.Recreation: return "PARK";
|
|
|
|
|
|
|
|
default: return "POI";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void ClearChildren()
|
|
|
|
void ClearChildren()
|
|
|
|
@@ -136,9 +305,12 @@ namespace Subsystems{
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var building = new GameObject($"Building_{b.Name ?? "Unknown"}");
|
|
|
|
var building = new GameObject($"Building_{b.Name ?? "Unknown"}");
|
|
|
|
|
|
|
|
|
|
|
|
// Výpočet středu budovy
|
|
|
|
// Výpočet středu budovy. Lift the base above kPathY so building
|
|
|
|
|
|
|
|
// walls visibly extrude *upward* from above the road/area layer
|
|
|
|
|
|
|
|
// instead of starting at ground (which made them clip into paved
|
|
|
|
|
|
|
|
// areas that share their footprint).
|
|
|
|
Vector3 center = CalculatePolygonCenter(b.Outline);
|
|
|
|
Vector3 center = CalculatePolygonCenter(b.Outline);
|
|
|
|
building.transform.position = center;
|
|
|
|
building.transform.position = center + Vector3.up * kBuildingBaseY;
|
|
|
|
|
|
|
|
|
|
|
|
// Vytvoření mesh pro budovu
|
|
|
|
// Vytvoření mesh pro budovu
|
|
|
|
MeshFilter meshFilter = building.AddComponent<MeshFilter>();
|
|
|
|
MeshFilter meshFilter = building.AddComponent<MeshFilter>();
|
|
|
|
@@ -149,8 +321,8 @@ namespace Subsystems{
|
|
|
|
switch (b.BuildingType.ToLower())
|
|
|
|
switch (b.BuildingType.ToLower())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
case "residential":
|
|
|
|
case "residential":
|
|
|
|
mat = _buildingSettings.ResidentalBuildingsMat;
|
|
|
|
mat = _buildingSettings.ResidentialBuildingsMat;
|
|
|
|
height = _buildingSettings.ResidentalBuildingHeight;
|
|
|
|
height = _buildingSettings.ResidentialBuildingHeight;
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case "commercial":
|
|
|
|
case "commercial":
|
|
|
|
mat = _buildingSettings.CommercialBuildingsMat;
|
|
|
|
mat = _buildingSettings.CommercialBuildingsMat;
|
|
|
|
@@ -169,8 +341,12 @@ namespace Subsystems{
|
|
|
|
meshFilter.mesh = mesh;
|
|
|
|
meshFilter.mesh = mesh;
|
|
|
|
|
|
|
|
|
|
|
|
//TODO: material by type
|
|
|
|
//TODO: material by type
|
|
|
|
// Použijeme barvu podle typu budovy
|
|
|
|
// Použijeme barvu podle typu budovy. Use sharedMaterial to keep
|
|
|
|
meshRenderer.material = mat;
|
|
|
|
// the project's Material asset reference - no clone, no leak.
|
|
|
|
|
|
|
|
// Y-position alone disambiguates building geometry from area/path
|
|
|
|
|
|
|
|
// layers; we don't need renderQueue overrides (which broke mobile
|
|
|
|
|
|
|
|
// rendering for transparent-class shaders in P3).
|
|
|
|
|
|
|
|
meshRenderer.sharedMaterial = mat;
|
|
|
|
|
|
|
|
|
|
|
|
// Přidání collideru pro interakci
|
|
|
|
// Přidání collideru pro interakci
|
|
|
|
building.AddComponent<MeshCollider>();
|
|
|
|
building.AddComponent<MeshCollider>();
|
|
|
|
@@ -229,15 +405,19 @@ namespace Subsystems{
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
line.material = mat;
|
|
|
|
// sharedMaterial avoids the LineRenderer cloning the project's
|
|
|
|
|
|
|
|
// shared path Material on every BuildMap call. Queue overrides
|
|
|
|
|
|
|
|
// dropped (P3 mobile-render regression cause).
|
|
|
|
|
|
|
|
line.sharedMaterial = mat;
|
|
|
|
line.widthMultiplier = width;
|
|
|
|
line.widthMultiplier = width;
|
|
|
|
|
|
|
|
|
|
|
|
// Nastavení bodů cesty
|
|
|
|
// Nastavení bodů cesty - kPathY sits above all area polygons but
|
|
|
|
|
|
|
|
// below building bases, so paths visibly run on top of areas.
|
|
|
|
line.positionCount = w.Points.Count;
|
|
|
|
line.positionCount = w.Points.Count;
|
|
|
|
for (int i = 0; i < w.Points.Count; i++)
|
|
|
|
for (int i = 0; i < w.Points.Count; i++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Vector3 pos = w.Points[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center);
|
|
|
|
Vector3 pos = w.Points[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center);
|
|
|
|
pos.y = 0.1f; // Mírně nad zemí
|
|
|
|
pos.y = kPathY;
|
|
|
|
line.SetPosition(i, pos);
|
|
|
|
line.SetPosition(i, pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path;
|
|
|
|
return path;
|
|
|
|
@@ -280,13 +460,58 @@ namespace Subsystems{
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
meshRenderer.material = mat;
|
|
|
|
// sharedMaterial: no per-area material clone. Render-queue forcing
|
|
|
|
|
|
|
|
// dropped in P8 (caused mobile-render regression). The Y-stagger
|
|
|
|
|
|
|
|
// below alone now drives "smaller polygon on top of larger one"
|
|
|
|
|
|
|
|
// depth ordering - which is what the depth buffer was always
|
|
|
|
|
|
|
|
// designed to do, and works on mobile GPUs with weak precision
|
|
|
|
|
|
|
|
// because the stagger spread (0.04 units) is well above any
|
|
|
|
|
|
|
|
// reasonable depth-buffer epsilon.
|
|
|
|
|
|
|
|
meshRenderer.sharedMaterial = mat;
|
|
|
|
|
|
|
|
|
|
|
|
area.transform.position = new Vector3(0, 0.05f, 0); // Těsně nad zemí
|
|
|
|
// Y stagger: smaller polygons sit a hair higher than larger ones,
|
|
|
|
|
|
|
|
// so depth-test draws them on top of bigger area polygons they sit
|
|
|
|
|
|
|
|
// inside (e.g. a playground inside a park). Total spread is 0.04
|
|
|
|
|
|
|
|
// units - visually invisible but plenty for the depth buffer.
|
|
|
|
|
|
|
|
float yStagger = ComputeAreaYStagger(a.Outline);
|
|
|
|
|
|
|
|
area.transform.position = new Vector3(0, kAreaBaseY + yStagger, 0);
|
|
|
|
|
|
|
|
|
|
|
|
return area;
|
|
|
|
return area;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//TODO: POIs
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Returns a non-negative size proxy used to bucket areas by footprint.
|
|
|
|
|
|
|
|
/// Larger polygons return higher numbers; used inversely for queue/Y.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private float AreaSizeBucket(List<Position> outline)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (outline == null || outline.Count < 3) return 1f;
|
|
|
|
|
|
|
|
// Cheap bbox area in lat-lon space scaled by 1e6 - we only need a
|
|
|
|
|
|
|
|
// monotonic ordering, not a real geographic area.
|
|
|
|
|
|
|
|
double minLat = outline[0].Lat, maxLat = outline[0].Lat;
|
|
|
|
|
|
|
|
double minLon = outline[0].Lon, maxLon = outline[0].Lon;
|
|
|
|
|
|
|
|
for (int i = 1; i < outline.Count; i++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (outline[i].Lat < minLat) minLat = outline[i].Lat;
|
|
|
|
|
|
|
|
if (outline[i].Lat > maxLat) maxLat = outline[i].Lat;
|
|
|
|
|
|
|
|
if (outline[i].Lon < minLon) minLon = outline[i].Lon;
|
|
|
|
|
|
|
|
if (outline[i].Lon > maxLon) maxLon = outline[i].Lon;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
double bbox = (maxLat - minLat) * (maxLon - minLon) * 1e6;
|
|
|
|
|
|
|
|
return (float)System.Math.Max(0.001, bbox);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Smaller areas get a higher Y so they render on top of any larger
|
|
|
|
|
|
|
|
/// area they overlap. Returns a value in [0, 0.04] units.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private float ComputeAreaYStagger(List<Position> outline)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
float bucket = AreaSizeBucket(outline);
|
|
|
|
|
|
|
|
// Inverse mapping: huge area -> 0, tiny area -> 0.04.
|
|
|
|
|
|
|
|
float t = Mathf.Clamp01(1f - bucket / (bucket + 50f));
|
|
|
|
|
|
|
|
return t * 0.04f;
|
|
|
|
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region Polygon Utils
|
|
|
|
#region Polygon Utils
|
|
|
|
private Vector3 CalculatePolygonCenter(List<Position> points)
|
|
|
|
private Vector3 CalculatePolygonCenter(List<Position> points)
|
|
|
|
@@ -298,19 +523,52 @@ namespace Subsystems{
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return center / points.Count;
|
|
|
|
return center / points.Count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Signed XZ shoelace area for a polygon expressed in local Vector3.
|
|
|
|
|
|
|
|
/// Positive = CCW (Unity left-handed Y-up: upward-facing normal),
|
|
|
|
|
|
|
|
/// negative = CW (downward-facing normal -> top face invisible from
|
|
|
|
|
|
|
|
/// above unless we reverse the winding before triangulating).
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private static float PolygonSignedAreaXZ(List<Vector3> verts)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
float area = 0f;
|
|
|
|
|
|
|
|
int n = verts.Count;
|
|
|
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var a = verts[i];
|
|
|
|
|
|
|
|
var b = verts[(i + 1) % n];
|
|
|
|
|
|
|
|
area += (b.x - a.x) * (a.z + b.z);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return area * 0.5f;
|
|
|
|
|
|
|
|
}
|
|
|
|
private Mesh CreateExtrudedPolygonMesh(List<Position> outline, float height)
|
|
|
|
private Mesh CreateExtrudedPolygonMesh(List<Position> outline, float height)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Mesh mesh = new Mesh();
|
|
|
|
Mesh mesh = new Mesh();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Reject degenerates - Recast/Overpass can hand back 1-2 vertex
|
|
|
|
|
|
|
|
// outlines on broken ways. Empty mesh -> renderer draws nothing,
|
|
|
|
|
|
|
|
// safer than a malformed triangle list.
|
|
|
|
|
|
|
|
if (outline == null || outline.Count < 3) return mesh;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Convert to local space first so we can run a winding check, then
|
|
|
|
|
|
|
|
// reverse if needed. Without this, CW outlines from Overpass yield
|
|
|
|
|
|
|
|
// downward-facing top normals and the building roof is invisible
|
|
|
|
|
|
|
|
// from the top-down map camera.
|
|
|
|
int vertexCount = outline.Count;
|
|
|
|
int vertexCount = outline.Count;
|
|
|
|
|
|
|
|
var localVerts = new List<Vector3>(vertexCount);
|
|
|
|
|
|
|
|
Vector3 center = CalculatePolygonCenter(outline);
|
|
|
|
|
|
|
|
for (int i = 0; i < vertexCount; i++)
|
|
|
|
|
|
|
|
localVerts.Add(outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (PolygonSignedAreaXZ(localVerts) < 0f)
|
|
|
|
|
|
|
|
localVerts.Reverse();
|
|
|
|
|
|
|
|
|
|
|
|
// Vertices - spodní a horní podstava
|
|
|
|
// Vertices - spodní a horní podstava
|
|
|
|
Vector3[] vertices = new Vector3[vertexCount * 2];
|
|
|
|
Vector3[] vertices = new Vector3[vertexCount * 2];
|
|
|
|
Vector3 center = CalculatePolygonCenter(outline);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < vertexCount; i++)
|
|
|
|
for (int i = 0; i < vertexCount; i++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Vector3 pos = outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center;
|
|
|
|
Vector3 pos = localVerts[i];
|
|
|
|
vertices[i] = pos; // Spodní
|
|
|
|
vertices[i] = pos; // Spodní
|
|
|
|
vertices[i + vertexCount] = pos + Vector3.up * height; // Horní
|
|
|
|
vertices[i + vertexCount] = pos + Vector3.up * height; // Horní
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -354,25 +612,30 @@ namespace Subsystems{
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Mesh mesh = new Mesh();
|
|
|
|
Mesh mesh = new Mesh();
|
|
|
|
|
|
|
|
|
|
|
|
int vertexCount = outline.Count;
|
|
|
|
// Reject degenerates (matches CreateExtrudedPolygonMesh).
|
|
|
|
Vector3[] vertices = new Vector3[vertexCount];
|
|
|
|
if (outline == null || outline.Count < 3) return mesh;
|
|
|
|
Vector3 center = CalculatePolygonCenter(outline);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int vertexCount = outline.Count;
|
|
|
|
|
|
|
|
var localVerts = new List<Vector3>(vertexCount);
|
|
|
|
|
|
|
|
Vector3 center = CalculatePolygonCenter(outline);
|
|
|
|
for (int i = 0; i < vertexCount; i++)
|
|
|
|
for (int i = 0; i < vertexCount; i++)
|
|
|
|
{
|
|
|
|
localVerts.Add(outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center);
|
|
|
|
vertices[i] = outline[i].ToLocalVector3(_gameClient.CurrentLobbyState.MapData.Center) - center;
|
|
|
|
|
|
|
|
}
|
|
|
|
// Force CCW so RecalculateNormals produces an upward-facing normal.
|
|
|
|
|
|
|
|
// CW polygons from Overpass would otherwise render as black voids
|
|
|
|
|
|
|
|
// when the top-down camera looks at their back face.
|
|
|
|
|
|
|
|
if (PolygonSignedAreaXZ(localVerts) < 0f)
|
|
|
|
|
|
|
|
localVerts.Reverse();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Vector3[] vertices = localVerts.ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
// Triangulace - fan pattern
|
|
|
|
// Triangulace - fan pattern
|
|
|
|
List<int> triangles = new List<int>();
|
|
|
|
List<int> triangles = new List<int>();
|
|
|
|
if (vertexCount >= 3)
|
|
|
|
for (int i = 1; i < vertexCount - 1; i++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
for (int i = 1; i < vertexCount - 1; i++)
|
|
|
|
triangles.Add(0);
|
|
|
|
{
|
|
|
|
triangles.Add(i);
|
|
|
|
triangles.Add(0);
|
|
|
|
triangles.Add(i + 1);
|
|
|
|
triangles.Add(i);
|
|
|
|
|
|
|
|
triangles.Add(i + 1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mesh.vertices = vertices;
|
|
|
|
mesh.vertices = vertices;
|
|
|
|
@@ -382,5 +645,164 @@ namespace Subsystems{
|
|
|
|
return mesh;
|
|
|
|
return mesh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Markers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void CreateTaskMarkers(List<GeoSus.Client.GameTask> tasks)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_mapCenterPoint == null) return;
|
|
|
|
|
|
|
|
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var md = _gameClient?.CurrentLobbyState?.MapData;
|
|
|
|
|
|
|
|
if (md != null) _centerPosition = md.Center;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0) return;
|
|
|
|
|
|
|
|
var taskColor = new Color(0.20f, 0.95f, 0.55f); // bright green - "GO HERE"
|
|
|
|
|
|
|
|
foreach (var task in tasks)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_taskMarkers.ContainsKey(task.TaskId)) continue;
|
|
|
|
|
|
|
|
var pos = task.Location.ToLocalVector3(_centerPosition);
|
|
|
|
|
|
|
|
var go = CreateMarkerWithLabel($"Task_{task.TaskId}", pos, taskColor, "TASK");
|
|
|
|
|
|
|
|
go.transform.parent = _mapCenterPoint.transform;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Pulsing point light so the task literally glows on the map.
|
|
|
|
|
|
|
|
var light = go.AddComponent<Light>();
|
|
|
|
|
|
|
|
light.color = taskColor;
|
|
|
|
|
|
|
|
light.intensity = 3f;
|
|
|
|
|
|
|
|
light.range = 25f;
|
|
|
|
|
|
|
|
_taskMarkers[task.TaskId] = go;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void RemoveTaskMarker(string taskId)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_taskMarkers.TryGetValue(taskId, out var go))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
UnityEngine.Object.Destroy(go);
|
|
|
|
|
|
|
|
_taskMarkers.Remove(taskId);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void CreateBodyMarker(string bodyId, Position location)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_mapCenterPoint == null) return;
|
|
|
|
|
|
|
|
if (_bodyMarkers.ContainsKey(bodyId)) return;
|
|
|
|
|
|
|
|
var pos = location.ToLocalVector3(_centerPosition);
|
|
|
|
|
|
|
|
// Bright red pillar with "BODY" label - players need to see this
|
|
|
|
|
|
|
|
// from across the map to call it in.
|
|
|
|
|
|
|
|
var go = CreateMarkerWithLabel($"Body_{bodyId}", pos,
|
|
|
|
|
|
|
|
new Color(0.96f, 0.18f, 0.18f), "BODY");
|
|
|
|
|
|
|
|
go.transform.parent = _mapCenterPoint?.transform;
|
|
|
|
|
|
|
|
_bodyMarkers[bodyId] = go;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void ClearBodyMarkers()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
foreach (var go in _bodyMarkers.Values)
|
|
|
|
|
|
|
|
if (go) UnityEngine.Object.Destroy(go);
|
|
|
|
|
|
|
|
_bodyMarkers.Clear();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ── Player avatar sizing ────────────────────────────────────────────
|
|
|
|
|
|
|
|
// The default Unity capsule primitive is 2m tall in local space. The
|
|
|
|
|
|
|
|
// map camera defaults to 150m orthographic-ish height (see
|
|
|
|
|
|
|
|
// MapCameraController), so anything smaller than ~3m world-size is a
|
|
|
|
|
|
|
|
// pixel on screen. Original code used scale=0.4 (~0.8m capsule) which
|
|
|
|
|
|
|
|
// was invisible. Markers (POIs/tasks/bodies) are 8m pillars; players
|
|
|
|
|
|
|
|
// need to be visibly distinct from those AND from each other. The
|
|
|
|
|
|
|
|
// local player gets a halo light + larger scale so the user can find
|
|
|
|
|
|
|
|
// themselves on the map at a glance.
|
|
|
|
|
|
|
|
private const float kLocalPlayerScale = 4f; // ~8m capsule (matches marker height)
|
|
|
|
|
|
|
|
private const float kRemotePlayerScale = 2f; // ~4m capsule (smaller than markers)
|
|
|
|
|
|
|
|
private const float kLocalPlayerHaloRange = 18f;
|
|
|
|
|
|
|
|
private const float kLocalPlayerHaloIntensity = 2.5f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void UpdatePlayerAvatars(Dictionary<string, PlayerPositionInfo> positions, string myUuid)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (_mapCenterPoint == null) return;
|
|
|
|
|
|
|
|
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var md = _gameClient?.CurrentLobbyState?.MapData;
|
|
|
|
|
|
|
|
if (md != null) _centerPosition = md.Center;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0) return;
|
|
|
|
|
|
|
|
foreach (var kvp in positions)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
string uuid = kvp.Key;
|
|
|
|
|
|
|
|
var info = kvp.Value;
|
|
|
|
|
|
|
|
bool isLocal = uuid == myUuid;
|
|
|
|
|
|
|
|
if (!_playerAvatars.TryGetValue(uuid, out var go) || go == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
go = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
|
|
|
|
|
|
|
go.name = $"Player_{uuid.Substring(0, Mathf.Min(8, uuid.Length))}";
|
|
|
|
|
|
|
|
go.transform.parent = _mapCenterPoint?.transform;
|
|
|
|
|
|
|
|
// Strip the auto-collider - avatars are visual only and the
|
|
|
|
|
|
|
|
// collider would interact with the map's MeshColliders.
|
|
|
|
|
|
|
|
var col = go.GetComponent<Collider>();
|
|
|
|
|
|
|
|
if (col != null) UnityEngine.Object.Destroy(col);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float scale = isLocal ? kLocalPlayerScale : kRemotePlayerScale;
|
|
|
|
|
|
|
|
go.transform.localScale = Vector3.one * scale;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isLocal)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// Halo light around the local player so the user can
|
|
|
|
|
|
|
|
// find themselves at a glance even at the widest zoom.
|
|
|
|
|
|
|
|
// Range/intensity tuned so it reads as "this is me"
|
|
|
|
|
|
|
|
// without bleeding far enough to drown POI markers.
|
|
|
|
|
|
|
|
var halo = go.AddComponent<Light>();
|
|
|
|
|
|
|
|
halo.color = new Color(0.30f, 1.00f, 0.55f); // matches green capsule color
|
|
|
|
|
|
|
|
halo.intensity = kLocalPlayerHaloIntensity;
|
|
|
|
|
|
|
|
halo.range = kLocalPlayerHaloRange;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_playerAvatars[uuid] = go;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Lift the avatar so the bottom of the capsule sits roughly at
|
|
|
|
|
|
|
|
// ground level despite the larger scale. Capsule's local pivot
|
|
|
|
|
|
|
|
// is at center, height = 2 * localScale.y world units, so we
|
|
|
|
|
|
|
|
// raise by half the local height.
|
|
|
|
|
|
|
|
float halfHeight = (isLocal ? kLocalPlayerScale : kRemotePlayerScale);
|
|
|
|
|
|
|
|
go.transform.position = info.Position.ToLocalVector3(_centerPosition)
|
|
|
|
|
|
|
|
+ Vector3.up * halfHeight;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var mr = go.GetComponent<MeshRenderer>();
|
|
|
|
|
|
|
|
if (mr)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (isLocal) mr.material.color = new Color(0.30f, 1.00f, 0.55f);
|
|
|
|
|
|
|
|
else if (info.State == GeoSus.Client.PlayerState.Dead) mr.material.color = Color.grey;
|
|
|
|
|
|
|
|
else mr.material.color = Color.white;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void CreateSabotageMarkers(List<RepairStationInfo> stations)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var color = new Color(1.0f, 0.55f, 0.0f); // strong orange = repair urgency
|
|
|
|
|
|
|
|
foreach (var station in stations)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var pos = station.Location.ToLocalVector3(_centerPosition);
|
|
|
|
|
|
|
|
var go = CreateMarkerWithLabel($"Sabotage_{station.StationId}", pos,
|
|
|
|
|
|
|
|
color, "REPAIR");
|
|
|
|
|
|
|
|
go.transform.parent = _mapCenterPoint?.transform;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Repair stations also pulse light so impostors and crew see
|
|
|
|
|
|
|
|
// the urgency from across the map.
|
|
|
|
|
|
|
|
var light = go.AddComponent<Light>();
|
|
|
|
|
|
|
|
light.color = color;
|
|
|
|
|
|
|
|
light.intensity = 4f;
|
|
|
|
|
|
|
|
light.range = 30f;
|
|
|
|
|
|
|
|
_sabotageMarkers.Add(go);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void ClearSabotageMarkers()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
foreach (var go in _sabotageMarkers)
|
|
|
|
|
|
|
|
if (go) UnityEngine.Object.Destroy(go);
|
|
|
|
|
|
|
|
_sabotageMarkers.Clear();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|