Betäubungs-Ring über dem Kopf (Gäste über Bit 18 der Rahmen-Flags), Nebel-Gitter LuneFogOverlay mit Shader Luneraria/FogOverlay aus LuneFog.Vision (10 Hz, weiche Kante), laufende Glyphe je Team als vier Zusatzwerte nach den Gebäude-Lebenspunkten. Reine Darstellung. Kern 627/627 (s4 8), EditMode 35/35, PlayMode 20/20, Verify 718/0, Selbsttest 30/30, Golden 0, Fingerabdruck unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
3.8 KiB
C#
91 lines
3.8 KiB
C#
using System.Collections.Generic;
|
|
using Luneraria.Core;
|
|
using UnityEngine;
|
|
|
|
namespace Luneraria.View
|
|
{
|
|
/// <summary>
|
|
/// S4 fog of war in the terrain: a grid over the map (2 m cells) that follows the ground and
|
|
/// darkens every point no vision source of the viewer's team reaches (LuneFog.Vision: own
|
|
/// units 9 m, own buildings 10 m, wards 12 m). The alpha has a soft 2 m edge and is
|
|
/// recomputed ten times a second. Presentation only; the rules never read it.
|
|
/// </summary>
|
|
public sealed class LuneFogOverlay : MonoBehaviour
|
|
{
|
|
public const float Cell = 2f;
|
|
public const float Darkness = .55f;
|
|
public const float Edge = 2f;
|
|
public const float Interval = .1f;
|
|
|
|
Mesh _mesh;
|
|
Vec2[] _points;
|
|
Color32[] _colors;
|
|
float _timer;
|
|
|
|
/// <summary>Darkness of every grid point (0 = seen), for tests.</summary>
|
|
public float[] Alpha { get; private set; }
|
|
public int Refreshes { get; private set; }
|
|
|
|
public void Build(LuneGround ground, Material material)
|
|
{
|
|
int side = Mathf.CeilToInt((float)(LuneMap.HalfExtent * 2.0) / Cell) + 1;
|
|
var vertices = new Vector3[side * side];
|
|
_points = new Vec2[side * side];
|
|
for (int row = 0; row < side; row++)
|
|
{
|
|
for (int column = 0; column < side; column++)
|
|
{
|
|
var map = new Vec2(-LuneMap.HalfExtent + column * Cell, -LuneMap.HalfExtent + row * Cell);
|
|
float height = ground != null ? ground.StandHeight(map) : 0f;
|
|
_points[row * side + column] = map;
|
|
vertices[row * side + column] = LuneSpace.World(map, height + .45f);
|
|
}
|
|
}
|
|
var triangles = new List<int>();
|
|
for (int row = 0; row + 1 < side; row++)
|
|
{
|
|
for (int column = 0; column + 1 < side; column++)
|
|
{
|
|
int a = row * side + column;
|
|
triangles.AddRange(new[] { a, a + side, a + 1, a + 1, a + side, a + side + 1 });
|
|
}
|
|
}
|
|
_colors = new Color32[vertices.Length];
|
|
Alpha = new float[vertices.Length];
|
|
_mesh = new Mesh { name = "FogOverlay", indexFormat = UnityEngine.Rendering.IndexFormat.UInt32 };
|
|
_mesh.SetVertices(vertices);
|
|
_mesh.SetTriangles(triangles, 0);
|
|
_mesh.SetColors(_colors);
|
|
_mesh.RecalculateBounds();
|
|
gameObject.AddComponent<MeshFilter>().sharedMesh = _mesh;
|
|
var renderer = gameObject.AddComponent<MeshRenderer>();
|
|
renderer.sharedMaterial = material;
|
|
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
|
renderer.receiveShadows = false;
|
|
}
|
|
|
|
/// <summary>Recomputes the darkness at most every Interval seconds (force: now).</summary>
|
|
public void Tick(float dt, List<KeyValuePair<Vec2, double>> sources, bool force = false)
|
|
{
|
|
_timer -= dt;
|
|
if (!force && _timer > 0f)
|
|
return;
|
|
_timer = Interval;
|
|
Refreshes++;
|
|
for (int i = 0; i < _points.Length; i++)
|
|
{
|
|
double best = double.PositiveInfinity;
|
|
foreach (KeyValuePair<Vec2, double> source in sources)
|
|
{
|
|
double gap = _points[i].DistanceTo(source.Key) - source.Value;
|
|
if (gap < best)
|
|
best = gap;
|
|
}
|
|
float t = Mathf.Clamp01((float)(best + Edge * .5) / Edge);
|
|
Alpha[i] = Darkness * t;
|
|
_colors[i] = new Color32(0, 0, 0, (byte)Mathf.RoundToInt(Alpha[i] * 255f));
|
|
}
|
|
_mesh.SetColors(_colors);
|
|
}
|
|
}
|
|
}
|