Das G3-HUD steht im Spiel; die Abnahme (U3D) folgt. - U3B: alle Bereiche und Komponenten aus Godot übertragen (LuneHud, Lanes, Bossgrotte, nächste Welle, Kaufkarten, Armee, Held mit Items und Talenten, Angriffschampions, Team, Ereignisse, Minimap, Tooltip, Optionen, Partieende). Porträts aus den echten Modellen, einmal gerendert. - Offene G3-Punkte umgesetzt: „1 WELLE“ als Band am Porträtrand, Schadens- und Rüstungssymbole auf den Karten, Armeewert, Heldenporträts auf der Minimap. Befehlsplatz für die Ultimates der geplanten Helden reserviert (verborgen). - U3C: LuneGame verdrahtet Befehle (gleiche Schnittstelle wie die Bots), Bereit mit 1-s-Sperre nach dem Kampf, Tempo und Pause, Tasten wie in G3, Kamera-Ziele, Auswahl im Feld, Planungsvorschau und Aufstellungsflächen. Ersetzt die U2-Debug-Anzeige. Bot-Partien und Benchmark unverändert. - Szenen- und Bibliotheksbauer idempotent: Animator-Controller und Post-Processing-Profil werden an Ort und Stelle aktualisiert. Die Szene bekommt bei jedem Bau neue Objekt-IDs, der Inhalt ist gleich. - Prüfung (Port von verify_g3, Teile A–H): 699 Prüfungen ohne Fehler. EditMode 23/23, PlayMode 12/12, Fingerabdrücke der Bot-Partien unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
524 lines
25 KiB
C#
524 lines
25 KiB
C#
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;
|
|
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);
|
|
RenderSettings.fog = false;
|
|
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;
|
|
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.</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.2f);
|
|
bloom.intensity.Override(.55f);
|
|
bloom.scatter.Override(.6f);
|
|
var colors = Override<ColorAdjustments>(profile);
|
|
colors.contrast.Override(12f);
|
|
colors.saturation.Override(18f);
|
|
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");
|
|
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);
|
|
var go = Place(label, group, mesh, new[] { terrain }, 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);
|
|
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);
|
|
go.isStatic = visible;
|
|
}
|
|
group.gameObject.SetActive(visible);
|
|
}
|
|
return meshes.Count + 2;
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|