Fester Simulationstakt und Ansichten, die nur lesen; Nachweis, dass die Partie bei jeder Bildrate dieselbe ist. - LuneClock: Akkumulator, ganze Schritte zu 0,05 s, Alpha zum Interpolieren. - LuneMatchRunner: Host-Ablauf einer Bot-Partie (Planung über die Befehlsschnittstelle, Kampf im festen Takt, Abrechnung, nächste Runde); LuneMatchDriver reicht in Update nur Bildzeit weiter. - LuneTrace: Fingerabdruck der Partie (jeder Schritt, jede Abrechnung) und der reine Kernlauf zum Vergleich. - Ansichten: LuneMatchView, LuneUnitView (interpolierte Position, Blickrichtung, Clips aus Kampfereignissen, Teamring, Lebensbalken), LuneBuildingView mit Einsturz und Ruine aus den autoritativen HP, Turmblitze, LuneCameraRig wie lune_camera.gd, vorläufige Debug-Anzeige (kein HUD; U3 portiert das G3-HUD). - Einheitenbibliothek: Kohlenbeißer aus 131 Teilen zu einem Mesh, Animator- Controller mit den Blender-Clips, Ring- und Balkenmeshes, Markermaterialien. - Tests: EditMode (30/60/120 fps, unbegrenzt, Ruckler, 8x Tempo; Seed 2 und Seed 9 ab Runde 21) und PlayMode in der Szene bei echten Bildraten 30, 60, 120 und unbegrenzt: überall derselbe Fingerabdruck wie der Kern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
145 lines
5.8 KiB
C#
145 lines
5.8 KiB
C#
using System.Collections.Generic;
|
|
using Luneraria.Core;
|
|
using UnityEngine;
|
|
|
|
namespace Luneraria.View
|
|
{
|
|
/// <summary>
|
|
/// Shows the driver's match (U2C): unit views per combat, building looks from the authoritative
|
|
/// HP, tower bolts from the towers' attack events. Runs after the driver in the same frame and
|
|
/// only reads the core.
|
|
/// </summary>
|
|
[DefaultExecutionOrder(100)]
|
|
public sealed class LuneMatchView : MonoBehaviour
|
|
{
|
|
public LuneMatchDriver Driver;
|
|
public LuneUnitLibrary Library;
|
|
public Camera ViewCamera;
|
|
|
|
readonly Dictionary<int, LuneUnitView> _units = new Dictionary<int, LuneUnitView>();
|
|
readonly Dictionary<string, LuneBuildingView> _buildings = new Dictionary<string, LuneBuildingView>();
|
|
readonly Dictionary<string, int> _towerSerials = new Dictionary<string, int>();
|
|
readonly List<Bolt> _bolts = new List<Bolt>();
|
|
LuneCombat _combat;
|
|
Transform _unitRoot;
|
|
bool _synced;
|
|
|
|
sealed class Bolt
|
|
{
|
|
public LineRenderer Line;
|
|
public float Age;
|
|
public LuneUnitView Target;
|
|
public Vector3 From;
|
|
}
|
|
|
|
public IEnumerable<LuneUnitView> Units { get { return _units.Values; } }
|
|
/// <summary>Main-thread cost of the last frame's view update (milliseconds).</summary>
|
|
public double LastSyncMs { get; private set; }
|
|
readonly System.Diagnostics.Stopwatch _clock = new System.Diagnostics.Stopwatch();
|
|
|
|
public LuneBuildingView Building(string id)
|
|
{
|
|
_buildings.TryGetValue(id, out LuneBuildingView view);
|
|
return view;
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
foreach (LuneBuildingView building in FindObjectsByType<LuneBuildingView>(FindObjectsSortMode.None))
|
|
_buildings[building.BuildingId] = building;
|
|
_unitRoot = new GameObject("Units").transform;
|
|
if (ViewCamera == null)
|
|
ViewCamera = Camera.main;
|
|
}
|
|
|
|
void LateUpdate()
|
|
{
|
|
if (Driver == null || Driver.Runner == null)
|
|
return;
|
|
_clock.Restart();
|
|
LuneMatch match = Driver.Runner.Match;
|
|
float dt = Time.deltaTime;
|
|
if (match.Combat != _combat)
|
|
{
|
|
foreach (LuneUnitView view in _units.Values)
|
|
Destroy(view.gameObject);
|
|
_units.Clear();
|
|
_towerSerials.Clear();
|
|
_combat = match.Combat;
|
|
}
|
|
bool showUnits = _combat != null && Driver.Runner.Current != LuneMatchRunner.Stage.Planning;
|
|
float alpha = Driver.Runner.Current == LuneMatchRunner.Stage.Combat ? (float)Driver.Runner.Clock.Alpha : 1f;
|
|
if (showUnits)
|
|
{
|
|
foreach (LuneUnit unit in _combat.Units)
|
|
{
|
|
if (!unit.IsMobile())
|
|
continue;
|
|
if (!_units.TryGetValue(unit.Id, out LuneUnitView view))
|
|
{
|
|
view = new GameObject().AddComponent<LuneUnitView>();
|
|
view.transform.SetParent(_unitRoot, false);
|
|
view.Bind(unit, Library);
|
|
_units[unit.Id] = view;
|
|
}
|
|
view.Sync(alpha, dt, ViewCamera != null ? ViewCamera.transform : null);
|
|
}
|
|
}
|
|
SyncBuildings(match);
|
|
SyncBolts(dt);
|
|
_synced = true;
|
|
LastSyncMs = _clock.Elapsed.TotalMilliseconds;
|
|
}
|
|
|
|
void SyncBuildings(LuneMatch match)
|
|
{
|
|
bool fighting = _combat != null && match.Phase == Phase.Combat;
|
|
foreach (BuildingDef definition in match.Map.Buildings)
|
|
{
|
|
if (!_buildings.TryGetValue(definition.Id, out LuneBuildingView view))
|
|
continue;
|
|
double hp = match.BuildingHp[definition.Id];
|
|
LuneUnit unit = null;
|
|
if (_combat != null && _combat.Buildings.TryGetValue(definition.Id, out unit) && fighting)
|
|
hp = unit.Hp;
|
|
view.SetHealth((float)hp, (float)definition.MaxHp, _synced);
|
|
if (unit != null && unit.Kind == "tower" && _combat != null)
|
|
TowerAttack(view, unit);
|
|
}
|
|
}
|
|
|
|
void TowerAttack(LuneBuildingView view, LuneUnit tower)
|
|
{
|
|
_towerSerials.TryGetValue(tower.BuildingId, out int last);
|
|
_towerSerials[tower.BuildingId] = tower.AttackSerial;
|
|
if (tower.AttackSerial == last || last == 0 && tower.AttackSerial == 0)
|
|
return;
|
|
if (tower.Target == null || !_units.TryGetValue(tower.Target.Id, out LuneUnitView target))
|
|
return;
|
|
var line = new GameObject("Bolt").AddComponent<LineRenderer>();
|
|
line.transform.SetParent(_unitRoot, false);
|
|
line.sharedMaterial = Library.Bolt[Mathf.Clamp(view.Team, 0, 2)];
|
|
line.widthMultiplier = view.Tier == "base" ? .22f : .16f;
|
|
line.positionCount = 2;
|
|
line.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
|
_bolts.Add(new Bolt { Line = line, Target = target, From = view.Muzzle });
|
|
}
|
|
|
|
void SyncBolts(float dt)
|
|
{
|
|
for (int i = _bolts.Count - 1; i >= 0; i--)
|
|
{
|
|
Bolt bolt = _bolts[i];
|
|
bolt.Age += dt;
|
|
if (bolt.Age > .25f || bolt.Target == null)
|
|
{
|
|
Destroy(bolt.Line.gameObject);
|
|
_bolts.RemoveAt(i);
|
|
continue;
|
|
}
|
|
bolt.Line.SetPosition(0, bolt.From);
|
|
bolt.Line.SetPosition(1, bolt.Target.AimPoint);
|
|
}
|
|
}
|
|
}
|
|
}
|