Files
Cielonos/Assets/Scripts/MainGame/Characters/Base/CharacterBase.cs
2026-05-10 11:47:55 -04:00

299 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Text;
using Cielonos.MainGame.Buffs.Character;
using Cielonos.MainGame.Characters.Inventory;
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;
public BaseAnimationGroup baseAnimationGroup;
[TitleGroup("Submodules")]
public SelfTimeSubmodule selfTimeSm;
public AttributeSubmodule attributeSm;
public EventSubmodule eventSm;
public BuffSubmodule buffSm;
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()
{
vfxData?.Initialize(this);
selfTimeSm?.SetUp(this);
if (fraction == Fraction.Enemy)
{
BattleManager.EnemySm.activeEnemiesList.Add(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);
}
protected virtual void InitializeSubcontrollers()
{
renderSc?.Initialize();
movementSc?.Initialize();
animationSc?.Initialize();
collisionSc?.Initialize();
bodyPartsSc?.Initialize();
audioSc?.Initialize();
reactionSc?.Initialize();
feedbackSc?.Initialize();
}
}
public partial class CharacterBase
{
public float GetDamageValue(AttackValue attackValue)
{
string dealtMultiplier = attackValue.attackType.AttackTypeToString() + "DamageDealtMultiplier";
string gainMultiplier = attackValue.attackType.AttackTypeToString() + "DamageGainMultiplier";
float baseDamage = attackValue.damage;
baseDamage *= attackValue.attacker is not null ? attackValue.attacker.attributeSm[dealtMultiplier] : 1;
baseDamage *= attributeSm[gainMultiplier];
baseDamage *= attackValue.attacker is not null ? attackValue.attacker.attributeSm["FinalDamageDealtMultiplier"] : 1;
baseDamage *= attributeSm["FinalDamageGainMultiplier"];
return (baseDamage + attackValue.additionalFlatDamage) * attackValue.damageMultiplier;
}
public void TakeDamage(ref AttackResult attackResult)
{
float damage = GetDamageValue(attackResult.attackValue);
if (attributeSm.Has("Shield") && attributeSm["Shield"] > 0)
{
attackResult.shieldBlockedDamage = Mathf.Min(damage, attributeSm["Shield"]);
//GeneralUtilities.InstantiateBlockedDamageNumber(flexibleCenterPoint.position, blockedDamage);
if(damage > attributeSm["Shield"])
{
damage -= attributeSm["Shield"];
attributeSm["Shield"] = 0;
}
else
{
attributeSm["Shield"] -= damage;
damage = 0;
}
}
attributeSm["Health"] -= damage;
attackResult.finalDamage = damage;
// 实际扣除了生命值才算受伤,被盾完全抵挡则不触发
if (damage > 0)
{
// 通过 EventSubmodule.onHurt 统一分发无攻击区域来源Buff路径时传 null
eventSm.onHurt.Invoke(null);
}
if (attributeSm["Health"] <= 0)
{
attackResult.causedDeath = true;
attributeSm["Health"] = 0;
Die();
}
else
{
attackResult.causedDeath = false;
}
}
}
public partial class CharacterBase
{
public virtual bool CheckBreakthrough(BreakthroughType breakthroughType)
{
return !reactionSc.breakthroughResistances[breakthroughType].Value;
}
public virtual bool CheckDisruption(DisruptionType disruptionType)
{
return animationSc.fullBodyFuncAnimSm.CheckDisruption(disruptionType);
}
public virtual bool GetHit(BreakthroughType breakthroughType, out float recoveryTime,
DisruptionType disruptionType = DisruptionType.NormalExternal,
Vector3 direction = default, string funcAnimName = "")
{
renderSc.GetHitBlink();
float intensity = breakthroughType switch
{
BreakthroughType.None => 0,
BreakthroughType.Weak => 0.2f,
BreakthroughType.Medium => 0.4f,
BreakthroughType.Heavy or BreakthroughType.Disruption or BreakthroughType.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 >= BreakthroughType.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(BreakthroughType breakthroughType, Vector3 direction)
{
string prefix = "GetHitMedium";
if (breakthroughType >= BreakthroughType.Medium)
{
prefix = breakthroughType switch
{
BreakthroughType.Medium => "GetHitMedium",
BreakthroughType.Heavy => "GetHitHeavy",
BreakthroughType.Disruption => "GetHitHeavy",
BreakthroughType.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);
}
}
}