509 lines
23 KiB
C#
509 lines
23 KiB
C#
using System.Collections;
|
|
using System.Linq;
|
|
using BumpCombat.Constants;
|
|
using BumpCombat.Defense;
|
|
using BumpCombat.Defense.Comparison;
|
|
using BumpCombat.Enemies;
|
|
using NUnit.Framework;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.TestTools;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace BumpCombat.PlayModeTests
|
|
{
|
|
public sealed class Defense25DTowerConsumerPlayModeTests
|
|
{
|
|
private const string SettingsPath =
|
|
"Assets/_Project/Constants/Defense25DComparison/ComparisonSettings.asset";
|
|
|
|
private Defense25DComparisonSettings settings;
|
|
private Defense25DComparison world;
|
|
private Defense25DRewardConstants rewardConstants;
|
|
private Defense25DTowerModifiers modifiers;
|
|
private DefenseTowerKind[] previousPortalImmunities;
|
|
private EnemyDefinition sourceEnemy;
|
|
private float previousTimeScale;
|
|
|
|
[UnitySetUp]
|
|
public IEnumerator SetUp()
|
|
{
|
|
previousTimeScale = Time.timeScale;
|
|
Time.timeScale = 1f;
|
|
DefenseConstants tuning = GameplayConstants.Current.Defense;
|
|
previousPortalImmunities = tuning.PortalImmuneTowerKinds;
|
|
tuning.PortalImmuneTowerKinds = Enumerable.Repeat(
|
|
DefenseTowerKind.Empowering,
|
|
previousPortalImmunities != null && previousPortalImmunities.Length > 0
|
|
? previousPortalImmunities.Length
|
|
: 6).ToArray();
|
|
|
|
settings = Object.Instantiate(
|
|
AssetDatabase.LoadAssetAtPath<Defense25DComparisonSettings>(SettingsPath));
|
|
Assert.That(settings, Is.Not.Null);
|
|
settings.Enemy = AssetDatabase.LoadAssetAtPath<EnemyDefinition>(
|
|
"Assets/_Project/Constants/Enemies/Slime.asset");
|
|
settings.EnemyArt = ReadCharacter("Slime", "Slime", "Attack");
|
|
settings.EnemyRadius = .24f;
|
|
settings.EnemyColliderOffset = new Vector2(0f, -.28f);
|
|
settings.EnemyVisualScale = 1f;
|
|
settings.EnemyFootOffset = -.4f;
|
|
settings.EnemyBodyCenterCorrectionPixels = 0f;
|
|
world = new GameObject("Tower consumer regression world")
|
|
.AddComponent<Defense25DComparison>();
|
|
world.Initialize(settings);
|
|
world.AutoSpawn = false;
|
|
world.ReadInput = false;
|
|
world.Player.ResetAt(Vector2.zero);
|
|
|
|
sourceEnemy = settings.Enemy;
|
|
settings.Enemy = Object.Instantiate(sourceEnemy);
|
|
SerializedObject serializedEnemy = new(settings.Enemy);
|
|
serializedEnemy.FindProperty("maxHealth").floatValue = 1000f;
|
|
serializedEnemy.ApplyModifiedPropertiesWithoutUndo();
|
|
|
|
rewardConstants = ScriptableObject.CreateInstance<Defense25DRewardConstants>();
|
|
modifiers = new Defense25DTowerModifiers(rewardConstants);
|
|
world.TowerManager.SetRunModifiers(modifiers);
|
|
yield return new WaitForFixedUpdate();
|
|
}
|
|
|
|
[UnityTearDown]
|
|
public IEnumerator TearDown()
|
|
{
|
|
if (world != null)
|
|
Object.Destroy(world.gameObject);
|
|
if (settings != null && settings.Enemy != sourceEnemy)
|
|
Object.Destroy(settings.Enemy);
|
|
if (settings != null)
|
|
Object.Destroy(settings);
|
|
if (rewardConstants != null)
|
|
Object.Destroy(rewardConstants);
|
|
if (previousPortalImmunities != null)
|
|
GameplayConstants.Current.Defense.PortalImmuneTowerKinds = previousPortalImmunities;
|
|
Time.timeScale = previousTimeScale;
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator LegacyComparisonKeepsSixConfiguredPads()
|
|
{
|
|
Assert.That(world.TowerManager.PadCount, Is.EqualTo(6));
|
|
Assert.That(world.TowerManager.GetPadPosition(5),
|
|
Is.EqualTo(GameplayConstants.Current.Defense.GetBuildPadPosition(5)));
|
|
Assert.Throws<System.ArgumentOutOfRangeException>(
|
|
() => world.TowerManager.GetPadPosition(6));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator DirectModifiersChangeRealDamageTargetCountAndProjectileVisuals()
|
|
{
|
|
AssertApply("fire.attack.common");
|
|
AssertApply("fire.targets.epic");
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Direct);
|
|
Defense25DEnemy first = SpawnTarget(tower.Position + Vector2.right * .8f);
|
|
Defense25DEnemy second = SpawnTarget(tower.Position + Vector2.right * 1.6f);
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.01f);
|
|
|
|
Assert.That(CountProjectileRoots(), Is.EqualTo(2));
|
|
Assert.That(HasVisibleProjectile(), Is.True);
|
|
tower.StepParityAttacks(.2f);
|
|
|
|
Assert.That(first.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(second.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(1000f - first.Actor.CurrentHealth,
|
|
Is.EqualTo(GameplayConstants.Current.Defense.DirectTowerDamage
|
|
* 1.1f * tower.SightEfficiency).Within(.01f));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator NewlyBuiltUpgradedTowerAppliesRunDamageBeforeOneOutsideSightCorrection()
|
|
{
|
|
AssertApply("fire.attack.common");
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Direct);
|
|
Assert.That(tower.TryUpgrade(DefenseTowerBranch.None), Is.True);
|
|
world.Player.ResetAt(tower.Position + Vector2.right
|
|
* (GameplayConstants.Current.Defense.PlayerSightRadius + 2f));
|
|
Assert.That(tower.SightEfficiency,
|
|
Is.EqualTo(GameplayConstants.Current.Defense.OutsideSightDamageEfficiency)
|
|
.Within(.001f));
|
|
Defense25DEnemy target = SpawnTarget(tower.Position + Vector2.right * .8f);
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.01f);
|
|
tower.StepParityAttacks(.2f);
|
|
|
|
float tierScale = 1f + GameplayConstants.Current.Defense.TowerDamageBonusPerTier;
|
|
float expectedDamage = GameplayConstants.Current.Defense.DirectTowerDamage
|
|
* tierScale * 1.1f
|
|
* GameplayConstants.Current.Defense.OutsideSightDamageEfficiency;
|
|
Assert.That(target.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(1000f - target.Actor.CurrentHealth,
|
|
Is.EqualTo(BumpCombat.Combat.DamageCalculator.FinalizeDamage(expectedDamage)),
|
|
"A newly built, upgraded tower applies run damage and the 70% sight rule once each.");
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator PlayerDeathPausesAnInFlightTowerAttackUntilRevival()
|
|
{
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Direct);
|
|
Defense25DEnemy target = SpawnTarget(tower.Position + Vector2.right * .8f);
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.01f);
|
|
Assert.That(CountProjectileRoots(), Is.EqualTo(1));
|
|
Assert.That(world.Player.TakeDamageWithResult(
|
|
100000f,
|
|
Vector2.left,
|
|
0f,
|
|
BumpCombat.Enemies.EnemyDamageSource.None,
|
|
ordinaryBump: false,
|
|
out _), Is.True);
|
|
Assert.That(world.TowersOperational, Is.False);
|
|
|
|
tower.StepParityAttacks(1f);
|
|
Assert.That(target.Actor.DamageEvents, Is.Zero,
|
|
"A tower flight must not deal damage while the player is dead.");
|
|
|
|
world.Player.ResetAt(tower.Position);
|
|
tower.StepParityAttacks(.2f);
|
|
|
|
Assert.That(target.Actor.DamageEvents, Is.EqualTo(1),
|
|
"The paused flight resumes once after the player is revived.");
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator DirectTierFourMeteorUsesRunDamageModifierOnce()
|
|
{
|
|
AssertApply("fire.attack.common");
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Direct);
|
|
Assert.That(tower.TryUpgrade(DefenseTowerBranch.None), Is.True);
|
|
Assert.That(tower.TryUpgrade(DefenseTowerBranch.None), Is.True);
|
|
Assert.That(tower.TryUpgrade(DefenseTowerBranch.First), Is.True);
|
|
Defense25DEnemy target = SpawnTarget(tower.Position + Vector2.right * .8f);
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.01f);
|
|
tower.StepParityAttacks(.32f);
|
|
|
|
float tierScale = 1f + GameplayConstants.Current.Defense.TowerDamageBonusPerTier * 3f;
|
|
float expectedDamage = GameplayConstants.Current.Defense.DirectTowerDamage
|
|
* tierScale * 1.6f * 1.1f * tower.SightEfficiency;
|
|
Assert.That(target.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(1000f - target.Actor.CurrentHealth,
|
|
Is.GreaterThan(expectedDamage - 2f),
|
|
"The run modifier applies after tier/meteor scaling without being multiplied twice.");
|
|
Assert.That(1000f - target.Actor.CurrentHealth,
|
|
Is.LessThan(expectedDamage + 1f));
|
|
Assert.That(HasVisibleTowerVisual(), Is.True);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator PlayerDeathStopsAStartedSummonAttack()
|
|
{
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Summon);
|
|
Defense25DEnemy target = SpawnTarget(tower.Position + Vector2.right * .2f);
|
|
tower.StepSummonPack(.01f);
|
|
Defense25DSummon[] pack = world.Summons
|
|
.Where(candidate => candidate.Owner == tower)
|
|
.OrderBy(candidate => candidate.SlotIndex)
|
|
.ToArray();
|
|
Assert.That(pack, Has.Length.EqualTo(GameplayConstants.Current.Defense.SummonUnitCount));
|
|
Defense25DSummon summon = pack[0];
|
|
summon.Step(.01f);
|
|
Assert.That(summon.IsAttacking, Is.True);
|
|
|
|
Assert.That(world.Player.TakeDamageWithResult(
|
|
100000f,
|
|
Vector2.left,
|
|
0f,
|
|
BumpCombat.Enemies.EnemyDamageSource.None,
|
|
ordinaryBump: false,
|
|
out _), Is.True);
|
|
summon.Step(.1f);
|
|
|
|
Assert.That(summon.IsAttacking, Is.False);
|
|
Assert.That(target.Actor.DamageEvents, Is.Zero,
|
|
"A summon cannot continue its attack after player death.");
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ColdAdditionalProjectileHitsDistinctTargetAndRendersBoth()
|
|
{
|
|
AssertApply("cold.projectile.epic");
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Cold);
|
|
Defense25DEnemy first = SpawnTarget(tower.Position + Vector2.right * .8f);
|
|
Defense25DEnemy second = SpawnTarget(tower.Position + Vector2.right * 1.6f);
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.01f);
|
|
|
|
Assert.That(CountProjectileRoots(), Is.EqualTo(2));
|
|
Assert.That(HasVisibleProjectile(), Is.True);
|
|
tower.StepParityAttacks(.2f);
|
|
tower.StepParityAttacks(.2f);
|
|
|
|
Assert.That(first.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(second.Actor.DamageEvents, Is.EqualTo(1));
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator LightningChainModifierHitsAndRendersAnExtraTarget()
|
|
{
|
|
AssertApply("lightning.chain.rare");
|
|
Assert.That(modifiers.GetEffectiveStats(new Defense25DTowerStatInput(
|
|
DefenseTowerKind.Lightning,
|
|
0f,
|
|
0f,
|
|
supportsChainTargets: true)).ChainCount, Is.EqualTo(1));
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Lightning);
|
|
Defense25DEnemy first = SpawnTarget(tower.Position + Vector2.right * .35f);
|
|
Defense25DEnemy alreadyInArea = SpawnTarget(tower.Position + Vector2.right * .65f);
|
|
float outsideBaseRadius = GameplayConstants.Current.Defense.LightningRadius + .35f;
|
|
Defense25DEnemy chained = SpawnTarget(
|
|
first.Actor.Position + Vector2.right * outsideBaseRadius);
|
|
Assert.That(world.IsTowerImmuneToEnemy(DefenseTowerKind.Lightning, chained), Is.False);
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.01f);
|
|
|
|
Assert.That(first.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(alreadyInArea.Actor.DamageEvents, Is.EqualTo(1),
|
|
"The chain must skip targets already hit by the initial area query.");
|
|
Assert.That(chained.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(CountTowerVisualRoots(), Is.GreaterThanOrEqualTo(2));
|
|
Assert.That(HasVisibleTowerVisual(), Is.True);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator SeismicAreaModifierExpandsActualHitAndVisibleMarker()
|
|
{
|
|
AssertApply("seismic.area.common");
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Seismic);
|
|
float baseRadius = GameplayConstants.Current.Defense.SeismicRadius;
|
|
Defense25DEnemy first = SpawnTarget(tower.Position + Vector2.right * .2f);
|
|
Defense25DEnemy edge = SpawnTarget(tower.Position
|
|
+ Vector2.right * (baseRadius * 1.05f));
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.01f);
|
|
|
|
Assert.That(first.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(edge.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(HasVisibleTowerVisual(), Is.True);
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ColdSplitHitsDistinctTargetsWithoutRecursiveSplitting()
|
|
{
|
|
AssertApply("cold.split.epic");
|
|
AssertApply("cold.return.epic");
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Cold);
|
|
Defense25DEnemy originalTarget = SpawnTarget(
|
|
tower.Position + Vector2.right * .8f);
|
|
tower.StepParityAttacks(.01f);
|
|
Vector2 projectileStart = Defense25DActor.Ground(
|
|
FindProjectileRoot().position);
|
|
Vector2 incomingDirection = (
|
|
originalTarget.Actor.Position - projectileStart).normalized;
|
|
Defense25DEnemy offAngleTarget = SpawnTarget(
|
|
originalTarget.Actor.Position + incomingDirection * .7f);
|
|
Defense25DEnemy[] splitTargets =
|
|
{
|
|
SpawnTarget(originalTarget.Actor.Position
|
|
+ Rotate(incomingDirection, 25f)),
|
|
SpawnTarget(originalTarget.Actor.Position
|
|
+ Rotate(incomingDirection, -25f)),
|
|
};
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.2f);
|
|
Assert.That(CountProjectileRoots(), Is.EqualTo(3),
|
|
"The original flight and its two fixed-angle child flights should exist.");
|
|
Assert.That(HasVisibleProjectile(), Is.True);
|
|
|
|
tower.StepParityAttacks(.02f);
|
|
float[] splitAngles = world.GetComponentsInChildren<Transform>(true)
|
|
.Where(transform => transform.name == "Tower Projectile")
|
|
.Select(transform => Defense25DActor.Ground(transform.position)
|
|
- originalTarget.Actor.Position)
|
|
.Where(offset => offset.sqrMagnitude > .01f)
|
|
.Select(offset => Vector2.SignedAngle(incomingDirection, offset.normalized))
|
|
.Where(angle => Mathf.Abs(angle) < 90f)
|
|
.OrderBy(angle => angle)
|
|
.ToArray();
|
|
Assert.That(splitAngles, Has.Length.EqualTo(2));
|
|
Assert.That(splitAngles[0], Is.EqualTo(-25f).Within(.5f));
|
|
Assert.That(splitAngles[1], Is.EqualTo(25f).Within(.5f));
|
|
|
|
tower.StepParityAttacks(.2f);
|
|
|
|
float originalDamage = GameplayConstants.Current.Defense.ColdDamage
|
|
* tower.SightEfficiency;
|
|
float expectedFullDamage = BumpCombat.Combat.DamageCalculator
|
|
.FinalizeDamage(originalDamage);
|
|
float expectedHalfDamage = BumpCombat.Combat.DamageCalculator
|
|
.FinalizeDamage(originalDamage * .5f);
|
|
Assert.That(originalTarget.Actor.DamageEvents, Is.EqualTo(2),
|
|
"The split source hits the original target outbound and once on its return.");
|
|
Assert.That(1000f - originalTarget.Actor.CurrentHealth,
|
|
Is.EqualTo(expectedFullDamage + expectedHalfDamage).Within(.001f));
|
|
Assert.That(offAngleTarget.Actor.DamageEvents, Is.Zero,
|
|
"Split children travel along the specified ±25° lanes, not toward the nearest off-angle target.");
|
|
foreach (Defense25DEnemy splitTarget in splitTargets)
|
|
{
|
|
Assert.That(splitTarget.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(1000f - splitTarget.Actor.CurrentHealth,
|
|
Is.EqualTo(expectedHalfDamage).Within(.001f));
|
|
}
|
|
yield return null;
|
|
Assert.That(CountProjectileRoots(), Is.Zero,
|
|
"Split children neither split again nor inherit the original flight's return effect.");
|
|
yield return null;
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ColdReturnDamagesAnInterveningTargetAndMovesVisualBackToOrigin()
|
|
{
|
|
AssertApply("cold.return.epic");
|
|
Defense25DTower tower = BuildTower(DefenseTowerKind.Cold);
|
|
Defense25DEnemy outboundTarget = SpawnTarget(
|
|
tower.Position + Vector2.right * 1.8f);
|
|
Physics.SyncTransforms();
|
|
|
|
tower.StepParityAttacks(.01f);
|
|
Defense25DEnemy returnTarget = SpawnTarget(
|
|
tower.Position + Vector2.right * 1.25f);
|
|
Physics.SyncTransforms();
|
|
tower.StepParityAttacks(.2f);
|
|
Assert.That(outboundTarget.Actor.DamageEvents, Is.EqualTo(1));
|
|
Transform projectile = FindProjectileRoot();
|
|
Assert.That(projectile, Is.Not.Null);
|
|
float beforeReturnStep = Vector2.Distance(
|
|
Defense25DActor.Ground(projectile.position), tower.Position);
|
|
|
|
float expectedFullDamage = BumpCombat.Combat.DamageCalculator
|
|
.FinalizeDamage(GameplayConstants.Current.Defense.ColdDamage
|
|
* tower.SightEfficiency);
|
|
float expectedReturnDamage = BumpCombat.Combat.DamageCalculator
|
|
.FinalizeDamage(GameplayConstants.Current.Defense.ColdDamage
|
|
* tower.SightEfficiency * .5f);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
tower.StepParityAttacks(.03f);
|
|
|
|
Assert.That(Vector2.Distance(Defense25DActor.Ground(projectile.position), tower.Position),
|
|
Is.LessThan(beforeReturnStep));
|
|
Assert.That(outboundTarget.Actor.DamageEvents, Is.EqualTo(2),
|
|
"Each target may be hit once outbound and once during the single return leg.");
|
|
Assert.That(1000f - outboundTarget.Actor.CurrentHealth,
|
|
Is.EqualTo(expectedFullDamage + expectedReturnDamage).Within(.001f));
|
|
Assert.That(returnTarget.Actor.DamageEvents, Is.EqualTo(1));
|
|
Assert.That(1000f - returnTarget.Actor.CurrentHealth,
|
|
Is.EqualTo(expectedReturnDamage).Within(.001f),
|
|
"The return path deals half damage and does not repeatedly hit a target across segments.");
|
|
Assert.That(HasVisibleProjectile(), Is.True);
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
tower.StepParityAttacks(.02f);
|
|
Assert.That(outboundTarget.Actor.DamageEvents, Is.EqualTo(2));
|
|
Assert.That(returnTarget.Actor.DamageEvents, Is.EqualTo(1));
|
|
yield return null;
|
|
}
|
|
|
|
private static Vector2 Rotate(Vector2 direction, float angleDegrees)
|
|
{
|
|
float angle = angleDegrees * Mathf.Deg2Rad;
|
|
float cosine = Mathf.Cos(angle);
|
|
float sine = Mathf.Sin(angle);
|
|
return new Vector2(
|
|
direction.x * cosine - direction.y * sine,
|
|
direction.x * sine + direction.y * cosine).normalized;
|
|
}
|
|
|
|
private Defense25DTower BuildTower(DefenseTowerKind kind)
|
|
{
|
|
Vector2 pad = GameplayConstants.Current.Defense.GetBuildPadPosition(0);
|
|
world.Player.ResetAt(pad);
|
|
Assert.That(world.TowerManager.TryBuildTowerAtPad(0, kind), Is.True);
|
|
return world.TowerManager.GetTowerAtPad(0);
|
|
}
|
|
|
|
private Defense25DEnemy SpawnTarget(Vector2 position)
|
|
{
|
|
Defense25DEnemy enemy = world.SpawnNextEnemy();
|
|
enemy.Actor.ResetAt(position);
|
|
return enemy;
|
|
}
|
|
|
|
private void AssertApply(string definitionId)
|
|
{
|
|
Defense25DTowerModifierDefinition definition =
|
|
rewardConstants.TowerModifierCatalog.Single(candidate =>
|
|
candidate.DefinitionId == definitionId);
|
|
Assert.That(modifiers.Apply(
|
|
Defense25DRewardChoice.CreateTowerModifier(definition)), Is.True);
|
|
}
|
|
|
|
private int CountProjectileRoots() => world.GetComponentsInChildren<Transform>(true)
|
|
.Count(transform => transform.name == "Tower Projectile");
|
|
|
|
private int CountTowerVisualRoots() => world.GetComponentsInChildren<Transform>(true)
|
|
.Count(transform => transform.name is "Tower Attack Effect" or "Lightning Attack Effect");
|
|
|
|
private Transform FindProjectileRoot() => world.GetComponentsInChildren<Transform>(true)
|
|
.FirstOrDefault(transform => transform.name == "Tower Projectile");
|
|
|
|
private bool HasVisibleProjectile()
|
|
{
|
|
foreach (Transform projectile in world.GetComponentsInChildren<Transform>(true)
|
|
.Where(transform => transform.name == "Tower Projectile"))
|
|
{
|
|
SpriteRenderer renderer = projectile.GetComponentInChildren<SpriteRenderer>(true);
|
|
if (renderer != null && renderer.enabled && renderer.sprite != null)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool HasVisibleTowerVisual() => world.GetComponentsInChildren<SpriteRenderer>(true)
|
|
.Any(renderer => renderer.enabled && renderer.sprite != null
|
|
&& renderer.transform.parent != null
|
|
&& renderer.transform.parent.name == "Tower Attack Effect");
|
|
|
|
private static ComparisonSpriteFrames ReadCharacter(
|
|
string folder,
|
|
string prefix,
|
|
string attack)
|
|
{
|
|
Sprite[] Read(string suffix) => AssetDatabase.LoadAllAssetsAtPath(
|
|
$"Assets/_Project/Art/Characters/{folder}/{prefix}_{suffix}.png")
|
|
.OfType<Sprite>()
|
|
.OrderBy(sprite => int.TryParse(
|
|
sprite.name[(sprite.name.LastIndexOf('_') + 1)..], out int frame)
|
|
? frame
|
|
: 0)
|
|
.ToArray();
|
|
return new ComparisonSpriteFrames
|
|
{
|
|
Idle = Read("Idle"),
|
|
Walk = Read("Walk"),
|
|
Attack = Read(attack),
|
|
Hurt = Read("Hurt"),
|
|
Death = Read("Death"),
|
|
};
|
|
}
|
|
}
|
|
}
|