2025-11-25 08:19:33 -05:00
|
|
|
|
using System;
|
2026-03-20 12:07:44 -04:00
|
|
|
|
using System.Text;
|
|
|
|
|
|
using Cielonos.MainGame.Buffs.Character;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
using Cielonos.MainGame.Inventory;
|
|
|
|
|
|
using DamageNumbersPro;
|
2025-12-23 19:47:06 -05:00
|
|
|
|
using SickscoreGames.HUDNavigationSystem;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
using Sirenix.OdinInspector;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
using SLSUtilities.General;
|
|
|
|
|
|
using SLSUtilities.WwiseAssistance;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-01-17 11:35:49 -05:00
|
|
|
|
public partial class CharacterBase : SerializedMonoBehaviour, IFuncAnimExecutor
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
public Fraction fraction;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public Transform centerPoint => bodyPartsSc.flexibleCenterPoint;
|
|
|
|
|
|
public Vector3 centerPosition => centerPoint.position;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
[TitleGroup("Data")]
|
2025-11-25 08:19:33 -05:00
|
|
|
|
public AttributeData attributeData;
|
2025-12-17 04:19:38 -05:00
|
|
|
|
public VFXData vfxData;
|
2026-03-20 12:07:44 -04:00
|
|
|
|
public BlockData blockData;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
[TitleGroup("Submodules")] [HideInEditorMode]
|
2025-11-25 08:19:33 -05:00
|
|
|
|
public SelfTimeSubmodule selfTimeSm;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
[HideInEditorMode] public AttributeSubmodule attributeSm;
|
|
|
|
|
|
[HideInEditorMode] public EventSubmodule eventSm;
|
|
|
|
|
|
[HideInEditorMode] public BuffSubmodule buffSm;
|
|
|
|
|
|
[HideInEditorMode] public StatusSubmodule statusSm;
|
|
|
|
|
|
|
|
|
|
|
|
[TitleGroup("Subcontrollers")] public CollisionSubcontrollerBase collisionSc;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
public MovementSubcontrollerBase movementSc;
|
|
|
|
|
|
public AnimationSubcontrollerBase animationSc;
|
|
|
|
|
|
public RenderSubcontrollerBase renderSc;
|
|
|
|
|
|
public BodyPartsSubcontroller bodyPartsSc;
|
|
|
|
|
|
public AudioSubcontroller audioSc;
|
|
|
|
|
|
public ReactionSubcontroller reactionSc;
|
|
|
|
|
|
public FeedbackSubcontroller feedbackSc;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
[TitleGroup("Navigation")] public HUDNavigationElement navigationElement;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
2026-01-03 18:19:39 -05:00
|
|
|
|
protected void Awake()
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
InitializeSubmodules();
|
2026-01-03 18:19:39 -05:00
|
|
|
|
InitializeSubcontrollers();
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void Start()
|
|
|
|
|
|
{
|
2026-01-03 18:19:39 -05:00
|
|
|
|
selfTimeSm?.SetUp(this);
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-05-26 00:21:27 -04:00
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
2025-12-08 05:27:53 -05:00
|
|
|
|
|
|
|
|
|
|
protected virtual void Update()
|
|
|
|
|
|
{
|
2026-03-20 12:07:44 -04:00
|
|
|
|
selfTimeSm.Update();
|
2025-12-08 05:27:53 -05:00
|
|
|
|
buffSm.Update();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
public virtual void Die()
|
|
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
Destroy(gameObject); //TODO: 后续改为死亡动画+回收
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class CharacterBase
|
|
|
|
|
|
{
|
|
|
|
|
|
protected virtual void InitializeSubmodules()
|
|
|
|
|
|
{
|
|
|
|
|
|
selfTimeSm ??= new SelfTimeSubmodule(this);
|
|
|
|
|
|
attributeSm ??= new AttributeSubmodule(this);
|
2025-12-08 05:27:53 -05:00
|
|
|
|
eventSm ??= new EventSubmodule(this);
|
|
|
|
|
|
buffSm ??= new BuffSubmodule(this);
|
|
|
|
|
|
statusSm ??= new StatusSubmodule(this);
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
RegisterAttributeCallbacks();
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册属性变更回调,使 Health/Energy 变更时自动触发对应的 EventSubmodule 事件。
|
|
|
|
|
|
/// 子类可 override 追加额外回调(如 UI 更新)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
protected virtual void InitializeSubcontrollers()
|
|
|
|
|
|
{
|
|
|
|
|
|
renderSc?.Initialize();
|
|
|
|
|
|
movementSc?.Initialize();
|
|
|
|
|
|
animationSc?.Initialize();
|
|
|
|
|
|
collisionSc?.Initialize();
|
|
|
|
|
|
bodyPartsSc?.Initialize();
|
|
|
|
|
|
audioSc?.Initialize();
|
|
|
|
|
|
reactionSc?.Initialize();
|
|
|
|
|
|
feedbackSc?.Initialize();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class CharacterBase
|
|
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 护甲减伤公式的缩放常量。armor / (armor + K) = 减伤比例。
|
|
|
|
|
|
/// <para> K = 100 时,100 护甲 = 50% 减伤,200 护甲 ≈ 66.7% 减伤。 </para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private const float ArmorScalingConstant = 100f;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据攻击者属性和自身属性计算最终伤害值。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="attacker">攻击来源角色,为 null 时跳过攻击者侧的伤害倍率。</param>
|
|
|
|
|
|
/// <param name="attackValue">攻击数值参数。</param>
|
|
|
|
|
|
public float GetDamageValue(CharacterBase attacker, Attack.Value attackValue)
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
string dealtMultiplier = attackValue.type.AttackTypeToString() + "DamageDealtMultiplier";
|
|
|
|
|
|
string receivedMultiplier = attackValue.type.AttackTypeToString() + "DamageReceivedMultiplier";
|
|
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
float baseDamage = attackValue.damage;
|
2026-04-18 13:57:19 -04:00
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
// 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];
|
2026-04-18 13:57:19 -04:00
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
return (baseDamage + attackValue.additionalFlatDamage) * attackValue.damageMultiplier;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 对角色施加伤害,填充 attackResult 中的最终伤害、护盾吸收和死亡标记。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void TakeDamage(Attack.Result attackResult)
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
float damage = GetDamageValue(attackResult.attacker, attackResult.value);
|
|
|
|
|
|
if (attributeSm.Has(CharacterAttribute.Shield) && attributeSm[CharacterAttribute.Shield] > 0)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
attackResult.shieldBlockedDamage = Mathf.Min(damage, attributeSm[CharacterAttribute.Shield]);
|
|
|
|
|
|
|
|
|
|
|
|
if (damage > attributeSm[CharacterAttribute.Shield])
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
damage -= attributeSm[CharacterAttribute.Shield];
|
|
|
|
|
|
attributeSm[CharacterAttribute.Shield] = 0;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
attributeSm[CharacterAttribute.Shield] -= damage;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
damage = 0;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
attributeSm[CharacterAttribute.Health] -= damage;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
attackResult.finalDamage = damage;
|
2026-05-10 11:47:55 -04:00
|
|
|
|
|
|
|
|
|
|
// 实际扣除了生命值才算受伤,被盾完全抵挡则不触发
|
|
|
|
|
|
if (damage > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 通过 EventSubmodule.onHurt 统一分发;无攻击区域来源(Buff路径)时传 null
|
|
|
|
|
|
eventSm.onHurt.Invoke(null);
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
if (attributeSm[CharacterAttribute.Health] <= 0)
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2026-02-13 09:22:11 -05:00
|
|
|
|
attackResult.causedDeath = true;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
attributeSm[CharacterAttribute.Health] = 0;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
Die();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-02-13 09:22:11 -05:00
|
|
|
|
attackResult.causedDeath = false;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 对角色施加伤害,并生成伤害数字。适用于有明确攻击来源和攻击数值的情况,如直接攻击;
|
|
|
|
|
|
/// 对于持续伤害等无明确攻击来源的情况,建议先调用 TakeDamage(Attack.Result) 再手动生成伤害数字,以避免重复计算伤害值。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class CharacterBase
|
|
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
public virtual bool CheckBreakthrough(Breakthrough.Type breakthroughType)
|
2026-03-20 12:07:44 -04:00
|
|
|
|
{
|
|
|
|
|
|
return !reactionSc.breakthroughResistances[breakthroughType].Value;
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
public virtual bool CheckDisruption(DisruptionType disruptionType)
|
|
|
|
|
|
{
|
|
|
|
|
|
return animationSc.fullBodyFuncAnimSm.CheckDisruption(disruptionType);
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
public virtual bool GetHit(Breakthrough.Type breakthroughType, out float recoveryTime,
|
|
|
|
|
|
DisruptionType disruptionType = DisruptionType.NormalExternal,
|
2026-03-20 12:07:44 -04:00
|
|
|
|
Vector3 direction = default, string funcAnimName = "")
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
|
|
|
|
|
renderSc.GetHitBlink();
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2025-12-17 04:19:38 -05:00
|
|
|
|
float intensity = breakthroughType switch
|
|
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
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,
|
2025-12-17 04:19:38 -05:00
|
|
|
|
_ => 0
|
|
|
|
|
|
};
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-04-12 02:11:15 -04:00
|
|
|
|
if (string.IsNullOrEmpty(funcAnimName))
|
|
|
|
|
|
{
|
|
|
|
|
|
funcAnimName = GetHitFuncAnimName(breakthroughType, direction);
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
if (CheckBreakthrough(breakthroughType))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!animationSc.fullBodyFuncAnimSm.Stop(disruptionType))
|
|
|
|
|
|
{
|
|
|
|
|
|
recoveryTime = 0f;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
if (breakthroughType >= Breakthrough.Type.Medium)
|
2026-03-20 12:07:44 -04:00
|
|
|
|
{
|
|
|
|
|
|
animationSc.PlayGetHitAnimation(funcAnimName, out recoveryTime);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
recoveryTime = 0f;
|
|
|
|
|
|
animationSc.PlayGetHitBoneShake(intensity, direction);
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
statusSm.AddStatus(StatusType.Stun, recoveryTime);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2025-12-17 04:19:38 -05:00
|
|
|
|
recoveryTime = 0f;
|
|
|
|
|
|
animationSc.PlayGetHitBoneShake(intensity, direction);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
protected virtual string GetHitFuncAnimName(Breakthrough.Type breakthroughType, Vector3 direction)
|
2026-03-20 12:07:44 -04:00
|
|
|
|
{
|
|
|
|
|
|
string prefix = "GetHitMedium";
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
if (breakthroughType >= Breakthrough.Type.Medium)
|
2026-03-20 12:07:44 -04:00
|
|
|
|
{
|
|
|
|
|
|
prefix = breakthroughType switch
|
|
|
|
|
|
{
|
2026-05-23 08:27:50 -04:00
|
|
|
|
Breakthrough.Type.Medium => "GetHitMedium",
|
|
|
|
|
|
Breakthrough.Type.Heavy => "GetHitHeavy",
|
|
|
|
|
|
Breakthrough.Type.Disruption => "GetHitHeavy",
|
|
|
|
|
|
Breakthrough.Type.Forced => "GetHitForced",
|
2026-03-20 12:07:44 -04:00
|
|
|
|
_ => "GetHit"
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
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;
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-04-12 02:11:15 -04:00
|
|
|
|
if (!animationSc.fullBodyFuncAnimSm.collection.ContainsKey(fullName))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (prefix == "GetHitForced")
|
|
|
|
|
|
{
|
|
|
|
|
|
prefix = "GetHitHeavy"; //如果没有专门的“强制等级”受击动画,就使用“重度等级”的动画
|
|
|
|
|
|
fullName = prefix + directionStr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-03-20 12:07:44 -04:00
|
|
|
|
return fullName;
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
2025-12-22 18:36:29 -05:00
|
|
|
|
|
|
|
|
|
|
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.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
return SpaceConverter.WorldPointToNormalizedScreenPoint(centerPoint.position, cam);
|
2025-12-22 18:36:29 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-23 08:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
|
public partial class CharacterBase
|
|
|
|
|
|
{
|
|
|
|
|
|
[Title("Editor Tools")]
|
|
|
|
|
|
[HideInPlayMode]
|
|
|
|
|
|
[Button]
|
|
|
|
|
|
protected virtual void CollectSubcontrollers()
|
|
|
|
|
|
{
|
|
|
|
|
|
movementSc ??= GetComponent<MovementSubcontrollerBase>();
|
|
|
|
|
|
animationSc ??= GetComponent<AnimationSubcontrollerBase>();
|
|
|
|
|
|
collisionSc ??= GetComponent<CollisionSubcontrollerBase>();
|
|
|
|
|
|
renderSc ??= GetComponent<RenderSubcontrollerBase>();
|
|
|
|
|
|
bodyPartsSc ??= GetComponent<BodyPartsSubcontroller>();
|
|
|
|
|
|
audioSc ??= GetComponent<AudioSubcontroller>();
|
|
|
|
|
|
reactionSc ??= GetComponent<ReactionSubcontroller>();
|
|
|
|
|
|
feedbackSc ??= GetComponent<FeedbackSubcontroller>();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|