Add fixed-camera tower effect preview scene

This commit is contained in:
2026-09-24 22:02:14 +09:00
parent 926b3f6c5d
commit 0aba40db2b
31 changed files with 2923 additions and 0 deletions
@@ -0,0 +1,159 @@
using System.Collections.Generic;
namespace BumpCombat.Defense
{
internal static class DefenseTowerArtDurations
{
private static readonly Dictionary<string, int[]> Values = new()
{
["Bodies/Fire/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T1-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T2-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T3-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/Base-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T4-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T1-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T2-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T3-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/Base-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Fire/T4-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T1-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T2-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T3-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/Base-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T4-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T1-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T2-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T3-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/Base-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Lightning/T4-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T1-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T2-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T3-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/Base-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T4-Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T1-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T2-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T3-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/Base-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Ice/T4-Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T1-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T2-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T3-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Control/Base-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T4-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T1-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T2-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T3-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/Base-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Control/T4-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T1-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T2-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T3-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Support/Base-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T4-Attack"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T1-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T2-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T3-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/Base-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Support/T4-Heal"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Summon/T1-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Summon/T2-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Summon/T3-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Summon/Base-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Bodies/Summon/T4-Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Effects/Fire/T1-Fireball"] = new[] { 90, 90, 90, 90 },
["Effects/Fire/T2-Fireball"] = new[] { 90, 90, 90, 90 },
["Effects/Fire/T3-Fireball"] = new[] { 90, 90, 90, 90 },
["Effects/Fire/Impact"] = new[] { 45, 55, 60, 50 },
["Effects/Fire/Meteor-Flight"] = new[] { 80, 80, 80, 80 },
["Effects/Fire/Meteor-Impact"] = new[] { 40, 50, 60, 70, 60, 60, 60, 80 },
["Sources/BlackKnight-NoSkull"] = new[] { 125, 125, 125, 125, 125, 125, 125 },
["Effects/Fire/Flamethrower"] = new[] { 80, 80, 80, 70, 65, 65, 70 },
["Effects/Lightning/T1-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 },
["Effects/Lightning/T2-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 },
["Effects/Lightning/T3-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 },
["Effects/Lightning/Arc"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 },
["Effects/Lightning/ImpactWave"] = new[] { 40, 40, 50, 50, 60, 60 },
["Effects/Lightning/LightningStorm"] = new[] { 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 },
["Effects/Ice/T1-Volley"] = new[] { 125 },
["Effects/Ice/T2-Volley"] = new[] { 125 },
["Effects/Ice/T3-Volley"] = new[] { 125 },
["Effects/Ice/Shard"] = new[] { 125 },
["Effects/Ice/Freezebolt"] = new[] { 85, 85, 85, 85 },
["Effects/Ice/Impact"] = new[] { 40, 50, 60, 70 },
["Effects/Ice/GlacialCage-Front"] = new[] { 65, 65, 90, 140, 140, 140, 70, 70 },
["Effects/Ice/GlacialCage-Back"] = new[] { 65, 65, 90, 140, 140, 140, 70, 70 },
["Effects/Control/T1-Aura"] = new[] { 140, 140, 140, 140, 140, 140 },
["Effects/Control/T2-Aura"] = new[] { 140, 140, 140, 140, 140, 140 },
["Effects/Control/T3-Aura"] = new[] { 140, 140, 140, 140, 140, 140 },
["Effects/Support/T1-Aura"] = new[] { 140, 140, 140, 140, 140, 140 },
["Effects/Support/T2-Aura"] = new[] { 140, 140, 140, 140, 140, 140 },
["Effects/Support/T3-Aura"] = new[] { 140, 140, 140, 140, 140, 140 },
["Effects/Control/StoneGaze"] = new[] { 90, 90, 90, 90, 90, 90, 90, 90 },
["Effects/Control/Temporal"] = new[] { 170, 170, 170, 170, 170, 170, 170, 170 },
["Effects/Support/Imbuing"] = new[] { 130, 130, 130, 130, 130, 130, 130, 130 },
["Effects/Support/Smothering"] = new[] { 150, 150, 150, 150, 150, 150, 150, 150 },
["Summons/Slime/Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/GiantSlime/Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/Slime/Walk"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/GiantSlime/Walk"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/Slime/Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/GiantSlime/Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/Slime/Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Summons/GiantSlime/Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Summons/Slime/Hurt"] = new[] { 125, 125, 125, 125 },
["Summons/GiantSlime/Hurt"] = new[] { 125, 125, 125, 125 },
["Summons/Slime/Death"] = new[] { 125, 125, 125, 125 },
["Summons/GiantSlime/Death"] = new[] { 125, 125, 125, 125 },
["Summons/LavaSlime/Idle"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/LavaSlime/Walk"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/LavaSlime/Attack01"] = new[] { 125, 125, 125, 125, 125, 125 },
["Summons/LavaSlime/Attack02"] = new[] { 125, 125, 125, 125, 125, 125, 125, 125, 125 },
["Summons/LavaSlime/Hurt"] = new[] { 125, 125, 125, 125 },
["Summons/LavaSlime/Death"] = new[] { 125, 125, 125, 125 },
["Summons/LavaSlime/GroundSpike-Segment"] = new[] { 60, 60, 60, 60, 60, 60, 60, 60, 60, 60 },
["Summons/LavaSlime/GroundSpike-Wave"] = new[] { 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60 },
};
public static ICollection<string> Keys => Values.Keys;
public static int FrameCount(string key)
{
return Values.TryGetValue(key, out int[] values) ? values.Length : 0;
}
public static float Seconds(string key, int frame)
{
if (!Values.TryGetValue(key, out int[] values) || values.Length == 0)
{
return 0.125f;
}
int safeFrame = frame < 0 ? 0 : frame >= values.Length ? values.Length - 1 : frame;
return values[safeFrame] / 1000f;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fe0c687a555c4bc291e278b2e7dabc04
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,123 @@
using System.Collections;
using UnityEngine;
namespace BumpCombat.Defense
{
public sealed class Defense25DPreview : MonoBehaviour
{
private const string BodyKey = "Bodies/Fire/T3-Idle";
private const string FlightKey = "Effects/Fire/Meteor-Flight";
private const string ImpactKey = "Effects/Fire/Meteor-Impact";
private const float AuraFrameSeconds = 0.14f;
[SerializeField] private SpriteRenderer body, auraBack, auraFront, meteor;
[SerializeField] private Sprite[] auraBackFrames, auraFrontFrames, meteorFlightFrames, meteorImpactFrames, bodyFrames;
[SerializeField] private Transform groundAnchor, bodyVisual;
[SerializeField] private float flightHeight = 1.7f;
public SpriteRenderer Body => body;
public SpriteRenderer AuraBack => auraBack;
public SpriteRenderer AuraFront => auraFront;
public SpriteRenderer Meteor => meteor;
public Sprite[] AuraBackFrames => auraBackFrames;
public Sprite[] AuraFrontFrames => auraFrontFrames;
public Sprite[] MeteorFlightFrames => meteorFlightFrames;
public Sprite[] MeteorImpactFrames => meteorImpactFrames;
public Sprite[] BodyFrames => bodyFrames;
public Transform GroundAnchor => groundAnchor;
public Transform BodyVisual => bodyVisual;
public float CurrentMeteorHeight { get; private set; }
public Vector2 CurrentMeteorGround { get; private set; }
public void Configure(Sprite[] back, Sprite[] front, Sprite[] flight, Sprite[] impact, Sprite[] character)
{
auraBackFrames = back; auraFrontFrames = front; meteorFlightFrames = flight;
meteorImpactFrames = impact; bodyFrames = character;
}
public static Vector3 ProjectGroundAndHeight(Vector2 groundPosition, float height) =>
new Vector3(groundPosition.x - height, groundPosition.y + height, 0f);
private IEnumerator Start()
{
StartCoroutine(AnimateAura());
StartCoroutine(AnimateBody());
StartCoroutine(MoveTarget());
while (true)
{
yield return new WaitForSeconds(0.25f);
yield return PlayMeteor();
yield return new WaitForSeconds(0.55f);
}
}
private IEnumerator MoveTarget()
{
while (true)
{
Vector3 p = groundAnchor.position;
p.x = Mathf.Sin(Time.time * 0.7f) * 0.65f;
groundAnchor.position = p;
yield return null;
}
}
private IEnumerator AnimateAura()
{
int frame = 0;
while (true)
{
auraBack.sprite = auraBackFrames[frame];
auraFront.sprite = auraFrontFrames[frame];
frame = (frame + 1) % auraBackFrames.Length;
yield return new WaitForSeconds(AuraFrameSeconds);
}
}
private IEnumerator AnimateBody()
{
int frame = 0;
while (true)
{
body.sprite = bodyFrames[frame];
frame = (frame + 1) % bodyFrames.Length;
yield return new WaitForSeconds(DefenseTowerArt.FrameSeconds(BodyKey, frame == 0 ? bodyFrames.Length - 1 : frame - 1));
}
}
private IEnumerator PlayMeteor()
{
CurrentMeteorGround = groundAnchor.position;
meteor.enabled = true;
CurrentMeteorHeight = flightHeight;
float flightDuration = 0f;
for (int i = 0; i < meteorFlightFrames.Length; i++) flightDuration += DefenseTowerArt.FrameSeconds(FlightKey, i);
float elapsed = 0f;
for (int i = 0; i < meteorFlightFrames.Length; i++)
{
meteor.sprite = meteorFlightFrames[i];
float frameSeconds = DefenseTowerArt.FrameSeconds(FlightKey, i);
float frameElapsed = 0f;
while (frameElapsed < frameSeconds)
{
CurrentMeteorHeight = flightHeight * (1f - elapsed / flightDuration);
meteor.transform.position = ProjectGroundAndHeight(CurrentMeteorGround, CurrentMeteorHeight);
float dt = Mathf.Min(Time.deltaTime, frameSeconds - frameElapsed);
frameElapsed += dt;
elapsed += dt;
yield return null;
}
}
CurrentMeteorHeight = 0f;
meteor.transform.position = ProjectGroundAndHeight(CurrentMeteorGround, 0f);
for (int i = 0; i < meteorImpactFrames.Length; i++)
{
meteor.sprite = meteorImpactFrames[i];
yield return new WaitForSeconds(DefenseTowerArt.FrameSeconds(ImpactKey, i));
}
meteor.enabled = false;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c00de25d48634556853f7a2df8445f18
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace BumpCombat.Defense
{
public static class DefenseTowerArt
{
private const string ResourceRoot = "DefenseTowers-v1/";
private static readonly Dictionary<string, Sprite[]> Frames = new();
public static ICollection<string> Keys => DefenseTowerArtDurations.Keys;
public static int ExpectedFrameCount(string key)
{
return DefenseTowerArtDurations.FrameCount(key);
}
public static Sprite[] Load(string key)
{
if (string.IsNullOrEmpty(key))
{
return Array.Empty<Sprite>();
}
if (!Frames.TryGetValue(key, out Sprite[] sprites))
{
sprites = Resources.LoadAll<Sprite>(ResourceRoot + key);
Array.Sort(sprites, CompareFrameNames);
Frames.Add(key, sprites);
}
return sprites;
}
public static float FrameSeconds(string key, int frame)
{
return DefenseTowerArtDurations.Seconds(key, frame);
}
public static string BodyKey(DefenseTowerKind kind, int tier, string motion)
{
string family = kind switch
{
DefenseTowerKind.Direct => "Fire",
DefenseTowerKind.Summon => "Summon",
DefenseTowerKind.Lightning => "Lightning",
DefenseTowerKind.Cold => "Ice",
DefenseTowerKind.Seismic => "Control",
_ => "Support",
};
return $"Bodies/{family}/T{tier}-{motion}";
}
private static int CompareFrameNames(Sprite left, Sprite right)
{
return FrameIndex(left.name).CompareTo(FrameIndex(right.name));
}
private static int FrameIndex(string name)
{
int separator = name.LastIndexOf('_');
return separator >= 0 && int.TryParse(name.Substring(separator + 1), out int index)
? index : 0;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 3608a711d7f74976b57e54ef7981c28a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,127 @@
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using BumpCombat.Defense;
namespace BumpCombat.Editor
{
public static class Defense25DPreviewSetup
{
private const string ScenePath = "Assets/_Project/Scenes/Defense25DPreview.unity";
private const string PreviewArt = "Assets/_Project/Art/Defense25DPreview";
private const string BackName = "Support-T3-Aura-Back";
private const string FrontName = "Support-T3-Aura-Front";
[MenuItem("BumpCombat/Defense 2.5D/Build and Open Preview Scene")]
public static void BuildAndOpen() {
if (!Application.isBatchMode && !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return;
ImportParentAuraArt();
AssetDatabase.Refresh();
Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
var cameraObject = new GameObject("Fixed Orthographic Camera");
var camera = cameraObject.AddComponent<Camera>();
camera.orthographic = true;
camera.orthographicSize = 2.8125f;
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(0.08f, 0.1f, 0.14f);
camera.transform.position = new Vector3(0f, 0.3f, -10f);
camera.tag = "MainCamera";
var anchor = new GameObject("Target Ground Anchor").transform;
var back = CreateSprite("Aura Back", anchor, 10);
back.transform.localScale = Vector3.one * 0.8f;
var bodyVisual = new GameObject("Character Visual (cell center above foot anchor)").transform;
bodyVisual.SetParent(anchor, false);
bodyVisual.localPosition = new Vector3(0f, 9f / 32f, 0f);
var body = CreateSprite("Fire T3 Character (effect target)", bodyVisual, 30);
Sprite[] bodyFrames = DefenseTowerArt.Load("Bodies/Fire/T3-Idle");
body.sprite = bodyFrames.FirstOrDefault();
var front = CreateSprite("Aura Front", anchor, 40);
front.transform.localScale = back.transform.localScale;
var meteor = CreateSprite("Meteor (ground position + height projection)", null, 50);
meteor.enabled = false;
var root = new GameObject("Defense 2.5D Preview");
var preview = root.AddComponent<Defense25DPreview>();
var so = new SerializedObject(preview);
so.FindProperty("flightHeight").floatValue = 1.7f;
so.FindProperty("body").objectReferenceValue = body;
so.FindProperty("auraBack").objectReferenceValue = back;
so.FindProperty("auraFront").objectReferenceValue = front;
so.FindProperty("meteor").objectReferenceValue = meteor;
so.FindProperty("groundAnchor").objectReferenceValue = anchor;
so.FindProperty("bodyVisual").objectReferenceValue = bodyVisual;
so.ApplyModifiedPropertiesWithoutUndo();
preview.Configure(LoadPreviewFrames(BackName), LoadPreviewFrames(FrontName),
DefenseTowerArt.Load("Effects/Fire/Meteor-Flight"), DefenseTowerArt.Load("Effects/Fire/Meteor-Impact"), bodyFrames);
EditorUtility.SetDirty(preview);
EditorSceneManager.SaveScene(scene, ScenePath);
Selection.activeGameObject = root;
EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath<SceneAsset>(ScenePath));
Debug.Log($"Defense 2.5D preview saved: {ScenePath}; aura={preview.AuraBackFrames.Length}/{preview.AuraFrontFrames.Length}; body=Fire T3 Idle.");
}
private static void ImportParentAuraArt()
{
string directory = Path.GetFullPath(Path.Combine(Application.dataPath, "_Project/Art/Defense25DPreview"));
Directory.CreateDirectory(directory);
string backTarget = Path.Combine(directory, BackName + ".png");
string frontTarget = Path.Combine(directory, FrontName + ".png");
string sourceRoot = File.Exists(backTarget) && File.Exists(frontTarget) ? null : FindParentOutputRoot();
ImportAura(sourceRoot == null ? null : Path.Combine(sourceRoot, BackName + ".png"), backTarget, $"{PreviewArt}/{BackName}.png");
ImportAura(sourceRoot == null ? null : Path.Combine(sourceRoot, FrontName + ".png"), frontTarget, $"{PreviewArt}/{FrontName}.png");
}
private static string FindParentOutputRoot()
{
for (DirectoryInfo folder = new DirectoryInfo(Application.dataPath); folder != null; folder = folder.Parent)
{
string candidate = Path.Combine(folder.FullName, "output/tower-25d-v1/assets");
if (File.Exists(Path.Combine(candidate, BackName + ".png")) && File.Exists(Path.Combine(candidate, FrontName + ".png"))) return candidate;
}
throw new FileNotFoundException("Could not find output/tower-25d-v1/assets with both parent aura sheets.");
}
private static void ImportAura(string source, string target, string assetPath)
{
if (!File.Exists(target)) File.Copy(source, target);
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
var importer = (TextureImporter)AssetImporter.GetAtPath(assetPath);
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Multiple;
importer.spritePixelsPerUnit = 32;
importer.filterMode = FilterMode.Point;
importer.mipmapEnabled = false;
importer.textureCompression = TextureImporterCompression.Uncompressed;
foreach (string platform in new[] { "DefaultTexturePlatform", "Standalone", "WebGL", "Android", "iPhone" })
{
TextureImporterPlatformSettings settings = importer.GetPlatformTextureSettings(platform);
settings.name = platform;
settings.overridden = true;
settings.textureCompression = TextureImporterCompression.Uncompressed;
importer.SetPlatformTextureSettings(settings);
}
var metas = new SpriteMetaData[6];
for (int i = 0; i < metas.Length; i++)
metas[i] = new SpriteMetaData { name = Path.GetFileNameWithoutExtension(target) + "_" + i,
rect = new Rect(i * 100, 0, 100, 100), alignment = (int)SpriteAlignment.Custom, pivot = new Vector2(0.5f, 0.4f) };
importer.spritesheet = metas;
importer.SaveAndReimport();
}
private static Sprite[] LoadPreviewFrames(string name) =>
AssetDatabase.LoadAllAssetsAtPath($"{PreviewArt}/{name}.png").OfType<Sprite>().OrderBy(s => s.name).ToArray();
private static SpriteRenderer CreateSprite(string name, Transform parent, int order)
{
var go = new GameObject(name);
if (parent != null) go.transform.SetParent(parent, false);
var renderer = go.AddComponent<SpriteRenderer>();
renderer.sortingOrder = order;
return renderer;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a3d92f18b0aa45df9a6f1d2a9fd12ac7