using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Rendering; namespace Luneraria.EditorTools { /// /// Renders every pipeline asset in a neutral preview scene (URP, one sun, 3/4 front view) to /// docs/port/screenshots/u2a/. Buildings show their intact state; the tower also its ruin; /// rigged figures their bind pose and the hit frame of attack_a. /// Unity -batchmode -projectPath . -executeMethod Luneraria.EditorTools.LuneAssetPreview.RenderAll /// public static class LuneAssetPreview { const int Width = 1600; const int Height = 1000; public static void RenderAll() { int code = 0; try { string folder = Path.Combine(Path.GetDirectoryName(Application.dataPath), "docs/port/screenshots/u2a"); Directory.CreateDirectory(folder); EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); var sun = new GameObject("Sun").AddComponent(); sun.type = LightType.Directional; sun.intensity = 1.6f; sun.color = new Color(1f, .89f, .74f); sun.shadows = LightShadows.Soft; sun.transform.rotation = Quaternion.Euler(48f, 150f, 0f); RenderSettings.ambientMode = AmbientMode.Trilight; RenderSettings.ambientSkyColor = new Color(.55f, .6f, .68f); RenderSettings.ambientEquatorColor = new Color(.42f, .42f, .44f); RenderSettings.ambientGroundColor = new Color(.2f, .19f, .18f); var ground = GameObject.CreatePrimitive(PrimitiveType.Plane); ground.transform.localScale = new Vector3(20f, 1f, 20f); var camera = new GameObject("PreviewCamera").AddComponent(); camera.clearFlags = CameraClearFlags.SolidColor; camera.backgroundColor = new Color(.16f, .18f, .21f); camera.fieldOfView = 30f; foreach (string model in ExportReport.AllModels("Assets/Luneraria/Art").OrderBy(p => p, StringComparer.Ordinal)) { ExportReport report = ExportReport.ForModel(model); var prefab = AssetDatabase.LoadAssetAtPath(model); foreach (var shot in Shots(report)) { var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab); shot.Value(instance); Render(camera, instance, Path.Combine(folder, report.Id + shot.Key + ".png")); UnityEngine.Object.DestroyImmediate(instance); } } } catch (Exception error) { Debug.LogError("LUNE_PREVIEW failed: " + error); code = 1; } EditorApplication.Exit(code); } static IEnumerable>> Shots(ExportReport report) { string group = (string)report.Data["group"]; if (group == "Buildings") { yield return Shot("", go => Show(go, n => !n.StartsWith("Upper_Damaged") && !n.StartsWith("Debris") && !n.StartsWith("Ruin"))); if (report.Id == "grenzwacht") yield return Shot("-ruin", go => Show(go, n => n.StartsWith("Ruin"))); } else if (group == "Environment") { yield return Shot("", Row); } else if (report.Skinned) { yield return Shot("", go => Sample(go, "idle", 0f)); yield return Shot("-attack_a", go => Sample(go, "attack_a", .9f)); } else { yield return Shot("", go => { }); } } static KeyValuePair> Shot(string suffix, Action prepare) { return new KeyValuePair>(suffix, prepare); } static void Show(GameObject root, Func visible) { foreach (Renderer renderer in root.GetComponentsInChildren(true)) renderer.enabled = visible(renderer.gameObject.name); } /// The prop library keeps every prop at the origin; the preview sets them out in a grid. static void Row(GameObject root) { const int columns = 5; const float cell = 7.5f; int index = 0; foreach (Transform child in root.transform.Cast().OrderBy(t => t.name, StringComparer.Ordinal).ToList()) { var renderer = child.GetComponent(); if (renderer == null) continue; Vector3 center = renderer.bounds.center; var slot = new Vector3((index % columns) * cell, 0f, -(index / columns) * cell); child.position += slot - new Vector3(center.x, 0f, center.z); index++; } } static void Sample(GameObject root, string clipName, float seconds) { string path = AssetDatabase.GetAssetPath(PrefabUtility.GetCorrespondingObjectFromSource(root)); AnimationClip clip = AssetDatabase.LoadAllAssetsAtPath(path).OfType().FirstOrDefault(c => c.name == clipName); if (clip != null) clip.SampleAnimation(root, seconds); } static void Render(Camera camera, GameObject root, string file) { var renderers = root.GetComponentsInChildren().Where(r => r.enabled).ToList(); Bounds bounds = renderers[0].bounds; foreach (Renderer renderer in renderers) bounds.Encapsulate(renderer.bounds); // 3/4 front view: the models face +Z, the camera stands in front, to the right, above. Vector3 direction = new Vector3(.62f, .42f, 1f).normalized; float radius = bounds.extents.magnitude; float distance = radius / Mathf.Sin(camera.fieldOfView * .5f * Mathf.Deg2Rad) * .92f; camera.aspect = (float)Width / Height; camera.transform.position = bounds.center + direction * distance; camera.transform.LookAt(bounds.center); camera.nearClipPlane = Mathf.Max(.01f, distance - radius * 2f); camera.farClipPlane = distance + radius * 4f; var target = new RenderTexture(Width, Height, 24, RenderTextureFormat.ARGB32) { antiAliasing = 8 }; camera.targetTexture = target; camera.Render(); RenderTexture.active = target; var image = new Texture2D(Width, Height, TextureFormat.RGB24, false); image.ReadPixels(new Rect(0, 0, Width, Height), 0, 0); image.Apply(); File.WriteAllBytes(file, image.EncodeToPNG()); RenderTexture.active = null; camera.targetTexture = null; UnityEngine.Object.DestroyImmediate(target); UnityEngine.Object.DestroyImmediate(image); Debug.Log("LUNE_PREVIEW wrote " + file); } } }