169 lines
9.2 KiB
C#
169 lines
9.2 KiB
C#
using System.IO;
|
|
using BumpCombat.Defense;
|
|
using BumpCombat.Defense.Comparison;
|
|
using NUnit.Framework;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.TestTools;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace BumpCombat.Tests
|
|
{
|
|
public sealed class Defense25DBodyColliderPlayModeTests
|
|
{
|
|
private const string ScenePath = "Assets/_Project/Scenes/Defense25DComparison.unity";
|
|
private const float Ppu = 32f;
|
|
private Scene ownedTestScene;
|
|
|
|
[UnityTest]
|
|
public System.Collections.IEnumerator BodyCoreCapsulesMatchFixedFamilyScreenBoundsAndCenters()
|
|
{
|
|
Time.timeScale = 1f;
|
|
yield return EditorSceneManager.LoadSceneAsyncInPlayMode(ScenePath,
|
|
new LoadSceneParameters(LoadSceneMode.Single));
|
|
ownedTestScene = SceneManager.GetActiveScene();
|
|
Defense25DComparison world = Object.FindAnyObjectByType<Defense25DComparison>();
|
|
Assert.That(world, Is.Not.Null);
|
|
yield return null;
|
|
world.AutoSpawn = false;
|
|
world.ReadInput = false;
|
|
world.ShowBoundaries = true;
|
|
world.SetPaused(true);
|
|
|
|
Vector2 origin = new(0f, -2f);
|
|
world.Player.ResetAt(origin + Vector2.left * 4f);
|
|
world.Enemies[0].Spawn(origin + Vector2.left * 2f);
|
|
Defense25DActor slime = Summon(world, "Body Slime", origin, 1, DefenseTowerBranch.First);
|
|
Defense25DActor giant = Summon(world, "Body Giant", origin + Vector2.right * 2f, 4, DefenseTowerBranch.First);
|
|
Defense25DActor lava = Summon(world, "Body Lava", origin + Vector2.right * 4f, 4, DefenseTowerBranch.Second);
|
|
lava.Facing = Vector2.left;
|
|
Physics.SyncTransforms();
|
|
yield return null;
|
|
yield return null;
|
|
|
|
Defense25DActor[] actors = { world.Player, world.Enemies[0].Actor, slime, giant, lava };
|
|
string output = System.Environment.GetEnvironmentVariable("DEFENSE25D_OUTPUT");
|
|
if (string.IsNullOrWhiteSpace(output)) output = Path.Combine(Path.GetTempPath(), "Defense25DValidation", "captures");
|
|
Directory.CreateDirectory(output);
|
|
string path = Path.Combine(output, "body-collider-overlay.png");
|
|
world.View.Camera.Render();
|
|
RenderTexture.active = world.View.Texture;
|
|
Texture2D image = new(world.View.Texture.width, world.View.Texture.height, TextureFormat.RGBA32, false);
|
|
image.ReadPixels(new Rect(0, 0, image.width, image.height), 0, 0);
|
|
image.Apply();
|
|
File.WriteAllBytes(path, image.EncodeToPNG());
|
|
Object.DestroyImmediate(image);
|
|
RenderTexture.active = null;
|
|
|
|
// Player/Skeleton retain their existing 2D-collider-derived baseline.
|
|
AssertShape(world.Player, .275f, .85f);
|
|
AssertShape(world.Enemies[0].Actor, .275f, .85f);
|
|
// Fixed inscribed body cores from the shadowless Idle audit, 32 PPU, actual variant render scale.
|
|
AssertShape(slime, .1890625f, .378125f); // 11px inner body core * 1.1 / 32
|
|
AssertShape(giant, .5328125f, 1.065625f); // 22px inner body core * 1.55 / 32
|
|
AssertShape(lava, .23359375f, .4671875f); // 13px inner body core * 1.15 / 32
|
|
AssertSkeletonCenter(world, world.Enemies[0].Actor);
|
|
AssertBodyCenter(world, slime, -1.5f, -1.5f);
|
|
AssertBodyCenter(world, giant, -3f, 17f);
|
|
AssertBodyCenter(world, lava, -2f, -.5f);
|
|
AssertCapsuleScreenCore(world, slime, 11f);
|
|
AssertCapsuleScreenCore(world, giant, 22f);
|
|
AssertCapsuleScreenCore(world, lava, 13f);
|
|
|
|
Defense25DActor probe = Summon(world, "Body Slime contact probe",
|
|
origin + Vector2.right * .37f, 1, DefenseTowerBranch.First);
|
|
Physics.SyncTransforms();
|
|
Assert.That(slime.GapTo(probe), Is.EqualTo(-.008125f).Within(.002f));
|
|
Assert.That(Physics.ComputePenetration(slime.BodyCollider, slime.transform.position,
|
|
slime.transform.rotation, probe.BodyCollider, probe.transform.position,
|
|
probe.transform.rotation, out _, out _), Is.True,
|
|
"The actual capsule bodies overlap at the tested body-core contact distance.");
|
|
probe.ResetAt(origin + Vector2.right * .4f);
|
|
Physics.SyncTransforms();
|
|
Assert.That(slime.GapTo(probe), Is.EqualTo(.021875f).Within(.002f));
|
|
Assert.That(Physics.ComputePenetration(slime.BodyCollider, slime.transform.position,
|
|
slime.transform.rotation, probe.BodyCollider, probe.transform.position,
|
|
probe.transform.rotation, out _, out _), Is.False,
|
|
"The actual capsule bodies miss beyond the tested body-core boundary.");
|
|
Assert.That(File.Exists(path), Is.True);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTearDown]
|
|
public System.Collections.IEnumerator UnloadOwnedTestScene()
|
|
{
|
|
Time.timeScale = 1f;
|
|
if (ownedTestScene.IsValid() && ownedTestScene.isLoaded)
|
|
{
|
|
Scene cleanup = SceneManager.CreateScene("BodyColliderTestCleanup");
|
|
SceneManager.SetActiveScene(cleanup);
|
|
AsyncOperation unload = SceneManager.UnloadSceneAsync(ownedTestScene);
|
|
if (unload != null) yield return unload;
|
|
}
|
|
ownedTestScene = default;
|
|
}
|
|
|
|
private static void AssertShape(Defense25DActor actor, float radius, float height)
|
|
{
|
|
Assert.That(actor.Radius, Is.EqualTo(radius).Within(.0001f), actor.name + " combat radius");
|
|
Assert.That(actor.BodyCollider.radius, Is.EqualTo(radius).Within(.0001f), actor.name + " capsule radius");
|
|
Assert.That(actor.BodyCollider.height, Is.EqualTo(height).Within(.0001f), actor.name + " capsule height");
|
|
}
|
|
|
|
private static void AssertBodyCenter(Defense25DComparison world, Defense25DActor actor,
|
|
float centerXFromPivotPixels, float centerUpFromPivotPixels)
|
|
{
|
|
float scale = actor.Visual.VisualScale;
|
|
Vector3 anchor = world.View.Camera.WorldToScreenPoint(actor.Visual.FootPosition);
|
|
Vector3 center = world.View.Camera.WorldToScreenPoint(
|
|
actor.BodyCollider.transform.TransformPoint(actor.BodyCollider.center));
|
|
float pixelsPerUnit = world.View.Camera.pixelHeight / (2f * world.View.Camera.orthographicSize);
|
|
float footCorrectionPixels = -actor.Visual.FootVisualOffset * Ppu / scale;
|
|
float facingSign = actor.Facing.x < 0f ? -1f : 1f;
|
|
float expectedX = centerXFromPivotPixels * facingSign * scale / Ppu * pixelsPerUnit;
|
|
float expectedY = (centerUpFromPivotPixels + footCorrectionPixels) * scale / Ppu * pixelsPerUnit;
|
|
Assert.That(center.x - anchor.x, Is.EqualTo(expectedX).Within(1.5f), actor.name + " projected body center X");
|
|
Assert.That(center.y - anchor.y, Is.EqualTo(expectedY).Within(1.5f), actor.name + " projected body center Y");
|
|
}
|
|
|
|
private static void AssertSkeletonCenter(Defense25DComparison world, Defense25DActor skeleton)
|
|
{
|
|
Camera camera = world.View.Camera;
|
|
Vector3 anchor = camera.WorldToScreenPoint(skeleton.Visual.FootPosition);
|
|
Vector3 center = camera.WorldToScreenPoint(
|
|
skeleton.BodyCollider.transform.TransformPoint(skeleton.BodyCollider.center));
|
|
float pixelsPerUnit = camera.pixelHeight / (2f * camera.orthographicSize);
|
|
float currentCenterHeight = Vector3.Dot(camera.transform.up,
|
|
Vector3.up * (skeleton.BodyCollider.height * .5f)) * pixelsPerUnit;
|
|
Assert.That(center.y - anchor.y, Is.EqualTo(currentCenterHeight + 3f).Within(1f),
|
|
"Skeleton keeps its 2D radius and raises the fixed 2.5D capsule center by the reviewed three internal pixels.");
|
|
}
|
|
|
|
private static void AssertCapsuleScreenCore(Defense25DComparison world,
|
|
Defense25DActor actor, float auditedCoreDiameterPixels)
|
|
{
|
|
float pixelsPerUnit = world.View.Camera.pixelHeight / (2f * world.View.Camera.orthographicSize);
|
|
float actualDiameterPixels = actor.BodyCollider.radius * 2f * pixelsPerUnit;
|
|
float expectedDiameterPixels = auditedCoreDiameterPixels * actor.Visual.VisualScale
|
|
/ Ppu * pixelsPerUnit;
|
|
Assert.That(actualDiameterPixels, Is.EqualTo(expectedDiameterPixels).Within(.5f),
|
|
actor.name + " projected capsule diameter must fit inside the literal audited body core height.");
|
|
}
|
|
|
|
private static Defense25DActor Summon(Defense25DComparison world, string name,
|
|
Vector2 position, int tier, DefenseTowerBranch branch)
|
|
{
|
|
GameObject root = new(name);
|
|
root.transform.SetParent(world.transform, false);
|
|
root.transform.position = Defense25DActor.OnGround(position, world.Settings.GroundY);
|
|
Defense25DActor actor = root.AddComponent<Defense25DActor>();
|
|
actor.Configure(world, ComparisonActorKind.Summon);
|
|
actor.Visual.ConfigureSummonArt(tier, branch);
|
|
actor.ResetAt(position);
|
|
return actor;
|
|
}
|
|
}
|
|
}
|
|
|