Separate defense build branches from enemy lanes
This commit is contained in:
@@ -40,12 +40,12 @@ MonoBehaviour:
|
|||||||
- {x: 9, y: 0}
|
- {x: 9, y: 0}
|
||||||
- {x: 9, y: 0}
|
- {x: 9, y: 0}
|
||||||
BuildPadPositions:
|
BuildPadPositions:
|
||||||
- {x: -3.25, y: 3.25}
|
- {x: -3.25, y: 4.9}
|
||||||
- {x: 3.25, y: -3.25}
|
- {x: 3.25, y: -4.9}
|
||||||
- {x: -3.25, y: -3.25}
|
- {x: -3.25, y: -4.9}
|
||||||
- {x: 3.25, y: 3.25}
|
- {x: 3.25, y: 4.9}
|
||||||
- {x: 0, y: 3.25}
|
- {x: 0, y: 4.9}
|
||||||
- {x: 0, y: -3.25}
|
- {x: 0, y: -4.9}
|
||||||
WaypointPositions:
|
WaypointPositions:
|
||||||
- {x: -3, y: 3.25}
|
- {x: -3, y: 3.25}
|
||||||
- {x: 3, y: 3.25}
|
- {x: 3, y: 3.25}
|
||||||
|
|||||||
@@ -43,14 +43,15 @@ namespace BumpCombat.Constants
|
|||||||
new(9f, 0f),
|
new(9f, 0f),
|
||||||
new(9f, 0f),
|
new(9f, 0f),
|
||||||
};
|
};
|
||||||
|
[Tooltip("Build-only branches sit outside the enemy travel lanes.")]
|
||||||
public Vector2[] BuildPadPositions =
|
public Vector2[] BuildPadPositions =
|
||||||
{
|
{
|
||||||
new(-3.25f, 3.25f),
|
new(-3.25f, 4.9f),
|
||||||
new(3.25f, -3.25f),
|
new(3.25f, -4.9f),
|
||||||
new(-3.25f, -3.25f),
|
new(-3.25f, -4.9f),
|
||||||
new(3.25f, 3.25f),
|
new(3.25f, 4.9f),
|
||||||
new(0f, 3.25f),
|
new(0f, 4.9f),
|
||||||
new(0f, -3.25f),
|
new(0f, -4.9f),
|
||||||
};
|
};
|
||||||
[Tooltip("Two ordered waypoints per lane, indexed by lane * 2 + waypoint.")]
|
[Tooltip("Two ordered waypoints per lane, indexed by lane * 2 + waypoint.")]
|
||||||
public Vector2[] WaypointPositions =
|
public Vector2[] WaypointPositions =
|
||||||
|
|||||||
@@ -964,6 +964,19 @@ namespace BumpCombat.Defense
|
|||||||
CreateLaneLine(lane, portal, tuning.GetWaypointPosition(lane, 0));
|
CreateLaneLine(lane, portal, tuning.GetWaypointPosition(lane, 0));
|
||||||
CreateLaneLine(lane, tuning.GetWaypointPosition(lane, 0), tuning.GetWaypointPosition(lane, 1));
|
CreateLaneLine(lane, tuning.GetWaypointPosition(lane, 0), tuning.GetWaypointPosition(lane, 1));
|
||||||
CreateLaneLine(lane, tuning.GetWaypointPosition(lane, 1), basePosition);
|
CreateLaneLine(lane, tuning.GetWaypointPosition(lane, 1), basePosition);
|
||||||
|
|
||||||
|
int[] padOrder = lane == 0
|
||||||
|
? new[] { 3, 4, 0 } : new[] { 1, 5, 2 };
|
||||||
|
Vector2 branchCorner = new(
|
||||||
|
basePosition.x, tuning.GetBuildPadPosition(padOrder[0]).y);
|
||||||
|
CreateLaneLine(lane, basePosition, branchCorner, true, 1);
|
||||||
|
Vector2 branchPoint = branchCorner;
|
||||||
|
for (int i = 0; i < padOrder.Length; i++)
|
||||||
|
{
|
||||||
|
Vector2 padPosition = tuning.GetBuildPadPosition(padOrder[i]);
|
||||||
|
CreateLaneLine(lane, branchPoint, padPosition, true, i + 2);
|
||||||
|
branchPoint = padPosition;
|
||||||
|
}
|
||||||
CreateShape(
|
CreateShape(
|
||||||
$"Portal {lane + 1}",
|
$"Portal {lane + 1}",
|
||||||
portal,
|
portal,
|
||||||
@@ -999,7 +1012,8 @@ namespace BumpCombat.Defense
|
|||||||
3);
|
3);
|
||||||
CreateWorldLabel(
|
CreateWorldLabel(
|
||||||
$"Build Pad {pad + 1} Label",
|
$"Build Pad {pad + 1} Label",
|
||||||
position + Vector2.down * 0.95f,
|
position + (position.y >= tuning.ArenaCenter.y
|
||||||
|
? Vector2.down : Vector2.up) * 0.95f,
|
||||||
"PAD " + (pad + 1));
|
"PAD " + (pad + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1060,21 +1074,29 @@ namespace BumpCombat.Defense
|
|||||||
+ upgrade;
|
+ upgrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateLaneLine(int lane, Vector2 from, Vector2 to)
|
private void CreateLaneLine(
|
||||||
|
int lane, Vector2 from, Vector2 to,
|
||||||
|
bool padBranch = false, int segment = 0)
|
||||||
{
|
{
|
||||||
GameObject lineObject = new($"Lane {lane + 1} Segment");
|
string name = padBranch
|
||||||
|
? $"Pad Branch {lane + 1} Segment {segment}"
|
||||||
|
: $"Lane {lane + 1} Segment";
|
||||||
|
GameObject lineObject = new(name);
|
||||||
lineObject.transform.SetParent(worldRoot.transform, false);
|
lineObject.transform.SetParent(worldRoot.transform, false);
|
||||||
LineRenderer line = lineObject.AddComponent<LineRenderer>();
|
LineRenderer line = lineObject.AddComponent<LineRenderer>();
|
||||||
line.useWorldSpace = true;
|
line.useWorldSpace = true;
|
||||||
line.positionCount = 2;
|
line.positionCount = 2;
|
||||||
line.SetPosition(0, from);
|
line.SetPosition(0, from);
|
||||||
line.SetPosition(1, to);
|
line.SetPosition(1, to);
|
||||||
line.startWidth = 0.08f;
|
line.startWidth = padBranch ? 0.12f : 0.08f;
|
||||||
line.endWidth = 0.08f;
|
line.endWidth = line.startWidth;
|
||||||
line.startColor = new Color32(108, 151, 177, 210);
|
Color32 color = padBranch
|
||||||
line.endColor = new Color32(108, 151, 177, 210);
|
? new Color32(91, 206, 160, 225)
|
||||||
|
: new Color32(108, 151, 177, 210);
|
||||||
|
line.startColor = color;
|
||||||
|
line.endColor = color;
|
||||||
line.material = new Material(Shader.Find("Sprites/Default"));
|
line.material = new Material(Shader.Find("Sprites/Default"));
|
||||||
line.sortingOrder = 1;
|
line.sortingOrder = padBranch ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GameObject CreateShape(
|
private GameObject CreateShape(
|
||||||
|
|||||||
@@ -151,6 +151,50 @@ namespace BumpCombat.PlayModeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[UnityTest]
|
||||||
|
public IEnumerator DefensePrototype_PadBranchesStaySeparateFromEnemyRoutes()
|
||||||
|
{
|
||||||
|
yield return StartDefenseRun();
|
||||||
|
DefensePrototypeController controller = DefensePrototypeController.Instance;
|
||||||
|
DefenseConstants tuning = controller.Tuning;
|
||||||
|
Vector2 home = tuning.GetBasePosition(0);
|
||||||
|
Assert.That(tuning.GetBasePosition(1), Is.EqualTo(home));
|
||||||
|
for (int pad = 0; pad < 6; pad++)
|
||||||
|
{
|
||||||
|
Vector2 position = tuning.GetBuildPadPosition(pad);
|
||||||
|
float distance = float.MaxValue;
|
||||||
|
for (int lane = 0; lane < 2; lane++)
|
||||||
|
{
|
||||||
|
Vector2 previous = tuning.GetPortalPosition(lane);
|
||||||
|
for (int waypoint = 0; waypoint < 3; waypoint++)
|
||||||
|
{
|
||||||
|
Vector2 next = waypoint < 2
|
||||||
|
? tuning.GetWaypointPosition(lane, waypoint)
|
||||||
|
: tuning.GetBasePosition(lane);
|
||||||
|
distance = Mathf.Min(distance,
|
||||||
|
DistanceToSegment(position, previous, next));
|
||||||
|
previous = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.That(distance, Is.GreaterThan(tuning.BuildInteractionRadius),
|
||||||
|
$"Pad {pad + 1} must not sit on an enemy route.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int lane = 0; lane < 2; lane++)
|
||||||
|
{
|
||||||
|
GameObject branch = GameObject.Find(
|
||||||
|
$"Pad Branch {lane + 1} Segment 1");
|
||||||
|
Assert.That(branch, Is.Not.Null,
|
||||||
|
"Each build branch must be visible separately from enemy routes.");
|
||||||
|
LineRenderer line = branch.GetComponent<LineRenderer>();
|
||||||
|
Assert.That(line, Is.Not.Null);
|
||||||
|
Assert.That((Vector2)line.GetPosition(0), Is.EqualTo(home));
|
||||||
|
float padY = tuning.GetBuildPadPosition(lane).y;
|
||||||
|
Assert.That((Vector2)line.GetPosition(1),
|
||||||
|
Is.EqualTo(new Vector2(home.x, padY)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator DefensePrototype_DefaultBuildsCanDamageTheirPortalLanes()
|
public IEnumerator DefensePrototype_DefaultBuildsCanDamageTheirPortalLanes()
|
||||||
{
|
{
|
||||||
@@ -637,7 +681,7 @@ namespace BumpCombat.PlayModeTests
|
|||||||
Assert.That(controller.CanOperateTowers, Is.True);
|
Assert.That(controller.CanOperateTowers, Is.True);
|
||||||
Assert.That(playerHealth.CurrentHealth,
|
Assert.That(playerHealth.CurrentHealth,
|
||||||
Is.EqualTo(playerHealth.MaxHealth));
|
Is.EqualTo(playerHealth.MaxHealth));
|
||||||
MovePlayerNear(player, Vector2.zero);
|
MovePlayerNear(player, new Vector2(10f, -5f));
|
||||||
|
|
||||||
SaveDefenseConstants(controller.Tuning);
|
SaveDefenseConstants(controller.Tuning);
|
||||||
touchedDefenseConstants.PortalSpawnInterval = 0.01f;
|
touchedDefenseConstants.PortalSpawnInterval = 0.01f;
|
||||||
@@ -653,6 +697,7 @@ namespace BumpCombat.PlayModeTests
|
|||||||
yield return DriveArrivalsUntilBaseDestroyed(controller);
|
yield return DriveArrivalsUntilBaseDestroyed(controller);
|
||||||
Assert.That(controller.GetBaseHealth(0), Is.EqualTo(0));
|
Assert.That(controller.GetBaseHealth(0), Is.EqualTo(0));
|
||||||
Assert.That(RunManager.Instance.IsGameOver, Is.True);
|
Assert.That(RunManager.Instance.IsGameOver, Is.True);
|
||||||
|
SetTestField(playerHealth, "invulnerableUntil", Time.time);
|
||||||
|
|
||||||
Assert.That(playerHealth.TryTakeDamage(
|
Assert.That(playerHealth.TryTakeDamage(
|
||||||
playerHealth.MaxHealth + 1f, Vector2.left, 0f), Is.True);
|
playerHealth.MaxHealth + 1f, Vector2.left, 0f), Is.True);
|
||||||
@@ -1986,6 +2031,17 @@ namespace BumpCombat.PlayModeTests
|
|||||||
Assert.That(runManager.IsSelectionOpen, Is.False);
|
Assert.That(runManager.IsSelectionOpen, Is.False);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
private static float DistanceToSegment(
|
||||||
|
Vector2 point, Vector2 from, Vector2 to)
|
||||||
|
{
|
||||||
|
Vector2 segment = to - from;
|
||||||
|
float fraction = segment.sqrMagnitude > 0f
|
||||||
|
? Mathf.Clamp01(Vector2.Dot(point - from, segment)
|
||||||
|
/ segment.sqrMagnitude)
|
||||||
|
: 0f;
|
||||||
|
return Vector2.Distance(point, from + segment * fraction);
|
||||||
|
}
|
||||||
|
|
||||||
private static DefenseTower BuildTower(
|
private static DefenseTower BuildTower(
|
||||||
DefensePrototypeController controller,
|
DefensePrototypeController controller,
|
||||||
PlayerController player,
|
PlayerController player,
|
||||||
@@ -2159,7 +2215,10 @@ namespace BumpCombat.PlayModeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(agent.HasArrived, Is.True,
|
Assert.That(agent.HasArrived, Is.True,
|
||||||
"A public route agent must reach the shared base when driven through its route.");
|
$"A public route agent must reach the shared base when driven through its route. "
|
||||||
|
+ $"Lane={agent.Lane}, position={agent.transform.position}, "
|
||||||
|
+ $"state={agent.Enemy?.State}, dead={agent.Enemy?.IsDead}, "
|
||||||
|
+ $"stunned={agent.Enemy?.IsStunned}, speed={agent.Enemy?.MovementSpeedMultiplier}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerator DriveArrivalsUntilBaseDestroyed(
|
private static IEnumerator DriveArrivalsUntilBaseDestroyed(
|
||||||
@@ -2175,6 +2234,7 @@ namespace BumpCombat.PlayModeTests
|
|||||||
{
|
{
|
||||||
if (agents[i] != null && !agents[i].HasArrived)
|
if (agents[i] != null && !agents[i].HasArrived)
|
||||||
{
|
{
|
||||||
|
yield return WaitForAgentReady(agents[i]);
|
||||||
DriveAgentToArrival(agents[i]);
|
DriveAgentToArrival(agents[i]);
|
||||||
if (controller.GetBaseHealth(0) <= 0)
|
if (controller.GetBaseHealth(0) <= 0)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user