using System; using System.Text; using Cielonos.MainGame.Buffs.Character; using Cielonos.MainGame.Inventory; using DamageNumbersPro; using SickscoreGames.HUDNavigationSystem; using Sirenix.OdinInspector; using SLSUtilities.General; using SLSUtilities.WwiseAssistance; using SLSUtilities.FunctionalAnimation; using UniRx; using UnityEngine; using UnityEngine.AI; using UnityEngine.Serialization; namespace Cielonos.MainGame.Characters { public enum Fraction { Player = 0, AlliedMinion = 1, Enemy = 10, Neutral = 20 } public partial class CharacterBase : SerializedMonoBehaviour, IFuncAnimExecutor { public Fraction fraction; public Transform centerPoint => bodyPartsSc.flexibleCenterPoint; public Vector3 centerPosition => centerPoint.position; [TitleGroup("Data")] public AttributeData attributeData; public VFXData vfxData; public BlockData blockData; [TitleGroup("Submodules")] [HideInEditorMode] public SelfTimeSubmodule selfTimeSm; [HideInEditorMode] public AttributeSubmodule attributeSm; [HideInEditorMode] public EventSubmodule eventSm; [HideInEditorMode] public BuffSubmodule buffSm; [HideInEditorMode] public StatusSubmodule statusSm; [TitleGroup("Subcontrollers")] public CollisionSubcontrollerBase collisionSc; public MovementSubcontrollerBase movementSc; public AnimationSubcontrollerBase animationSc; public RenderSubcontrollerBase renderSc; public BodyPartsSubcontroller bodyPartsSc; public AudioSubcontroller audioSc; public ReactionSubcontroller reactionSc; public FeedbackSubcontroller feedbackSc; [TitleGroup("Navigation")] public HUDNavigationElement navigationElement; protected void Awake() { InitializeSubmodules(); InitializeSubcontrollers(); } protected virtual void Start() { selfTimeSm?.SetUp(this); } protected virtual void Update() { selfTimeSm.Update(); buffSm.Update(); } public virtual void Die() { Destroy(gameObject); //TODO: 后续改为死亡动画+回收 } } public partial class CharacterBase { protected virtual void InitializeSubmodules() { selfTimeSm ??= new SelfTimeSubmodule(this); attributeSm ??= new AttributeSubmodule(this); eventSm ??= new EventSubmodule(this); buffSm ??= new BuffSubmodule(this); statusSm ??= new StatusSubmodule(this); RegisterAttributeCallbacks(); } /// /// 注册属性变更回调,使 Health/Energy 变更时自动触发对应的 EventSubmodule 事件。 /// 子类可 override 追加额外回调(如 UI 更新)。 /// protected virtual void RegisterAttributeCallbacks() { attributeSm.RegisterValueChangedCallback(CharacterAttribute.Health, (oldVal, newVal) => eventSm.onHealthChanged.Invoke(newVal - oldVal)); attributeSm.RegisterValueChangedCallback(CharacterAttribute.Energy, (oldVal, newVal) => eventSm.onEnergyChanged.Invoke(newVal - oldVal)); } protected virtual void InitializeSubcontrollers() { renderSc?.Initialize(); movementSc?.Initialize(); animationSc?.Initialize(); collisionSc?.Initialize(); bodyPartsSc?.Initialize(); audioSc?.Initialize(); reactionSc?.Initialize(); feedbackSc?.Initialize(); } } public partial class CharacterBase { /// /// 护甲减伤公式的缩放常量。armor / (armor + K) = 减伤比例。 /// K = 100 时,100 护甲 = 50% 减伤,200 护甲 ≈ 66.7% 减伤。 /// private const float ArmorScalingConstant = 100f; /// /// 根据攻击者属性和自身属性计算最终伤害值。 /// /// 攻击来源角色,为 null 时跳过攻击者侧的伤害倍率。 /// 攻击数值参数。 public float GetDamageValue(CharacterBase attacker, Attack.Value attackValue) { string dealtMultiplier = attackValue.type.AttackTypeToString() + "DamageDealtMultiplier"; string receivedMultiplier = attackValue.type.AttackTypeToString() + "DamageReceivedMultiplier"; float baseDamage = attackValue.damage; // Phase 1: 攻击者侧倍率 baseDamage *= attacker is not null ? attacker.attributeSm[dealtMultiplier] : 1; baseDamage *= attacker is not null ? attacker.attributeSm[CharacterAttribute.FinalDamageDealtMultiplier] : 1; // Phase 2: 护甲减伤(非线性,先于 ReceivedMultiplier) float armor = attributeSm[CharacterAttribute.Armor]; if (armor > 0f) { float armorReduction = armor / (armor + ArmorScalingConstant); baseDamage *= 1f - armorReduction; } else { // 负护甲增加伤害,线性叠加,每 -100 护甲增加 100% 伤害 float negativeArmor = Mathf.Abs(armor); float negativeArmorIncrease = negativeArmor / ArmorScalingConstant; baseDamage *= 1f + negativeArmorIncrease; } // Phase 3: 受击者侧倍率 baseDamage *= attributeSm[receivedMultiplier]; baseDamage *= attributeSm[CharacterAttribute.FinalDamageReceivedMultiplier]; return (baseDamage + attackValue.additionalFlatDamage) * attackValue.damageMultiplier; } /// /// 对角色施加伤害,填充 attackResult 中的最终伤害、护盾吸收和死亡标记。 /// public void TakeDamage(Attack.Result attackResult) { float damage = GetDamageValue(attackResult.attacker, attackResult.value); if (attributeSm.Has(CharacterAttribute.Shield) && attributeSm[CharacterAttribute.Shield] > 0) { attackResult.shieldBlockedDamage = Mathf.Min(damage, attributeSm[CharacterAttribute.Shield]); if (damage > attributeSm[CharacterAttribute.Shield]) { damage -= attributeSm[CharacterAttribute.Shield]; attributeSm[CharacterAttribute.Shield] = 0; } else { attributeSm[CharacterAttribute.Shield] -= damage; damage = 0; } } attributeSm[CharacterAttribute.Health] -= damage; attackResult.finalDamage = damage; // 实际扣除了生命值才算受伤,被盾完全抵挡则不触发 if (damage > 0) { // 通过 EventSubmodule.onHurt 统一分发;无攻击区域来源(Buff路径)时传 null eventSm.onHurt.Invoke(null); } if (attributeSm[CharacterAttribute.Health] <= 0) { attackResult.causedDeath = true; attributeSm[CharacterAttribute.Health] = 0; Die(); } else { attackResult.causedDeath = false; } } /// /// 对角色施加伤害,并生成伤害数字。适用于有明确攻击来源和攻击数值的情况,如直接攻击; /// 对于持续伤害等无明确攻击来源的情况,建议先调用 TakeDamage(Attack.Result) 再手动生成伤害数字,以避免重复计算伤害值。 /// public void TakeDamage(Attack.Result attackResult, out DamageNumber damageNumber) { TakeDamage(attackResult); // 护盾吸收伤害单独显示 if (attackResult.shieldBlockedDamage > 0) { MainGameBaseCollection.Instance.ShieldedDamageNumber() .Spawn(attackResult.hitPosition, attackResult.shieldBlockedDamage, centerPoint); } Attack.Type type = attackResult.value.type; bool isCritical = attackResult.value.isCritical; damageNumber = MainGameBaseCollection.Instance.DamageNumber(type, isCritical) .Spawn(attackResult.hitPosition, attackResult.finalDamage, attackResult.target.centerPoint); damageNumber.SetSpamGroup(attackResult.spamGroupID); } public virtual void Heal(float healAmount) { if (healAmount <= 0) return; attributeSm[CharacterAttribute.Health] += healAmount; attributeSm[CharacterAttribute.Health] = Mathf.Min(attributeSm[CharacterAttribute.Health], attributeSm[CharacterAttribute.MaximumHealth]); MainGameBaseCollection.Instance.HealText().Spawn(centerPosition, healAmount, centerPoint); } } public partial class CharacterBase { public virtual bool CheckBreakthrough(Breakthrough.Type breakthroughType) { return !reactionSc.breakthroughResistances[breakthroughType].Value; } public virtual bool CheckDisruption(DisruptionType disruptionType) { return animationSc.fullBodyFuncAnimSm.CheckDisruption(disruptionType); } public virtual bool GetHit(Breakthrough.Type breakthroughType, out float recoveryTime, DisruptionType disruptionType = DisruptionType.NormalExternal, Vector3 direction = default, string funcAnimName = "") { renderSc.GetHitBlink(); float intensity = breakthroughType switch { Breakthrough.Type.None => 0, Breakthrough.Type.Weak => 0.2f, Breakthrough.Type.Medium => 0.4f, Breakthrough.Type.Heavy or Breakthrough.Type.Disruption or Breakthrough.Type.Forced => 0.8f, _ => 0 }; if (string.IsNullOrEmpty(funcAnimName)) { funcAnimName = GetHitFuncAnimName(breakthroughType, direction); } if (CheckBreakthrough(breakthroughType)) { if (!animationSc.fullBodyFuncAnimSm.Stop(disruptionType)) { recoveryTime = 0f; return true; } if (breakthroughType >= Breakthrough.Type.Medium) { animationSc.PlayGetHitAnimation(funcAnimName, out recoveryTime); } else { recoveryTime = 0f; animationSc.PlayGetHitBoneShake(intensity, direction); } statusSm.AddStatus(StatusType.Stun, recoveryTime); return true; } recoveryTime = 0f; animationSc.PlayGetHitBoneShake(intensity, direction); return false; } protected virtual string GetHitFuncAnimName(Breakthrough.Type breakthroughType, Vector3 direction) { string prefix = "GetHitMedium"; if (breakthroughType >= Breakthrough.Type.Medium) { prefix = breakthroughType switch { Breakthrough.Type.Medium => "GetHitMedium", Breakthrough.Type.Heavy => "GetHitHeavy", Breakthrough.Type.Disruption => "GetHitHeavy", Breakthrough.Type.Forced => "GetHitForced", _ => "GetHit" }; } string directionStr = "Front"; if (direction != default) { float angle = Vector3.SignedAngle(transform.forward, direction, Vector3.up); directionStr = angle switch { > -45f and <= 45f => "Back", > 45f and <= 135f => "Left", > -135f and <= -45f => "Right", _ => "Front" }; if (directionStr != "Front" && !animationSc.fullBodyFuncAnimSm.collection.ContainsKey(prefix + directionStr)) { directionStr = "Front"; } } string fullName = prefix + directionStr; if (!animationSc.fullBodyFuncAnimSm.collection.ContainsKey(fullName)) { if (prefix == "GetHitForced") { prefix = "GetHitHeavy"; //如果没有专门的“强制等级”受击动画,就使用“重度等级”的动画 fullName = prefix + directionStr; } } return fullName; } } public partial class CharacterBase { public Vector2 GetNormalizedScreenPosition(Camera cam = null) { if (this is Player player) { cam ??= player.viewSc.playerCamera; } else { if (cam == null) { throw new ArgumentNullException(nameof(cam), "Camera must be provided for non-player characters."); } } return SpaceConverter.WorldPointToNormalizedScreenPoint(centerPoint.position, cam); } } #if UNITY_EDITOR public partial class CharacterBase { [Title("Editor Tools")] [HideInPlayMode] [Button] protected virtual void CollectSubcontrollers() { movementSc ??= GetComponent(); animationSc ??= GetComponent(); collisionSc ??= GetComponent(); renderSc ??= GetComponent(); bodyPartsSc ??= GetComponent(); audioSc ??= GetComponent(); reactionSc ??= GetComponent(); feedbackSc ??= GetComponent(); } } #endif }