Aetheria/Assets/Scripts/Editor/ScenarioPickerCapture.cs
soul 09de7e5eb2 ScenarioPicker: selected row in the menu accent, title is group over name, panes docked 128 apart; verify and capture run green in Unity; metas
The prefab row tint is nearly invisible on the row's graphic, the long scenario name overflowed a 480-wide title, and the list scrolls to keep the selection in view.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 01:42:56 +02:00

107 lines
4.9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.UI;
// A render of the New Game screen as the game builds it: the real Main Menu scene's panel prototype, docked and filled
// by ScenarioPicker.Dock and ScenarioPicker.Show with every scenario group, on a plain dark background (the cloud
// backdrop is a play-mode renderer). Writes <project>/Temp/scenario-picker.png at 1920x1080, or the path after -pngOut (Unity empties Temp on exit).
//
// Unity -batchmode -projectPath <project> -executeMethod ScenarioPickerCapture.Run [-pngOut <file>] -logFile <log>
//
// Needs a GPU device; do not pass -nographics or -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
public static class ScenarioPickerCapture
{
private const int Width = 1920, Height = 1080;
public static void Run()
{
try
{
Capture();
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
// The menu's two panes built the way MainMenu.Start builds them, against the Main Menu scene.
internal static (MainMenu menu, PropertiesPanel list, PropertiesPanel detail) Build()
{
EditorSceneManager.OpenScene("Assets/Scenes/Main Menu.unity", OpenSceneMode.Single);
var menu = UnityEngine.Object.FindObjectsByType<MainMenu>(FindObjectsInactive.Include, FindObjectsSortMode.None)[0];
menu.PanelPrototype.gameObject.SetActive(false);
var list = menu.PanelPrototype.Instantiate<PropertiesPanel>();
var detail = menu.PanelPrototype.Instantiate<PropertiesPanel>();
// Edit mode runs no Awake, which is what activates each row a panel adds.
list.Awake();
detail.Awake();
ScenarioPicker.Dock(detail, list);
return (menu, list, detail);
}
internal static List<(string label, Scenario[] scenarios)> AllGroups() => new List<(string label, Scenario[] scenarios)>
{
("modes", Scenarios.Modes), ("development", Scenarios.Development), ("test arenas", Scenarios.Tests)
};
private static void Capture()
{
var (menu, list, detail) = Build();
menu.Dialog.gameObject.SetActive(false);
list.Title.text = MainMenu.TitleSubtitle("new game", "scenarios");
ScenarioPicker.Show(list, detail, AllGroups(), _ => { }, () => { });
list.RefreshValues();
detail.RefreshValues();
var canvas = menu.GetComponentInParent<Canvas>(true).rootCanvas;
var target = new RenderTexture(Width, Height, 24, RenderTextureFormat.ARGB32);
var cameraObject = new GameObject("ScenarioPickerCapture camera");
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = true;
camera.orthographicSize = 5;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(.04f, .05f, .08f);
camera.cullingMask = 1 << LayerMask.NameToLayer("UI");
camera.targetTexture = target;
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.worldCamera = camera;
canvas.planeDistance = 10;
canvas.gameObject.SetActive(true);
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) list.transform);
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform) detail.transform);
foreach (var text in list.transform.GetComponentsInChildren<TMPro.TMP_Text>())
text.ForceMeshUpdate();
Canvas.ForceUpdateCanvases();
camera.Render();
var previous = RenderTexture.active;
RenderTexture.active = target;
var read = new Texture2D(Width, Height, TextureFormat.RGBA32, false);
read.ReadPixels(new Rect(0, 0, Width, Height), 0, 0);
RenderTexture.active = previous;
var background = read.GetPixel(0, 0);
var differing = 0;
foreach (var pixel in read.GetPixels32())
if (Mathf.Abs(pixel.r / 255f - background.r) + Mathf.Abs(pixel.g / 255f - background.g) + Mathf.Abs(pixel.b / 255f - background.b) > .05f)
differing++;
if (differing < Width * Height / 100)
throw new InvalidOperationException($"The capture is uniform: only {differing} pixels differ from the background.");
var path = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Temp", "scenario-picker.png"));
var args = Environment.GetCommandLineArgs();
var outIndex = Array.IndexOf(args, "-pngOut");
if (outIndex >= 0) path = Path.GetFullPath(args[outIndex + 1]);
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllBytes(path, read.EncodeToPNG());
Debug.Log($"[ScenarioPickerCapture] {path} ({differing} of {Width * Height} pixels differ from the background)");
}
}