Seige tower
This commit is contained in:
parent
430e66bfbf
commit
4f57499e40
18 changed files with 898 additions and 92 deletions
190
Assets/_Project/Scripts/Editor/Towers/TowerIconGenerator.cs
Normal file
190
Assets/_Project/Scripts/Editor/Towers/TowerIconGenerator.cs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de83f68d5958449185d2a0c8199c41ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Add table
Add a link
Reference in a new issue