using System; using System.Collections.Generic; using Luneraria.Core; using UnityEngine; using UnityEngine.Rendering; namespace Luneraria.View { /// /// 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). /// public sealed class LuneUnitLibrary : ScriptableObject { [Serializable] public sealed class UnitModel { public string Id; public GameObject Prefab; /// Set for skinned figures; rigid models have none. public RuntimeAnimatorController Controller; } public List Models = new List(); 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; /// H1: expanding ring of a hero's ultimate (moon light). public Material MoonBurst; /// S4: fog of war overlay (Luneraria/FogOverlay, vertex alpha). public Material FogOverlay; public Material BarBack; public Material[] BarFill = new Material[3]; public Material[] Bolt = new Material[3]; /// U5 spark colour of a tower shot (team light colour of lune_materials.gd). public Color[] BoltLight = new Color[3]; /// Wide owner ring of a deployed boss (lune_unit_view.gd, G3): round for Blau, diamond for Rot. public Mesh OwnerRingRound; public Mesh OwnerRingDiamond; public Material[] OwnerRing = new Material[2]; /// Fill of the Ancients' health bars (team light colour, lune_ancient_view.gd). public Material[] AncientBarFill = new Material[2]; Dictionary _index; readonly Dictionary _combined = new Dictionary(); public UnitModel Find(string id) { if (_index == null) { _index = new Dictionary(); foreach (UnitModel model in Models) _index[model.Id] = model; } _index.TryGetValue(id, out UnitModel found); return found; } /// Model id of a unit: the boss model for bosses, otherwise the definition id. 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); } /// One-mesh template of a rigid figure (parts merged per material), built once. GameObject Combined(GameObject source) { if (_combined.TryGetValue(source, out GameObject template) && template != null) return template; var groups = new Dictionary>(); var order = new List(); foreach (MeshFilter filter in source.GetComponentsInChildren(true)) { Material[] slots = filter.GetComponent().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 list)) { groups[material] = list = new List(); 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().sharedMesh = mesh; template.AddComponent().sharedMaterials = order.ToArray(); _combined[source] = template; return template; } } }