champions-of-luneraria/Assets/Luneraria/View/LuneUnitLibrary.cs
Martin Stickelbroeck 3ca38a8443 U2 Batch Asset Import
Alle 86 Modelle der Godot-Referenz laufen durch die Blender→FBX-Pipeline
(nach bestandenem Vertical Function Test); jede Einheit zeigt ihr eigenes
Modell.

- Exportmanifest aus den Godot-Manifesten (tools/blender/make_manifest.py):
  Units, Enemies, Props, Characters, Peoples, Buildings, Environment; die
  drei Vergleichs-GLBs als Aliase. Neu in der Pipeline: Tangenten berechnet
  Unity mit MikkTSpace, Generatoren laufen in einer temporären Kopie, die
  Grüne Kluft wird wie das GLB zusammengefasst, Texturen kommen mit.
  1.616 Blender- und 1.247 Unity-Prüfungen grün, zwei Exportläufe
  byteidentisch; Kontaktbögen je Gruppe.
- Einheitenbibliothek nach Godots AssetLibrary: 70 Einträge der
  Einheitengruppen, Figuren mit Skelett ersetzen das starre Modell ihrer ID.
  Starre Modelle werden zur Laufzeit je Material zusammengefasst; Controller
  werden an Ort und Stelle aktualisiert.
- Tests mit allen Modellen: EditMode 11/11, PlayMode 2/2. Vertical Function
  Test Seed 2 DCE95C96556F3A1A und Seed 9 ab Runde 21 7B8E43AEFC0D60C5,
  beide gleich dem Kern (Nachweise mit LUNE_EVIDENCE=u2a-batch; die Belege
  von U2C und U2D bleiben unverändert).
- macOS-Player mit allen Modellen: 158-160 fps in der Übersicht
  (116 Einheiten), 205-207 fps auf der Mid-Lane; Haupt- und Render-Thread
  etwa doppelt so lang wie mit dem Platzhalter, Present-Ausreißer kürzer.
- Befund iCloud Drive: Konfliktkopien ("Name 2.ext") im Build-Cache ließen
  den Build die Einheitenbibliothek mit altem Feldlayout schreiben; der
  Player meldete sharedassets0.assets als beschädigt. LunePlayerBuild
  entfernt solche Kopien vor dem Build und bricht ab, wenn während des
  Builds neue entstehen. Bereits committete Kopien: Controller-Duplikate und
  World/Generated 2.meta entfernt, drei U2D-Benchmarkläufe als -run1 bis
  -run3 benannt.
- Dokumentation: U2-WORLD (Batch-Import), PORT-STATUS (Entscheidungen:
  Modelle direkt in Git, Projektort außerhalb von iCloud Drive), PORT-QA.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 19:10:35 +02:00

118 lines
5.1 KiB
C#

using System;
using System.Collections.Generic;
using Luneraria.Core;
using UnityEngine;
using UnityEngine.Rendering;
namespace Luneraria.View
{
/// <summary>
/// What a unit looks like (port of AssetLibrary.instantiate_fighter): the model id is the
/// unit definition's id (troops, monsters, heroes and champions carry their model there), the
/// boss is the Schlundkrone. Skinned figures (Ringborke for w04, Basaltbastion for g04, ...)
/// replace the rigid Phase-4 model of the same id, as in Godot. Rigid models keep their named
/// parts in the FBX; at first use they are combined into one mesh per material and cached
/// (Godot's rig groups did the same at load time).
/// </summary>
public sealed class LuneUnitLibrary : ScriptableObject
{
[Serializable]
public sealed class UnitModel
{
public string Id;
public GameObject Prefab;
/// <summary>Set for skinned figures; rigid models have none.</summary>
public RuntimeAnimatorController Controller;
}
public List<UnitModel> Models = new List<UnitModel>();
public string BossModel = "schlundkrone";
public string FallbackModel = "g01";
public LuneMaterialSet Materials;
public LuneGround Ground;
public Mesh RingRound;
public Mesh RingDiamond;
public Mesh RingHexagon;
public Mesh Quad;
public Material[] TeamRing = new Material[3];
public Material HeroRing;
public Material BarBack;
public Material[] BarFill = new Material[3];
public Material[] Bolt = new Material[3];
Dictionary<string, UnitModel> _index;
readonly Dictionary<GameObject, GameObject> _combined = new Dictionary<GameObject, GameObject>();
public UnitModel Find(string id)
{
if (_index == null)
{
_index = new Dictionary<string, UnitModel>();
foreach (UnitModel model in Models)
_index[model.Id] = model;
}
_index.TryGetValue(id, out UnitModel found);
return found;
}
/// <summary>Model id of a unit: the boss model for bosses, otherwise the definition id.</summary>
public string ModelId(LuneUnit unit)
{
return unit.Kind == "boss" ? BossModel : unit.Def.Id;
}
public GameObject PrefabFor(LuneUnit unit, out RuntimeAnimatorController controller)
{
UnitModel model = Find(ModelId(unit)) ?? Find(FallbackModel);
controller = model.Controller;
return controller != null ? model.Prefab : Combined(model.Prefab);
}
/// <summary>One-mesh template of a rigid figure (parts merged per material), built once.</summary>
GameObject Combined(GameObject source)
{
if (_combined.TryGetValue(source, out GameObject template) && template != null)
return template;
var groups = new Dictionary<Material, List<CombineInstance>>();
var order = new List<Material>();
foreach (MeshFilter filter in source.GetComponentsInChildren<MeshFilter>(true))
{
Material[] slots = filter.GetComponent<MeshRenderer>().sharedMaterials;
Matrix4x4 toRoot = source.transform.worldToLocalMatrix * filter.transform.localToWorldMatrix;
for (int sub = 0; sub < filter.sharedMesh.subMeshCount; sub++)
{
Material material = slots[Mathf.Min(sub, slots.Length - 1)];
if (!groups.TryGetValue(material, out List<CombineInstance> list))
{
groups[material] = list = new List<CombineInstance>();
order.Add(material);
}
list.Add(new CombineInstance { mesh = filter.sharedMesh, subMeshIndex = sub, transform = toRoot });
}
}
var parts = new CombineInstance[order.Count];
for (int i = 0; i < order.Count; i++)
{
var merged = new Mesh { indexFormat = IndexFormat.UInt32 };
merged.CombineMeshes(groups[order[i]].ToArray(), true, true);
parts[i] = new CombineInstance { mesh = merged, transform = Matrix4x4.identity };
}
var mesh = new Mesh { name = source.name + "_combined", indexFormat = IndexFormat.UInt32 };
mesh.CombineMeshes(parts, false, false);
mesh.RecalculateBounds();
foreach (CombineInstance part in parts)
{
if (Application.isPlaying)
Destroy(part.mesh);
else
DestroyImmediate(part.mesh);
}
template = new GameObject(source.name + "_combined") { hideFlags = HideFlags.HideAndDontSave };
template.SetActive(false);
template.AddComponent<MeshFilter>().sharedMesh = mesh;
template.AddComponent<MeshRenderer>().sharedMaterials = order.ToArray();
_combined[source] = template;
return template;
}
}
}