190 lines
7.4 KiB
C#
190 lines
7.4 KiB
C#
// Assets/_Project/Scripts/Editor/Towers/TowerIconGenerator.cs
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using TD.Towers;
|
|
|
|
namespace TD.Towers.Editor
|
|
{
|
|
/// <summary>
|
|
/// Editor utility that renders each <see cref="TowerDefinition"/>'s prefab to a square
|
|
/// thumbnail PNG and assigns it back to the definition's <c>Icon</c> field, so the HUD
|
|
/// build menu shows "how the building looks when built" on each tower tile.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Run via <b>TD > Generate Tower Icons</b>. Uses <see cref="PreviewRenderUtility"/>
|
|
/// (synchronous, controllable) rather than the async <c>AssetPreview</c> API so the whole
|
|
/// pass completes within the menu callback. Generated PNGs are written to
|
|
/// <c>Assets/_Project/Art/Sprites/Towers/</c>, imported as Sprites, and wired onto the
|
|
/// matching asset. Re-running regenerates and overwrites in place.
|
|
/// </remarks>
|
|
public static class TowerIconGenerator
|
|
{
|
|
private const string OutputFolder = "Assets/_Project/Art/Sprites/Towers";
|
|
private const int IconSize = 128;
|
|
|
|
[MenuItem("TD/Generate Tower Icons")]
|
|
public static void GenerateAll()
|
|
{
|
|
EnsureFolder(OutputFolder);
|
|
|
|
string[] guids = AssetDatabase.FindAssets("t:TowerDefinition");
|
|
if (guids.Length == 0)
|
|
{
|
|
Debug.LogWarning("[TowerIconGenerator] No TowerDefinition assets found.");
|
|
return;
|
|
}
|
|
|
|
int generated = 0;
|
|
try
|
|
{
|
|
foreach (string guid in guids)
|
|
{
|
|
string defPath = AssetDatabase.GUIDToAssetPath(guid);
|
|
var def = AssetDatabase.LoadAssetAtPath<TowerDefinition>(defPath);
|
|
if (def == null || def.TowerPrefab == null)
|
|
{
|
|
Debug.LogWarning($"[TowerIconGenerator] Skipping '{defPath}' — " +
|
|
"missing TowerDefinition or TowerPrefab.");
|
|
continue;
|
|
}
|
|
|
|
Texture2D icon = RenderPrefabIcon(def.TowerPrefab);
|
|
if (icon == null) continue;
|
|
|
|
string pngPath = $"{OutputFolder}/{def.name}.png";
|
|
File.WriteAllBytes(pngPath, icon.EncodeToPNG());
|
|
Object.DestroyImmediate(icon);
|
|
|
|
AssetDatabase.ImportAsset(pngPath, ImportAssetOptions.ForceSynchronousImport);
|
|
ConfigureAsSprite(pngPath);
|
|
|
|
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(pngPath);
|
|
AssignIcon(def, sprite);
|
|
generated++;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
}
|
|
|
|
Debug.Log($"[TowerIconGenerator] Generated {generated} tower icon(s) into {OutputFolder}.");
|
|
}
|
|
|
|
// ----- Rendering --------------------------------------------------
|
|
|
|
private static Texture2D RenderPrefabIcon(GameObject prefab)
|
|
{
|
|
var pru = new PreviewRenderUtility();
|
|
GameObject instance = null;
|
|
try
|
|
{
|
|
instance = Object.Instantiate(prefab);
|
|
instance.hideFlags = HideFlags.HideAndDontSave;
|
|
pru.AddSingleGO(instance);
|
|
|
|
if (!TryGetWorldBounds(instance, out Bounds bounds))
|
|
{
|
|
Debug.LogWarning($"[TowerIconGenerator] '{prefab.name}' has no renderers to capture.");
|
|
return null;
|
|
}
|
|
|
|
// 3/4 view framed to the prefab's combined bounds.
|
|
const float fov = 30f;
|
|
float radius = Mathf.Max(bounds.extents.magnitude, 0.01f);
|
|
float distance = radius / Mathf.Sin(Mathf.Deg2Rad * fov * 0.5f) * 1.15f;
|
|
Quaternion dir = Quaternion.Euler(20f, 135f, 0f);
|
|
|
|
var cam = pru.camera;
|
|
cam.fieldOfView = fov;
|
|
cam.transform.rotation = dir;
|
|
cam.transform.position = bounds.center - dir * Vector3.forward * distance;
|
|
cam.nearClipPlane = 0.01f;
|
|
cam.farClipPlane = distance * 4f;
|
|
cam.clearFlags = CameraClearFlags.SolidColor;
|
|
cam.backgroundColor = new Color(0f, 0f, 0f, 0f); // transparent
|
|
|
|
pru.lights[0].intensity = 1.2f;
|
|
pru.lights[0].transform.rotation = Quaternion.Euler(40f, 40f, 0f);
|
|
pru.lights[1].intensity = 0.6f;
|
|
pru.ambientColor = new Color(0.35f, 0.35f, 0.35f, 1f);
|
|
|
|
var rect = new Rect(0, 0, IconSize, IconSize);
|
|
pru.BeginPreview(rect, GUIStyle.none);
|
|
pru.camera.Render();
|
|
Texture rendered = pru.EndPreview();
|
|
|
|
var result = new Texture2D(IconSize, IconSize, TextureFormat.RGBA32, false);
|
|
RenderTexture prev = RenderTexture.active;
|
|
RenderTexture.active = rendered as RenderTexture;
|
|
result.ReadPixels(rect, 0, 0);
|
|
result.Apply();
|
|
RenderTexture.active = prev;
|
|
return result;
|
|
}
|
|
finally
|
|
{
|
|
if (instance != null) Object.DestroyImmediate(instance);
|
|
pru.Cleanup();
|
|
}
|
|
}
|
|
|
|
private static bool TryGetWorldBounds(GameObject go, out Bounds bounds)
|
|
{
|
|
var renderers = go.GetComponentsInChildren<Renderer>();
|
|
bounds = default;
|
|
bool any = false;
|
|
foreach (var r in renderers)
|
|
{
|
|
// Skip projector/decal-style renderers that don't represent the tower body.
|
|
if (r is MeshRenderer || r is SkinnedMeshRenderer)
|
|
{
|
|
if (!any) { bounds = r.bounds; any = true; }
|
|
else { bounds.Encapsulate(r.bounds); }
|
|
}
|
|
}
|
|
return any;
|
|
}
|
|
|
|
// ----- Asset wiring -----------------------------------------------
|
|
|
|
private static void ConfigureAsSprite(string pngPath)
|
|
{
|
|
var importer = AssetImporter.GetAtPath(pngPath) as TextureImporter;
|
|
if (importer == null) return;
|
|
|
|
importer.textureType = TextureImporterType.Sprite;
|
|
importer.spriteImportMode = SpriteImportMode.Single;
|
|
importer.alphaIsTransparency = true;
|
|
importer.mipmapEnabled = false;
|
|
importer.SaveAndReimport();
|
|
}
|
|
|
|
private static void AssignIcon(TowerDefinition def, Sprite sprite)
|
|
{
|
|
if (sprite == null) return;
|
|
var so = new SerializedObject(def);
|
|
so.FindProperty("Icon").objectReferenceValue = sprite;
|
|
so.ApplyModifiedPropertiesWithoutUndo();
|
|
EditorUtility.SetDirty(def);
|
|
}
|
|
|
|
private static void EnsureFolder(string assetFolder)
|
|
{
|
|
if (AssetDatabase.IsValidFolder(assetFolder)) return;
|
|
|
|
string[] parts = assetFolder.Split('/');
|
|
string current = parts[0]; // "Assets"
|
|
for (int i = 1; i < parts.Length; i++)
|
|
{
|
|
string next = $"{current}/{parts[i]}";
|
|
if (!AssetDatabase.IsValidFolder(next))
|
|
AssetDatabase.CreateFolder(current, parts[i]);
|
|
current = next;
|
|
}
|
|
}
|
|
}
|
|
}
|