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,121 @@
using System.Collections;
using System.IO;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
#if UNITY_EDITOR
using UnityEditor.SceneManagement;
#endif
using BumpCombat.Defense;
namespace BumpCombat.PlayModeTests
{
public sealed class Defense25DPreviewTests
{
private float previousTimeScale;
[UnitySetUp]
public IEnumerator SetNormalTimeScale()
{
previousTimeScale = Time.timeScale;
Time.timeScale = 1f;
yield return null;
}
[UnityTearDown]
public IEnumerator RestoreTimeScale()
{
Time.timeScale = previousTimeScale;
yield return null;
}
[UnityTest]
public IEnumerator FireTargetAuraAndMeteor_KeepGroundHeightLayersAndCaptureRuntimeFrames()
{
#if UNITY_EDITOR
EditorSceneManager.LoadSceneInPlayMode("Assets/_Project/Scenes/Defense25DPreview.unity", new LoadSceneParameters(LoadSceneMode.Single));
#else
SceneManager.LoadScene("Defense25DPreview");
#endif
yield return null;
var preview = Object.FindFirstObjectByType<Defense25DPreview>();
Assert.That(preview, Is.Not.Null);
Assert.That(preview.AuraBackFrames.Length, Is.EqualTo(6));
Assert.That(preview.AuraFrontFrames.Length, Is.EqualTo(6));
Assert.That(preview.BodyFrames.Length, Is.EqualTo(6));
Assert.That(preview.MeteorFlightFrames.Length, Is.EqualTo(4));
Assert.That(preview.MeteorImpactFrames.Length, Is.EqualTo(8));
Assert.That(preview.Body.sprite.name, Does.StartWith("T3-Idle_"));
Assert.That(preview.Body.transform.parent.GetComponentsInChildren<SpriteRenderer>().Length, Is.EqualTo(1));
Assert.That(preview.AuraBack.sortingOrder, Is.LessThan(preview.Body.sortingOrder));
Assert.That(preview.Body.sortingOrder, Is.LessThan(preview.AuraFront.sortingOrder));
Assert.That(preview.AuraBack.transform.position, Is.EqualTo(preview.AuraFront.transform.position));
Assert.That(preview.AuraBackFrames[0].pivot / preview.AuraBackFrames[0].rect.size, Is.EqualTo(new Vector2(0.5f, 0.4f)));
Assert.That(Camera.main.orthographicSize, Is.EqualTo(2.8125f));
Assert.That(preview.AuraBack.transform.localScale, Is.EqualTo(preview.AuraFront.transform.localScale));
Assert.That(preview.BodyVisual.localPosition.y, Is.EqualTo(9f / 32f).Within(0.0001f));
Assert.That(Defense25DPreview.ProjectGroundAndHeight(new Vector2(3f, 4f), 2f), Is.EqualTo(new Vector3(1f, 6f, 0f)));
string output = FindOutputDirectory();
float deadline = Time.realtimeSinceStartup + 5f;
while (!preview.Meteor.enabled && Time.realtimeSinceStartup < deadline) yield return null;
Assert.That(preview.Meteor.enabled, Is.True, "Meteor did not start within five realtime seconds.");
Capture(Camera.main, Path.Combine(output, "runtime-01-meteor-early.png"));
Vector3 earlyViewport = Camera.main.WorldToViewportPoint(preview.Meteor.transform.position);
Assert.That(earlyViewport.y, Is.InRange(0f, 1f), "Early meteor must stay within the camera frame.");
float initialHeight = preview.CurrentMeteorHeight;
deadline = Time.realtimeSinceStartup + 5f;
while (!(preview.CurrentMeteorHeight > 0f && preview.CurrentMeteorHeight <= initialHeight * 0.6f) &&
Time.realtimeSinceStartup < deadline) yield return null;
Assert.That(preview.CurrentMeteorHeight, Is.GreaterThan(0f).And.LessThanOrEqualTo(initialHeight * 0.6f),
"Midflight capture did not reach the height threshold within five realtime seconds.");
Capture(Camera.main, Path.Combine(output, "runtime-02-meteor-midflight.png"));
Assert.That(preview.Meteor.transform.position, Is.Not.EqualTo(preview.GroundAnchor.position));
Assert.That(preview.CurrentMeteorGround, Is.Not.EqualTo((Vector2)preview.GroundAnchor.position));
deadline = Time.realtimeSinceStartup + 5f;
while (!(preview.Meteor.enabled && preview.CurrentMeteorHeight <= 0f) &&
Time.realtimeSinceStartup < deadline) yield return null;
Assert.That(preview.Meteor.enabled && preview.CurrentMeteorHeight <= 0f, Is.True,
"Meteor did not reach ground height within five realtime seconds.");
Capture(Camera.main, Path.Combine(output, "runtime-03-meteor-impact.png"));
deadline = Time.realtimeSinceStartup + 5f;
while (preview.Meteor.enabled && Time.realtimeSinceStartup < deadline) yield return null;
Assert.That(preview.Meteor.enabled, Is.False, "Meteor impact sequence did not finish within five realtime seconds.");
Assert.That(preview.AuraBack.transform.position.x, Is.EqualTo(preview.GroundAnchor.position.x).Within(0.0001f));
}
private static void Capture(Camera camera, string path)
{
const int width = 1280, height = 720;
var texture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
var image = new Texture2D(width, height, TextureFormat.RGB24, false);
camera.targetTexture = texture;
camera.Render();
RenderTexture previous = RenderTexture.active;
RenderTexture.active = texture;
image.ReadPixels(new Rect(0, 0, width, height), 0, 0);
image.Apply();
File.WriteAllBytes(path, image.EncodeToPNG());
camera.targetTexture = null;
RenderTexture.active = previous;
Object.Destroy(texture);
Object.Destroy(image);
}
private static string FindOutputDirectory()
{
for (DirectoryInfo folder = new DirectoryInfo(Application.dataPath); folder != null; folder = folder.Parent)
{
string output = Path.Combine(folder.FullName, "output/tower-25d-v1");
if (Directory.Exists(output)) return output;
}
string projectOutput = Path.GetFullPath(Path.Combine(Application.dataPath, "../output/tower-25d-v1"));
Directory.CreateDirectory(projectOutput);
return projectOutput;
}
}
}