using System.Reflection; using System.Linq; using BumpCombat.Constants; using BumpCombat.Defense; using BumpCombat.Defense.Comparison; using BumpCombat.Enemies; using NUnit.Framework; using UnityEditor; using UnityEngine; namespace BumpCombat.Tests { public sealed class Defense25DTowerBodyAnimationPlayModeTests { private Defense25DComparison world; private Defense25DComparisonSettings settings; private EnemyDefinition highHealthEnemy; [SetUp] public void SetUp() { settings = Object.Instantiate(AssetDatabase.LoadAssetAtPath( "Assets/_Project/Constants/Defense25DComparison/ComparisonSettings.asset")); EnemyDefinition sourceEnemy = settings.Enemy; highHealthEnemy = Object.Instantiate(sourceEnemy); highHealthEnemy.name = sourceEnemy.name + " Tower Body Animation Test"; highHealthEnemy.Configure(sourceEnemy.Kind, sourceEnemy.AttackShape, 10000f, sourceEnemy.MoveSpeed, sourceEnemy.AttackDamage, sourceEnemy.WarningDuration, sourceEnemy.ActiveDuration, sourceEnemy.RecoveryDuration, sourceEnemy.AttackRange, sourceEnemy.PreferredRange, sourceEnemy.AttackLength, sourceEnemy.AttackWidth, sourceEnemy.AttackRadius, sourceEnemy.ExperienceValue, sourceEnemy.AttackAnimationLeadTime); highHealthEnemy.ConfigureRole(sourceEnemy.Role); highHealthEnemy.ConfigureAttackPatterns(sourceEnemy.AttackPatterns); settings.Enemy = highHealthEnemy; world = new GameObject("2.5D tower body animation test").AddComponent(); world.Initialize(settings); world.AutoSpawn = false; world.ReadInput = false; } [TearDown] public void TearDown() { if (world != null) Object.DestroyImmediate(world.gameObject); if (highHealthEnemy != null) Object.DestroyImmediate(highHealthEnemy); if (settings != null) Object.DestroyImmediate(settings); } [Test] public void SuccessfulAttackAdvancesAuthoredFramesThenReturnsToIdle() { Defense25DTower tower = BuildTower(DefenseTowerKind.Direct, 0); Defense25DEnemy target = SpawnTarget(tower); Sprite[] attack = DefenseTowerArt.Load(DefenseTowerArt.BodyKey(tower.Kind, tower.Tier, "Attack01")); Sprite[] idle = DefenseTowerArt.Load(DefenseTowerArt.BodyKey(tower.Kind, tower.Tier, "Idle")); SpriteRenderer body = tower.transform.Find("Tower billboard").GetComponent(); tower.Step(0f); Assert.That(tower.IsBodyAttacking, Is.True); Assert.That(body.sprite, Is.EqualTo(attack[0])); tower.Step(.125f); Assert.That(tower.BodyAnimationFrame, Is.EqualTo(1)); Assert.That(body.sprite, Is.EqualTo(attack[1])); target.Actor.ResetAt(tower.Position + Vector2.right * (GameplayConstants.Current.Defense.TowerRange + 2f)); Physics.SyncTransforms(); tower.Step(.625f); Assert.That(tower.IsBodyAttacking, Is.False); Assert.That(tower.BodyAnimationFrame, Is.EqualTo(0)); Assert.That(body.sprite, Is.EqualTo(idle[0])); Assert.That(target.IsAvailable, Is.True); } [Test] public void FailedShotRestoresThePriorBodyAnimationState() { Defense25DTower tower = BuildTower(DefenseTowerKind.Lightning, 0); tower.Step(.125f); SpriteRenderer body = tower.transform.Find("Tower billboard").GetComponent(); Sprite previousSprite = body.sprite; bool previousFlip = body.flipX; int previousFrame = tower.BodyAnimationFrame; bool previousAttacking = tower.IsBodyAttacking; Defense25DEnemy target = SpawnTarget(tower); target.gameObject.layer = 0; Physics.SyncTransforms(); tower.StepParityAttacks(0f); Assert.That(tower.BodyAnimationFrame, Is.EqualTo(previousFrame)); Assert.That(tower.IsBodyAttacking, Is.EqualTo(previousAttacking)); Assert.That(body.flipX, Is.EqualTo(previousFlip)); Assert.That(body.sprite, Is.EqualTo(previousSprite)); } [Test] public void ProjectileLaunchMatchesRenderedStaffTipThroughPitchedCamera() { Defense25DTower tower = BuildTower(DefenseTowerKind.Direct, 0); SpawnTarget(tower); tower.Step(0f); SpriteRenderer body = tower.transform.Find("Tower billboard").GetComponent(); Camera camera = CreatePitchedCamera(body.transform.position); Vector3 staffTip = tower.ParityStaffTipVisualWorldPosition; GameObject projectile = GameObject.Find("Tower Projectile"); Assert.That(projectile, Is.Not.Null); Vector3 projectedStaffTip = camera.WorldToScreenPoint(staffTip); Vector3 projectedLaunch = camera.WorldToScreenPoint(projectile.transform.position); Assert.That(projectedStaffTip.z, Is.GreaterThan(0f)); Assert.That(projectedLaunch.z, Is.GreaterThan(0f)); Assert.That(Vector2.Distance(projectedStaffTip, projectedLaunch), Is.LessThan(.01f), "The projectile's rendered launch point must coincide with the active frame's staff tip."); } [Test] public void SupportAuraUsesMaskFrameAndTracksBillboardTransform() { Defense25DTower support = BuildTower(DefenseTowerKind.Empowering, 0); Defense25DTower receiver = BuildTower(DefenseTowerKind.Direct, 4); Assert.That(Vector2.Distance(support.Position, receiver.Position), Is.LessThanOrEqualTo(GameplayConstants.Current.Defense.SupportRadius)); world.TowerManager.Step(0f); SpriteRenderer body = receiver.transform.Find("Tower billboard").GetComponent(); SpriteRenderer aura = receiver.transform.Find("Support Aura Art").GetComponent(); Assert.That(receiver.BodySupportAuraTier, Is.EqualTo(support.Tier)); Assert.That(aura.enabled, Is.True); Assert.That(aura.sprite.name, Is.EqualTo(body.sprite.name)); Assert.That(aura.flipX, Is.EqualTo(body.flipX)); Assert.That(Vector3.Distance(aura.transform.position, body.transform.position), Is.LessThan(.001f)); Assert.That(Vector3.Distance(aura.transform.localScale, body.transform.localScale), Is.LessThan(.001f)); } private Defense25DTower BuildTower(DefenseTowerKind kind, int pad) { world.Player.ResetAt(GameplayConstants.Current.Defense.GetBuildPadPosition(pad)); world.TowerManager.AddCurrency(1000); Assert.That(world.TowerManager.TryBuildTowerAtPad(pad, kind), Is.True, $"pad={pad} kind={kind}"); Defense25DTower tower = world.TowerManager.GetTowerAtPad(pad); tower.SendMessage("LateUpdate", SendMessageOptions.DontRequireReceiver); return tower; } private Defense25DEnemy SpawnTarget(Defense25DTower tower) { Defense25DEnemy target = Enumerable.Range(0, GameplayConstants.Current.Defense.PortalEnemyCount * 2) .Select(_ => world.SpawnNextEnemy()) .First(enemy => enemy != null && !world.IsTowerImmuneToEnemy(tower.Kind, enemy)); target.Actor.ResetAt(tower.Position + Vector2.right); target.ApplyStun(30f); Physics.SyncTransforms(); return target; } private Camera CreatePitchedCamera(Vector3 target) { GameObject cameraObject = new("Staff tip launch projection camera"); Camera camera = cameraObject.AddComponent(); camera.orthographic = true; camera.transform.rotation = Quaternion.Euler(settings.CameraPitch, 0f, 0f); camera.transform.position = target - camera.transform.forward * 10f; camera.transform.SetParent(world.transform, true); return camera; } } }