成就系统 巨大修改 新的ui 黑白效果 等一堆

This commit is contained in:
FloatGaming
2026-01-23 18:43:23 +08:00
parent f3ece026ca
commit 0e47864569
256 changed files with 134131 additions and 31192 deletions
+493 -26
View File
@@ -4,13 +4,13 @@ using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// EffectSystem 提供技能效果的统一分发接口。
/// - 选择器范围(Selector)决定目标集合(自己、全体友军、除自己外友军、相邻友军、场上敌人、所有人)
/// - 效果类型(EffectType)定义了即时/持续的伤害/治疗/增益/减益等。
/// EffectSystem Чͳһַӿڡ
/// - ѡΧSelectorĿϣԼȫѾԼѾѾϵˡˣ
/// - ЧͣEffectType˼ʱ/˺///ȡ
///
/// 目标实体应实现 ICombatant 接口以便 EffectSystem 能调用统一方法来处理伤害/治疗/增益等;如果没有实现,会在日志中提醒。
/// ĿʵӦʵ ICombatant ӿԱ EffectSystem ܵͳһ˺//ȣûʵ֣־ѡ
///
/// 该脚本为框架:不包含具体技能配置,只提供 ApplyEffect(...) 等方法供其他脚本调用。
/// ýűΪܣãֻṩ ApplyEffect(...) ȷűá
/// </summary>
public class EffectSystem : MonoBehaviour
{
@@ -23,22 +23,32 @@ public class EffectSystem : MonoBehaviour
}
/// <summary>
/// ApplyEffect: 对指定选择器范围内目标应用效果。
/// - selector: 目标选择器
/// - effectType: 效果类型(即时/持续等)
/// - amount: 数值(伤害或治疗总量或效果强度)
/// - duration: 持续时间(对持续效果或 Buff/Debuff 有效),即时效果可为 0
/// - source: 发起者(用于 Self/AlliesExceptSelf 判断或记录)
/// - specificTarget: selector CurrentEnemies 且只想指定单体时可传入具体目标
/// - tickInterval: 持续效果的 tick 间隔(秒),默认 1
/// ApplyEffect: ָѡΧĿӦЧ
/// - selector: Ŀѡ
/// - effectType: Чͣʱ/ȣ
/// - amount: ֵ˺Чǿȣ
/// - duration: ʱԳЧ Buff/Debuff ЧʱЧΪ 0
/// - source: ߣ Self/AlliesExceptSelf жϻ¼
/// - specificTarget: selector Ϊ CurrentEnemies ָֻʱɴĿ
/// - tickInterval: Ч tick Ĭ 1
/// </summary>
public void ApplyEffect(Selector selector, EffectType effectType, float amount, float duration = 0f, GameObject source = null, GameObject specificTarget = null, float tickInterval = 1f)
{
List<GameObject> targets = ResolveTargets(selector, source, specificTarget);
// If caller provided a specificTarget, prefer it as the single target regardless of selector.
// Many callers pass specificTarget when they intend to hit a single unit (ally or enemy).
List<GameObject> targets;
if (specificTarget != null)
{
targets = new List<GameObject> { specificTarget };
}
else
{
targets = ResolveTargets(selector, source, specificTarget);
}
if (targets == null || targets.Count == 0)
{
Debug.LogWarning($"[EffectSystem] No targets resolved for selector={selector}");
Debug.LogWarning($"[EffectSystem] No targets resolved for selector={selector} specificTarget={(specificTarget ? specificTarget.name : "null")}");
return;
}
@@ -67,9 +77,16 @@ public class EffectSystem : MonoBehaviour
break;
case EffectType.DamageSingleAlly:
// damage a single ally (first target)
if (targets.Count > 0)
// damage a single ally (first target) - if selector resolved multiple allies, apply to all to support "AllAllies + DamageSingleAlly" designer usage
if (targets.Count == 1)
{
ApplyInstantDamage(targets[0], amount, source);
}
else if (targets.Count > 1)
{
foreach (var t in targets)
ApplyInstantDamage(t, amount, source);
}
break;
case EffectType.DamageOverTimeAlly:
@@ -155,6 +172,235 @@ public class EffectSystem : MonoBehaviour
}
break;
// New handling for max HP / max Mana modifications via EffectSystem (mirrors direct path in SkillBuilder)
case EffectType.IncreaseMaxHP:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
ally.SetMaxHP(ally.maxHP + Mathf.CeilToInt(amount), false);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy))
{
enemy.SetMaxHP(enemy.maxHP + Mathf.CeilToInt(amount), false);
}
}
break;
case EffectType.DecreaseMaxHP:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
ally.SetMaxHP(Mathf.Max(1, ally.maxHP - Mathf.CeilToInt(amount)), false);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy2))
{
enemy2.SetMaxHP(Mathf.Max(1, enemy2.maxHP - Mathf.CeilToInt(amount)), false);
}
}
break;
case EffectType.IncreaseMaxMana:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
ally.SetMaxMana(ally.maxMana + Mathf.CeilToInt(amount), false);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy3))
{
enemy3.SetMaxMana(enemy3.maxMana + Mathf.CeilToInt(amount), false);
}
}
break;
case EffectType.DecreaseMaxMana:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
ally.SetMaxMana(Mathf.Max(1, ally.maxMana - Mathf.CeilToInt(amount)), false);
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy4))
{
enemy4.SetMaxMana(Mathf.Max(1, enemy4.maxMana - Mathf.CeilToInt(amount)), false);
}
}
break;
// New: Increase/Decrease ScoreEfficiency handled by EffectSystem (mirrors SkillBuilder direct path)
case EffectType.IncreaseScoreEfficiency:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
if (duration <= 0f)
{
ally.scoreEfficiency += amount;
}
else
{
// temporary additive change, revert after duration
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeCoroutine(ally, amount, duration));
}
}
}
break;
case EffectType.DecreaseScoreEfficiency:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
if (duration <= 0f)
{
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency - amount);
}
else
{
StartCoroutine(ApplyTemporaryScoreEfficiencyChangeCoroutine(ally, -amount, duration));
}
}
}
break;
// New: Increase/Decrease Attack handled by EffectSystem (mirrors SkillBuilder direct path)
case EffectType.IncreaseAttack:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
ally.attack += delta;
}
else
{
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(ally, delta, duration));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy5))
{
int deltaE = Mathf.CeilToInt(amount);
if (duration <= 0f)
enemy5.attack += deltaE;
else
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(enemy5, deltaE, duration));
}
}
break;
case EffectType.DecreaseAttack:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
int delta = Mathf.CeilToInt(amount);
if (duration <= 0f)
{
ally.attack = Mathf.Max(0, ally.attack - delta);
}
else
{
// apply decrease now, revert after duration
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(ally, -delta, duration));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemy6))
{
int deltaE = Mathf.CeilToInt(amount);
if (duration <= 0f)
enemy6.attack = Mathf.Max(0, enemy6.attack - deltaE);
else
StartCoroutine(ApplyTemporaryAttackChangeCoroutine(enemy6, -deltaE, duration));
}
}
break;
case EffectType.IncreaseDamageResistance:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
// amount interpreted as additive delta (0..1). Clamp result 0..0.9 to avoid invulnerability
if (duration <= 0f)
{
ally.damageResistance = Mathf.Clamp01(ally.damageResistance + amount);
Debug.Log($"[EffectSystem] IncreaseDamageResistance applied to {t.name}: new resistance={ally.damageResistance}");
}
else
{
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(ally.gameObject, amount, duration));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemyR))
{
if (duration <= 0f)
{
enemyR.damageResistance = Mathf.Clamp01(enemyR.damageResistance + amount);
Debug.Log($"[EffectSystem] IncreaseDamageResistance applied to enemy {t.name}: new resistance={enemyR.damageResistance}");
}
else
{
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(enemyR.gameObject, amount, duration));
}
}
}
break;
case EffectType.DecreaseDamageResistance:
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
if (duration <= 0f)
{
ally.damageResistance = Mathf.Clamp01(ally.damageResistance - amount);
Debug.Log($"[EffectSystem] DecreaseDamageResistance applied to {t.name}: new resistance={ally.damageResistance}");
}
else
{
// apply negative delta
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(ally.gameObject, -amount, duration));
}
}
else if (t.TryGetComponent<EnemyCombatant>(out var enemyR2))
{
if (duration <= 0f)
{
enemyR2.damageResistance = Mathf.Clamp01(enemyR2.damageResistance - amount);
Debug.Log($"[EffectSystem] DecreaseDamageResistance applied to enemy {t.name}: new resistance={enemyR2.damageResistance}");
}
else
{
StartCoroutine(ApplyTemporaryDamageResistanceChangeCoroutine(enemyR2.gameObject, -amount, duration));
}
}
}
break;
default:
Debug.LogWarning($"[EffectSystem] Unhandled EffectType {effectType}");
break;
@@ -171,6 +417,9 @@ public class EffectSystem : MonoBehaviour
{
List<GameObject> list = new List<GameObject>();
// prefer teamUIController when available for authoritative ally/enemy objects
var ui = teamUIController.Instance ?? FindObjectOfType<teamUIController>();
switch (selector)
{
case Selector.Self:
@@ -178,13 +427,39 @@ public class EffectSystem : MonoBehaviour
break;
case Selector.AllAllies:
list.AddRange(FindAllAllies());
if (ui != null)
{
int count = ui.allySlotIds != null ? ui.allySlotIds.Count : 5;
for (int i = 0; i < count; i++)
{
var go = ui.GetAllyObjectBySlot(i);
if (go != null && !list.Contains(go)) list.Add(go);
}
}
else
{
list.AddRange(FindAllAllies());
}
break;
case Selector.AllAlliesExceptSelf:
list.AddRange(FindAllAllies());
if (source != null)
list.RemoveAll(g => g == null || g == source || g.gameObject == source.gameObject);
if (ui != null)
{
int count = ui.allySlotIds != null ? ui.allySlotIds.Count : 5;
for (int i = 0; i < count; i++)
{
var go = ui.GetAllyObjectBySlot(i);
if (go != null && !list.Contains(go)) list.Add(go);
}
if (source != null)
list.RemoveAll(g => g == null || g == source || g.gameObject == source.gameObject);
}
else
{
list.AddRange(FindAllAllies());
if (source != null)
list.RemoveAll(g => g == null || g == source || g.gameObject == source.gameObject);
}
break;
case Selector.AdjacentAllies:
@@ -214,7 +489,6 @@ public class EffectSystem : MonoBehaviour
if (slotIndex >= 0)
{
var ui = teamUIController.Instance;
if (ui != null)
{
try
@@ -283,18 +557,41 @@ public class EffectSystem : MonoBehaviour
var uniq = new List<GameObject>();
foreach (var g in list)
if (!uniq.Contains(g)) uniq.Add(g);
// Debug: log resolved targets for diagnosis
if (uniq.Count == 0)
Debug.LogWarning($"[EffectSystem] ResolveTargets -> selector={selector} returned 0 targets (source={(source ? source.name : "null")}, specificTarget={(specificTarget ? specificTarget.name : "null")})");
else
Debug.Log($"[EffectSystem] ResolveTargets -> selector={selector} resolved {uniq.Count} targets: {string.Join(",", uniq.ConvertAll(x => x != null ? x.name : "null"))}");
return uniq;
}
private IEnumerable<GameObject> FindAllAllies()
{
List<GameObject> allies = new List<GameObject>();
// try names ally_01..ally_05
// prefer teamUIController if available to get authoritative ally objects
var ui = teamUIController.Instance;
if (ui != null)
{
try
{
int count = ui.allySlotIds != null ? ui.allySlotIds.Count : 5;
for (int i = 0; i < count; i++)
{
var go = ui.GetAllyObjectBySlot(i);
if (go != null && !allies.Contains(go)) allies.Add(go);
}
}
catch { /* ignore and fallback */ }
}
// fallback: try names ally_01..ally_05 (keeps compatibility with legacy scene setups)
for (int i = 1; i <= 5; i++)
{
string name = $"ally_0{i}"; // ally_01..ally_05
var go = GameObject.Find(name);
if (go != null) allies.Add(go);
if (go != null && !allies.Contains(go)) allies.Add(go);
}
// also include objects tagged "Ally"
@@ -341,13 +638,53 @@ public class EffectSystem : MonoBehaviour
}
#region Instant / OverTime Helpers
private bool AreAllEnemiesDead()
{
var enemies = GameObject.FindObjectsOfType<EnemyCombatant>(true);
if (enemies.Length == 0) return true;
foreach (var e in enemies)
{
if (e != null && !e.IsDead && e.currentHP > 0)
{
return false;
}
}
return true;
}
private void ApplyInstantDamage(GameObject target, float amount, GameObject source)
{
if (target == null) return;
// If target is an enemy, check if all enemies are already dead
if (target.GetComponent<EnemyCombatant>() != null && AreAllEnemiesDead())
{
Debug.Log($"[EffectSystem] Skip ApplyInstantDamage to {target.name} because all enemies are dead.");
return;
}
var comp = target.GetComponent<ICombatant>();
if (comp != null)
{
comp.ReceiveDamage(amount, source);
// Record stats if source is an ally
if (source != null && target.GetComponent<EnemyCombatant>() != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null)
{
teamUIController.Instance.RecordDamage(ally.slotIndex, amount);
}
}
// Record damage taken if target is an ally
var targetAlly = target.GetComponent<AllyCombatant>();
if (targetAlly != null && teamUIController.Instance != null)
{
teamUIController.Instance.RecordDamageTaken(targetAlly.slotIndex, amount);
}
}
else
{
@@ -362,6 +699,16 @@ public class EffectSystem : MonoBehaviour
if (comp != null)
{
comp.ReceiveHeal(amount, source);
// Record stats if source is an ally
if (source != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null)
{
teamUIController.Instance.RecordHeal(ally.slotIndex, amount);
}
}
}
else
{
@@ -381,7 +728,23 @@ public class EffectSystem : MonoBehaviour
if (duration <= 0f)
{
// check death state for immediate damage
if (target.GetComponent<EnemyCombatant>() != null && AreAllEnemiesDead())
{
Debug.Log($"[EffectSystem] Skip DOT (immediate) to {target.name} because all enemies are dead.");
yield break;
}
comp.ReceiveDamage(totalAmount, source);
// Record stats
if (source != null && target.GetComponent<EnemyCombatant>() != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamage(ally.slotIndex, totalAmount);
}
// Record damage taken
var targetAlly = target.GetComponent<AllyCombatant>();
if (targetAlly != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamageTaken(targetAlly.slotIndex, totalAmount);
yield break;
}
@@ -390,7 +753,25 @@ public class EffectSystem : MonoBehaviour
float elapsed = 0f;
while (elapsed < duration)
{
if (target == null) yield break;
// check death state inside loop
if (target.GetComponent<EnemyCombatant>() != null && AreAllEnemiesDead())
{
Debug.Log($"[EffectSystem] Stopping DOT to {target.name} because all enemies are dead.");
yield break;
}
comp.ReceiveDamage(perTick, source);
// Record stats
if (source != null && target.GetComponent<EnemyCombatant>() != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamage(ally.slotIndex, perTick);
}
// Record damage taken
var targetAlly = target.GetComponent<AllyCombatant>();
if (targetAlly != null && teamUIController.Instance != null) teamUIController.Instance.RecordDamageTaken(targetAlly.slotIndex, perTick);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
@@ -409,6 +790,12 @@ public class EffectSystem : MonoBehaviour
if (duration <= 0f)
{
comp.ReceiveHeal(totalAmount, source);
// Record stats
if (source != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null) teamUIController.Instance.RecordHeal(ally.slotIndex, totalAmount);
}
yield break;
}
@@ -417,7 +804,14 @@ public class EffectSystem : MonoBehaviour
float elapsed = 0f;
while (elapsed < duration)
{
if (target == null) yield break;
comp.ReceiveHeal(perTick, source);
// Record stats
if (source != null)
{
var ally = source.GetComponent<AllyCombatant>();
if (ally != null && teamUIController.Instance != null) teamUIController.Instance.RecordHeal(ally.slotIndex, perTick);
}
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
@@ -438,7 +832,16 @@ public class EffectSystem : MonoBehaviour
if (duration <= 0f)
{
// immediate add totalAmount to mana if ally present
if (ally != null) ally.ModifyMana(Mathf.CeilToInt(totalAmount), true);
if (ally != null)
{
ally.ModifyMana(Mathf.CeilToInt(totalAmount), true, true);
// Record stats
if (source != null)
{
var sourceAlly = source.GetComponent<AllyCombatant>();
if (sourceAlly != null && teamUIController.Instance != null) teamUIController.Instance.RecordMana(sourceAlly.slotIndex, totalAmount);
}
}
yield break;
}
@@ -447,7 +850,17 @@ public class EffectSystem : MonoBehaviour
float elapsed = 0f;
while (elapsed < duration)
{
if (ally != null) ally.ModifyMana(Mathf.CeilToInt(perTick), true);
if (target == null) yield break;
if (ally != null)
{
ally.ModifyMana(Mathf.CeilToInt(perTick), true, true);
// Record stats
if (source != null)
{
var sourceAlly = source.GetComponent<AllyCombatant>();
if (sourceAlly != null && teamUIController.Instance != null) teamUIController.Instance.RecordMana(sourceAlly.slotIndex, perTick);
}
}
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
@@ -483,5 +896,59 @@ public class EffectSystem : MonoBehaviour
yield return new WaitForSeconds(debuff.duration);
comp.RemoveBuff(debuff.buffId);
}
// Coroutine to apply temporary scoreEfficiency change and revert after duration
private IEnumerator ApplyTemporaryScoreEfficiencyChangeCoroutine(AllyCombatant ally, float delta, float duration)
{
if (ally == null) yield break;
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency + delta);
yield return new WaitForSeconds(duration);
ally.scoreEfficiency = Mathf.Max(0f, ally.scoreEfficiency - delta);
}
// Coroutine to apply temporary damageResistance change and revert after duration
private IEnumerator ApplyTemporaryDamageResistanceChangeCoroutine(GameObject target, float delta, float duration)
{
if (target == null) yield break;
// support both ally and enemy
var ally = target.GetComponent<AllyCombatant>();
if (ally != null)
{
float before = ally.damageResistance;
ally.damageResistance = Mathf.Clamp01(ally.damageResistance + delta);
yield return new WaitForSeconds(duration);
// revert by subtracting delta, clamp
ally.damageResistance = Mathf.Clamp01(ally.damageResistance - delta);
yield break;
}
var enemy = target.GetComponent<EnemyCombatant>();
if (enemy != null)
{
float beforeE = enemy.damageResistance;
enemy.damageResistance = Mathf.Clamp01(enemy.damageResistance + delta);
yield return new WaitForSeconds(duration);
enemy.damageResistance = Mathf.Clamp01(enemy.damageResistance - delta);
yield break;
}
}
// Coroutine to apply temporary attack change for AllyCombatant
private IEnumerator ApplyTemporaryAttackChangeCoroutine(AllyCombatant ally, int delta, float duration)
{
if (ally == null) yield break;
ally.attack = Mathf.Max(0, ally.attack + delta);
yield return new WaitForSeconds(duration);
ally.attack = Mathf.Max(0, ally.attack - delta);
}
// Coroutine to apply temporary attack change for EnemyCombatant
private IEnumerator ApplyTemporaryAttackChangeCoroutine(EnemyCombatant enemy, int delta, float duration)
{
if (enemy == null) yield break;
enemy.attack = Mathf.Max(0, enemy.attack + delta);
yield return new WaitForSeconds(duration);
enemy.attack = Mathf.Max(0, enemy.attack - delta);
}
#endregion
}