Implement passive relics and six-family defense towers
This commit is contained in:
@@ -16,12 +16,14 @@ namespace BumpCombat.Defense
|
||||
public sealed class DefensePrototypeController : MonoBehaviour
|
||||
{
|
||||
private const int LaneCount = 2;
|
||||
private const int BuildPadCount = 6;
|
||||
|
||||
private static Sprite whiteSprite;
|
||||
|
||||
private readonly Dictionary<EnemyController, DefenseRouteAgent> activeAgents = new();
|
||||
private readonly DefenseTower[] towers = new DefenseTower[LaneCount];
|
||||
private readonly bool[] padsBuilt = new bool[LaneCount];
|
||||
private readonly DefenseTower[] towers = new DefenseTower[BuildPadCount];
|
||||
private readonly Dictionary<DefenseTower, float> boostedTowers = new();
|
||||
private readonly bool[] padsBuilt = new bool[BuildPadCount];
|
||||
private readonly int[] suppliedPerLane = new int[LaneCount];
|
||||
private readonly DefenseWaveLedger[] portalLedgers = new DefenseWaveLedger[LaneCount];
|
||||
private readonly bool[] portalSupplyEnded = new bool[LaneCount];
|
||||
@@ -200,16 +202,25 @@ namespace BumpCombat.Defense
|
||||
|
||||
if (Keyboard.current != null && CanOperateTowers)
|
||||
{
|
||||
if (Keyboard.current.digit1Key.wasPressedThisFrame
|
||||
|| Keyboard.current.numpad1Key.wasPressedThisFrame)
|
||||
{
|
||||
Keyboard keys = Keyboard.current;
|
||||
if (keys.digit1Key.wasPressedThisFrame || keys.numpad1Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Direct);
|
||||
}
|
||||
else if (Keyboard.current.digit2Key.wasPressedThisFrame
|
||||
|| Keyboard.current.numpad2Key.wasPressedThisFrame)
|
||||
{
|
||||
else if (keys.digit2Key.wasPressedThisFrame || keys.numpad2Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Summon);
|
||||
}
|
||||
else if (keys.digit3Key.wasPressedThisFrame || keys.numpad3Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Lightning);
|
||||
else if (keys.digit4Key.wasPressedThisFrame || keys.numpad4Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Cold);
|
||||
else if (keys.digit5Key.wasPressedThisFrame || keys.numpad5Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Seismic);
|
||||
else if (keys.digit6Key.wasPressedThisFrame || keys.numpad6Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Empowering);
|
||||
else if (keys.uKey.wasPressedThisFrame)
|
||||
TryUpgradeNearestTower(DefenseTowerBranch.None);
|
||||
else if (keys.jKey.wasPressedThisFrame)
|
||||
TryUpgradeNearestTower(DefenseTowerBranch.First);
|
||||
else if (keys.kKey.wasPressedThisFrame)
|
||||
TryUpgradeNearestTower(DefenseTowerBranch.Second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +252,7 @@ namespace BumpCombat.Defense
|
||||
failed = false;
|
||||
playerDead = false;
|
||||
constructionCurrency = tuning.InitialConstructionCurrency;
|
||||
boostedTowers.Clear();
|
||||
pendingPassiveRelicChoices = 0;
|
||||
baseHealth = tuning.BaseMaximumHealth;
|
||||
activeAgents.Clear();
|
||||
@@ -253,8 +265,12 @@ namespace BumpCombat.Defense
|
||||
portalRewardObjectCreated[lane] = false;
|
||||
portalRewardObjects[lane] = null;
|
||||
portalRewardLabels[lane] = null;
|
||||
padsBuilt[lane] = false;
|
||||
towers[lane] = null;
|
||||
}
|
||||
|
||||
for (int pad = 0; pad < BuildPadCount; pad++)
|
||||
{
|
||||
padsBuilt[pad] = false;
|
||||
towers[pad] = null;
|
||||
}
|
||||
|
||||
nextSupplyLane = 0;
|
||||
@@ -268,14 +284,15 @@ namespace BumpCombat.Defense
|
||||
{
|
||||
return TryBuildTowerAtPad(
|
||||
padIndex,
|
||||
padIndex == 0 ? DefenseTowerKind.Direct : DefenseTowerKind.Summon);
|
||||
Tuning.GetBuildPadPosition(padIndex).y >= Tuning.ArenaCenter.y
|
||||
? DefenseTowerKind.Summon : DefenseTowerKind.Direct);
|
||||
}
|
||||
|
||||
public bool TryBuildTowerAtPad(int padIndex, DefenseTowerKind kind)
|
||||
{
|
||||
if (!CanOperateTowers
|
||||
|| padIndex < 0
|
||||
|| padIndex >= LaneCount
|
||||
|| padIndex >= BuildPadCount
|
||||
|| padsBuilt[padIndex]
|
||||
|| playerController == null)
|
||||
{
|
||||
@@ -289,16 +306,17 @@ namespace BumpCombat.Defense
|
||||
return false;
|
||||
}
|
||||
|
||||
int cost = kind == DefenseTowerKind.Direct
|
||||
? tuning.DirectTowerCost
|
||||
: tuning.SummonTowerCost;
|
||||
int cost = GetBuildCost(kind);
|
||||
if (cost < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (constructionCurrency < cost)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject towerObject = new(
|
||||
kind == DefenseTowerKind.Direct ? "Direct Damage Tower" : "Summon Tower");
|
||||
GameObject towerObject = new($"{kind} Tower");
|
||||
towerObject.transform.SetParent(worldRoot.transform, false);
|
||||
towerObject.transform.position = position;
|
||||
DefenseTower tower = towerObject.AddComponent<DefenseTower>();
|
||||
@@ -309,6 +327,95 @@ namespace BumpCombat.Defense
|
||||
return true;
|
||||
}
|
||||
|
||||
private int GetBuildCost(DefenseTowerKind kind)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => tuning.DirectTowerCost,
|
||||
DefenseTowerKind.Summon => tuning.SummonTowerCost,
|
||||
DefenseTowerKind.Lightning => tuning.LightningTowerCost,
|
||||
DefenseTowerKind.Cold => tuning.ColdTowerCost,
|
||||
DefenseTowerKind.Seismic => tuning.SeismicTowerCost,
|
||||
DefenseTowerKind.Empowering => tuning.EmpoweringTowerCost,
|
||||
_ => -1,
|
||||
};
|
||||
}
|
||||
|
||||
public bool TryUpgradeTowerAtPad(int padIndex, DefenseTowerBranch branch)
|
||||
{
|
||||
if (!CanOperateTowers || playerController == null
|
||||
|| padIndex < 0 || padIndex >= BuildPadCount
|
||||
|| towers[padIndex] == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
DefenseTower tower = towers[padIndex];
|
||||
if (tower.Tier >= 4
|
||||
|| (tower.Tier < 3 && branch != DefenseTowerBranch.None)
|
||||
|| (tower.Tier == 3 && branch != DefenseTowerBranch.First
|
||||
&& branch != DefenseTowerBranch.Second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector2 position = tuning.GetBuildPadPosition(padIndex);
|
||||
if (((Vector2)playerController.transform.position - position).sqrMagnitude
|
||||
> tuning.BuildInteractionRadius * tuning.BuildInteractionRadius)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int cost = tower.Tier switch
|
||||
{
|
||||
1 => tuning.Tier2UpgradeCost,
|
||||
2 => tuning.Tier3UpgradeCost,
|
||||
_ => tuning.Tier4UpgradeCost,
|
||||
};
|
||||
if (constructionCurrency < cost || !tower.Upgrade(branch))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
constructionCurrency -= cost;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryUpgradeNearestTower(DefenseTowerBranch branch)
|
||||
{
|
||||
DefenseTower nearest = GetNearestBuiltTower();
|
||||
return nearest != null && TryUpgradeTowerAtPad(nearest.PadIndex, branch);
|
||||
}
|
||||
|
||||
private DefenseTower GetNearestBuiltTower()
|
||||
{
|
||||
if (playerController == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DefenseTower nearest = null;
|
||||
float nearestDistance = tuning.BuildInteractionRadius
|
||||
* tuning.BuildInteractionRadius;
|
||||
for (int pad = 0; pad < BuildPadCount; pad++)
|
||||
{
|
||||
if (towers[pad] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance = ((Vector2)playerController.transform.position
|
||||
- tuning.GetBuildPadPosition(pad)).sqrMagnitude;
|
||||
if (distance <= nearestDistance)
|
||||
{
|
||||
nearestDistance = distance;
|
||||
nearest = towers[pad];
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
private bool TryBuildNearestTower(DefenseTowerKind kind)
|
||||
{
|
||||
if (playerController == null)
|
||||
@@ -318,7 +425,7 @@ namespace BumpCombat.Defense
|
||||
|
||||
int nearestPad = -1;
|
||||
float nearestDistance = tuning.BuildInteractionRadius * tuning.BuildInteractionRadius;
|
||||
for (int pad = 0; pad < LaneCount; pad++)
|
||||
for (int pad = 0; pad < BuildPadCount; pad++)
|
||||
{
|
||||
if (padsBuilt[pad])
|
||||
{
|
||||
@@ -344,17 +451,14 @@ namespace BumpCombat.Defense
|
||||
return false;
|
||||
}
|
||||
|
||||
DefenseRouteAgent target = FindNearestTarget(
|
||||
tower.transform.position,
|
||||
tuning.TowerRange,
|
||||
tower.Kind);
|
||||
return tower.TryAttack(target);
|
||||
return tower.TryPulse();
|
||||
}
|
||||
|
||||
public DefenseRouteAgent FindNearestTarget(
|
||||
Vector2 sourcePosition,
|
||||
float range,
|
||||
DefenseTowerKind sourceKind)
|
||||
DefenseTowerKind sourceKind,
|
||||
bool ignoreTowerImmunity = false)
|
||||
{
|
||||
DefenseRouteAgent nearest = null;
|
||||
float nearestDistance = range * range;
|
||||
@@ -364,9 +468,10 @@ namespace BumpCombat.Defense
|
||||
|| agent.HasArrived
|
||||
|| agent.Enemy == null
|
||||
|| agent.Enemy.IsDead
|
||||
|| DefensePrototypeRules.IsTowerImmune(
|
||||
sourceKind,
|
||||
agent.ImmuneTowerKind))
|
||||
|| (!ignoreTowerImmunity
|
||||
&& DefensePrototypeRules.IsTowerImmune(
|
||||
sourceKind,
|
||||
agent.ImmuneTowerKind)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -382,6 +487,133 @@ namespace BumpCombat.Defense
|
||||
return nearest;
|
||||
}
|
||||
|
||||
public List<DefenseRouteAgent> GetTargetsInRange(Vector2 position, float radius)
|
||||
{
|
||||
List<DefenseRouteAgent> targets = new();
|
||||
float radiusSquared = radius * radius;
|
||||
foreach (DefenseRouteAgent agent in activeAgents.Values)
|
||||
{
|
||||
if (agent != null && !agent.HasArrived
|
||||
&& agent.Enemy != null && !agent.Enemy.IsDead
|
||||
&& ((Vector2)agent.transform.position - position).sqrMagnitude
|
||||
<= radiusSquared)
|
||||
{
|
||||
targets.Add(agent);
|
||||
}
|
||||
}
|
||||
|
||||
targets.Sort((a, b) =>
|
||||
((Vector2)a.transform.position - position).sqrMagnitude
|
||||
.CompareTo(((Vector2)b.transform.position - position).sqrMagnitude));
|
||||
return targets;
|
||||
}
|
||||
|
||||
public float GetTowerAttackIntervalMultiplier(DefenseTower tower)
|
||||
{
|
||||
if (!CanOperateTowers || tower == null)
|
||||
{
|
||||
return 1f;
|
||||
}
|
||||
|
||||
foreach (DefenseTower support in towers)
|
||||
{
|
||||
if (support != null && support != tower
|
||||
&& support.IsOperational
|
||||
&& support.Kind == DefenseTowerKind.Empowering
|
||||
&& support.Tier == 4
|
||||
&& support.Branch == DefenseTowerBranch.First
|
||||
&& Vector2.Distance(support.transform.position,
|
||||
tower.transform.position) <= tuning.SupportRadius)
|
||||
{
|
||||
float sightEfficiency = GetSupportSightEfficiency(support);
|
||||
return 1f - (1f - tuning.ImbuingAttackIntervalMultiplier)
|
||||
* sightEfficiency;
|
||||
}
|
||||
}
|
||||
|
||||
return 1f;
|
||||
}
|
||||
|
||||
private float GetSupportSightEfficiency(DefenseTower support)
|
||||
{
|
||||
return playerHealth == null
|
||||
? 0f
|
||||
: DefensePrototypeRules.GetTowerEfficiency(
|
||||
Vector2.Distance(playerHealth.transform.position,
|
||||
support.transform.position),
|
||||
tuning.PlayerSightRadius, 1f,
|
||||
tuning.OutsideSightDamageEfficiency);
|
||||
}
|
||||
|
||||
public bool TryApplyTowerSlow(
|
||||
DefenseTower tower, DefenseRouteAgent target,
|
||||
float duration, float fraction)
|
||||
{
|
||||
if (!CanOperateTowers || tower == null || target == null
|
||||
|| target.HasArrived || target.Enemy == null || playerHealth == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float efficiency = DefensePrototypeRules.GetTowerEfficiency(
|
||||
Vector2.Distance(playerHealth.transform.position, tower.transform.position),
|
||||
tuning.PlayerSightRadius, 1f, tuning.OutsideSightDamageEfficiency);
|
||||
return target.Enemy.ApplySlow(duration, fraction * efficiency);
|
||||
}
|
||||
|
||||
public bool TryApplyTowerStun(
|
||||
DefenseTower tower, DefenseRouteAgent target, float duration)
|
||||
{
|
||||
if (!CanOperateTowers || tower == null || target == null
|
||||
|| target.HasArrived || target.Enemy == null || playerHealth == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float efficiency = DefensePrototypeRules.GetTowerEfficiency(
|
||||
Vector2.Distance(playerHealth.transform.position, tower.transform.position),
|
||||
tuning.PlayerSightRadius, 1f, tuning.OutsideSightDamageEfficiency);
|
||||
return target.Enemy.ApplyStun(duration * efficiency);
|
||||
}
|
||||
|
||||
public bool TryBoostVisibleTowers(float duration)
|
||||
{
|
||||
if (!CanOperateTowers || duration <= 0f || playerHealth == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool applied = false;
|
||||
foreach (DefenseTower tower in towers)
|
||||
{
|
||||
if (tower == null
|
||||
|| !tower.IsOperational
|
||||
|| !tower.IsDamaging
|
||||
|| tower.Kind == DefenseTowerKind.Summon
|
||||
|| Vector2.Distance(
|
||||
playerHealth.transform.position,
|
||||
tower.transform.position) > tuning.PlayerSightRadius)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
boostedTowers[tower] = Time.time + duration;
|
||||
applied = true;
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
public bool TryAwardBondLedgerCurrency(int amount)
|
||||
{
|
||||
if (!active || failed || !PlayerIsAlive || amount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
constructionCurrency += amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryApplyTowerDamage(
|
||||
DefenseTower tower,
|
||||
DefenseRouteAgent target,
|
||||
@@ -395,9 +627,44 @@ namespace BumpCombat.Defense
|
||||
float distanceToPlayer = Vector2.Distance(
|
||||
playerHealth.transform.position,
|
||||
tower.transform.position);
|
||||
float multiplier = 1f;
|
||||
foreach (DefenseTower support in towers)
|
||||
{
|
||||
if (support == null || support == tower
|
||||
|| support.Kind != DefenseTowerKind.Empowering
|
||||
|| !support.IsOperational)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Vector2.Distance(support.transform.position,
|
||||
tower.transform.position) <= tuning.SupportRadius)
|
||||
{
|
||||
multiplier = Mathf.Max(multiplier,
|
||||
1f + tuning.SupportDamageBonusPerTier * support.Tier
|
||||
* GetSupportSightEfficiency(support));
|
||||
}
|
||||
|
||||
if (support.Tier == 4
|
||||
&& support.Branch == DefenseTowerBranch.Second
|
||||
&& Vector2.Distance(support.transform.position,
|
||||
target.transform.position) <= tuning.SupportRadius)
|
||||
{
|
||||
multiplier *= 1f + tuning.SmotheringDamageBonus
|
||||
* GetSupportSightEfficiency(support);
|
||||
}
|
||||
}
|
||||
|
||||
if (boostedTowers.TryGetValue(tower, out float buffUntil)
|
||||
&& Time.time < buffUntil)
|
||||
{
|
||||
multiplier *= 1f + GameplayConstants.Current.PassiveRelics
|
||||
.BattleBatonTowerDamageIncrease;
|
||||
}
|
||||
|
||||
return target.TryTakeTowerDamage(
|
||||
tower.Kind,
|
||||
damage,
|
||||
damage * multiplier,
|
||||
distanceToPlayer,
|
||||
tuning.PlayerSightRadius,
|
||||
tuning.OutsideSightDamageEfficiency,
|
||||
@@ -528,9 +795,12 @@ namespace BumpCombat.Defense
|
||||
enemy.OnDied -= HandleEnemyDied;
|
||||
activeAgents.Remove(enemy);
|
||||
int lane = Mathf.Clamp(agent.Lane, 0, LaneCount - 1);
|
||||
portalLedgers[lane]?.TryResolve(
|
||||
if (portalLedgers[lane]?.TryResolve(
|
||||
enemy.SpawnLifetimeIdentity,
|
||||
DefenseEnemyResolution.Death);
|
||||
DefenseEnemyResolution.Death) == true)
|
||||
{
|
||||
constructionCurrency += tuning.EnemyKillCurrency;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEnemyArrived(DefenseRouteAgent agent)
|
||||
@@ -700,7 +970,12 @@ namespace BumpCombat.Defense
|
||||
new Vector2(0.85f, 1.25f),
|
||||
new Color32(179, 84, 220, 255),
|
||||
5);
|
||||
CreateWorldLabel($"Portal {lane + 1} Label", portal + Vector2.right * 1.6f + Vector2.up * 0.9f, $"P{lane + 1} PORTAL\nIMMUNE {tuning.GetPortalImmuneTowerKind(lane).ToString().ToUpperInvariant()}");
|
||||
DefenseTowerKind immune = tuning.GetPortalImmuneTowerKind(lane);
|
||||
string immuneName = immune == DefenseTowerKind.Direct
|
||||
? "FIRE" : immune.ToString().ToUpperInvariant();
|
||||
CreateWorldLabel($"Portal {lane + 1} Label",
|
||||
portal + Vector2.right * 1.6f + Vector2.up * 0.9f,
|
||||
$"P{lane + 1} PORTAL\nIMMUNE {immuneName}");
|
||||
if (lane == 0)
|
||||
{
|
||||
CreateShape(
|
||||
@@ -713,7 +988,7 @@ namespace BumpCombat.Defense
|
||||
}
|
||||
}
|
||||
|
||||
for (int pad = 0; pad < LaneCount; pad++)
|
||||
for (int pad = 0; pad < BuildPadCount; pad++)
|
||||
{
|
||||
Vector2 position = tuning.GetBuildPadPosition(pad);
|
||||
CreateShape(
|
||||
@@ -748,10 +1023,10 @@ namespace BumpCombat.Defense
|
||||
rect.anchorMax = new Vector2(0f, 1f);
|
||||
rect.pivot = new Vector2(0f, 1f);
|
||||
rect.anchoredPosition = new Vector2(28f, -452f);
|
||||
rect.sizeDelta = new Vector2(900f, 110f);
|
||||
rect.sizeDelta = new Vector2(1200f, 165f);
|
||||
hudText = hudObject.GetComponent<Text>();
|
||||
hudText.font = BumpCombat.UI.PresentationUiStyle.GetFont();
|
||||
hudText.fontSize = 24;
|
||||
hudText.fontSize = 19;
|
||||
hudText.color = new Color32(236, 240, 246, 255);
|
||||
hudText.alignment = TextAnchor.UpperLeft;
|
||||
hudText.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
@@ -767,11 +1042,22 @@ namespace BumpCombat.Defense
|
||||
|
||||
string status = playerDead
|
||||
? $"PLAYER DOWN revive {Mathf.CeilToInt(reviveRemaining)}s"
|
||||
: IsRewardVisible ? "REWARD READY move to portal" : $"1 DIRECT {tuning.DirectTowerCost} / 2 SUMMON {tuning.SummonTowerCost} near a pad";
|
||||
: IsRewardVisible ? "REWARD READY move to portal"
|
||||
: $"1 FIRE {tuning.DirectTowerCost} 2 SUMMON {tuning.SummonTowerCost} 3 LIGHTNING {tuning.LightningTowerCost}";
|
||||
DefenseTower nearby = GetNearestBuiltTower();
|
||||
string upgrade = nearby == null
|
||||
? "Move near a built tower to upgrade"
|
||||
: nearby.Tier == 4
|
||||
? $"{nearby.Kind} final branch: {DefenseTower.GetBranchName(nearby.Kind, nearby.Branch)}"
|
||||
: nearby.Tier == 3
|
||||
? $"J {DefenseTower.GetBranchName(nearby.Kind, DefenseTowerBranch.First)} / K {DefenseTower.GetBranchName(nearby.Kind, DefenseTowerBranch.Second)} {tuning.Tier4UpgradeCost}"
|
||||
: $"U TIER {nearby.Tier + 1} {(nearby.Tier == 1 ? tuning.Tier2UpgradeCost : tuning.Tier3UpgradeCost)}";
|
||||
hudText.text =
|
||||
$"DEFENSE TEST BASE {baseHealth}/{tuning.BaseMaximumHealth}\n"
|
||||
+ $"BUILD {constructionCurrency} P1 WAIT {GetPortalRemainingSupply(0)} LIVE {CountActiveAgentsForLane(0)} P2 WAIT {GetPortalRemainingSupply(1)} LIVE {CountActiveAgentsForLane(1)}\n"
|
||||
+ status;
|
||||
+ status + "\n"
|
||||
+ $"4 COLD {tuning.ColdTowerCost} 5 SEISMIC {tuning.SeismicTowerCost} 6 SUPPORT {tuning.EmpoweringTowerCost}\n"
|
||||
+ upgrade;
|
||||
}
|
||||
|
||||
private void CreateLaneLine(int lane, Vector2 from, Vector2 to)
|
||||
|
||||
@@ -4,8 +4,19 @@ namespace BumpCombat.Defense
|
||||
{
|
||||
public enum DefenseTowerKind
|
||||
{
|
||||
Direct,
|
||||
Summon,
|
||||
Direct = 0,
|
||||
Summon = 1,
|
||||
Lightning,
|
||||
Cold,
|
||||
Seismic,
|
||||
Empowering,
|
||||
}
|
||||
|
||||
public enum DefenseTowerBranch
|
||||
{
|
||||
None,
|
||||
First,
|
||||
Second,
|
||||
}
|
||||
|
||||
public enum DefenseEnemyResolution
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace BumpCombat.Defense
|
||||
Vector2 next = Vector2.MoveTowards(
|
||||
current,
|
||||
target,
|
||||
routeSpeed * Mathf.Max(0f, deltaTime));
|
||||
routeSpeed * enemy.MovementSpeedMultiplier * Mathf.Max(0f, deltaTime));
|
||||
if (!enemy.TryReposition(next))
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -5,11 +5,15 @@ namespace BumpCombat.Defense
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class DefenseSummonUnit : MonoBehaviour
|
||||
{
|
||||
private static Sprite[] idleFrames;
|
||||
private static Sprite[] attackFrames;
|
||||
|
||||
private DefenseTower owner;
|
||||
private DefensePrototypeController controller;
|
||||
private Vector2 anchorLocalPosition;
|
||||
private SpriteRenderer spriteRenderer;
|
||||
private float attackCooldown;
|
||||
private float attackAnimationTime = -1f;
|
||||
|
||||
public void Configure(
|
||||
DefenseTower tower,
|
||||
@@ -20,18 +24,22 @@ namespace BumpCombat.Defense
|
||||
controller = prototypeController;
|
||||
anchorLocalPosition = localAnchor;
|
||||
spriteRenderer = gameObject.AddComponent<SpriteRenderer>();
|
||||
Sprite[] frames = Resources.LoadAll<Sprite>("Tutorial/Swordsman_Idle");
|
||||
if (frames != null && frames.Length > 0)
|
||||
idleFrames ??= Resources.LoadAll<Sprite>("Tutorial/Swordsman_Idle");
|
||||
attackFrames ??= Resources.LoadAll<Sprite>("Tutorial/Swordsman_BumpAttack-v1");
|
||||
if (idleFrames.Length > 0)
|
||||
{
|
||||
spriteRenderer.sprite = frames[0];
|
||||
spriteRenderer.sprite = idleFrames[0];
|
||||
}
|
||||
spriteRenderer.color = new Color32(255, 234, 137, 255);
|
||||
spriteRenderer.sortingOrder = 11;
|
||||
transform.localScale = new Vector3(0.9f, 0.9f, 1f);
|
||||
float scale = owner.Tier == 4 && owner.Branch == DefenseTowerBranch.First
|
||||
? 1.35f : 0.9f;
|
||||
transform.localScale = new Vector3(scale, scale, 1f);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateAttackSprite();
|
||||
if (owner == null
|
||||
|| !owner.isActiveAndEnabled
|
||||
|| controller == null
|
||||
@@ -41,11 +49,11 @@ namespace BumpCombat.Defense
|
||||
}
|
||||
|
||||
attackCooldown = Mathf.Max(0f, attackCooldown - Time.deltaTime);
|
||||
Vector2 anchor = owner.transform.TransformPoint(anchorLocalPosition);
|
||||
DefenseRouteAgent target = controller.FindNearestTarget(
|
||||
transform.position,
|
||||
anchor,
|
||||
controller.Tuning.SummonAreaRadius,
|
||||
DefenseTowerKind.Summon);
|
||||
Vector2 anchor = owner.transform.TransformPoint(anchorLocalPosition);
|
||||
if (target == null)
|
||||
{
|
||||
transform.position = Vector2.MoveTowards(
|
||||
@@ -56,24 +64,56 @@ namespace BumpCombat.Defense
|
||||
}
|
||||
|
||||
Vector2 targetPosition = target.transform.position;
|
||||
Vector2 fromAnchor = targetPosition - anchor;
|
||||
if (fromAnchor.magnitude > controller.Tuning.SummonAreaRadius)
|
||||
{
|
||||
targetPosition = anchor + fromAnchor.normalized * controller.Tuning.SummonAreaRadius;
|
||||
}
|
||||
|
||||
spriteRenderer.flipX = targetPosition.x < transform.position.x;
|
||||
transform.position = Vector2.MoveTowards(
|
||||
transform.position,
|
||||
targetPosition,
|
||||
controller.Tuning.SummonUnitMoveSpeed * Time.deltaTime);
|
||||
float attackRange = controller.Tuning.SummonAttackRange
|
||||
* (owner.Tier == 4 && owner.Branch == DefenseTowerBranch.Second ? 2.5f : 1f);
|
||||
if (attackCooldown <= 0f
|
||||
&& (target.transform.position - transform.position).sqrMagnitude <= controller.Tuning.SummonAttackRange * controller.Tuning.SummonAttackRange)
|
||||
&& (targetPosition - (Vector2)transform.position).sqrMagnitude
|
||||
<= attackRange * attackRange
|
||||
&& owner.TryAttack(target))
|
||||
{
|
||||
if (owner.TryAttack(target))
|
||||
{
|
||||
attackCooldown = controller.Tuning.SummonAttackInterval;
|
||||
}
|
||||
PlayAttackSprite();
|
||||
owner.ShowSummonAttackEffect(transform.position, targetPosition);
|
||||
attackCooldown = controller.Tuning.SummonAttackInterval
|
||||
/ (1f + 0.15f * (owner.Tier - 1))
|
||||
* controller.GetTowerAttackIntervalMultiplier(owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayAttackSprite()
|
||||
{
|
||||
if (attackFrames.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
attackAnimationTime = 0f;
|
||||
spriteRenderer.sprite = attackFrames[0];
|
||||
}
|
||||
|
||||
private void UpdateAttackSprite()
|
||||
{
|
||||
if (attackAnimationTime < 0f || attackFrames == null
|
||||
|| attackFrames.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
attackAnimationTime += Time.deltaTime;
|
||||
int frame = Mathf.FloorToInt(attackAnimationTime * 12f);
|
||||
if (frame >= attackFrames.Length)
|
||||
{
|
||||
attackAnimationTime = -1f;
|
||||
if (idleFrames != null && idleFrames.Length > 0)
|
||||
{
|
||||
spriteRenderer.sprite = idleFrames[0];
|
||||
}
|
||||
return;
|
||||
}
|
||||
spriteRenderer.sprite = attackFrames[frame];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,18 @@ namespace BumpCombat.Defense
|
||||
private DefenseConstants tuning;
|
||||
private float attackCooldown;
|
||||
private TextMesh label;
|
||||
private LineRenderer effectRange;
|
||||
private static Material effectMaterial;
|
||||
private int summonCount;
|
||||
|
||||
public DefenseTowerKind Kind { get; private set; }
|
||||
public DefenseTowerBranch Branch { get; private set; }
|
||||
public int Tier { get; private set; } = 1;
|
||||
public int PadIndex { get; private set; }
|
||||
public bool IsOperational => isActiveAndEnabled
|
||||
&& owner != null
|
||||
&& owner.CanOperateTowers;
|
||||
public bool IsDamaging => Kind != DefenseTowerKind.Empowering;
|
||||
|
||||
public void Configure(
|
||||
DefensePrototypeController controller,
|
||||
@@ -27,26 +33,57 @@ namespace BumpCombat.Defense
|
||||
Kind = kind;
|
||||
PadIndex = padIndex;
|
||||
tuning = constants;
|
||||
Tier = 1;
|
||||
Branch = DefenseTowerBranch.None;
|
||||
CreateVisuals();
|
||||
if (Kind == DefenseTowerKind.Summon)
|
||||
{
|
||||
for (int i = 0; i < tuning.SummonUnitCount; i++)
|
||||
AddSummonUnits(tuning.SummonUnitCount);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Upgrade(DefenseTowerBranch branch)
|
||||
{
|
||||
if (Tier >= 4 || (Tier < 3 && branch != DefenseTowerBranch.None)
|
||||
|| (Tier == 3 && branch != DefenseTowerBranch.First
|
||||
&& branch != DefenseTowerBranch.Second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Tier++;
|
||||
if (Tier == 4)
|
||||
{
|
||||
Branch = branch;
|
||||
}
|
||||
|
||||
if (Kind == DefenseTowerKind.Summon)
|
||||
{
|
||||
if (Tier == 4 && Branch == DefenseTowerBranch.First)
|
||||
{
|
||||
GameObject unitObject = new($"Summon Unit {i + 1}");
|
||||
unitObject.transform.SetParent(transform, false);
|
||||
unitObject.transform.localPosition = new Vector3(
|
||||
(i - (tuning.SummonUnitCount - 1) * 0.5f) * 0.42f,
|
||||
-0.5f,
|
||||
0f);
|
||||
DefenseSummonUnit unit = unitObject.AddComponent<DefenseSummonUnit>();
|
||||
unit.Configure(this, controller, unitObject.transform.localPosition);
|
||||
foreach (DefenseSummonUnit unit in GetComponentsInChildren<DefenseSummonUnit>())
|
||||
{
|
||||
unit.enabled = false;
|
||||
Destroy(unit.gameObject);
|
||||
}
|
||||
summonCount = 0;
|
||||
AddSummonUnits(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddSummonUnits(Tier == 4 ? 2 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLabel();
|
||||
UpdateEffectRange();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsOperational || Kind != DefenseTowerKind.Direct)
|
||||
if (!IsOperational || Kind == DefenseTowerKind.Summon
|
||||
|| Kind == DefenseTowerKind.Empowering)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -57,12 +94,386 @@ namespace BumpCombat.Defense
|
||||
return;
|
||||
}
|
||||
|
||||
float interval = Kind == DefenseTowerKind.Direct
|
||||
? tuning.DirectTowerAttackInterval
|
||||
: tuning.SummonAttackInterval;
|
||||
if (owner.TryAttackFromTower(this))
|
||||
{
|
||||
attackCooldown = Mathf.Max(0.05f, interval);
|
||||
float interval = Kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => tuning.DirectTowerAttackInterval,
|
||||
DefenseTowerKind.Lightning => tuning.LightningInterval,
|
||||
DefenseTowerKind.Cold => tuning.ColdInterval,
|
||||
_ => tuning.SeismicInterval,
|
||||
};
|
||||
if (Tier == 4 && Kind == DefenseTowerKind.Direct
|
||||
&& Branch == DefenseTowerBranch.Second)
|
||||
{
|
||||
interval *= 0.4f;
|
||||
}
|
||||
|
||||
attackCooldown = Mathf.Max(0.05f,
|
||||
interval / (1f + 0.15f * (Tier - 1))
|
||||
* owner.GetTowerAttackIntervalMultiplier(this));
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPulse()
|
||||
{
|
||||
if (!IsOperational || Kind == DefenseTowerKind.Empowering
|
||||
|| Kind == DefenseTowerKind.Summon)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float range = GetEffectRange();
|
||||
DefenseRouteAgent target = owner.FindNearestTarget(
|
||||
transform.position, range, Kind,
|
||||
Kind == DefenseTowerKind.Cold || Kind == DefenseTowerKind.Seismic);
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector2 center = target.transform.position;
|
||||
float scale = 1f + tuning.TowerDamageBonusPerTier * (Tier - 1);
|
||||
float impactRadius = 0f;
|
||||
bool cone = false;
|
||||
bool applied;
|
||||
switch (Kind)
|
||||
{
|
||||
case DefenseTowerKind.Direct:
|
||||
if (Tier == 4 && Branch == DefenseTowerBranch.Second)
|
||||
{
|
||||
cone = true;
|
||||
impactRadius = range;
|
||||
applied = AttackCone(center - (Vector2)transform.position,
|
||||
range, tuning.DirectTowerDamage * scale * 0.4f);
|
||||
}
|
||||
else if (Tier == 4)
|
||||
{
|
||||
impactRadius = 1.8f;
|
||||
applied = AttackArea(center, impactRadius,
|
||||
tuning.DirectTowerDamage * scale * 1.6f);
|
||||
}
|
||||
else
|
||||
{
|
||||
applied = owner.TryApplyTowerDamage(this, target,
|
||||
tuning.DirectTowerDamage * scale);
|
||||
}
|
||||
break;
|
||||
|
||||
case DefenseTowerKind.Lightning:
|
||||
impactRadius = Tier == 4
|
||||
? (Branch == DefenseTowerBranch.First ? 3f : 2.4f)
|
||||
: tuning.LightningRadius;
|
||||
applied = AttackArea(center, impactRadius,
|
||||
tuning.LightningDamage * scale
|
||||
* (Tier == 4 && Branch == DefenseTowerBranch.Second ? 1.4f : 1f),
|
||||
Tier == 4 && Branch == DefenseTowerBranch.First ? 3 : int.MaxValue);
|
||||
break;
|
||||
|
||||
case DefenseTowerKind.Cold:
|
||||
if (Tier == 4 && Branch == DefenseTowerBranch.Second)
|
||||
{
|
||||
impactRadius = 1.65f;
|
||||
applied = AttackArea(center, impactRadius,
|
||||
tuning.ColdDamage * scale * 0.5f,
|
||||
int.MaxValue, tuning.ColdSlowSeconds * 1.5f,
|
||||
tuning.ColdSlowFraction, 0.35f);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool slowed = owner.TryApplyTowerSlow(this, target,
|
||||
tuning.ColdSlowSeconds * (1f + 0.25f * (Tier - 1)),
|
||||
tuning.ColdSlowFraction);
|
||||
bool damaged = owner.TryApplyTowerDamage(this, target,
|
||||
tuning.ColdDamage * scale * (Tier == 4 ? 1.8f : 1f));
|
||||
if (Tier == 4)
|
||||
{
|
||||
owner.TryApplyTowerStun(this, target, 0.3f);
|
||||
}
|
||||
applied = slowed || damaged;
|
||||
}
|
||||
break;
|
||||
|
||||
case DefenseTowerKind.Seismic:
|
||||
impactRadius = Tier == 4
|
||||
? (Branch == DefenseTowerBranch.Second ? 2.2f : 1.8f)
|
||||
: tuning.SeismicRadius;
|
||||
applied = Tier == 4 && Branch == DefenseTowerBranch.Second
|
||||
? AttackArea(center, impactRadius,
|
||||
tuning.SeismicDamage * scale, int.MaxValue, 2f, 0.5f)
|
||||
: AttackArea(center, impactRadius,
|
||||
tuning.SeismicDamage * scale, int.MaxValue,
|
||||
0f, 0f,
|
||||
tuning.SeismicStunSeconds * (Tier == 4 ? 2f : 1f));
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (applied)
|
||||
{
|
||||
ShowAttackEffect(center, impactRadius, cone);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
private bool AttackCone(Vector2 direction, float range, float damage)
|
||||
{
|
||||
bool applied = false;
|
||||
Vector2 facing = direction.normalized;
|
||||
foreach (DefenseRouteAgent agent in owner.GetTargetsInRange(
|
||||
transform.position, range))
|
||||
{
|
||||
Vector2 toAgent = ((Vector2)agent.transform.position
|
||||
- (Vector2)transform.position).normalized;
|
||||
if (toAgent.sqrMagnitude < 0.000001f
|
||||
|| Vector2.Dot(facing, toAgent) >= 0.5f)
|
||||
{
|
||||
applied |= owner.TryApplyTowerDamage(this, agent, damage);
|
||||
}
|
||||
}
|
||||
|
||||
return applied;
|
||||
}
|
||||
|
||||
private bool AttackArea(
|
||||
Vector2 center,
|
||||
float radius,
|
||||
float damage,
|
||||
int maxTargets = int.MaxValue,
|
||||
float slowSeconds = 0f,
|
||||
float slowFraction = 0f,
|
||||
float stunSeconds = 0f)
|
||||
{
|
||||
bool applied = false;
|
||||
int count = 0;
|
||||
foreach (DefenseRouteAgent agent in owner.GetTargetsInRange(center, radius))
|
||||
{
|
||||
if (count++ >= maxTargets)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bool damaged = owner.TryApplyTowerDamage(this, agent, damage);
|
||||
bool slowed = slowSeconds > 0f
|
||||
&& owner.TryApplyTowerSlow(this, agent, slowSeconds, slowFraction);
|
||||
bool stunned = stunSeconds > 0f
|
||||
&& owner.TryApplyTowerStun(this, agent, stunSeconds);
|
||||
applied |= damaged || slowed || stunned;
|
||||
}
|
||||
|
||||
return applied;
|
||||
}
|
||||
|
||||
public bool TryAttack(DefenseRouteAgent target)
|
||||
{
|
||||
if (target == null || owner == null || !IsOperational)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float damage = Kind == DefenseTowerKind.Summon
|
||||
? tuning.SummonDamage : tuning.DirectTowerDamage;
|
||||
damage *= 1f + tuning.TowerDamageBonusPerTier * (Tier - 1);
|
||||
if (Tier == 4 && Kind == DefenseTowerKind.Summon
|
||||
&& Branch == DefenseTowerBranch.First)
|
||||
{
|
||||
damage *= 1.8f;
|
||||
}
|
||||
|
||||
bool hit = owner.TryApplyTowerDamage(this, target, damage);
|
||||
if (hit && Tier == 4 && Kind == DefenseTowerKind.Summon
|
||||
&& Branch == DefenseTowerBranch.First)
|
||||
{
|
||||
owner.TryApplyTowerStun(this, target, 0.3f);
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
public void ShowSummonAttackEffect(Vector2 origin, Vector2 target)
|
||||
{
|
||||
if (IsOperational && Kind == DefenseTowerKind.Summon)
|
||||
{
|
||||
ShowAttackEffect(target, 0f, false, origin);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetEffectRange()
|
||||
{
|
||||
if (Kind == DefenseTowerKind.Summon)
|
||||
{
|
||||
return tuning.SummonAreaRadius;
|
||||
}
|
||||
if (Kind == DefenseTowerKind.Empowering)
|
||||
{
|
||||
return tuning.SupportRadius;
|
||||
}
|
||||
return Kind == DefenseTowerKind.Direct && Tier == 4
|
||||
&& Branch == DefenseTowerBranch.Second
|
||||
? tuning.TowerRange * 0.5f : tuning.TowerRange;
|
||||
}
|
||||
|
||||
private void CreateEffectRange()
|
||||
{
|
||||
GameObject rangeObject = new("Effect Range");
|
||||
rangeObject.transform.SetParent(transform, false);
|
||||
effectRange = rangeObject.AddComponent<LineRenderer>();
|
||||
effectRange.useWorldSpace = false;
|
||||
effectRange.loop = true;
|
||||
effectRange.positionCount = 48;
|
||||
effectRange.sharedMaterial = GetEffectMaterial();
|
||||
effectRange.sortingOrder = 3;
|
||||
Color color = GetComponent<SpriteRenderer>().color;
|
||||
color.a = Kind == DefenseTowerKind.Empowering ? 0.35f : 0.22f;
|
||||
effectRange.startColor = color;
|
||||
effectRange.endColor = color;
|
||||
UpdateEffectRange();
|
||||
}
|
||||
|
||||
private void UpdateEffectRange()
|
||||
{
|
||||
if (effectRange == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
float worldScale = Mathf.Max(0.001f, transform.lossyScale.x);
|
||||
float radius = GetEffectRange() / worldScale;
|
||||
effectRange.startWidth = 0.055f / worldScale;
|
||||
effectRange.endWidth = effectRange.startWidth;
|
||||
for (int i = 0; i < effectRange.positionCount; i++)
|
||||
{
|
||||
float angle = i * Mathf.PI * 2f / effectRange.positionCount;
|
||||
effectRange.SetPosition(i, new Vector3(
|
||||
Mathf.Cos(angle) * radius,
|
||||
Mathf.Sin(angle) * radius, 0f));
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowAttackEffect(
|
||||
Vector2 center, float radius, bool cone, Vector2? source = null)
|
||||
{
|
||||
Vector2 origin = source ?? (Vector2)transform.position;
|
||||
GameObject effect = new($"{Kind} Attack Effect");
|
||||
effect.transform.SetParent(transform.parent, false);
|
||||
effect.transform.position = center;
|
||||
Color color = GetComponent<SpriteRenderer>().color;
|
||||
color.a = 0.9f;
|
||||
Vector2 direction = center - origin;
|
||||
|
||||
if (Kind == DefenseTowerKind.Lightning && direction.sqrMagnitude > 0.001f)
|
||||
{
|
||||
LineRenderer bolt = CreateEffectLine(effect.transform, "Bolt", color, 0.13f);
|
||||
bolt.positionCount = 5;
|
||||
Vector2 side = new(-direction.y, direction.x);
|
||||
side.Normalize();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
float fraction = i / 4f;
|
||||
float offset = i == 1 ? 0.22f : i == 2 ? -0.16f
|
||||
: i == 3 ? 0.18f : 0f;
|
||||
bolt.SetPosition(i, origin - center
|
||||
+ direction * fraction + side * offset);
|
||||
}
|
||||
}
|
||||
else if (Kind != DefenseTowerKind.Seismic
|
||||
&& direction.sqrMagnitude > 0.001f)
|
||||
{
|
||||
SpriteRenderer shot = CreateEffectSprite(
|
||||
effect.transform, "Shot Sprite",
|
||||
origin + direction * 0.5f,
|
||||
new Vector2(direction.magnitude,
|
||||
Kind == DefenseTowerKind.Direct ? 0.2f : 0.13f), color);
|
||||
shot.transform.rotation = Quaternion.Euler(
|
||||
0f, 0f, Mathf.Atan2(direction.y, direction.x)
|
||||
* Mathf.Rad2Deg);
|
||||
}
|
||||
|
||||
CreateEffectSprite(effect.transform, "Impact Sprite",
|
||||
center, new Vector2(0.42f, 0.42f), color);
|
||||
if (cone)
|
||||
{
|
||||
LineRenderer outline = CreateEffectLine(
|
||||
effect.transform, "Cone Radius", color, 0.07f);
|
||||
outline.positionCount = 3;
|
||||
Vector2 forward = direction.normalized * radius;
|
||||
outline.SetPosition(0, origin - center);
|
||||
outline.SetPosition(1, origin - center
|
||||
+ (Vector2)(Quaternion.Euler(0f, 0f, -60f) * forward));
|
||||
outline.SetPosition(2, origin - center
|
||||
+ (Vector2)(Quaternion.Euler(0f, 0f, 60f) * forward));
|
||||
}
|
||||
else if (radius > 0f)
|
||||
{
|
||||
LineRenderer area = CreateEffectLine(
|
||||
effect.transform, "Impact Radius", color, 0.07f);
|
||||
area.loop = true;
|
||||
area.positionCount = 40;
|
||||
for (int i = 0; i < area.positionCount; i++)
|
||||
{
|
||||
float angle = i * Mathf.PI * 2f / area.positionCount;
|
||||
area.SetPosition(i, new Vector3(
|
||||
Mathf.Cos(angle) * radius,
|
||||
Mathf.Sin(angle) * radius, 0f));
|
||||
}
|
||||
}
|
||||
|
||||
Destroy(effect, 0.22f);
|
||||
}
|
||||
|
||||
private static SpriteRenderer CreateEffectSprite(
|
||||
Transform parent, string name, Vector2 position,
|
||||
Vector2 size, Color color)
|
||||
{
|
||||
GameObject shape = new(name);
|
||||
shape.transform.SetParent(parent, false);
|
||||
shape.transform.position = position;
|
||||
shape.transform.localScale = new Vector3(size.x, size.y, 1f);
|
||||
SpriteRenderer renderer = shape.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = DefensePrototypeController.WhiteSprite;
|
||||
renderer.color = color;
|
||||
renderer.sortingOrder = 15;
|
||||
return renderer;
|
||||
}
|
||||
|
||||
private static LineRenderer CreateEffectLine(
|
||||
Transform parent, string name, Color color, float width)
|
||||
{
|
||||
GameObject shape = new(name);
|
||||
shape.transform.SetParent(parent, false);
|
||||
LineRenderer line = shape.AddComponent<LineRenderer>();
|
||||
line.useWorldSpace = false;
|
||||
line.sharedMaterial = GetEffectMaterial();
|
||||
line.startWidth = width;
|
||||
line.endWidth = width;
|
||||
line.startColor = color;
|
||||
line.endColor = color;
|
||||
line.sortingOrder = 14;
|
||||
return line;
|
||||
}
|
||||
|
||||
private static Material GetEffectMaterial()
|
||||
{
|
||||
if (effectMaterial == null)
|
||||
{
|
||||
effectMaterial = new Material(Shader.Find("Sprites/Default"));
|
||||
effectMaterial.hideFlags = HideFlags.HideAndDontSave;
|
||||
}
|
||||
return effectMaterial;
|
||||
}
|
||||
|
||||
private void AddSummonUnits(int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int unitIndex = summonCount++;
|
||||
GameObject unitObject = new($"Summon Unit {unitIndex + 1}");
|
||||
unitObject.transform.SetParent(transform, false);
|
||||
unitObject.transform.localPosition = new Vector3(
|
||||
(unitIndex % 3 - 1) * 0.42f, -0.5f - 0.25f * (unitIndex / 3), 0f);
|
||||
DefenseSummonUnit unit = unitObject.AddComponent<DefenseSummonUnit>();
|
||||
unit.Configure(this, owner, unitObject.transform.localPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +485,15 @@ namespace BumpCombat.Defense
|
||||
renderer = gameObject.AddComponent<SpriteRenderer>();
|
||||
}
|
||||
renderer.sprite = DefensePrototypeController.WhiteSprite;
|
||||
renderer.color = Kind == DefenseTowerKind.Direct
|
||||
? new Color32(65, 211, 240, 255)
|
||||
: new Color32(244, 154, 74, 255);
|
||||
renderer.color = Kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => new Color32(240, 96, 65, 255),
|
||||
DefenseTowerKind.Summon => new Color32(244, 154, 74, 255),
|
||||
DefenseTowerKind.Lightning => new Color32(238, 218, 83, 255),
|
||||
DefenseTowerKind.Cold => new Color32(65, 211, 240, 255),
|
||||
DefenseTowerKind.Seismic => new Color32(175, 141, 100, 255),
|
||||
_ => new Color32(148, 228, 122, 255),
|
||||
};
|
||||
renderer.sortingOrder = 8;
|
||||
transform.localScale = new Vector3(0.72f, 0.72f, 1f);
|
||||
|
||||
@@ -93,26 +510,43 @@ namespace BumpCombat.Defense
|
||||
labelObject.transform.SetParent(transform, false);
|
||||
labelObject.transform.localPosition = new Vector3(0f, 0.85f, 0f);
|
||||
label = labelObject.AddComponent<TextMesh>();
|
||||
label.text = Kind == DefenseTowerKind.Direct ? "DIRECT" : "SUMMON";
|
||||
label.anchor = TextAnchor.MiddleCenter;
|
||||
label.alignment = TextAlignment.Center;
|
||||
label.characterSize = 0.16f;
|
||||
label.fontSize = 32;
|
||||
label.color = Color.white;
|
||||
label.GetComponent<MeshRenderer>().sortingOrder = 10;
|
||||
UpdateLabel();
|
||||
CreateEffectRange();
|
||||
}
|
||||
|
||||
public bool TryAttack(DefenseRouteAgent target)
|
||||
private void UpdateLabel()
|
||||
{
|
||||
if (target == null || owner == null)
|
||||
if (label == null)
|
||||
{
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
float damage = Kind == DefenseTowerKind.Direct
|
||||
? tuning.DirectTowerDamage
|
||||
: tuning.SummonDamage;
|
||||
return owner.TryApplyTowerDamage(this, target, damage);
|
||||
string family = Kind == DefenseTowerKind.Direct ? "FIRE" : Kind.ToString().ToUpperInvariant();
|
||||
string branchName = Tier == 4
|
||||
? GetBranchName(Kind, Branch) : string.Empty;
|
||||
label.text = Tier == 4
|
||||
? $"{family} IV\n{branchName}"
|
||||
: $"{family} {Tier}";
|
||||
}
|
||||
|
||||
public static string GetBranchName(DefenseTowerKind kind, DefenseTowerBranch branch)
|
||||
{
|
||||
bool first = branch == DefenseTowerBranch.First;
|
||||
return kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => first ? "METEOR" : "FLAMETHROWER",
|
||||
DefenseTowerKind.Lightning => first ? "ARC" : "LIGHTNING STORM",
|
||||
DefenseTowerKind.Cold => first ? "FREEZEBOLT" : "GLACIAL CAGE",
|
||||
DefenseTowerKind.Seismic => first ? "STONE GAZE" : "TEMPORAL",
|
||||
DefenseTowerKind.Summon => first ? "SENTINEL" : "SCOUT",
|
||||
_ => first ? "IMBUING" : "SMOTHERING",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user