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(); 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().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; } } }