fix multiple issues
This commit is contained in:
@@ -54,6 +54,8 @@
|
|||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TMPro_InstructionOverlay.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TMPro_InstructionOverlay.cs" />
|
||||||
<Compile Include="Assets\Scripts\Enemy.cs" />
|
<Compile Include="Assets\Scripts\Enemy.cs" />
|
||||||
<Compile Include="Assets\Scripts\ShockwaveEffect.cs" />
|
<Compile Include="Assets\Scripts\ShockwaveEffect.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\RarityPulse.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\FloatingWorldText.cs" />
|
||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\VertexShakeB.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\VertexShakeB.cs" />
|
||||||
<Compile Include="Assets\Scripts\CannonAreaEffect.cs" />
|
<Compile Include="Assets\Scripts\CannonAreaEffect.cs" />
|
||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\VertexJitter.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\VertexJitter.cs" />
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TMP_ExampleScript_01.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TMP_ExampleScript_01.cs" />
|
||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TMPro_InstructionOverlay.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TMPro_InstructionOverlay.cs" />
|
||||||
<Compile Include="Assets\Scripts\Enemy.cs" />
|
<Compile Include="Assets\Scripts\Enemy.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\RarityPulse.cs" />
|
||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\VertexShakeB.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\VertexShakeB.cs" />
|
||||||
<Compile Include="Assets\Scripts\CannonAreaEffect.cs" />
|
<Compile Include="Assets\Scripts\CannonAreaEffect.cs" />
|
||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\VertexJitter.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\VertexJitter.cs" />
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TextConsoleSimulator.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TextConsoleSimulator.cs" />
|
||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TeleType.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TeleType.cs" />
|
||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TMP_TextEventCheck.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TMP_TextEventCheck.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\FloatingWorldText.cs" />
|
||||||
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TextMeshProFloatingText.cs" />
|
<Compile Include="Assets\TextMesh Pro\Examples & Extras\Scripts\TextMeshProFloatingText.cs" />
|
||||||
<None Include="Assets\TextMesh Pro\Shaders\TMPro.cginc" />
|
<None Include="Assets\TextMesh Pro\Shaders\TMPro.cginc" />
|
||||||
<None Include="Assets\TextMesh Pro\Shaders\TMP_SDF-Mobile Overlay.shader" />
|
<None Include="Assets\TextMesh Pro\Shaders\TMP_SDF-Mobile Overlay.shader" />
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public class Bullet : MonoBehaviour
|
|||||||
{
|
{
|
||||||
Collider2D directCollider = directTarget.GetComponent<Collider2D>();
|
Collider2D directCollider = directTarget.GetComponent<Collider2D>();
|
||||||
if (directCollider != null)
|
if (directCollider != null)
|
||||||
ApplyExplosionControl(directCollider, center);
|
ApplyExplosionControl(directCollider, center - lastKnownDirection * 0.35f);
|
||||||
|
|
||||||
int directDamage = CalculateDirectDamage(directTarget);
|
int directDamage = CalculateDirectDamage(directTarget);
|
||||||
directKilled = directTarget.TakeDamage(directDamage);
|
directKilled = directTarget.TakeDamage(directDamage);
|
||||||
@@ -228,6 +228,7 @@ public class Bullet : MonoBehaviour
|
|||||||
if (enemyHealth == null)
|
if (enemyHealth == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
CreateFragmentVisual(center, collider.transform.position);
|
||||||
enemyHealth.TakeDamage(Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * 0.35f)));
|
enemyHealth.TakeDamage(Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * 0.35f)));
|
||||||
fragmentsLeft--;
|
fragmentsLeft--;
|
||||||
}
|
}
|
||||||
@@ -249,7 +250,8 @@ public class Bullet : MonoBehaviour
|
|||||||
effect.Configure(
|
effect.Configure(
|
||||||
Mathf.Max(stats.burningGroundDuration, stats.craterDuration),
|
Mathf.Max(stats.burningGroundDuration, stats.craterDuration),
|
||||||
stats.burningGroundDuration > 0f ? Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * 0.18f)) : 0,
|
stats.burningGroundDuration > 0f ? Mathf.Max(1, Mathf.RoundToInt(stats.directDamage * 0.18f)) : 0,
|
||||||
stats.craterDuration > 0f ? stats.craterSlowMultiplier : 1f);
|
stats.craterDuration > 0f ? stats.craterSlowMultiplier : 1f,
|
||||||
|
stats.splashRadius * 0.72f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TriggerChainDetonation(Vector2 center)
|
void TriggerChainDetonation(Vector2 center)
|
||||||
@@ -319,4 +321,23 @@ public class Bullet : MonoBehaviour
|
|||||||
|
|
||||||
ring.GetComponent<ShockwaveEffect>().Configure(0.28f);
|
ring.GetComponent<ShockwaveEffect>().Configure(0.28f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void CreateFragmentVisual(Vector2 start, Vector2 end)
|
||||||
|
{
|
||||||
|
GameObject fragment = new GameObject("ShrapnelFragment", typeof(LineRenderer), typeof(ShockwaveEffect));
|
||||||
|
fragment.transform.position = start;
|
||||||
|
|
||||||
|
LineRenderer line = fragment.GetComponent<LineRenderer>();
|
||||||
|
line.useWorldSpace = true;
|
||||||
|
line.positionCount = 2;
|
||||||
|
line.SetPosition(0, start);
|
||||||
|
line.SetPosition(1, end);
|
||||||
|
line.startWidth = 0.045f;
|
||||||
|
line.endWidth = 0.012f;
|
||||||
|
line.material = new Material(Shader.Find("Sprites/Default"));
|
||||||
|
line.startColor = new Color(0.82f, 0.84f, 0.88f, 0.95f);
|
||||||
|
line.endColor = new Color(0.82f, 0.84f, 0.88f, 0.1f);
|
||||||
|
|
||||||
|
fragment.GetComponent<ShockwaveEffect>().Configure(0.18f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,29 @@ public class CannonAreaEffect : MonoBehaviour
|
|||||||
int damagePerSecond;
|
int damagePerSecond;
|
||||||
float slowMultiplier = 1f;
|
float slowMultiplier = 1f;
|
||||||
float expiresAt;
|
float expiresAt;
|
||||||
|
SpriteRenderer visual;
|
||||||
|
|
||||||
public void Configure(float effectDuration, int burnDamagePerSecond, float craterSlowMultiplier)
|
public void Configure(float effectDuration, int burnDamagePerSecond, float craterSlowMultiplier, float radius)
|
||||||
{
|
{
|
||||||
duration = effectDuration;
|
duration = effectDuration;
|
||||||
damagePerSecond = burnDamagePerSecond;
|
damagePerSecond = burnDamagePerSecond;
|
||||||
slowMultiplier = Mathf.Clamp(craterSlowMultiplier, 0.1f, 1f);
|
slowMultiplier = Mathf.Clamp(craterSlowMultiplier, 0.1f, 1f);
|
||||||
expiresAt = Time.time + duration;
|
expiresAt = Time.time + duration;
|
||||||
|
CreateVisual(radius);
|
||||||
Destroy(gameObject, duration);
|
Destroy(gameObject, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
if (visual == null || duration <= 0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float remaining = Mathf.Clamp01((expiresAt - Time.time) / duration);
|
||||||
|
Color color = visual.color;
|
||||||
|
color.a = Mathf.Lerp(0f, color.a, remaining);
|
||||||
|
visual.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
void OnTriggerStay2D(Collider2D other)
|
void OnTriggerStay2D(Collider2D other)
|
||||||
{
|
{
|
||||||
if (!other.CompareTag("Enemy"))
|
if (!other.CompareTag("Enemy"))
|
||||||
@@ -44,4 +57,44 @@ public class CannonAreaEffect : MonoBehaviour
|
|||||||
health.TakeDamage(Mathf.Max(1, damagePerSecond));
|
health.TakeDamage(Mathf.Max(1, damagePerSecond));
|
||||||
nextDamageTick[health] = Time.time + 1f;
|
nextDamageTick[health] = Time.time + 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CreateVisual(float radius)
|
||||||
|
{
|
||||||
|
GameObject visualObject = new GameObject("Visual", typeof(SpriteRenderer));
|
||||||
|
visualObject.transform.SetParent(transform, false);
|
||||||
|
visualObject.transform.localScale = Vector3.one * radius * 2f;
|
||||||
|
|
||||||
|
visual = visualObject.GetComponent<SpriteRenderer>();
|
||||||
|
visual.sprite = CreateCircleSprite();
|
||||||
|
visual.sortingOrder = -1;
|
||||||
|
|
||||||
|
if (damagePerSecond > 0 && slowMultiplier < 1f)
|
||||||
|
visual.color = new Color(0.95f, 0.33f, 0.12f, 0.34f);
|
||||||
|
else if (damagePerSecond > 0)
|
||||||
|
visual.color = new Color(1f, 0.32f, 0.08f, 0.34f);
|
||||||
|
else
|
||||||
|
visual.color = new Color(0.34f, 0.24f, 0.16f, 0.38f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Sprite CreateCircleSprite()
|
||||||
|
{
|
||||||
|
const int size = 64;
|
||||||
|
Texture2D texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
|
||||||
|
texture.filterMode = FilterMode.Bilinear;
|
||||||
|
Vector2 center = new Vector2((size - 1) * 0.5f, (size - 1) * 0.5f);
|
||||||
|
float radius = size * 0.46f;
|
||||||
|
|
||||||
|
for (int y = 0; y < size; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < size; x++)
|
||||||
|
{
|
||||||
|
float distance = Vector2.Distance(new Vector2(x, y), center);
|
||||||
|
float alpha = Mathf.Clamp01((radius - distance) / 5f);
|
||||||
|
texture.SetPixel(x, y, new Color(1f, 1f, 1f, alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
texture.Apply();
|
||||||
|
return Sprite.Create(texture, new Rect(0f, 0f, size, size), new Vector2(0.5f, 0.5f), size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-1
@@ -7,6 +7,8 @@ public class Enemy : MonoBehaviour
|
|||||||
private EnemyHealth enemyHealth;
|
private EnemyHealth enemyHealth;
|
||||||
private float slowMultiplier = 1f;
|
private float slowMultiplier = 1f;
|
||||||
private float slowEndTime;
|
private float slowEndTime;
|
||||||
|
private Vector2 knockbackVelocity;
|
||||||
|
private float knockbackEndTime;
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
@@ -25,12 +27,21 @@ public class Enemy : MonoBehaviour
|
|||||||
if (Time.time >= slowEndTime)
|
if (Time.time >= slowEndTime)
|
||||||
slowMultiplier = 1f;
|
slowMultiplier = 1f;
|
||||||
|
|
||||||
|
if (Time.time < knockbackEndTime && knockbackVelocity.sqrMagnitude > 0.01f)
|
||||||
|
{
|
||||||
|
transform.position += (Vector3)(knockbackVelocity * Time.deltaTime);
|
||||||
|
knockbackVelocity = Vector2.Lerp(knockbackVelocity, Vector2.zero, 8f * Time.deltaTime);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
transform.position = Vector2.MoveTowards(transform.position, tower.position, speed * slowMultiplier * Time.deltaTime);
|
transform.position = Vector2.MoveTowards(transform.position, tower.position, speed * slowMultiplier * Time.deltaTime);
|
||||||
if (Vector2.Distance(transform.position, tower.position) < 0.5f)
|
if (Vector2.Distance(transform.position, tower.position) < 0.5f)
|
||||||
{
|
{
|
||||||
int damageDealt = enemyHealth.GetCurrentHealth();
|
int damageDealt = enemyHealth.GetCurrentHealth();
|
||||||
tower.GetComponent<TowerHealth>().TakeDamage(damageDealt);
|
tower.GetComponent<TowerHealth>().TakeDamage(damageDealt);
|
||||||
enemyHealth.NotifySpawner();
|
enemyHealth.NotifySpawner();
|
||||||
|
if (enemyHealth.isBoss && enemyHealth.spawner != null)
|
||||||
|
enemyHealth.spawner.HideBossHealth(enemyHealth);
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +58,8 @@ public class Enemy : MonoBehaviour
|
|||||||
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
transform.position += (Vector3)(direction * distance);
|
transform.position += (Vector3)(direction * distance * 0.45f);
|
||||||
|
knockbackVelocity = direction * Mathf.Max(2.5f, distance * 12f);
|
||||||
|
knockbackEndTime = Time.time + 0.22f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
public class EnemyHealth : MonoBehaviour
|
public class EnemyHealth : MonoBehaviour
|
||||||
@@ -5,6 +6,7 @@ public class EnemyHealth : MonoBehaviour
|
|||||||
public int health = 1;
|
public int health = 1;
|
||||||
public GameObject shatterEffectPrefab;
|
public GameObject shatterEffectPrefab;
|
||||||
public EnemySpawner spawner;
|
public EnemySpawner spawner;
|
||||||
|
public bool isBoss;
|
||||||
private int maxHealth = 1;
|
private int maxHealth = 1;
|
||||||
|
|
||||||
public bool TakeDamage(int damage)
|
public bool TakeDamage(int damage)
|
||||||
@@ -12,9 +14,16 @@ public class EnemyHealth : MonoBehaviour
|
|||||||
if (health <= 0)
|
if (health <= 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
ShowDamageNumber(damage);
|
||||||
health -= damage;
|
health -= damage;
|
||||||
|
if (isBoss && spawner != null)
|
||||||
|
spawner.UpdateBossHealth(this, health, maxHealth);
|
||||||
|
|
||||||
if (health <= 0)
|
if (health <= 0)
|
||||||
{
|
{
|
||||||
|
if (isBoss && spawner != null)
|
||||||
|
spawner.HideBossHealth(this);
|
||||||
|
|
||||||
NotifySpawner();
|
NotifySpawner();
|
||||||
ShatterEffect();
|
ShatterEffect();
|
||||||
NotifyUpgradeManager();
|
NotifyUpgradeManager();
|
||||||
@@ -34,6 +43,8 @@ public class EnemyHealth : MonoBehaviour
|
|||||||
{
|
{
|
||||||
health = newHealth;
|
health = newHealth;
|
||||||
maxHealth = Mathf.Max(1, newHealth);
|
maxHealth = Mathf.Max(1, newHealth);
|
||||||
|
if (isBoss && spawner != null)
|
||||||
|
spawner.UpdateBossHealth(this, health, maxHealth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetHealthPercent()
|
public float GetHealthPercent()
|
||||||
@@ -45,6 +56,8 @@ public class EnemyHealth : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (spawner != null)
|
if (spawner != null)
|
||||||
{
|
{
|
||||||
|
if (isBoss)
|
||||||
|
spawner.HideBossHealth(this);
|
||||||
spawner.EnemyDefeated();
|
spawner.EnemyDefeated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,4 +78,18 @@ public class EnemyHealth : MonoBehaviour
|
|||||||
Instantiate(shatterEffectPrefab, transform.position, Quaternion.identity);
|
Instantiate(shatterEffectPrefab, transform.position, Quaternion.identity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShowDamageNumber(int damage)
|
||||||
|
{
|
||||||
|
GameObject textObject = new GameObject("EnemyDamageText", typeof(TextMeshPro));
|
||||||
|
textObject.transform.position = transform.position + new Vector3(Random.Range(-0.18f, 0.18f), 0.72f, 0f);
|
||||||
|
|
||||||
|
TextMeshPro text = textObject.GetComponent<TextMeshPro>();
|
||||||
|
text.text = damage.ToString();
|
||||||
|
text.fontSize = 3.1f;
|
||||||
|
text.alignment = TextAlignmentOptions.Center;
|
||||||
|
text.color = Color.white;
|
||||||
|
text.sortingOrder = 50;
|
||||||
|
textObject.AddComponent<FloatingWorldText>().Configure(0.75f, new Vector3(0f, 0.58f, 0f));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,14 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
private int enemiesRemaining = 0;
|
private int enemiesRemaining = 0;
|
||||||
private int baseEnemyCount = 5;
|
private int baseEnemyCount = 5;
|
||||||
private int enemyCountIncrease = 3;
|
private int enemyCountIncrease = 3;
|
||||||
private float enemySpeed = 1.5f;
|
private float enemySpeed = 1.16f;
|
||||||
private int enemyBaseHP = 1;
|
private int enemyBaseHP = 1;
|
||||||
private float maxEnemySpeed = 6f;
|
private float maxEnemySpeed = 6f;
|
||||||
private int maxEnemyHP = 20;
|
private int maxEnemyHP = 20;
|
||||||
|
private RectTransform bossHealthBar;
|
||||||
|
private Image bossHealthFill;
|
||||||
|
private TextMeshProUGUI bossHealthText;
|
||||||
|
private EnemyHealth activeBoss;
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
@@ -74,9 +78,8 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
Enemy enemyComponent = newEnemy.GetComponent<Enemy>();
|
Enemy enemyComponent = newEnemy.GetComponent<Enemy>();
|
||||||
if (enemyComponent != null)
|
if (enemyComponent != null)
|
||||||
{
|
{
|
||||||
// Lower speed scale to 0.1f
|
float waveSpeedBonus = currentWave * 0.028f;
|
||||||
float waveSpeedBonus = currentWave * 0.1f;
|
float stageSpeedBonus = (currentStage - 1) * 0.025f;
|
||||||
float stageSpeedBonus = (currentStage - 1) * 0.08f;
|
|
||||||
enemyComponent.speed = Mathf.Min(maxEnemySpeed, enemySpeed + waveSpeedBonus + stageSpeedBonus);
|
enemyComponent.speed = Mathf.Min(maxEnemySpeed, enemySpeed + waveSpeedBonus + stageSpeedBonus);
|
||||||
if (boss)
|
if (boss)
|
||||||
enemyComponent.speed *= 0.72f;
|
enemyComponent.speed *= 0.72f;
|
||||||
@@ -85,14 +88,14 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
EnemyHealth enemyHealth = newEnemy.GetComponent<EnemyHealth>();
|
EnemyHealth enemyHealth = newEnemy.GetComponent<EnemyHealth>();
|
||||||
if (enemyHealth != null)
|
if (enemyHealth != null)
|
||||||
{
|
{
|
||||||
// For HP: baseHP + wave * 0.3 (round it)
|
int scaledHP = enemyBaseHP + Mathf.FloorToInt(currentWave * 0.14f) + Mathf.FloorToInt((currentStage - 1) * 0.25f);
|
||||||
int scaledHP = enemyBaseHP + Mathf.RoundToInt(currentWave * 0.3f) + Mathf.RoundToInt((currentStage - 1) * 0.5f);
|
|
||||||
if (boss)
|
if (boss)
|
||||||
scaledHP = Mathf.RoundToInt((scaledHP + currentStage + currentWave) * 4.5f);
|
scaledHP = Mathf.RoundToInt((scaledHP + currentStage + currentWave * 0.48f) * 3.05f);
|
||||||
scaledHP = Mathf.Min(boss ? maxEnemyHP * 4 : maxEnemyHP, scaledHP);
|
scaledHP = Mathf.Min(boss ? maxEnemyHP * 4 : maxEnemyHP, scaledHP);
|
||||||
enemyHealth.SetHealth(scaledHP);
|
enemyHealth.SetHealth(scaledHP);
|
||||||
|
|
||||||
enemyHealth.spawner = this;
|
enemyHealth.spawner = this;
|
||||||
|
enemyHealth.isBoss = boss;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boss)
|
if (boss)
|
||||||
@@ -102,6 +105,44 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
SpriteRenderer spriteRenderer = newEnemy.GetComponent<SpriteRenderer>();
|
SpriteRenderer spriteRenderer = newEnemy.GetComponent<SpriteRenderer>();
|
||||||
if (spriteRenderer != null)
|
if (spriteRenderer != null)
|
||||||
spriteRenderer.color = new Color(0.95f, 0.32f, 0.28f, 1f);
|
spriteRenderer.color = new Color(0.95f, 0.32f, 0.28f, 1f);
|
||||||
|
|
||||||
|
activeBoss = enemyHealth;
|
||||||
|
EnsureBossHealthBar();
|
||||||
|
UpdateBossHealth(enemyHealth, enemyHealth.GetCurrentHealth(), enemyHealth.GetCurrentHealth());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ApplyEnemyVariant(newEnemy, enemyComponent, enemyHealth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyEnemyVariant(GameObject enemyObject, Enemy enemyComponent, EnemyHealth enemyHealth)
|
||||||
|
{
|
||||||
|
if (enemyComponent == null || enemyHealth == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float waveFactor = Mathf.Clamp01(currentWave / 15f);
|
||||||
|
float fastChance = Mathf.Lerp(0.08f, 0.24f, waveFactor);
|
||||||
|
float tankChance = Mathf.Lerp(0.05f, 0.2f, waveFactor);
|
||||||
|
float roll = Random.value;
|
||||||
|
|
||||||
|
SpriteRenderer spriteRenderer = enemyObject.GetComponent<SpriteRenderer>();
|
||||||
|
if (roll < fastChance)
|
||||||
|
{
|
||||||
|
enemyObject.name = "Fast Enemy";
|
||||||
|
enemyComponent.speed *= 1.65f;
|
||||||
|
enemyObject.transform.localScale *= 0.82f;
|
||||||
|
if (spriteRenderer != null)
|
||||||
|
spriteRenderer.color = new Color(0.35f, 0.78f, 1f, 1f);
|
||||||
|
}
|
||||||
|
else if (roll < fastChance + tankChance)
|
||||||
|
{
|
||||||
|
enemyObject.name = "Tank Enemy";
|
||||||
|
enemyComponent.speed *= 0.62f;
|
||||||
|
enemyObject.transform.localScale *= 1.32f;
|
||||||
|
enemyHealth.SetHealth(Mathf.Max(2, Mathf.RoundToInt(enemyHealth.GetCurrentHealth() * 2.35f)));
|
||||||
|
if (spriteRenderer != null)
|
||||||
|
spriteRenderer.color = new Color(0.78f, 0.56f, 0.28f, 1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +178,7 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
UpgradeManager upgradeManager = FindAnyObjectByType<UpgradeManager>();
|
UpgradeManager upgradeManager = FindAnyObjectByType<UpgradeManager>();
|
||||||
if (upgradeManager != null)
|
if (upgradeManager != null)
|
||||||
{
|
{
|
||||||
upgradeManager.ShowWaveUpgradeChoice(PrepareNextWave);
|
StartCoroutine(ShowUpgradeChoiceAfterIntermission(upgradeManager, IsBossWave(currentWave)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +193,15 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
UpdateWaveText();
|
UpdateWaveText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerator ShowUpgradeChoiceAfterIntermission(UpgradeManager upgradeManager, bool bossWave)
|
||||||
|
{
|
||||||
|
yield return new WaitForSeconds(1f);
|
||||||
|
if (upgradeManager != null)
|
||||||
|
upgradeManager.ShowWaveUpgradeChoice(PrepareNextWave, bossWave);
|
||||||
|
else
|
||||||
|
PrepareNextWave();
|
||||||
|
}
|
||||||
|
|
||||||
int GetNormalEnemyCount(int wave)
|
int GetNormalEnemyCount(int wave)
|
||||||
{
|
{
|
||||||
return baseEnemyCount + wave * enemyCountIncrease + Mathf.Max(0, currentStage - 1);
|
return baseEnemyCount + wave * enemyCountIncrease + Mathf.Max(0, currentStage - 1);
|
||||||
@@ -171,12 +221,47 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
{
|
{
|
||||||
stageComplete = true;
|
stageComplete = true;
|
||||||
isSpawning = false;
|
isSpawning = false;
|
||||||
|
HideBossHealth(activeBoss);
|
||||||
int nextStage = currentStage + 1;
|
int nextStage = currentStage + 1;
|
||||||
PlayerPrefs.SetInt(CurrentStageKey, nextStage);
|
PlayerPrefs.SetInt(CurrentStageKey, nextStage);
|
||||||
PlayerPrefs.Save();
|
PlayerPrefs.Save();
|
||||||
ShowWinScreen(nextStage);
|
ShowWinScreen(nextStage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateBossHealth(EnemyHealth boss, int currentHealth, int maxHealth)
|
||||||
|
{
|
||||||
|
if (boss == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
activeBoss = boss;
|
||||||
|
EnsureBossHealthBar();
|
||||||
|
if (bossHealthBar != null)
|
||||||
|
bossHealthBar.gameObject.SetActive(true);
|
||||||
|
|
||||||
|
float percent = maxHealth <= 0 ? 0f : Mathf.Clamp01((float)Mathf.Max(0, currentHealth) / maxHealth);
|
||||||
|
if (bossHealthFill != null)
|
||||||
|
{
|
||||||
|
bossHealthFill.fillAmount = 1f;
|
||||||
|
bossHealthFill.color = Color.Lerp(new Color(0.55f, 0.05f, 0.06f, 1f), new Color(0.95f, 0.2f, 0.18f, 1f), percent);
|
||||||
|
RectTransform fillRect = bossHealthFill.rectTransform;
|
||||||
|
fillRect.anchorMax = new Vector2(percent, fillRect.anchorMax.y);
|
||||||
|
fillRect.offsetMax = Vector2.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bossHealthText != null)
|
||||||
|
bossHealthText.text = $"BOSS {Mathf.Max(0, currentHealth)}/{maxHealth}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideBossHealth(EnemyHealth boss)
|
||||||
|
{
|
||||||
|
if (boss != null && activeBoss != null && boss != activeBoss)
|
||||||
|
return;
|
||||||
|
|
||||||
|
activeBoss = null;
|
||||||
|
if (bossHealthBar != null)
|
||||||
|
bossHealthBar.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
void ShowWinScreen(int nextStage)
|
void ShowWinScreen(int nextStage)
|
||||||
{
|
{
|
||||||
Time.timeScale = 0f;
|
Time.timeScale = 0f;
|
||||||
@@ -214,6 +299,66 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
SceneManager.LoadScene("MainMenu");
|
SceneManager.LoadScene("MainMenu");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EnsureBossHealthBar()
|
||||||
|
{
|
||||||
|
if (bossHealthBar != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Canvas canvas = FindAnyObjectByType<Canvas>();
|
||||||
|
if (canvas == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Transform existing = canvas.transform.Find("BossHealthBar");
|
||||||
|
bossHealthBar = existing != null ? existing.GetComponent<RectTransform>() : CreateRect("BossHealthBar", canvas.transform);
|
||||||
|
bossHealthBar.anchorMin = new Vector2(0.5f, 1f);
|
||||||
|
bossHealthBar.anchorMax = new Vector2(0.5f, 1f);
|
||||||
|
bossHealthBar.pivot = new Vector2(0.5f, 1f);
|
||||||
|
bossHealthBar.anchoredPosition = new Vector2(0f, -126f);
|
||||||
|
bossHealthBar.sizeDelta = new Vector2(360f, 48f);
|
||||||
|
bossHealthBar.gameObject.SetActive(false);
|
||||||
|
|
||||||
|
Image panel = bossHealthBar.GetComponent<Image>();
|
||||||
|
if (panel == null)
|
||||||
|
panel = bossHealthBar.gameObject.AddComponent<Image>();
|
||||||
|
panel.color = new Color(0.05f, 0.03f, 0.035f, 0.88f);
|
||||||
|
|
||||||
|
RectTransform track = bossHealthBar.Find("Track")?.GetComponent<RectTransform>();
|
||||||
|
if (track == null)
|
||||||
|
{
|
||||||
|
track = CreateRect("Track", bossHealthBar);
|
||||||
|
track.anchorMin = new Vector2(0f, 0f);
|
||||||
|
track.anchorMax = new Vector2(1f, 0f);
|
||||||
|
track.pivot = new Vector2(0.5f, 0f);
|
||||||
|
track.anchoredPosition = new Vector2(0f, 8f);
|
||||||
|
track.sizeDelta = new Vector2(-24f, 16f);
|
||||||
|
Image trackImage = track.gameObject.AddComponent<Image>();
|
||||||
|
trackImage.color = new Color(0.16f, 0.08f, 0.08f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bossHealthFill = track.Find("Fill")?.GetComponent<Image>();
|
||||||
|
if (bossHealthFill == null)
|
||||||
|
{
|
||||||
|
RectTransform fill = CreateRect("Fill", track);
|
||||||
|
fill.anchorMin = Vector2.zero;
|
||||||
|
fill.anchorMax = Vector2.one;
|
||||||
|
fill.offsetMin = Vector2.zero;
|
||||||
|
fill.offsetMax = Vector2.zero;
|
||||||
|
bossHealthFill = fill.gameObject.AddComponent<Image>();
|
||||||
|
}
|
||||||
|
bossHealthFill.type = Image.Type.Simple;
|
||||||
|
|
||||||
|
bossHealthText = bossHealthBar.Find("Text")?.GetComponent<TextMeshProUGUI>();
|
||||||
|
if (bossHealthText == null)
|
||||||
|
{
|
||||||
|
bossHealthText = CreateOverlayText("Text", bossHealthBar, "BOSS", 15f, FontStyles.Bold);
|
||||||
|
bossHealthText.rectTransform.anchorMin = new Vector2(0f, 1f);
|
||||||
|
bossHealthText.rectTransform.anchorMax = new Vector2(1f, 1f);
|
||||||
|
bossHealthText.rectTransform.pivot = new Vector2(0.5f, 1f);
|
||||||
|
bossHealthText.rectTransform.anchoredPosition = new Vector2(0f, -4f);
|
||||||
|
bossHealthText.rectTransform.sizeDelta = new Vector2(-16f, 20f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EnsureWaveText()
|
void EnsureWaveText()
|
||||||
{
|
{
|
||||||
if (waveText != null)
|
if (waveText != null)
|
||||||
@@ -345,6 +490,14 @@ public class EnemySpawner : MonoBehaviour
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RectTransform CreateRect(string name, Transform parent)
|
||||||
|
{
|
||||||
|
GameObject obj = new GameObject(name, typeof(RectTransform));
|
||||||
|
RectTransform rect = obj.GetComponent<RectTransform>();
|
||||||
|
rect.SetParent(parent, false);
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
static void PlaceOverlayRect(RectTransform rect, float x, float y, float width, float height)
|
static void PlaceOverlayRect(RectTransform rect, float x, float y, float width, float height)
|
||||||
{
|
{
|
||||||
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class FloatingWorldText : MonoBehaviour
|
||||||
|
{
|
||||||
|
TextMeshPro text;
|
||||||
|
Vector3 startPosition;
|
||||||
|
Vector3 offset = new Vector3(0f, 0.6f, 0f);
|
||||||
|
Color startColor = Color.white;
|
||||||
|
float duration = 0.75f;
|
||||||
|
float elapsed;
|
||||||
|
|
||||||
|
public void Configure(float fadeDuration, Vector3 moveOffset)
|
||||||
|
{
|
||||||
|
duration = Mathf.Max(0.05f, fadeDuration);
|
||||||
|
offset = moveOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
text = GetComponent<TextMeshPro>();
|
||||||
|
startPosition = transform.position;
|
||||||
|
if (text != null)
|
||||||
|
startColor = text.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
elapsed += Time.deltaTime;
|
||||||
|
float t = Mathf.Clamp01(elapsed / duration);
|
||||||
|
transform.position = Vector3.Lerp(startPosition, startPosition + offset, t);
|
||||||
|
|
||||||
|
if (text != null)
|
||||||
|
text.color = new Color(startColor.r, startColor.g, startColor.b, 1f - t);
|
||||||
|
|
||||||
|
if (elapsed >= duration)
|
||||||
|
Destroy(gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
public class RarityPulse : MonoBehaviour
|
||||||
|
{
|
||||||
|
Outline outline;
|
||||||
|
Color baseColor = Color.white;
|
||||||
|
|
||||||
|
public void Configure(Outline targetOutline, Color color)
|
||||||
|
{
|
||||||
|
outline = targetOutline;
|
||||||
|
baseColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
if (outline == null)
|
||||||
|
outline = GetComponent<Outline>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
if (outline == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float pulse = 0.55f + Mathf.Sin(Time.unscaledTime * 4.5f) * 0.25f;
|
||||||
|
Color color = baseColor;
|
||||||
|
color.a = Mathf.Clamp01(pulse);
|
||||||
|
outline.effectColor = color;
|
||||||
|
float distance = 2.5f + Mathf.Sin(Time.unscaledTime * 4.5f) * 0.8f;
|
||||||
|
outline.effectDistance = new Vector2(distance, -distance);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,8 @@ public class Tower : MonoBehaviour
|
|||||||
private int shotsFired;
|
private int shotsFired;
|
||||||
private float cooldownUntil;
|
private float cooldownUntil;
|
||||||
private bool cannonBaselineApplied;
|
private bool cannonBaselineApplied;
|
||||||
|
private SpriteRenderer rangeCircle;
|
||||||
|
private Camera mainCamera;
|
||||||
public int extraBullets = 0; // For "Shoot +1 bullet"
|
public int extraBullets = 0; // For "Shoot +1 bullet"
|
||||||
public bool homingEnabled = false;
|
public bool homingEnabled = false;
|
||||||
public bool ricochetEnabled = false;
|
public bool ricochetEnabled = false;
|
||||||
@@ -51,10 +53,15 @@ public class Tower : MonoBehaviour
|
|||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
ApplyCannonBaseline();
|
ApplyCannonBaseline();
|
||||||
|
EnsureRangeCircle();
|
||||||
|
FitCameraToRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
|
UpdateRangeCircle();
|
||||||
|
FitCameraToRange();
|
||||||
|
|
||||||
if (Time.time < cooldownUntil)
|
if (Time.time < cooldownUntil)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -65,6 +72,69 @@ public class Tower : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EnsureRangeCircle()
|
||||||
|
{
|
||||||
|
if (rangeCircle != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GameObject rangeObject = new GameObject("TowerRangeCircle", typeof(SpriteRenderer));
|
||||||
|
rangeObject.transform.position = transform.position;
|
||||||
|
rangeCircle = rangeObject.GetComponent<SpriteRenderer>();
|
||||||
|
rangeCircle.sprite = CreateCircleSprite();
|
||||||
|
rangeCircle.color = new Color(0.72f, 0.74f, 0.78f, 0.16f);
|
||||||
|
rangeCircle.sortingOrder = -10;
|
||||||
|
UpdateRangeCircle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateRangeCircle()
|
||||||
|
{
|
||||||
|
if (rangeCircle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
rangeCircle.transform.position = transform.position;
|
||||||
|
rangeCircle.transform.localScale = Vector3.one * (attackRange * 2.08f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FitCameraToRange()
|
||||||
|
{
|
||||||
|
if (mainCamera == null)
|
||||||
|
mainCamera = Camera.main;
|
||||||
|
if (mainCamera == null || !mainCamera.orthographic)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float padding = 0.45f;
|
||||||
|
float verticalSize = attackRange + padding;
|
||||||
|
float horizontalSize = (attackRange + padding) / Mathf.Max(0.1f, mainCamera.aspect);
|
||||||
|
mainCamera.orthographicSize = Mathf.Max(5.5f, verticalSize, horizontalSize);
|
||||||
|
Vector3 cameraPosition = transform.position;
|
||||||
|
cameraPosition.z = mainCamera.transform.position.z;
|
||||||
|
mainCamera.transform.position = cameraPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Sprite CreateCircleSprite()
|
||||||
|
{
|
||||||
|
const int size = 128;
|
||||||
|
Texture2D texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
|
||||||
|
texture.filterMode = FilterMode.Bilinear;
|
||||||
|
Vector2 center = new Vector2((size - 1) * 0.5f, (size - 1) * 0.5f);
|
||||||
|
float radius = size * 0.47f;
|
||||||
|
|
||||||
|
for (int y = 0; y < size; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < size; x++)
|
||||||
|
{
|
||||||
|
float distance = Vector2.Distance(new Vector2(x, y), center);
|
||||||
|
float fillAlpha = distance <= radius ? 0.75f : 0f;
|
||||||
|
float edge = Mathf.Clamp01(1f - Mathf.Abs(distance - radius) / 2.2f);
|
||||||
|
float alpha = Mathf.Max(fillAlpha * 0.18f, edge);
|
||||||
|
texture.SetPixel(x, y, new Color(1f, 1f, 1f, alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
texture.Apply();
|
||||||
|
return Sprite.Create(texture, new Rect(0f, 0f, size, size), new Vector2(0.5f, 0.5f), size);
|
||||||
|
}
|
||||||
|
|
||||||
void ApplyCannonBaseline()
|
void ApplyCannonBaseline()
|
||||||
{
|
{
|
||||||
if (cannonBaselineApplied)
|
if (cannonBaselineApplied)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
||||||
public class TowerHealth : MonoBehaviour
|
public class TowerHealth : MonoBehaviour
|
||||||
@@ -15,6 +16,7 @@ public class TowerHealth : MonoBehaviour
|
|||||||
public Color lowHealthColor = new Color(0.95f, 0.22f, 0.18f);
|
public Color lowHealthColor = new Color(0.95f, 0.22f, 0.18f);
|
||||||
|
|
||||||
private int currentHealth;
|
private int currentHealth;
|
||||||
|
private bool isDead;
|
||||||
private SpriteRenderer spriteRenderer;
|
private SpriteRenderer spriteRenderer;
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
@@ -32,6 +34,9 @@ public class TowerHealth : MonoBehaviour
|
|||||||
|
|
||||||
public void TakeDamage(int damage)
|
public void TakeDamage(int damage)
|
||||||
{
|
{
|
||||||
|
if (isDead)
|
||||||
|
return;
|
||||||
|
|
||||||
currentHealth -= damage;
|
currentHealth -= damage;
|
||||||
if (currentHealth < 0) currentHealth = 0;
|
if (currentHealth < 0) currentHealth = 0;
|
||||||
ShowFloatingDamage(damage);
|
ShowFloatingDamage(damage);
|
||||||
@@ -39,7 +44,8 @@ public class TowerHealth : MonoBehaviour
|
|||||||
|
|
||||||
if (currentHealth <= 0)
|
if (currentHealth <= 0)
|
||||||
{
|
{
|
||||||
Debug.Log("Tower Destroyed! Game Over.");
|
isDead = true;
|
||||||
|
ShowStageLostScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,8 +55,12 @@ public class TowerHealth : MonoBehaviour
|
|||||||
|
|
||||||
if (healthFill != null)
|
if (healthFill != null)
|
||||||
{
|
{
|
||||||
healthFill.fillAmount = healthPercentage;
|
healthFill.fillAmount = 1f;
|
||||||
healthFill.color = Color.Lerp(lowHealthColor, highHealthColor, healthPercentage);
|
healthFill.color = Color.Lerp(lowHealthColor, highHealthColor, healthPercentage);
|
||||||
|
|
||||||
|
RectTransform fillRect = healthFill.rectTransform;
|
||||||
|
fillRect.anchorMax = new Vector2(healthPercentage, fillRect.anchorMax.y);
|
||||||
|
fillRect.offsetMax = Vector2.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (healthText != null)
|
if (healthText != null)
|
||||||
@@ -152,6 +162,56 @@ public class TowerHealth : MonoBehaviour
|
|||||||
healthFill.fillOrigin = (int)Image.OriginHorizontal.Left;
|
healthFill.fillOrigin = (int)Image.OriginHorizontal.Left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
healthFill.type = Image.Type.Simple;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowStageLostScreen()
|
||||||
|
{
|
||||||
|
Time.timeScale = 0f;
|
||||||
|
|
||||||
|
Canvas canvas = FindAnyObjectByType<Canvas>();
|
||||||
|
if (canvas == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GameObject overlay = new GameObject("StageLostOverlay", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||||
|
RectTransform rect = overlay.GetComponent<RectTransform>();
|
||||||
|
rect.SetParent(canvas.transform, false);
|
||||||
|
rect.anchorMin = Vector2.zero;
|
||||||
|
rect.anchorMax = Vector2.one;
|
||||||
|
rect.offsetMin = Vector2.zero;
|
||||||
|
rect.offsetMax = Vector2.zero;
|
||||||
|
rect.SetAsLastSibling();
|
||||||
|
|
||||||
|
Image background = overlay.GetComponent<Image>();
|
||||||
|
background.color = new Color(0f, 0f, 0f, 0.8f);
|
||||||
|
background.raycastTarget = true;
|
||||||
|
|
||||||
|
TextMeshProUGUI title = CreateText("Title", rect, "STAGE LOST", 44f);
|
||||||
|
title.alignment = TextAlignmentOptions.Center;
|
||||||
|
PlaceOverlayRect(title.rectTransform, 0f, 64f, 420f, 68f);
|
||||||
|
|
||||||
|
TextMeshProUGUI subtitle = CreateText("Subtitle", rect, "Wave progress and upgrades were lost.", 19f);
|
||||||
|
subtitle.fontStyle = FontStyles.Normal;
|
||||||
|
subtitle.alignment = TextAlignmentOptions.Center;
|
||||||
|
PlaceOverlayRect(subtitle.rectTransform, 0f, 12f, 520f, 42f);
|
||||||
|
|
||||||
|
Button menuButton = CreateOverlayButton("MenuButton", rect, "MAIN MENU", ReturnToMenu);
|
||||||
|
PlaceOverlayRect(menuButton.GetComponent<RectTransform>(), 0f, -64f, 220f, 54f);
|
||||||
|
|
||||||
|
StartCoroutine(ReturnToMenuAfterDelay());
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator ReturnToMenuAfterDelay()
|
||||||
|
{
|
||||||
|
yield return new WaitForSecondsRealtime(2.25f);
|
||||||
|
ReturnToMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReturnToMenu()
|
||||||
|
{
|
||||||
|
Time.timeScale = 1f;
|
||||||
|
SceneManager.LoadScene("MainMenu");
|
||||||
}
|
}
|
||||||
|
|
||||||
static RectTransform CreateRect(string name, Transform parent)
|
static RectTransform CreateRect(string name, Transform parent)
|
||||||
@@ -177,6 +237,44 @@ public class TowerHealth : MonoBehaviour
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Button CreateOverlayButton(string name, Transform parent, string label, UnityEngine.Events.UnityAction onClick)
|
||||||
|
{
|
||||||
|
GameObject obj = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
|
||||||
|
RectTransform rect = obj.GetComponent<RectTransform>();
|
||||||
|
rect.SetParent(parent, false);
|
||||||
|
|
||||||
|
Image image = obj.GetComponent<Image>();
|
||||||
|
image.color = new Color(0.18f, 0.5f, 0.74f, 1f);
|
||||||
|
|
||||||
|
Button button = obj.GetComponent<Button>();
|
||||||
|
ColorBlock colors = button.colors;
|
||||||
|
colors.normalColor = image.color;
|
||||||
|
colors.highlightedColor = new Color(0.25f, 0.62f, 0.88f, 1f);
|
||||||
|
colors.pressedColor = new Color(0.12f, 0.36f, 0.56f, 1f);
|
||||||
|
colors.selectedColor = colors.highlightedColor;
|
||||||
|
colors.disabledColor = new Color(0.16f, 0.18f, 0.22f, 1f);
|
||||||
|
colors.colorMultiplier = 1f;
|
||||||
|
button.colors = colors;
|
||||||
|
button.onClick.AddListener(onClick);
|
||||||
|
|
||||||
|
TextMeshProUGUI text = CreateText("Text", rect, label, 18f);
|
||||||
|
text.alignment = TextAlignmentOptions.Center;
|
||||||
|
text.rectTransform.anchorMin = Vector2.zero;
|
||||||
|
text.rectTransform.anchorMax = Vector2.one;
|
||||||
|
text.rectTransform.offsetMin = Vector2.zero;
|
||||||
|
text.rectTransform.offsetMax = Vector2.zero;
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PlaceOverlayRect(RectTransform rect, float x, float y, float width, float height)
|
||||||
|
{
|
||||||
|
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||||
|
rect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||||
|
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||||
|
rect.anchoredPosition = new Vector2(x, y);
|
||||||
|
rect.sizeDelta = new Vector2(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
void ShowFloatingDamage(int damage)
|
void ShowFloatingDamage(int damage)
|
||||||
{
|
{
|
||||||
if (floatingTextPrefab != null)
|
if (floatingTextPrefab != null)
|
||||||
|
|||||||
@@ -41,9 +41,12 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
readonly Dictionary<UpgradeCard.UpgradeType, int> upgradeCosts = new Dictionary<UpgradeCard.UpgradeType, int>();
|
readonly Dictionary<UpgradeCard.UpgradeType, int> upgradeCosts = new Dictionary<UpgradeCard.UpgradeType, int>();
|
||||||
|
|
||||||
GameObject choiceOverlay;
|
GameObject choiceOverlay;
|
||||||
|
RectTransform currentCardRow;
|
||||||
|
bool currentGuaranteeEpicOrHigher;
|
||||||
Action currentChoiceComplete;
|
Action currentChoiceComplete;
|
||||||
int bonusBitsPerKill;
|
int bonusBitsPerKill;
|
||||||
float hitBitChance;
|
float hitBitChance;
|
||||||
|
int rerollCost = 1;
|
||||||
|
|
||||||
static readonly CardDefinition[] CannonCards =
|
static readonly CardDefinition[] CannonCards =
|
||||||
{
|
{
|
||||||
@@ -99,7 +102,7 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowWaveUpgradeChoice(Action onChoiceComplete)
|
public void ShowWaveUpgradeChoice(Action onChoiceComplete, bool guaranteeEpicOrHigher = false)
|
||||||
{
|
{
|
||||||
if (choiceOverlay != null)
|
if (choiceOverlay != null)
|
||||||
return;
|
return;
|
||||||
@@ -107,8 +110,10 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
InitializeUpgrades();
|
InitializeUpgrades();
|
||||||
EnsureBitsText();
|
EnsureBitsText();
|
||||||
currentChoiceComplete = onChoiceComplete;
|
currentChoiceComplete = onChoiceComplete;
|
||||||
|
currentGuaranteeEpicOrHigher = guaranteeEpicOrHigher;
|
||||||
|
rerollCost = 1;
|
||||||
|
|
||||||
List<UpgradeOffer> offers = BuildOffers();
|
List<UpgradeOffer> offers = BuildOffers(guaranteeEpicOrHigher);
|
||||||
if (offers.Count == 0)
|
if (offers.Count == 0)
|
||||||
{
|
{
|
||||||
CompleteChoice();
|
CompleteChoice();
|
||||||
@@ -117,23 +122,9 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
|
|
||||||
Time.timeScale = 0f;
|
Time.timeScale = 0f;
|
||||||
choiceOverlay = CreateOverlay(offers[0].isFree);
|
choiceOverlay = CreateOverlay(offers[0].isFree);
|
||||||
RectTransform row = CreateRect("UpgradeChoices", choiceOverlay.transform);
|
currentCardRow = CreateChoiceRow(choiceOverlay.transform);
|
||||||
row.anchorMin = new Vector2(0.5f, 0.5f);
|
PopulateChoiceCards(offers);
|
||||||
row.anchorMax = new Vector2(0.5f, 0.5f);
|
CreateRerollButton(choiceOverlay.transform);
|
||||||
row.pivot = new Vector2(0.5f, 0.5f);
|
|
||||||
row.anchoredPosition = new Vector2(0f, -12f);
|
|
||||||
row.sizeDelta = new Vector2(700f, 212f);
|
|
||||||
|
|
||||||
HorizontalLayoutGroup layout = row.gameObject.AddComponent<HorizontalLayoutGroup>();
|
|
||||||
layout.spacing = 16f;
|
|
||||||
layout.childAlignment = TextAnchor.MiddleCenter;
|
|
||||||
layout.childControlWidth = true;
|
|
||||||
layout.childControlHeight = true;
|
|
||||||
layout.childForceExpandWidth = false;
|
|
||||||
layout.childForceExpandHeight = false;
|
|
||||||
|
|
||||||
foreach (UpgradeOffer offer in offers)
|
|
||||||
CreateChoiceCard(offer, row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowSpecialUpgradeChoice()
|
public void ShowSpecialUpgradeChoice()
|
||||||
@@ -160,7 +151,7 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<UpgradeOffer> BuildOffers()
|
List<UpgradeOffer> BuildOffers(bool guaranteeEpicOrHigher)
|
||||||
{
|
{
|
||||||
List<UpgradeOffer> paidOffers = new List<UpgradeOffer>();
|
List<UpgradeOffer> paidOffers = new List<UpgradeOffer>();
|
||||||
foreach (CardDefinition card in CannonCards)
|
foreach (CardDefinition card in CannonCards)
|
||||||
@@ -182,7 +173,7 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (paidOffers.Count > 0)
|
if (paidOffers.Count > 0)
|
||||||
return PickRandomWeighted(paidOffers, 3);
|
return PickRandomWeighted(paidOffers, 3, guaranteeEpicOrHigher);
|
||||||
|
|
||||||
return new List<UpgradeOffer>
|
return new List<UpgradeOffer>
|
||||||
{
|
{
|
||||||
@@ -207,11 +198,28 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<UpgradeOffer> PickRandomWeighted(List<UpgradeOffer> source, int count)
|
static List<UpgradeOffer> PickRandomWeighted(List<UpgradeOffer> source, int count, bool guaranteeEpicOrHigher)
|
||||||
{
|
{
|
||||||
List<UpgradeOffer> pool = new List<UpgradeOffer>(source);
|
List<UpgradeOffer> pool = new List<UpgradeOffer>(source);
|
||||||
List<UpgradeOffer> result = new List<UpgradeOffer>();
|
List<UpgradeOffer> result = new List<UpgradeOffer>();
|
||||||
|
|
||||||
|
if (guaranteeEpicOrHigher)
|
||||||
|
{
|
||||||
|
List<UpgradeOffer> highRarity = new List<UpgradeOffer>();
|
||||||
|
foreach (UpgradeOffer offer in pool)
|
||||||
|
{
|
||||||
|
if (IsEpicOrHigher(offer.rarity))
|
||||||
|
highRarity.Add(offer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highRarity.Count > 0)
|
||||||
|
{
|
||||||
|
UpgradeOffer guaranteed = highRarity[UnityEngine.Random.Range(0, highRarity.Count)];
|
||||||
|
result.Add(guaranteed);
|
||||||
|
pool.Remove(guaranteed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (pool.Count > 0 && result.Count < count)
|
while (pool.Count > 0 && result.Count < count)
|
||||||
{
|
{
|
||||||
float totalWeight = 0f;
|
float totalWeight = 0f;
|
||||||
@@ -237,6 +245,11 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool IsEpicOrHigher(string rarity)
|
||||||
|
{
|
||||||
|
return rarity == "Epic" || rarity == "Legendary";
|
||||||
|
}
|
||||||
|
|
||||||
static float GetRarityWeight(string rarity)
|
static float GetRarityWeight(string rarity)
|
||||||
{
|
{
|
||||||
switch (rarity)
|
switch (rarity)
|
||||||
@@ -265,6 +278,12 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
Image image = cardObject.GetComponent<Image>();
|
Image image = cardObject.GetComponent<Image>();
|
||||||
image.color = offer.isFree ? new Color(0.11f, 0.17f, 0.13f, 0.98f) : GetRarityColor(offer.rarity);
|
image.color = offer.isFree ? new Color(0.11f, 0.17f, 0.13f, 0.98f) : GetRarityColor(offer.rarity);
|
||||||
|
|
||||||
|
Outline outline = cardObject.AddComponent<Outline>();
|
||||||
|
outline.effectDistance = new Vector2(3f, -3f);
|
||||||
|
outline.effectColor = GetRarityBorderColor(offer.rarity);
|
||||||
|
RarityPulse pulse = cardObject.AddComponent<RarityPulse>();
|
||||||
|
pulse.Configure(outline, GetRarityBorderColor(offer.rarity));
|
||||||
|
|
||||||
Button button = cardObject.GetComponent<Button>();
|
Button button = cardObject.GetComponent<Button>();
|
||||||
ColorBlock colors = button.colors;
|
ColorBlock colors = button.colors;
|
||||||
colors.normalColor = image.color;
|
colors.normalColor = image.color;
|
||||||
@@ -288,6 +307,77 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
PlaceCardText(footer.rectTransform, 12f, 12f, -12f, 28f, false);
|
PlaceCardText(footer.rectTransform, 12f, 12f, -12f, 28f, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RectTransform CreateChoiceRow(Transform parent)
|
||||||
|
{
|
||||||
|
RectTransform row = CreateRect("UpgradeChoices", parent);
|
||||||
|
row.anchorMin = new Vector2(0.5f, 0.5f);
|
||||||
|
row.anchorMax = new Vector2(0.5f, 0.5f);
|
||||||
|
row.pivot = new Vector2(0.5f, 0.5f);
|
||||||
|
row.anchoredPosition = new Vector2(0f, -12f);
|
||||||
|
row.sizeDelta = new Vector2(700f, 212f);
|
||||||
|
|
||||||
|
HorizontalLayoutGroup layout = row.gameObject.AddComponent<HorizontalLayoutGroup>();
|
||||||
|
layout.spacing = 16f;
|
||||||
|
layout.childAlignment = TextAnchor.MiddleCenter;
|
||||||
|
layout.childControlWidth = true;
|
||||||
|
layout.childControlHeight = true;
|
||||||
|
layout.childForceExpandWidth = false;
|
||||||
|
layout.childForceExpandHeight = false;
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopulateChoiceCards(List<UpgradeOffer> offers)
|
||||||
|
{
|
||||||
|
if (currentCardRow == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = currentCardRow.childCount - 1; i >= 0; i--)
|
||||||
|
Destroy(currentCardRow.GetChild(i).gameObject);
|
||||||
|
|
||||||
|
foreach (UpgradeOffer offer in offers)
|
||||||
|
CreateChoiceCard(offer, currentCardRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateRerollButton(Transform parent)
|
||||||
|
{
|
||||||
|
Button rerollButton = CreateButton("RerollButton", parent, GetRerollLabel(), RerollOffers);
|
||||||
|
RectTransform rect = rerollButton.GetComponent<RectTransform>();
|
||||||
|
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||||
|
rect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||||
|
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||||
|
rect.anchoredPosition = new Vector2(0f, -156f);
|
||||||
|
rect.sizeDelta = new Vector2(190f, 42f);
|
||||||
|
rerollButton.interactable = bits >= rerollCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RerollOffers()
|
||||||
|
{
|
||||||
|
if (bits < rerollCost || currentCardRow == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bits -= rerollCost;
|
||||||
|
rerollCost++;
|
||||||
|
UpdateBitsText();
|
||||||
|
PopulateChoiceCards(BuildOffers(currentGuaranteeEpicOrHigher));
|
||||||
|
|
||||||
|
Transform rerollTransform = choiceOverlay != null ? choiceOverlay.transform.Find("RerollButton") : null;
|
||||||
|
if (rerollTransform != null)
|
||||||
|
{
|
||||||
|
TextMeshProUGUI label = rerollTransform.GetComponentInChildren<TextMeshProUGUI>();
|
||||||
|
if (label != null)
|
||||||
|
label.text = GetRerollLabel();
|
||||||
|
|
||||||
|
Button button = rerollTransform.GetComponent<Button>();
|
||||||
|
if (button != null)
|
||||||
|
button.interactable = bits >= rerollCost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetRerollLabel()
|
||||||
|
{
|
||||||
|
return $"REROLL ({rerollCost} BIT{(rerollCost == 1 ? "" : "S")})";
|
||||||
|
}
|
||||||
|
|
||||||
static Color GetRarityColor(string rarity)
|
static Color GetRarityColor(string rarity)
|
||||||
{
|
{
|
||||||
switch (rarity)
|
switch (rarity)
|
||||||
@@ -301,6 +391,19 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Color GetRarityBorderColor(string rarity)
|
||||||
|
{
|
||||||
|
switch (rarity)
|
||||||
|
{
|
||||||
|
case "Common": return new Color(0.62f, 0.64f, 0.66f, 0.85f);
|
||||||
|
case "Uncommon": return new Color(0.22f, 0.9f, 0.34f, 0.9f);
|
||||||
|
case "Rare": return new Color(0.22f, 0.48f, 1f, 0.92f);
|
||||||
|
case "Epic": return new Color(0.68f, 0.25f, 1f, 0.95f);
|
||||||
|
case "Legendary": return new Color(1f, 0.55f, 0.08f, 0.98f);
|
||||||
|
default: return new Color(0.62f, 0.64f, 0.66f, 0.85f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SelectOffer(UpgradeOffer offer)
|
void SelectOffer(UpgradeOffer offer)
|
||||||
{
|
{
|
||||||
if (offer.isFree)
|
if (offer.isFree)
|
||||||
@@ -439,6 +542,8 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
Destroy(choiceOverlay);
|
Destroy(choiceOverlay);
|
||||||
|
|
||||||
choiceOverlay = null;
|
choiceOverlay = null;
|
||||||
|
currentCardRow = null;
|
||||||
|
rerollCost = 1;
|
||||||
Time.timeScale = 1f;
|
Time.timeScale = 1f;
|
||||||
|
|
||||||
Action callback = currentChoiceComplete;
|
Action callback = currentChoiceComplete;
|
||||||
@@ -588,6 +693,34 @@ public class UpgradeManager : MonoBehaviour
|
|||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Button CreateButton(string name, Transform parent, string label, UnityEngine.Events.UnityAction onClick)
|
||||||
|
{
|
||||||
|
GameObject obj = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
|
||||||
|
RectTransform rect = obj.GetComponent<RectTransform>();
|
||||||
|
rect.SetParent(parent, false);
|
||||||
|
|
||||||
|
Image image = obj.GetComponent<Image>();
|
||||||
|
image.color = new Color(0.18f, 0.5f, 0.74f, 1f);
|
||||||
|
|
||||||
|
Button button = obj.GetComponent<Button>();
|
||||||
|
ColorBlock colors = button.colors;
|
||||||
|
colors.normalColor = image.color;
|
||||||
|
colors.highlightedColor = new Color(0.25f, 0.62f, 0.88f, 1f);
|
||||||
|
colors.pressedColor = new Color(0.12f, 0.36f, 0.56f, 1f);
|
||||||
|
colors.selectedColor = colors.highlightedColor;
|
||||||
|
colors.disabledColor = new Color(0.16f, 0.18f, 0.22f, 0.82f);
|
||||||
|
colors.colorMultiplier = 1f;
|
||||||
|
button.colors = colors;
|
||||||
|
button.onClick.AddListener(onClick);
|
||||||
|
|
||||||
|
TextMeshProUGUI text = CreateText("Text", rect, label, 16f, FontStyles.Bold, TextAlignmentOptions.Center);
|
||||||
|
text.rectTransform.anchorMin = Vector2.zero;
|
||||||
|
text.rectTransform.anchorMax = Vector2.one;
|
||||||
|
text.rectTransform.offsetMin = Vector2.zero;
|
||||||
|
text.rectTransform.offsetMax = Vector2.zero;
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
static TextMeshProUGUI CreateText(string name, Transform parent, string text, float fontSize, FontStyles style, TextAlignmentOptions alignment)
|
static TextMeshProUGUI CreateText(string name, Transform parent, string text, float fontSize, FontStyles style, TextAlignmentOptions alignment)
|
||||||
{
|
{
|
||||||
GameObject obj = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
|
GameObject obj = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
|
||||||
|
|||||||
Reference in New Issue
Block a user