champions-of-luneraria/Assets/Luneraria/Editor/World/LuneWorldBuilder.cs
Martin Stickelbroeck 07fa4572e0 U5.3: Gebäude, Boss und Ringborke auf PBR; Bildfehler aus U5 behoben
Fortsetzung des angefangenen U5.3-Stands vom 2. Oktober.

- Fünf neue Sätze aus bake_pbr.py (metal, cloth, hide, bone, wood) für
  Türme, Ancients, Grotte und die Schlundkrone. Ringborkes Rinde (RB_Bark)
  auf ihren eigenen UVs mit dem 4k-Satz (Rauigkeit zu ORM gepackt),
  Normalen im Tangentenraum des Meshes. Aufnahmen aller drei Lanes.
- Befund: Seit U5 war die Welt halb so hell und ohne Grün (Mid-Lane
  Helligkeit 0,27, Grün 0 %). Ursachen und Korrekturen:
  - Die PBR-Albedos wurden über die Godot-Farben multipliziert (Rasen
    etwa x0,05). Jetzt teilt der Shader jede Karte durch ihren Mittel-
    wert; die Godot-Farbe bleibt der Durchschnitt, die Karte liefert
    Struktur. Ebenso die Rauigkeit, wo das Material eine eigene hat.
  - Normal- und ORM-Karten waren als sRGB-Farbe importiert. Jetzt
    Normalkarte bzw. linear.
  - bake_pbr.py verzerrte alle Muster 10:1 (Regenstreifen). Stein, Erde,
    Laub, Wasser und die neuen Sätze ohne Maserung neu gebacken.
  - Der Treffer-Flash ersetzte die Grundfarbe aller Materialplätze; im
    Nahkampf waren die Armeen weiß und der Boss gelb. Jetzt Tönung der
    eigenen Farbe je Platz, wenig Emission, 16 Stufen.
  - Nebel 0,0085 verschleierte die Kartenansicht zu 35-50 %; jetzt 0,0045.
  Ergebnis Mid-Lane: Helligkeit 0,41-0,44, Grün 31-39 % (U3: 0,45 / 27 %).
- Tests: EditMode 29/29, PlayMode 13/13, Verify 718/0, Kerntests .NET
  457/457, Fingerabdrücke unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5).
- Leistung: A/B mit einem U5.2-Player unter gleicher Last, kein messbarer
  Unterschied (91,4 gegen 93,8 fps Karte, 124,9 gegen 125,3 fps Mid).
- Messwerkzeug tools/port/png_stats.py; Doku U5-VISUAL (U5.3),
  PORT-STATUS, PORT-QA; Bilder screenshots/u5.3/ als JPEG.

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

561 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Luneraria.Core;
using Luneraria.View;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Luneraria.EditorTools
{
/// <summary>
/// Builds the G1 map scene (U2B) reproducibly from three sources:
/// the world bake of the Godot generator (terrain, lanes, platform, bridge, bases, landmarks,
/// vegetation, veins, grotto stairs, deployment areas, lights),
/// the FBX models of the Blender pipeline (towers, Ancients, grotto, props),
/// the C# core's map (building ids, positions and facing).
/// Unity -batchmode -projectPath . -executeMethod Luneraria.EditorTools.LuneWorldBuilder.BuildScene
/// The scene owns no rules; buildings carry views that the match binding (U2C) drives.
/// </summary>
public static class LuneWorldBuilder
{
public const string ScenePath = "Assets/Luneraria/Scenes/G1_Map.unity";
public const string GeneratedFolder = "Assets/Luneraria/World/Generated";
public const string GroundPath = "Assets/Luneraria/World/LuneGround.asset";
public const string VolumePath = "Assets/Luneraria/Settings/Rendering/LuneVolume.asset";
const string Art = "Assets/Luneraria/Art/";
public static void BuildScene()
{
int code = 0;
try
{
Build();
}
catch (Exception error)
{
Debug.LogError("LUNE_WORLD failed: " + error);
code = 1;
}
EditorApplication.Exit(code);
}
public static void Build()
{
LuneProjectSetup.Configure();
LuneWorldBake bake = LuneWorldBake.Load();
LuneMaterialSet materials = LuneMaterialFactory.Build(bake, out Dictionary<int, Material> worldMaterials);
LuneGround ground = BuildGround(bake);
Directory.CreateDirectory(GeneratedFolder);
_written.Clear();
Directory.CreateDirectory(Path.GetDirectoryName(ScenePath));
AssetDatabase.Refresh();
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
Environment(bake);
var world = new GameObject("World").transform;
Terrain(bake, world, worldMaterials);
int meshes = Nodes(bake, world, worldMaterials, materials);
var buildings = Buildings(materials, ground);
LuneUnitLibrary library = LuneUnitLibraryBuilder.Build(materials, ground);
var camera = new GameObject("Main Camera");
camera.tag = "MainCamera";
var unityCamera = camera.AddComponent<Camera>();
unityCamera.fieldOfView = LuneCameraMath.FieldOfView;
unityCamera.nearClipPlane = .5f;
unityCamera.farClipPlane = 700f;
camera.AddComponent<UniversalAdditionalCameraData>().renderPostProcessing = true;
LuneCameraMath.Pose(new Vector3(0f, ground.StandHeight(new Vec2(0f, 0f)), 0f), LuneCameraMath.OverviewZoom, 0f, out Vector3 position, out Quaternion rotation);
camera.transform.SetPositionAndRotation(position, rotation);
var rig = camera.AddComponent<LuneCameraRig>();
rig.Ground = ground;
rig.Zoom = LuneCameraMath.OverviewZoom;
// The match on the host and its views; the game controller with the HUD (U3) lets the
// human seat play. Tests and the benchmark start bot matches themselves.
var matchObject = new GameObject("Match");
var driver = matchObject.AddComponent<LuneMatchDriver>();
var view = matchObject.AddComponent<LuneMatchView>();
view.Driver = driver;
view.Library = library;
view.ViewCamera = unityCamera;
var game = matchObject.AddComponent<Luneraria.UI.LuneGame>();
game.Driver = driver;
game.View = view;
game.Rig = rig;
game.UiAssets = AssetDatabase.LoadAssetAtPath<Luneraria.UI.LuneUiAssets>(LuneUiAssetsBuilder.AssetPath);
var benchmark = matchObject.AddComponent<LuneBenchmark>();
benchmark.Driver = driver;
benchmark.View = view;
benchmark.Rig = rig;
// Meshes of an earlier build that this build no longer produces.
foreach (string stale in AssetDatabase.FindAssets("t:Mesh", new[] { GeneratedFolder }).Select(AssetDatabase.GUIDToAssetPath).Where(p => !_written.Contains(p)).ToList())
AssetDatabase.DeleteAsset(stale);
EditorSceneManager.SaveScene(scene, ScenePath);
var scenes = EditorBuildSettings.scenes.Where(s => s.path != ScenePath).ToList();
scenes.Insert(0, new EditorBuildSettingsScene(ScenePath, true));
EditorBuildSettings.scenes = scenes.ToArray();
AssetDatabase.SaveAssets();
Debug.Log(string.Format("LUNE_WORLD built {0}: {1} generated meshes, {2} buildings", ScenePath, meshes, buildings));
}
// ------------------------------------------------------------ ground heights
static LuneGround BuildGround(LuneWorldBake bake)
{
var ground = AssetDatabase.LoadAssetAtPath<LuneGround>(GroundPath);
if (ground == null)
{
ground = ScriptableObject.CreateInstance<LuneGround>();
AssetDatabase.CreateAsset(ground, GroundPath);
}
JToken inner = bake.Data["terrain"]["inner"];
ground.WalkExtent = (float)inner["extent"];
ground.WalkStep = (float)inner["step"];
ground.WalkSize = (int)inner["size"];
ground.Walk = bake.Floats(inner["walk"]);
JToken stand = bake.Data["stand"];
ground.StandExtent = (float)stand["extent"];
ground.StandStep = (float)stand["step"];
ground.StandSize = (int)stand["size"];
ground.Stand = bake.Floats(stand["heights"]);
EditorUtility.SetDirty(ground);
return ground;
}
// ------------------------------------------------------------ environment
static void Environment(LuneWorldBake bake)
{
JToken lights = bake.Data["lights"];
JProperty sunEntry = ((JObject)lights).Properties().First(p => p.Name != "fill" && p.Name != "environment");
Light sun = DirectionalLight("Sun", sunEntry.Value, true);
sun.shadowStrength = 1f;
// U5: crisp cascades over the Mid-Lane slice (was Unity's default 2).
sun.shadowBias = .02f;
sun.shadowNormalBias = .35f;
sun.shadowNearPlane = .2f;
DirectionalLight("Fill", lights["fill"], false);
JToken environment = lights["environment"];
RenderSettings.ambientMode = AmbientMode.Flat;
RenderSettings.ambientLight = LuneMaterialFactory.GammaEncoded(LuneMaterialFactory.ColorOf(environment["ambient_color"]), (float)environment["ambient_energy"] * 1.6f);
// U5: aerial depth over the valley; the Mid-Lane reads further away than the towers.
// U5.3: density .0085 veiled the map view (zoom 64–70, 70–90 m away) by 35–50 % in
// the fog colour and took the colour out of the armies; .0045 keeps 10–15 % there.
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogColor = LuneMaterialFactory.Hex("6a7f96");
RenderSettings.fogDensity = .0045f;
RenderSettings.sun = sun;
const string skyPath = "Assets/Luneraria/Settings/Rendering/LuneSky.mat";
var sky = AssetDatabase.LoadAssetAtPath<Material>(skyPath);
if (sky == null)
{
sky = new Material(Shader.Find("Skybox/Procedural"));
AssetDatabase.CreateAsset(sky, skyPath);
}
sky.SetColor("_SkyTint", LuneMaterialFactory.Hex("2a3d58"));
sky.SetColor("_GroundColor", LuneMaterialFactory.Hex("1a1c24"));
sky.SetFloat("_Exposure", 1.05f);
sky.SetFloat("_AtmosphereThickness", .8f);
EditorUtility.SetDirty(sky);
RenderSettings.skybox = sky;
RenderSettings.defaultReflectionMode = DefaultReflectionMode.Skybox;
RenderSettings.reflectionIntensity = 1f;
var volume = new GameObject("PostProcess").AddComponent<Volume>();
volume.isGlobal = true;
volume.sharedProfile = VolumeProfile();
}
/// <summary>Godot light: rotation_degrees (Euler YXZ), shining along its local -Z.</summary>
static Light DirectionalLight(string name, JToken entry, bool shadows)
{
var degrees = entry["rotation_degrees"];
float x = (float)degrees[0] * Mathf.Deg2Rad;
float y = (float)degrees[1] * Mathf.Deg2Rad;
// Ry(y) * Rx(x) * (0, 0, -1)
var afterX = new Vector3(0f, Mathf.Sin(x), -Mathf.Cos(x));
var godotDirection = new Vector3(afterX.x * Mathf.Cos(y) + afterX.z * Mathf.Sin(y), afterX.y, -afterX.x * Mathf.Sin(y) + afterX.z * Mathf.Cos(y));
var light = new GameObject(name).AddComponent<Light>();
light.type = LightType.Directional;
light.transform.rotation = Quaternion.LookRotation(LuneSpace.FromGodot(godotDirection), Vector3.up);
light.color = LuneMaterialFactory.ColorOf(entry["color"]);
light.intensity = (float)entry["energy"];
light.shadows = shadows ? LightShadows.Soft : LightShadows.None;
return light;
}
static T Override<T>(VolumeProfile profile) where T : VolumeComponent
{
if (profile.TryGet(out T component))
return component;
component = profile.Add<T>(true);
AssetDatabase.AddObjectToAsset(component, profile);
return component;
}
/// <summary>ACES tone mapping, glow and colour adjustment of scripts/presentation/lighting.gd (U5 Mid-Lane).</summary>
static VolumeProfile VolumeProfile()
{
var profile = AssetDatabase.LoadAssetAtPath<VolumeProfile>(VolumePath);
if (profile == null)
{
profile = ScriptableObject.CreateInstance<VolumeProfile>();
AssetDatabase.CreateAsset(profile, VolumePath);
}
// Updated in place: recreating the overrides gave them new file ids on every build.
var tonemapping = Override<Tonemapping>(profile);
tonemapping.mode.Override(TonemappingMode.ACES);
var bloom = Override<Bloom>(profile);
bloom.threshold.Override(1.15f);
bloom.intensity.Override(.62f);
bloom.scatter.Override(.68f);
var colors = Override<ColorAdjustments>(profile);
colors.contrast.Override(14f);
colors.saturation.Override(16f);
// U5: a cool valley shadow, warm sun on stone — stylized realism of the reference MOBAs.
var white = Override<WhiteBalance>(profile);
white.temperature.Override(8f);
white.tint.Override(-4f);
var vignette = Override<Vignette>(profile);
vignette.intensity.Override(.18f);
vignette.smoothness.Override(.4f);
EditorUtility.SetDirty(profile);
return profile;
}
// ------------------------------------------------------------ terrain
static void Terrain(LuneWorldBake bake, Transform world, Dictionary<int, Material> materials)
{
Material terrain = AssetDatabase.LoadAssetAtPath<Material>(LuneMaterialFactory.Folder + "/World/Terrain.mat");
Material mountains = AssetDatabase.LoadAssetAtPath<Material>(LuneMaterialFactory.Folder + "/World/TerrainEdge.mat") ?? terrain;
var group = new GameObject("Terrain").transform;
group.SetParent(world, false);
foreach (string key in new[] { "inner", "outer" })
{
JToken grid = bake.Data["terrain"][key];
Mesh mesh = GridMesh(key == "inner" ? "Ground" : "Mountains", bake.Floats(grid["ground"]), bake.Floats(grid["colors"], 4),
(int)grid["size"], (float)grid["extent"], (float)grid["step"], (float)grid["hole"]);
string label = mesh.name;
mesh = Save(mesh);
// Weg 2: the walkable ground is the lane tier, the surrounding mountains the edge tier.
var go = Place(label, group, mesh, new[] { key == "inner" ? terrain : mountains }, key == "inner");
go.isStatic = true;
}
}
/// <summary>Port of LuneTerrain._mesh: grid with alternating diagonals, normals from neighbours.</summary>
static Mesh GridMesh(string name, float[] heights, float[] colors, int n, float extent, float step, float hole)
{
var vertices = new Vector3[n * n];
var normals = new Vector3[n * n];
var tints = new Color[n * n];
for (int j = 0; j < n; j++)
{
for (int i = 0; i < n; i++)
{
int index = j * n + i;
vertices[index] = LuneSpace.FromGodot(new Vector3(-extent + i * step, heights[index], -extent + j * step));
float left = heights[j * n + Math.Max(0, i - 1)];
float right = heights[j * n + Math.Min(n - 1, i + 1)];
float up = heights[Math.Max(0, j - 1) * n + i];
float down = heights[Math.Min(n - 1, j + 1) * n + i];
normals[index] = LuneSpace.FromGodot(new Vector3(left - right, 2f * step, up - down).normalized);
tints[index] = new Color(colors[index * 4], colors[index * 4 + 1], colors[index * 4 + 2], colors[index * 4 + 3]);
}
}
var indices = new List<int>((n - 1) * (n - 1) * 6);
for (int j = 0; j < n - 1; j++)
{
for (int i = 0; i < n - 1; i++)
{
if (hole > 0f)
{
float cx = Mathf.Abs(-extent + (i + .5f) * step);
float cz = Mathf.Abs(-extent + (j + .5f) * step);
if (Mathf.Max(cx, cz) < hole)
continue;
}
int a = j * n + i;
int b = a + 1;
int c = a + n;
int d = c + 1;
if ((i + j) % 2 == 0)
indices.AddRange(new[] { a, b, d, a, d, c });
else
indices.AddRange(new[] { a, b, c, b, d, c });
}
}
var mesh = new Mesh { name = name, indexFormat = IndexFormat.UInt32 };
mesh.SetVertices(vertices);
mesh.SetNormals(normals);
mesh.SetColors(tints);
mesh.SetTriangles(indices, 0);
mesh.RecalculateBounds();
return mesh;
}
// ------------------------------------------------------------ baked nodes
static int Nodes(LuneWorldBake bake, Transform world, Dictionary<int, Material> worldMaterials, LuneMaterialSet set)
{
var meshes = new Dictionary<int, Mesh>();
var bakedMeshes = (JArray)bake.Data["meshes"];
var props = PropLibrary(set);
var map = new LuneMap(new LuneBalance());
foreach (JObject node in bake.Data["nodes"])
{
string name = (string)node["name"];
if (name.StartsWith("@"))
name = "Part";
Mesh mesh;
Material[] materials;
if (node["prop"] != null)
{
var prop = props[(string)node["prop"]];
mesh = prop.Key;
materials = prop.Value;
}
else
{
int index = (int)node["mesh"];
var baked = (JObject)bakedMeshes[index];
if (!meshes.TryGetValue(index, out mesh))
{
mesh = Save(BakedMesh(bake, baked, name + "_" + index));
meshes[index] = mesh;
}
var surfaces = (JArray)baked["surfaces"];
materials = new Material[surfaces.Count];
for (int s = 0; s < surfaces.Count; s++)
{
JToken reference = node["material"] ?? surfaces[s]["material"];
materials[s] = reference != null && worldMaterials.TryGetValue((int)reference, out Material found) ? found : null;
}
if (name.StartsWith("Vein_"))
{
JToken parameters = node["instance_params"];
materials = new[] { LuneMaterialFactory.VeinMaterial(name, LuneMaterialFactory.ColorOf(parameters["tint"]), (float)parameters["seed"]) };
}
}
var transforms = (JArray)node["transforms"];
bool shadows = (bool)node["cast_shadow"];
bool visible = (bool)node["visible"];
var group = new GameObject(name).transform;
group.SetParent(world, false);
for (int k = 0; k < transforms.Count; k++)
{
float[] t = LuneWorldBake.Transform(transforms[k]);
LuneSpace.FromGodot(t, 0, out Vector3 position, out Quaternion rotation, out Vector3 scale);
GameObject go = transforms.Count == 1 ? group.gameObject : new GameObject(name + "_" + k);
if (transforms.Count > 1)
go.transform.SetParent(group, false);
go.transform.localPosition = position;
go.transform.localRotation = rotation;
go.transform.localScale = scale;
AddRenderer(go, mesh, materials, shadows);
ApplyTier(map, go);
go.isStatic = visible;
}
group.gameObject.SetActive(visible);
}
return meshes.Count + 2;
}
/// <summary>
/// Weg 2: a node beyond the lane corridor keeps the edge tier (Surface _Quality 0),
/// so the mountains and rim vegetation skip the PBR maps the slice pays for.
/// </summary>
static void ApplyTier(LuneMap map, GameObject go)
{
float quality = LuneZones.QualityOfWorld(map, go.transform.position);
if (quality > .5f)
return;
var block = new MaterialPropertyBlock();
block.SetFloat("_Quality", 0f);
foreach (Renderer renderer in go.GetComponentsInChildren<Renderer>(true))
renderer.SetPropertyBlock(block);
}
/// <summary>Prop meshes of map_props.fbx by name, with their surfaces dressed (ENV_* materials).</summary>
static Dictionary<string, KeyValuePair<Mesh, Material[]>> PropLibrary(LuneMaterialSet set)
{
var result = new Dictionary<string, KeyValuePair<Mesh, Material[]>>();
var root = AssetDatabase.LoadAssetAtPath<GameObject>(Art + "Environment/map_props.fbx");
foreach (MeshFilter filter in root.GetComponentsInChildren<MeshFilter>(true))
{
var renderer = filter.GetComponent<MeshRenderer>();
Material[] materials = renderer.sharedMaterials.Select(m => set.Get(LuneMaterialSet.KeyOf(m), 2) ?? m).ToArray();
result[filter.gameObject.name] = new KeyValuePair<Mesh, Material[]>(filter.sharedMesh, materials);
}
return result;
}
/// <summary>A baked Godot mesh in Unity: x mirrored (with the handedness change the picture and
/// the clockwise front faces are kept, so the index order stays).</summary>
static Mesh BakedMesh(LuneWorldBake bake, JObject baked, string name)
{
var vertices = new List<Vector3>();
var normals = new List<Vector3>();
var uvs = new List<Vector2>();
var colors = new List<Color>();
var submeshes = new List<int[]>();
bool hasNormals = true;
bool hasUvs = true;
bool hasColors = true;
foreach (JObject surface in baked["surfaces"])
{
int start = vertices.Count;
float[] v = bake.Floats(surface["vertices"], 3);
int count = v.Length / 3;
for (int i = 0; i < count; i++)
vertices.Add(LuneSpace.FromGodot(new Vector3(v[i * 3], v[i * 3 + 1], v[i * 3 + 2])));
if (surface["normals"] != null)
{
float[] n = bake.Floats(surface["normals"], 3);
for (int i = 0; i < count; i++)
normals.Add(LuneSpace.FromGodot(new Vector3(n[i * 3], n[i * 3 + 1], n[i * 3 + 2])));
}
else
{
hasNormals = false;
}
if (surface["uvs"] != null)
{
float[] uv = bake.Floats(surface["uvs"], 2);
for (int i = 0; i < count; i++)
uvs.Add(new Vector2(uv[i * 2], uv[i * 2 + 1]));
}
else
{
hasUvs = false;
for (int i = 0; i < count; i++)
uvs.Add(Vector2.zero);
}
if (surface["colors"] != null)
{
float[] c = bake.Floats(surface["colors"], 4);
for (int i = 0; i < count; i++)
colors.Add(new Color(c[i * 4], c[i * 4 + 1], c[i * 4 + 2], c[i * 4 + 3]));
}
else
{
hasColors = false;
for (int i = 0; i < count; i++)
colors.Add(Color.white);
}
int[] indices = surface["indices"] != null ? bake.Ints(surface["indices"]) : Enumerable.Range(0, count).ToArray();
submeshes.Add(indices.Select(i => i + start).ToArray());
}
var mesh = new Mesh { name = name, indexFormat = vertices.Count > 65000 ? IndexFormat.UInt32 : IndexFormat.UInt16 };
mesh.SetVertices(vertices);
if (hasUvs)
mesh.SetUVs(0, uvs);
if (hasColors)
mesh.SetColors(colors);
mesh.subMeshCount = submeshes.Count;
for (int s = 0; s < submeshes.Count; s++)
mesh.SetTriangles(submeshes[s], s);
if (hasNormals && normals.Count == vertices.Count)
mesh.SetNormals(normals);
else
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
static readonly HashSet<string> _written = new HashSet<string>();
/// <summary>Writes a generated mesh; an existing asset is updated in place so its GUID (and the
/// scene's reference) stays the same from build to build.</summary>
static Mesh Save(Mesh mesh)
{
string path = GeneratedFolder + "/" + mesh.name + ".asset";
if (!_written.Add(path))
throw new InvalidOperationException("duplicate generated mesh " + path);
var existing = AssetDatabase.LoadAssetAtPath<Mesh>(path);
if (existing == null)
{
AssetDatabase.CreateAsset(mesh, path);
return mesh;
}
EditorUtility.CopySerialized(mesh, existing);
EditorUtility.SetDirty(existing);
UnityEngine.Object.DestroyImmediate(mesh);
return existing;
}
static GameObject Place(string name, Transform parent, Mesh mesh, Material[] materials, bool shadows)
{
var go = new GameObject(name);
go.transform.SetParent(parent, false);
AddRenderer(go, mesh, materials, shadows);
return go;
}
static void AddRenderer(GameObject go, Mesh mesh, Material[] materials, bool shadows)
{
go.AddComponent<MeshFilter>().sharedMesh = mesh;
var renderer = go.AddComponent<MeshRenderer>();
renderer.sharedMaterials = materials;
renderer.shadowCastingMode = shadows ? ShadowCastingMode.On : ShadowCastingMode.Off;
}
// ------------------------------------------------------------ strategic buildings
static int Buildings(LuneMaterialSet materials, LuneGround ground)
{
var root = new GameObject("Buildings").transform;
var map = new LuneMap(new LuneBalance());
const float PlazaLift = .08f;
int count = 0;
foreach (BuildingDef definition in map.Buildings)
{
Vec2 at = definition.Position;
string asset;
Vec2 facing;
float height = ground.WalkHeight(at);
if (definition.Tier == "core")
{
asset = "ancient";
facing = (-at).Normalized();
height += PlazaLift;
}
else if (definition.Tier == "base")
{
asset = "kernwacht";
facing = (at - map.CorePosition(definition.Team)).Normalized();
}
else
{
asset = definition.Tier == "outer" ? "grenzwacht" : "bastion";
facing = (map.LanePoint(definition.Team, definition.Lane, definition.Progress) - at).Normalized();
}
var view = new GameObject(definition.Id).AddComponent<LuneBuildingView>();
view.transform.SetParent(root, false);
view.transform.position = LuneSpace.World(at, height);
view.BuildingId = definition.Id;
view.Team = definition.Team;
view.Tier = definition.Tier;
GameObject model = Model(asset, view.transform, materials, definition.Team);
model.transform.localRotation = LuneSpace.Facing(facing);
view.Show(LuneBuildingView.Look.Intact);
count++;
}
var grotto = new GameObject("BossGrotto").transform;
grotto.SetParent(root, false);
grotto.position = LuneSpace.World(LuneMap.Grotto, 1.7f);
Model("bossgrotte", grotto, materials, 2);
return count + 1;
}
static GameObject Model(string asset, Transform parent, LuneMaterialSet materials, int team)
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(Art + "Buildings/" + asset + ".fbx");
var model = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
model.name = "Model";
materials.Dress(model, team);
return model;
}
}
}