489 lines
22 KiB
C#
489 lines
22 KiB
C#
using System.Collections.Generic;
|
|
using BumpCombat.Core;
|
|
using BumpCombat.Enemies;
|
|
using BumpCombat.Player;
|
|
using BumpCombat.Spawning;
|
|
using BumpCombat.Stage;
|
|
using NUnit.Framework;
|
|
using UnityEngine;
|
|
|
|
namespace BumpCombat.Tests
|
|
{
|
|
public sealed class LevelPresetRuntimeTests
|
|
{
|
|
private GameObject stageManagerObject;
|
|
private StageManager stageManager;
|
|
private GameObject spawnDirectorObject;
|
|
private SpawnDirector spawnDirector;
|
|
private GameObject playerObject;
|
|
private PlayerController player;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
stageManagerObject = new GameObject("StageManager_RuntimeTest");
|
|
stageManager = stageManagerObject.AddComponent<StageManager>();
|
|
|
|
spawnDirectorObject = new GameObject("SpawnDirector_RuntimeTest");
|
|
spawnDirector = spawnDirectorObject.AddComponent<SpawnDirector>();
|
|
|
|
playerObject = new GameObject("Player_RuntimeTest");
|
|
player = playerObject.AddComponent<PlayerController>();
|
|
|
|
// Setup mock enemy prefabs on SpawnDirector via reflection
|
|
SetupMockEnemyPrefabs();
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
if (stageManagerObject != null) Object.DestroyImmediate(stageManagerObject);
|
|
if (spawnDirectorObject != null) Object.DestroyImmediate(spawnDirectorObject);
|
|
if (playerObject != null) Object.DestroyImmediate(playerObject);
|
|
|
|
ArenaBounds.Instance?.ResetToDefaultBounds();
|
|
}
|
|
|
|
private void SetupMockEnemyPrefabs()
|
|
{
|
|
#if UNITY_EDITOR
|
|
EnemyController batPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<EnemyController>("Assets/_Project/Prefabs/Enemies/Bat.prefab");
|
|
EnemyController skelPrefab = UnityEditor.AssetDatabase.LoadAssetAtPath<EnemyController>("Assets/_Project/Prefabs/Enemies/Skeleton.prefab");
|
|
if (batPrefab != null && skelPrefab != null)
|
|
{
|
|
var field = typeof(SpawnDirector).GetField("enemyPrefabs", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
field?.SetValue(spawnDirector, new EnemyController[] { skelPrefab, batPrefab });
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private ZoneInstance CreateZoneInstanceWithS1A(int nodeId, Vector2 worldPos)
|
|
{
|
|
return CreateZoneInstanceWithPreset("GRAVE_S1_COMBAT_A", nodeId, worldPos);
|
|
}
|
|
|
|
private ZoneInstance CreateZoneInstanceWithPreset(
|
|
string presetId,
|
|
int nodeId,
|
|
Vector2 worldPos,
|
|
bool connectEast = true)
|
|
{
|
|
StageNode node = new StageNode
|
|
{
|
|
Id = nodeId,
|
|
NodeType = StageNodeType.Combat,
|
|
PresetId = presetId,
|
|
GridPosition = Vector2Int.zero,
|
|
};
|
|
node.SetConnection(StageDirection.North, 2);
|
|
node.SetConnection(StageDirection.South, 3);
|
|
node.SetConnection(StageDirection.West, 4);
|
|
if (connectEast)
|
|
{
|
|
node.SetConnection(StageDirection.East, 5);
|
|
}
|
|
|
|
GameObject zoneObj = new GameObject($"Zone_{nodeId}_Combat");
|
|
zoneObj.transform.position = worldPos;
|
|
zoneObj.transform.SetParent(stageManager.transform);
|
|
|
|
ZoneInstance zone = zoneObj.AddComponent<ZoneInstance>();
|
|
zone.Initialize(node, new Vector2(9f, 6f));
|
|
|
|
// Call private BuildZoneGeometry via reflection
|
|
var method = typeof(StageManager).GetMethod("BuildZoneGeometry",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
method?.Invoke(stageManager, new object[] { zoneObj, zone, node });
|
|
|
|
return zone;
|
|
}
|
|
|
|
[Test]
|
|
public void ZoneConnector_ExistingBarrierObjectWithoutCollider_AddsBoxCollider2D()
|
|
{
|
|
GameObject connectorObject = new GameObject("ZoneConnector_RuntimeTest");
|
|
connectorObject.transform.SetParent(stageManager.transform);
|
|
connectorObject.AddComponent<BoxCollider2D>();
|
|
|
|
GameObject barrierObject = new GameObject("BarrierCollider");
|
|
barrierObject.transform.SetParent(connectorObject.transform, false);
|
|
|
|
connectorObject.AddComponent<ZoneConnector>();
|
|
|
|
Assert.IsNotNull(barrierObject.GetComponent<BoxCollider2D>());
|
|
}
|
|
|
|
[Test]
|
|
public void S1A_Geometry_EmptyCellsBlocked_And_ObstaclesPlacedCorrectly()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithS1A(10, Vector2.zero);
|
|
Assert.IsNotNull(zone);
|
|
|
|
// S1-A (18x12 = 216 cells). Count empty and obstacle objects
|
|
int emptyColliderCount = 0;
|
|
int obstacleColliderCount = 0;
|
|
|
|
for (int i = 0; i < zone.transform.childCount; i++)
|
|
{
|
|
Transform child = zone.transform.GetChild(i);
|
|
if (child.name.StartsWith("EmptyCell_")) emptyColliderCount++;
|
|
if (child.name.StartsWith("Obstacle_")) obstacleColliderCount++;
|
|
}
|
|
|
|
// Approved S1-A layout uses five broken cover cells.
|
|
Assert.AreEqual(5, obstacleColliderCount, "S1-A must have exactly 5 obstacle objects");
|
|
|
|
// Approved S1-A obstacle cells.
|
|
Assert.IsNotNull(zone.transform.Find("Obstacle_6_3"));
|
|
Assert.IsNotNull(zone.transform.Find("Obstacle_6_4"));
|
|
Assert.IsNotNull(zone.transform.Find("Obstacle_12_4"));
|
|
Assert.IsNotNull(zone.transform.Find("Obstacle_10_6"));
|
|
Assert.IsNotNull(zone.transform.Find("Obstacle_10_7"));
|
|
|
|
// Empty cells must have colliders to block entry
|
|
Assert.Greater(emptyColliderCount, 50, "Empty cells must have blocking colliders");
|
|
Assert.IsNotNull(zone.transform.Find("EmptyCell_0_0"), "Corner (0,0) must be blocked");
|
|
Assert.IsNotNull(zone.transform.Find("EmptyCell_17_0"), "Corner (17,0) must be blocked");
|
|
Assert.IsNotNull(zone.transform.Find("EmptyCell_0_11"), "Corner (0,11) must be blocked");
|
|
Assert.IsNotNull(zone.transform.Find("EmptyCell_17_11"), "Corner (17,11) must be blocked");
|
|
}
|
|
|
|
[Test]
|
|
public void S1AExpanded_Geometry_UsesOneUnitCellsAndOneCellDoors()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithPreset(
|
|
LevelPresetCatalog.S1AComparisonExpandedPresetId,
|
|
11,
|
|
Vector2.zero);
|
|
Assert.IsNotNull(zone);
|
|
Assert.AreEqual(36, zone.Preset.GridWidth);
|
|
Assert.AreEqual(24, zone.Preset.GridHeight);
|
|
|
|
int obstacleCount = 0;
|
|
for (int i = 0; i < zone.transform.childCount; i++)
|
|
{
|
|
Transform child = zone.transform.GetChild(i);
|
|
if (!child.name.StartsWith("Obstacle_"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
obstacleCount++;
|
|
BoxCollider2D collider = child.GetComponent<BoxCollider2D>();
|
|
Assert.IsNotNull(collider);
|
|
Assert.AreEqual(new Vector2(1f, 1f), collider.size);
|
|
}
|
|
|
|
Assert.AreEqual(8, obstacleCount);
|
|
foreach (StageDirection direction in System.Enum.GetValues(typeof(StageDirection)))
|
|
{
|
|
ZoneConnector connector = zone.GetConnector(direction);
|
|
Assert.IsNotNull(connector, $"Expanded S1-A must have {direction} connector");
|
|
BoxCollider2D trigger = connector.GetComponent<BoxCollider2D>();
|
|
Assert.AreEqual(new Vector2(1f, 1f), trigger.size,
|
|
$"Expanded S1-A {direction} door must remain one cell wide and one cell deep");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void S1A_PerimeterAndSockets_MatchPhysicalSocketCentersAndWidths()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithS1A(10, Vector2.zero);
|
|
Assert.IsNotNull(zone);
|
|
|
|
// W1 Socket: West, cell (0, 4), width 1.
|
|
// Physical outer opening: x = -9.0f, y = 1.5f. Trigger size: (1.5f, 1.0f)
|
|
ZoneConnector w1Connector = zone.GetConnector("W1");
|
|
Assert.IsNotNull(w1Connector, "W1 connector must exist");
|
|
Assert.AreEqual(StageDirection.West, w1Connector.Direction);
|
|
Assert.AreEqual(-9.0f, w1Connector.transform.localPosition.x, 0.01f);
|
|
Assert.AreEqual(1.5f, w1Connector.transform.localPosition.y, 0.01f);
|
|
BoxCollider2D w1Trigger = w1Connector.GetComponent<BoxCollider2D>();
|
|
Assert.AreEqual(1.0f, w1Trigger.size.y, 0.01f);
|
|
|
|
// E1 Socket: East, cell (17, 4), width 1.
|
|
// Physical outer opening: x = 9.0f, y = 1.5f. Trigger size: (1.5f, 1.0f)
|
|
ZoneConnector e1Connector = zone.GetConnector("E1");
|
|
Assert.IsNotNull(e1Connector, "E1 connector must exist");
|
|
Assert.AreEqual(StageDirection.East, e1Connector.Direction);
|
|
Assert.AreEqual(9.0f, e1Connector.transform.localPosition.x, 0.01f);
|
|
Assert.AreEqual(1.5f, e1Connector.transform.localPosition.y, 0.01f);
|
|
BoxCollider2D e1Trigger = e1Connector.GetComponent<BoxCollider2D>();
|
|
Assert.AreEqual(1.0f, e1Trigger.size.y, 0.01f);
|
|
|
|
// N1 Socket: North, cell (7, 0), width 1.
|
|
// Physical outer opening: x = -1.5f, y = 6.0f. Trigger size: (1.0f, 1.5f)
|
|
ZoneConnector n1Connector = zone.GetConnector("N1");
|
|
Assert.IsNotNull(n1Connector, "N1 connector must exist");
|
|
Assert.AreEqual(StageDirection.North, n1Connector.Direction);
|
|
Assert.AreEqual(-1.5f, n1Connector.transform.localPosition.x, 0.01f);
|
|
Assert.AreEqual(6.0f, n1Connector.transform.localPosition.y, 0.01f);
|
|
BoxCollider2D n1Trigger = n1Connector.GetComponent<BoxCollider2D>();
|
|
Assert.AreEqual(1.0f, n1Trigger.size.x, 0.01f);
|
|
|
|
// S1 Socket: South, cell (7, 11), width 1.
|
|
// Physical outer opening: x = -1.5f, y = -6.0f. Trigger size: (1.0f, 1.5f)
|
|
ZoneConnector s1Connector = zone.GetConnector("S1");
|
|
Assert.IsNotNull(s1Connector, "S1 connector must exist");
|
|
Assert.AreEqual(StageDirection.South, s1Connector.Direction);
|
|
Assert.AreEqual(-1.5f, s1Connector.transform.localPosition.x, 0.01f);
|
|
Assert.AreEqual(-6.0f, s1Connector.transform.localPosition.y, 0.01f);
|
|
BoxCollider2D s1Trigger = s1Connector.GetComponent<BoxCollider2D>();
|
|
Assert.AreEqual(1.0f, s1Trigger.size.x, 0.01f);
|
|
}
|
|
|
|
[Test]
|
|
public void S3D_InactiveEastSocket_CorridorBeyondDeclaredCellsIsClosed()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithPreset(
|
|
"GRAVE_S3_FINAL_NECROMANCER",
|
|
30,
|
|
Vector2.zero,
|
|
false);
|
|
|
|
RoomPreset preset = zone.Preset;
|
|
Assert.IsNotNull(preset);
|
|
Assert.AreEqual(PresetCellType.Empty, preset.GridMap[22, 7]);
|
|
Assert.AreEqual(PresetCellType.Empty, preset.GridMap[23, 7]);
|
|
|
|
for (int y = 7; y <= 9; y++)
|
|
{
|
|
Assert.AreEqual(PresetCellType.Empty, zone.GetRuntimeCell(21, y),
|
|
$"Inactive E1 declared cell (21,{y}) must be closed.");
|
|
Assert.AreEqual(PresetCellType.Empty, zone.GetRuntimeCell(22, y),
|
|
$"Inactive E1 outward cell (22,{y}) must be closed.");
|
|
Assert.AreEqual(PresetCellType.Empty, zone.GetRuntimeCell(23, y),
|
|
$"Inactive E1 outward cell (23,{y}) must be closed.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void S1A_EncounterA_SpawnsExactBat3AndSkeleton2_WithSeparation()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithS1A(10, Vector2.zero);
|
|
|
|
// Set player at W1 entry position
|
|
Vector2 entryPos = zone.GetEntryPosition(StageDirection.West);
|
|
player.transform.position = entryPos;
|
|
|
|
// Spawn encounter
|
|
spawnDirector.SpawnZoneEncounter(zone);
|
|
|
|
// Canonical S1-A Encounter A: Bat 3 + Skeleton 2 = 5 enemies total
|
|
Assert.AreEqual(5, zone.SpawnedEnemies.Count, "Canonical Encounter A must spawn exactly 5 enemies");
|
|
|
|
int batCount = 0;
|
|
int skeletonCount = 0;
|
|
foreach (var enemy in zone.SpawnedEnemies)
|
|
{
|
|
if (enemy.name.StartsWith("Bat")) batCount++;
|
|
if (enemy.name.StartsWith("Skeleton")) skeletonCount++;
|
|
}
|
|
Assert.AreEqual(3, batCount, "Canonical S1-A must have exactly 3 Bat enemies");
|
|
Assert.AreEqual(2, skeletonCount, "Must have exactly 2 Skeleton enemies");
|
|
|
|
// Verify no overlapping colliders (separation >= 0.8 units)
|
|
for (int i = 0; i < zone.SpawnedEnemies.Count; i++)
|
|
{
|
|
for (int j = i + 1; j < zone.SpawnedEnemies.Count; j++)
|
|
{
|
|
float dist = Vector2.Distance(zone.SpawnedEnemies[i].transform.position, zone.SpawnedEnemies[j].transform.position);
|
|
Assert.GreaterOrEqual(dist, 0.8f, $"Enemies {i} and {j} must not overlap (dist={dist})");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void S1AExpanded_EncounterA_DoublesCountsAndKeepsGroupsBounded()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithPreset(
|
|
LevelPresetCatalog.S1AComparisonExpandedPresetId,
|
|
12,
|
|
Vector2.zero);
|
|
player.transform.position = zone.GetEntryPosition(StageDirection.West);
|
|
|
|
spawnDirector.SpawnZoneEncounter(zone);
|
|
|
|
Assert.AreEqual(10, zone.SpawnedEnemies.Count,
|
|
"Expanded Encounter A must spawn Bat 6 + Skeleton 4");
|
|
int batCount = 0;
|
|
int skeletonCount = 0;
|
|
Dictionary<int, int> groupCounts = new();
|
|
foreach (EnemyController enemy in zone.SpawnedEnemies)
|
|
{
|
|
if (enemy.name.StartsWith("Bat")) batCount++;
|
|
if (enemy.name.StartsWith("Skeleton")) skeletonCount++;
|
|
if (!groupCounts.ContainsKey(enemy.EncounterGroupId))
|
|
{
|
|
groupCounts[enemy.EncounterGroupId] = 0;
|
|
}
|
|
groupCounts[enemy.EncounterGroupId]++;
|
|
}
|
|
|
|
Assert.AreEqual(6, batCount);
|
|
Assert.AreEqual(4, skeletonCount);
|
|
foreach (int groupCount in groupCounts.Values)
|
|
{
|
|
Assert.That(groupCount, Is.InRange(1, 3),
|
|
"Expanded encounters must retain the existing 1-3 member group rule");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void S1A_EntrySafeDistance_GuaranteedFromWestEntrySocket()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithS1A(10, Vector2.zero);
|
|
|
|
// Position player at W1 entry position (inward offset 2.5f from socket)
|
|
Vector2 playerEntryPos = zone.GetEntryPosition(StageDirection.West);
|
|
player.transform.position = playerEntryPos;
|
|
|
|
spawnDirector.SpawnZoneEncounter(zone);
|
|
|
|
// Verify safe distance for each enemy
|
|
foreach (var enemy in zone.SpawnedEnemies)
|
|
{
|
|
float dist = Vector2.Distance(playerEntryPos, enemy.transform.position);
|
|
if (enemy.name.StartsWith("Bat"))
|
|
{
|
|
// Fast / Crowd enemy requires >= 5.0 units
|
|
Assert.GreaterOrEqual(dist, 5.0f - 0.01f, $"Bat at {enemy.transform.position} must be >= 5.0 units from player {playerEntryPos} (dist={dist})");
|
|
}
|
|
else if (enemy.name.StartsWith("Skeleton"))
|
|
{
|
|
// Normal melee enemy requires >= 4.0 units
|
|
Assert.GreaterOrEqual(dist, 4.0f - 0.01f, $"Skeleton at {enemy.transform.position} must be >= 4.0 units from player {playerEntryPos} (dist={dist})");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void S1A_Breakthrough_AllowsSocketCrossingWithAliveEnemies_AndPreservesEnemies()
|
|
{
|
|
stageManager.StartRun(9999);
|
|
|
|
// Create S1-A combat zone and adjacent exit zone
|
|
ZoneInstance combatZone = CreateZoneInstanceWithS1A(100, Vector2.zero);
|
|
ZoneInstance nextZone = CreateZoneInstanceWithS1A(101, new Vector2(36f, 0f));
|
|
|
|
// Register zones in stageManager's zoneMap via reflection
|
|
var zoneMapField = typeof(StageManager).GetField("zoneMap",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
var zoneMap = (Dictionary<int, ZoneInstance>)zoneMapField.GetValue(stageManager);
|
|
zoneMap[100] = combatZone;
|
|
zoneMap[101] = nextZone;
|
|
|
|
// Set currentZone to combatZone
|
|
var currentZoneField = typeof(StageManager).GetField("currentZone",
|
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
currentZoneField.SetValue(stageManager, combatZone);
|
|
|
|
// Activate combat zone
|
|
combatZone.ActivateZone();
|
|
spawnDirector.SpawnZoneEncounter(combatZone);
|
|
|
|
// Canonical S1-A Encounter A has 5 enemies alive
|
|
Assert.AreEqual(5, combatZone.SpawnedEnemies.Count);
|
|
Assert.IsFalse(combatZone.IsCleared, "Combat zone is not cleared yet");
|
|
|
|
// Verify normal combat zone exit socket is NOT locked (Breakthrough allowed)
|
|
ZoneConnector e1Connector = combatZone.GetConnector("E1");
|
|
Assert.IsNotNull(e1Connector);
|
|
Assert.IsFalse(e1Connector.IsLocked, "Normal combat room exit must not be locked when enemies are alive");
|
|
|
|
// Player requests transition through E1 with enemies alive
|
|
stageManager.RequestZoneTransition(101, StageDirection.West);
|
|
|
|
// Current zone switched to nextZone
|
|
Assert.AreEqual(101, stageManager.CurrentZone.NodeId);
|
|
|
|
// Previous combat zone enemies must be preserved (still 5 alive, but deactivated)
|
|
Assert.AreEqual(5, combatZone.SpawnedEnemies.Count);
|
|
foreach (var enemy in combatZone.SpawnedEnemies)
|
|
{
|
|
Assert.IsNotNull(enemy);
|
|
Assert.IsFalse(enemy.gameObject.activeSelf, "Previous zone enemies must be deactivated while outside");
|
|
}
|
|
Assert.IsFalse(combatZone.IsCleared, "Bypassed room remains uncleared");
|
|
|
|
// Return to combat zone
|
|
stageManager.RequestZoneTransition(100, StageDirection.East);
|
|
Assert.AreEqual(100, stageManager.CurrentZone.NodeId);
|
|
|
|
// Enemies reactivated upon re-entry
|
|
foreach (var enemy in combatZone.SpawnedEnemies)
|
|
{
|
|
Assert.IsTrue(enemy.gameObject.activeSelf, "Enemies must be reactivated upon re-entering room");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void S1A_Visuals_LinesUsedForBoundariesAndObstacles_NoStretchedVisuals()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithS1A(20, Vector2.zero);
|
|
Assert.IsNotNull(zone);
|
|
|
|
// Verify perimeter walls use BoundaryLine with line thickness (not stretched GraveSlab)
|
|
bool foundBoundaryLine = false;
|
|
for (int i = 0; i < zone.transform.childCount; i++)
|
|
{
|
|
Transform child = zone.transform.GetChild(i);
|
|
if (child.name.StartsWith("Wall_"))
|
|
{
|
|
Transform boundaryLine = child.Find("BoundaryLine");
|
|
if (boundaryLine != null)
|
|
{
|
|
foundBoundaryLine = true;
|
|
// Either x or y must be thin line (0.08f)
|
|
Assert.IsTrue(Mathf.Approximately(boundaryLine.localScale.x, 0.08f) || Mathf.Approximately(boundaryLine.localScale.y, 0.08f),
|
|
"Boundary line must have 0.08f thickness");
|
|
}
|
|
}
|
|
}
|
|
Assert.IsTrue(foundBoundaryLine, "At least one wall must contain BoundaryLine");
|
|
|
|
// Verify obstacles use line segments (Fill / Line_N / Line_S / etc.) instead of stretched GraveSlab
|
|
bool foundObstacleLines = false;
|
|
for (int i = 0; i < zone.transform.childCount; i++)
|
|
{
|
|
Transform child = zone.transform.GetChild(i);
|
|
if (child.name.StartsWith("Obstacle_"))
|
|
{
|
|
Transform fill = child.Find("Fill");
|
|
Assert.IsNotNull(fill, "Obstacle must have semi-transparent fill");
|
|
foundObstacleLines = true;
|
|
}
|
|
}
|
|
Assert.IsTrue(foundObstacleLines, "Obstacles must be rendered with Fill and outline lines");
|
|
}
|
|
|
|
[Test]
|
|
public void S1A_Encounter_PrioritizesMeleeAnchors_BeforeCrowd()
|
|
{
|
|
ZoneInstance zone = CreateZoneInstanceWithS1A(21, Vector2.zero);
|
|
spawnDirector.SpawnZoneEncounter(zone);
|
|
|
|
Assert.AreEqual(5, zone.SpawnedEnemies.Count);
|
|
|
|
// Skeletons (Melee) should occupy authored NormalMelee anchors
|
|
RoomPreset preset = zone.Preset;
|
|
Vector2 m01Pos = preset.CellToWorldPosition(zone.ZoneCenter, new Vector2Int(4, 3));
|
|
Vector2 m02Pos = preset.CellToWorldPosition(zone.ZoneCenter, new Vector2Int(10, 2));
|
|
|
|
bool skeletonAtM01 = false;
|
|
bool skeletonAtM02 = false;
|
|
|
|
foreach (var enemy in zone.SpawnedEnemies)
|
|
{
|
|
if (enemy.name.StartsWith("Skeleton"))
|
|
{
|
|
if (Vector2.Distance(enemy.transform.position, m01Pos) < 0.1f) skeletonAtM01 = true;
|
|
if (Vector2.Distance(enemy.transform.position, m02Pos) < 0.1f) skeletonAtM02 = true;
|
|
}
|
|
}
|
|
|
|
Assert.IsTrue(skeletonAtM01 || skeletonAtM02, "At least one Skeleton must occupy a priority NormalMelee anchor (M01 or M02)");
|
|
}
|
|
}
|
|
}
|